Документация СУБД «Квант-Гибрид» v.1.5.2

СУБД «Квант-Гибрид» представляет собой цифровую платформу общего назначения, применимую для широкого круга разработчиков государственных информационных систем, приложений, а также для корпоративных отделов по цифровой трансформации предприятий крупного и среднего бизнеса.

Документация СУБД «Квант-Гибрид» (Quantum Hybrid Base, далее по тексту QHB) представлена в 3 частях и приложениях:

Часть I — общее знакомство с внутренним строением QHB

Часть II — рассматриваются темы, представляющие интерес для администратора базы данных QHB

Часть III — рассматриваются темы, связанные с формальными знаниями языка SQL

Приложения

Состояние документирования

Мы, разработчики QHB, с признательностью примем от Вас информацию об ошибках в Документации на почтовый адрес qhb.support@quantom.info. Мы не скрываем, что продукт QHB создавался как форк от свободно распространяемого open source продукта PostgreSQL. Вероятные ошибки в документации могут быть связаны именно с этим.

О состоянии Документации и ее перевода мы будем регулярно сообщать в сопроводительной документации к каждому релизу. Недостающие главы Вы можете найти на официальной странице документации PostgreSQL по адресу: https://www.postgresql.org/docs/.

Версия документации: 1.5.2
Номер ревизии: 0e3df8c6

Дополнительно

Вы можете скачать дополнительно:

Документация в формате epub

Ознакомьтесь со списком поддерживаемых платформ и замечаниями к релизу QHB.

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

Документация на СУБД «Квант-Гибрид» архивных версий:

Документация на QHB 1.4.0

Документация на QHB 1.3.0 | Документация на QHB 1.3.1

Документация на QHB 1.2.0

Документация на QHB 1.1.0

Юридическое уведомление

Программное обеспечение СУБД «Квант-Гибрид» v.1.5.2

Copyright © KVANTOM LLC, 2019-2024

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

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

Portions Copyright © 1996-2024, PostgreSQL Global Development Group

Portions Copyright © 1994 Regents of the University of California

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

НИ В КАКОМ СЛУЧАЕ КАЛИФОРНИЙСКИЙ УНИВЕРСИТЕТ НЕ НЕСЕТ ОТВЕТСТВЕННОСТИ ЗА ЛЮБЫЕ ПРЯМЫЕ, НЕПРЯМЫЕ, ОСОБЫЕ, СЛУЧАЙНЫЕ ИЛИ КОСВЕННЫЕ УБЫТКИ, ВКЛЮЧАЯ ПОТЕРЮ ПРИБЫЛИ, ВОЗНИКАЮЩИЕ В СВЯЗИ С ИСПОЛЬЗОВАНИЕМ ДАННОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ЕГО ДОКУМЕНТАЦИИ, ДАЖЕ ЕСЛИ КАЛИФОРНИЙСКИЙ УНИВЕРСИТЕТ БЫЛ ИЗВЕЩЕН О ВОЗМОЖНОСТИ ВОЗНИКНОВЕНИЯ ПОДОБНЫХ УБЫТКОВ.

КАЛИФОРНИЙСКИЙ УНИВЕРСИТЕТ ПРЯМО ОТКАЗЫВАЕТСЯ ПРЕДОСТАВЛЯТЬ ЛЮБЫЕ ГАРАНТИИ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ПОДРАЗУМЕВАЕМЫМИ ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ И СООТВЕТСТВИЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ ПО ПРИНЦИПУ «КАК ЕСТЬ», И КАЛИФОРНИЙСКИЙ УНИВЕРСИТЕТ НЕ ОБЯЗАН ПРЕДОСТАВЛЯТЬ СОПРОВОЖДЕНИЕ, ПОДДЕРЖКУ, ОБНОВЛЕНИЯ, МОДЕРНИЗАЦИЮ ИЛИ ИЗМЕНЕНИЯ.

Кроме того, Программное обеспечение содержит модули собственной разработки:

TARQ — самобалансирующийся менеджер кэша дисковых блоков с автоматической компенсацией нагрузки на дисковую систему;

QCP — балансировщик сетевой нагрузки предназначенный для оптимального использования серверных подключений;

библиотечный кэш разобранных запросов;

серверный процесс, организующий фоновую запись на диск;

RBytea — модуль для внешнего хранения больших бинарных объектов с сохранением способа их обработки в прикладных системах;

QSS — прозрачное шифрование данных с использованием алгоритма ГОСТ Р 3412-15 «Кузнечик» для произвольных объектов, включая внешние большие объекты;

подсистема сбора и агрегации метрик;

QDL — модуль для прямой загрузки больших объемов данных из текстового представления непосредственно в страницы данных;

модуль хранения Append_Only;

модуль хранения Holdmem;

Qbackup — подсистема резервного копирования и восстановления с поддержкой каталога, инкрементальных резервных копий, архивации резервных копий;

2Q — встроенная очередь сообщений;

UMCA — дамп и загрузка содержимого менеджера кэша дисковых блоков;

Qbim — расширение, реализующее битемпоральную модель данных;

Qbayes — расширение, инструментарий для задач расчета вероятностных моделей с применением сетей Байеса;

бинарные утилиты для управления СУБД;

подсистема интернационализации i18n.

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

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

Настоящее соглашение регулируется действующим законодательством Российской Федерации.

Соглашения

В синопсисе команд используются следующие соглашения: квадратные скобки ([ и ]) обозначают необязательные части. Фигурные скобки ({ и }) и вертикальная черта (|) показывают, что вы должны выбрать один из предложенных вариантов. Многоточие (...) означает, что предыдущий элемент можно повторить. Все остальные символы, включая кавычки и апострофы, следует воспринимать буквально.

Там, где требуется бо́льшая ясность, команды SQL предваряются приглашением =>, а команды оболочки — приглашением $. Однако обычно приглашения отсутствуют.

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

Правила сообщения об ошибках

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

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

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

Выявление ошибок

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

Далее приведены лишь некоторые примеры возможных ошибок:

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

Программа выдает неправильный результат для любых входных данных.

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

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

Не получается скомпилировать, собрать или установить QHB на поддерживаемых платформах, действуя в соответствии с инструкциями.

Здесь под определением «программа» подразумевается любой исполняемый файл, а не только серверный процесс.

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

Что сообщить

Самое важное, что следует помнить о сообщениях об ошибках, — сообщайте все факты и только факты. Не стройте предположения о том, что, по вашему мнению, пошло не так, что «кажется, оно делало», или какая часть программы содержит ошибку. Если вы не знакомы с реализацией, вы, скорее всего, ошибетесь и не поможете нам. И даже если вы не ошиблись, грамотные объяснения являются отличным дополнением к фактам, но не заменяют их. Если мы собираемся исправить ошибку, мы все равно сами должны будем посмотреть, в чем она заключается. Сообщить голые факты относительно просто (можно просто скопировать и вставить текст с экрана), но слишком часто важные детали не упоминаются, потому что считаются незначительными и неважными или кажется, что отчет будет понятен и без них.

В каждом отчете об ошибке следует указать следующую информацию:

Точную последовательность действий, начиная с запуска программы, необходимую для воспроизведения проблемы. Она должна быть полной; если вывод зависит от данных в таблицах, то недостаточно указать только SELECT без предшествующих операторов CREATE TABLE и INSERT. У нас не будет времени на восстановление вашей схемы базы данных, и если предполагается, что мы создадим собственные данные, вероятно, мы пропустим эту проблему.

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

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

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

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

Крайне важно описать результат, который вы ожидали получить. Если вы просто напишете «Эта команда выдает мне такой результат» или «Это не то, чего я ожидал», мы можем запустить ваш пример сами, просмотреть вывод и решить, что все в порядке и результат соответствует ожиданиям. Мы вряд ли будем тратить время на расшифровку точного смысла ваших команд. В частности, воздержитесь от заявлений вроде «Это не то, что делает SQL/Oracle». Выяснение корректного согласно стандартам SQL поведения — не слишком увлекательное занятие, а логика работы других реляционных баз данных может быть не полностью документирована или отличаться в нюансах. (Разумеется, если ваша проблема — аварийное завершение программы, можно пропустить этот пункт).

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

Все, что вы сделали не так, как написано в инструкции по установке.

Версию QHB. Чтобы узнать версию сервера, к которому вы подключены можете выполнить команду, SELECT version();. Большинство исполняемых программ тоже поддерживает параметр --version; по крайней мере qhb --version и psql --version должно работать. Если функция или параметры не существуют, значит, скорее всего, вашу версию пора обновить. Если вы запускаете предварительно упакованную версию, например RPM, укажите это, включая любую подверсию, которую может иметь пакет. Если речь идет о снимке Git, укажите это, а также хеш коммита.
Если ваша версия старее актуальной, мы почти наверняка предложим вам ее обновить. В каждом новом выпуске содержится много исправлений и улучшений, поэтому вполне возможно, что ошибка, с которой вы столкнулись в более старой версии QHB, уже исправлена. Мы можем предоставить только ограниченную поддержку для систем, использующих старые версии QHB; если вам этого недостаточно — рассмотрите возможность заключения договора о коммерческой поддержке.

Информация о платформе. Включает в себя имя и версию ядра, библиотеку C/RUST, процессор, информацию о памяти и т. д. В большинстве случаев достаточно сообщить поставщика и версию, но не следует предполагать, что все знают, что именно содержит «Debian» или что все работают на x86_64. Если у вас есть проблемы с установкой, то также понадобится информация о наборе инструментов на вашем компьютере (компилятор, make и т.д.).

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

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

При написании отчета об ошибке избегайте путаницы в терминологии. Официальное наименование программного пакета — «КВАНТ-ГИБРИД», для краткости в документации, скорее всего, будет использовано название «QHB». Если вы говорите конкретно о серверном процессе, отметьте это, а не просто говорите «Сбои в QHB». Сбой одного серверного процесса сильно отличается от сбоя родительского процесса «qhb»; пожалуйста, не пишите «сбой сервера», когда имеете в виду, что отключился один серверный процесс, и наоборот. Кроме того, клиентские программы, такие как интерактивный интерфейс «psql», полностью отделены от серверной части. Пожалуйста, постарайтесь указать, где проявляется проблема: на стороне клиента или сервера.

Как сообщать об ошибках

Отправляйте отчеты об ошибках по адресу:

qhb.support@quantom.info

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

Часть I. Внутреннее устройство

Добро пожаловать в руководство по внутреннему устройству СУБД «Квант-Гибрид» (QHB). Основная цель этой части — познакомить вас на практике с основными аспектами системы QHB без глубокого погружения в рассматриваемые темы.

Освоив это руководство, вы можете перейти к чтению части II для установки и администрирования своего собственного сервера или части III, для получения информации по использованию языка SQL в QHB.

Содержание

Основы архитектуры

Индексы

Введение

Типы индексов

Многостолбцовые индексы

Индексы и ORDER BY

Объединение нескольких индексов

Уникальные индексы

Индексы по выражениям

Частичные индексы

Сканирование только по индексу и покрывающие индексы

Классы операторов и семейства операторов

Индексы и правила сортировки

Анализ использования индексов

Индексы B-деревья

Введение

Поведение классов операторов B-деревьев

Вспомогательные функции B-деревьев

Реализация

Индексы GiST

Введение

Встроенные классы операторов

Расширяемость

Реализация

Примеры

Индексы SP-GiST

Введение

Встроенные классы операторов

Расширяемость

Реализация

Индексы GIN

Введение

Встроенные классы операторов

Расширяемость

Реализация

Советы и приемы для индексов GIN

Ограничения

Примеры

Индексы BRIN

Введение

Встроенные классы операторов

Расширяемость

Хеш-индексы

Обзор

Реализация

Полнотекстовый поиск

Введение

Таблицы и индексы

Управление текстовым поиском

Дополнительная функциональность

Синтаксические анализаторы

Словари

Пример конфигурации

Тестирование и отладка текстового поиска

Предпочтительные типы индексов для текстового поиска

Поддержка psql

Ограничения

Управление параллельным доступом

Введение

Изоляция транзакций

Явная блокировка

Проверка согласованности данных на уровне приложения

Обработка сбоев сериализации

Ограничения применимости

Блокировка и индексы

libpq — библиотека для языка C

Функции управления подключением к базе данных

Функции, показывающие состояние подключения

Функции для выполнения команд

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

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

Построчное извлечение результатов запроса

Отмена запросов в процессе выполнения

Интерфейс быстрого пути

Асинхронное уведомление

Функции, связанные с командой COPY

Функции управления

Прочие функции

Обработка замечаний

Система событий

Переменные среды

Файл паролей

Файл подключений служб

Поиск параметров подключения через LDAP

Поддержка SSL

Поведение в многопоточных программах

Сборка программ с libpq

Примеры программ

Большие объекты

Введение

Особенности реализации

Клиентские интерфейсы

Серверные функции

Пример программы

Информационная схема

Схема

Типы данных

information_schema_catalog_name

administrable_role_​authorizations

applicable_roles

attributes

character_sets

check_constraint_routine_usage

check_constraints

collations

collation_character_set_​applicability

column_column_usage

column_domain_usage

column_options

column_privileges

column_udt_usage

columns

constraint_column_usage

constraint_table_usage

data_type_privileges

domain_constraints

domain_udt_usage

domains

element_types

enabled_roles

foreign_data_wrapper_options

foreign_data_wrappers

foreign_server_options

foreign_servers

foreign_table_options

foreign_tables

key_column_usage

parameters

referential_constraints

role_column_grants

role_routine_grants

role_table_grants

role_udt_grants

role_usage_grants

routine_column_usage

routine_privileges

routine_routine_usage

routine_sequence_usage

routine_table_usage

routines

schemata

sequences

sql_features

sql_implementation_info

sql_parts

sql_sizing

table_constraints

table_privileges

tables

transforms

triggered_update_columns

triggers

udt_privileges

usage_privileges

user_defined_types

user_mapping_options

user_mappings

view_column_usage

view_routine_usage

view_table_usage

views

Расширение SQL

Как работает расширяемость

Система типов QHB

Пользовательские функции

Пользовательские процедуры

Функции на языке запросов (SQL)

Перегрузка функций

Категории изменчивости функций

Функции на процедурном языке

Внутренние функции

Функции на нативном языке

Информация по оптимизации функций

Пользовательские агрегаты

Пользовательские типы

Пользовательские операторы

Информация по оптимизации операторов

Интерфейсные расширения для индексов

Упаковка связанных объектов в расширение

Инфраструктура сборки расширений

Триггеры

Обзор поведения триггеров

Видимость изменений данных

Написание триггерных функций на C/RUST

Полный пример триггера

Триггеры событий

Обзор поведения триггеров событий

Матрица срабатывания триггеров событий

Написание триггерных функций событий на C/RUST

Полный пример триггера события

Пример триггера события перезаписи таблицы

Интерфейс программирования сервера

Функции интерфейса

Вспомогательные функции интерфейса

Управление памятью

Управление транзакциями

Видимость изменений данных

Примеры

Фоновые рабочие процессы

Логическое декодирование

Примеры логического декодирования

Принципы логического декодирования

Интерфейс протокола потоковой репликации

SQL-интерфейс логического декодирования

Системные каталоги, связанные с логическим декодированием

Плагины вывода логического декодирования

Запись вывода логического декодирования

Поддержка синхронной репликации для логического декодирования

Потоковая передача больших транзакций для логического декодирования

Поддержка двухфазной фиксации для логического декодирования

Отслеживание прогресса репликации

Модули архивирования

Функции инициализации

Функции обратного вызова модулей архивирования

Пример

Процесс qhb

Описание

Параметры

Переменные среды

Диагностика

Примечания

Ошибки

Примеры

См. также

Обзор внутренних компонентов QHB

Путь запроса

Как устанавливаются соединения

Этап синтаксического анализа

Система правил QHB

Планировщик/оптимизатор

Исполнитель

Системные каталоги

Обзор

pg_aggregate

pg_am

pg_amop

pg_amproc

pg_attrdef

pg_attribute

pg_authid

pg_auth_members

pg_cast

pg_class

pg_collation

pg_constraint

pg_conversion

pg_database

pg_db_role_setting

pg_default_acl

pg_depend

pg_description

pg_enum

pg_event_trigger

pg_extension

pg_foreign_data_wrapper

pg_foreign_server

pg_foreign_table

pg_index

pg_inherits

pg_init_privs

pg_language

pg_largeobject

pg_largeobject_metadata

pg_namespace

pg_opclass

pg_operator

pg_opfamily

pg_parameter_acl

pg_partitioned_table

pg_policy

pg_proc

pg_publication

pg_publication_namespace

pg_publication_rel

pg_range

pg_replication_origin

pg_rewrite

pg_seclabel

pg_sequence

pg_shdepend

pg_shdescription

pg_shseclabel

pg_statistic

pg_statistic_ext

pg_statistic_ext_data

pg_subscription

pg_subscription_rel

pg_tablespace

pg_transform

pg_trigger

pg_ts_config

pg_ts_config_map

pg_ts_dict

pg_ts_parser

pg_ts_template

pg_type

pg_user_mapping

Системные представления

Обзор

pg_available_extensions

pg_available_extension_versions

pg_config

pg_cursors

pg_file_settings

pg_group

pg_hba_file_rules

pg_ident_file_mappings

pg_indexes

pg_locks

pg_matviews

pg_policies

pg_prepared_statements

pg_prepared_xacts

pg_publication_tables

pg_replication_origin_status

pg_replication_slots

pg_roles

pg_rules

pg_seclabels

pg_sequences

pg_settings

pg_shadow

pg_stats

pg_stats_ext

pg_tables

pg_timezone_abbrevs

pg_timezone_names

pg_user

pg_user_mappings

pg_views

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

Обзор

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

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

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

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

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

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

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

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

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

Определение интерфейса для табличных методов доступа

Определение интерфейса для индексных методов доступа

Базовая структура API для индексов

Функции индексных методов доступа

Сканирование индекса

Особенности блокировки индексов

Проверки уникальности индекса

Функции оценки стоимости индекса

Типовые записи WAL

Пользовательские менеджеры ресурсов WAL

Физическое хранение базы данных

Структура файлов базы данных

TOAST

Карта свободного пространства

Карта видимости

Ветвь инициализации

Структура страницы базы данных

Кортежи только в куче (Heap-Only Tuples, HOT)

Объявления и начальное содержимое системных каталогов

Правила объявления системных каталогов

Исходные данные системных каталогов

Формат файла BKI

Команды BKI

Структура файла BKI начальной загрузки

Пример BKI

Как планировщик использует статистику

Примеры оценки количества строк

Примеры многомерной статистики

Статистика планировщика и безопасность

Формат манифеста резервной копии

Объект верхнего уровня в манифесте копии

Объект файла в манифесте копии

Объект диапазона WAL в манифесте копии

Модуль безопасного хранения QSS

Описание

Использование

Обслуживание

Асинхронный пул соединений QCP

Общий принцип работы

Запуск и работа

Потребление памяти

Модуль прямой загрузки больших объемов данных QDL

Поддерживаемые типы полей

Синтаксис

Команда create_table

Команда insert_values

Команда validate

Файл конфигурации

Сценарий использования

Пример использования QDL

Общие замечания

Вариант использования QDL в случае загрузки данных в новую партицию существующей таблицы

Пример использования QDL в случае логической репликации таблицы

Заключение

Модуль загрузки данных метрик в базу данных QDLM

Настройка загрузки данных метрик в таблицу базы данных

Установка QDLM

Запуск утилиты QDLM

Результаты обработки

Менеджер кэша дисковых блоков TARQ

Дамп и загрузка содержимого менеджера кэша дисковых блоков UMCA

Создание дампа

Загрузка дампа

Просмотр файлов дампа

Параметр HOLDMEM и дополнительные кэши дисковых блоков

Значения HOLDMEM

Совместимость

См. также

Таблицы APPEND_ONLY

Сервер метрик

Настройка сервера метрик

Настройка метрик queryid

Встроенная очередь сообщений

Описание

Необходимые системные настройки

Запуск брокера очереди сообщений при старте СУБД

Функции для работы с очередью сообщений

Брокер очереди сообщений

Процедура обработки сообщений

Типизация

Поддержка транзакционности

Внутреннее состояние очереди сообщений

Планировщик заданий QSched

Описание

Использование

Число допустимых параллельных процессов

Ограничения QSched

Переменные уровня сеанса qhb-variables

Описание

Функции qhb-variables

Другие примеры использования

Профилирование долгих запросов

Подготовка

Анализ информации

Ограничения

Основы архитектуры

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

Говоря терминами баз данных, QHB использует модель клиент/сервер. Сеанс QHB состоит из следующих взаимодействующих процессов (программ):

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

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

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

Сервер QHB может обрабатывать несколько одновременных подключений от клиентов. Для этого он запускает («ответвляет») новый процесс для каждого подключения. С этого момента клиент и новый серверный процесс обмениваются данными без вмешательства главного процесса qhb. Таким образом, главный процесс сервера работает постоянно, ожидая клиентских подключений, тогда как процессы клиента и связанного сервера создаются и удаляются. (Разумеется, все это происходит незаметно для пользователя. Здесь это упоминается только для полноты картины.)

Индексы

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

Введение

Предположим, у нас есть таблица, подобная этой:

CREATE TABLE test1 (
    id integer,
    content varchar
);

и приложение делает много запросов вида

SELECT content FROM test1 WHERE id = константа;

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

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

Следующая команда может использоваться для создания индекса для вышеупомянутого столбца id:

CREATE INDEX test1_id_index ON test1 (id);

Имя test1_id_index можно выбрать любое, но лучше подобрать что-то, напоминающее о назначении индекса.

Чтобы удалить индекс, воспользуйтесь командой DROP INDEX. Индексы можно добавлять и удалять из таблиц в любое время.

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

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

Создание индекса для большой таблицы может занять много времени. По умолчанию QHB позволяет выполнять чтение (операторы SELECT) из таблицы параллельно с созданием индекса, но операции записи (INSERT, UPDATE, DELETE) блокируются до завершения построения индекса. В производственной среде это зачастую недопустимо. Можно разрешить запись параллельно с созданием индекса, но следует учитывать несколько моментов — дополнительную информацию см. в разделе Неблокирующее построение индексов.

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

Типы индексов

QHB предоставляет несколько типов индексов: B-дерево, хеш, GiST, SP-GiST, GIN, BRIN и расширение bloom. Для каждого типа индекса применяется свой алгоритм, который наиболее пригоден для определенного типа запросов. По умолчанию команда CREATE INDEX создает индексы B-деревья, потому что они подходят в большинстве ситуаций. Другие типы индексов можно выбрать, написав ключевое слово USING, а за ним — название типа индекса. Например, создать хеш-индекс можно так:

CREATE INDEX имя ON таблица USING HASH (столбец);

B-дерево

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

<   <=   =   >=   >

Конструкции, равнозначные комбинациям этих операторов, такие как BETWEEN и IN, также могут быть реализованы с помощью поиска по индексу B-дереву. Кроме того, B-дерево можно использовать с условием IS NULL или IS NOT NULL на столбец индекса.

Оптимизатор также может использовать индексы B-деревья для запросов, включающих операторы сопоставления с шаблоном LIKE и ~, если этот шаблон является константой и привязан к началу строки; например, col LIKE 'foo%' или col ~ '^foo', но не col LIKE '%bar'. Однако если в базе данных не используется локаль C, нужно будет создать индекс со специальным классом операторов для поддержки индексации запросов на сопоставление с шаблоном; см. раздел Классы операторов и семейства операторов ниже. Индексы B-деревья можно использовать и для ILIKE и ~*, но только если шаблон начинается с символов, отличных от алфавитных, т. е. символов, не подверженных преобразованию регистра.

Индексы B-деревья также можно использовать для извлечения данных, отсортированных по порядку. Это не всегда быстрее, чем простое сканирование и сортировка, но зачастую полезно.

Хеш

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

=

GiST

Индексы GiST — это не единая категория индексов, а скорее инфраструктура, в которой можно реализовать множество различных стратегий индексации. Соответственно, конкретные операторы, с которыми может использоваться индекс GiST, варьируются в зависимости от стратегии индексации (класса операторов). Например, стандартный дистрибутив QHB включает классы операторов GiST для нескольких двумерных геометрических типов данных, которые поддерживают индексированные запросы с использованием следующих операторов:

<<   &<   &>   >>   <<|   &<|   |&>   |>>   @>   <@   ~=   &&

(Значение этих операторов см. в разделе Геометрические функции и операторы). Классы операторов GiST, включенные в стандартный дистрибутив, приведены в таблице Встроенные классы операторов GiST. Многие другие классы операторов GiST доступны в коллекции share/extension или в виде отдельных проектов. Подробную информацию см. в главе Индексы GiST.

Индексы GiST также могут оптимизировать поиск «ближайшего соседа», например, такой запрос:

SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;

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

SP-GiST

Индексы SP-GiST, так же, как и индексы GiST, предлагают инфраструктуру, которая поддерживает различные виды поиска. SP-GiST позволяет реализовать широкий спектр различных несбалансированных дисковых структур данных, таких как дерево квадрантов, k-мерные и префиксные деревья (Tries). Например, стандартный дистрибутив QHB включает классы операторов SP-GiST для точек в двумерном пространстве, поддерживающие индексные запросы с использованием следующих операторов:

<<   >>   ~=   <@   <<|   |>>

(Значение этих операторов см. в разделе Геометрические функции и операторы). Классы операторов SP-GiST, включенные в стандартный дистрибутив, приведены в таблице Встроенные классы операторов SP-GiST. Подробную информации см. в главе Индексы SP-GiST.

Как и GiST, SP-GiST поддерживает поиск «ближайшего соседа». Для классов операторов SP-GiST, которые поддерживают дистанционное упорядочение, соответствующие операторы перечислены в столбце «Операторы сортировки» таблицы Встроенные классы операторов SP-GiST.

GIN

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

Подобно GiST и SP-GiST, GIN может поддерживать множество различных пользовательских стратегий индексирования, и конкретные операторы, с которыми может использоваться индекс GIN, различаются в зависимости от стратегии индексирования. Например, в стандартный дистрибутив QHB включен класс операторов GIN для массивов, который поддерживает индексированные запросы с использованием следующих операторов:

<@   @>   =   &&

(Значение этих операторов см. в разделе Функции и операторы для массивов.) Классы операторов GIN, включенные в стандартный дистрибутив, перечислены в таблице Встроенные классы операторов GIN. Многие другие классы операторов GIN доступны в коллекции share/extension или в виде отдельных проектов. Подробную информацию см. в главе Индексы GIN.

BRIN

Индексы BRIN (сокращение от Block Range Indexes, индекс диапазона блоков) хранят сводные данные о значениях, хранящихся в последовательных диапазонах физических блоков таблицы. Поэтому они наиболее эффективны для столбцов, значения в которых хорошо коррелируют с физическим порядком строк таблицы. Как и GiST, SP-GiST и GIN, BRIN могут поддерживать множество различных стратегий индексирования, и конкретные операторы, с которыми может использоваться индекс BRIN, различаются в зависимости от стратегии индексирования. Для типов данных, имеющих линейный порядок сортировки, индекс хранит минимальные и максимальные значения данных в столбце для каждого диапазона блоков. Это позволяет поддерживать запросы, использующие следующие операторы:

<   <=   =   >=   >

Классы операторов BRIN, включенные в стандартный дистрибутив, перечислены в таблице Встроенные классы операторов BRIN. Подробную информацию см. в главе Индексы BRIN.

Многостолбцовые индексы

Индекс может быть определен для более чем одного столбца таблицы. Например, если у вас есть такая таблица:

CREATE TABLE test2 (
  major int,
  minor int,
  name varchar
);

(предположим, что вы так храните каталог /dev в базе данных), и вы часто делаете запросы вида:

SELECT name FROM test2 WHERE major = константа AND minor = константа;

то, возможно, целесообразно создать индекс по двум столбцам (major и minor), например:

CREATE INDEX test2_mm_idx ON test2 (major, minor);

В настоящее время только индексы типов B-дерево, GiST, GIN и BRIN можно делать многостолбцовыми. Возможность создания индекса по нескольким ключевым столбцам не зависит от возможности добавления в этот индекс дополнительных столбцов (INCLUDE). В индексах может быть до 32 столбцов, включая столбцы INCLUDE.

Многостолбцовый индекс B-дерево можно использовать с условиями запроса, включающими любое подмножество столбцов индекса, но наиболее эффективен этот индекс, когда заданы ограничения на ведущие (крайние левые) столбцы. Точное правило заключается в том, что условия равенства на ведущие столбцы вместе с условиями неравенства на первый столбец, у которого нет условия равенства, будут использоваться для ограничения сканируемой области индекса. Условия на столбцы правее них тоже проверяются по индексу, избавляя от необходимости посещать таблицу, но область индекса, которую нужно просканировать, они не уменьшают. Например, если есть индекс по (a, b, c) и условие запроса WHERE a = 5 AND b >= 42 AND c < 77, то индекс будет сканироваться от первой записи с a = 5 и b = 42 до последней записи с a = 5. Записи индекса с c >= 77 будут пропускаться, но все равно будут просканированы. Этот индекс, в принципе, можно использовать и для запросов которые имеют ограничения на b и/или c без ограничения на a — но в этом случае будет сканироваться весь индекс, поэтому в большинстве случаев планировщик предпочтет использованию этого индекса последовательное сканирование таблицы.

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

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

Многостолбцовый индекс BRIN можно использовать с условиями запроса, включающими любое подмножество столбцов индекса. Подобно GIN и в отличие от B-дерева или GiST, эффективность поиска по индексу одинакова независимо от того, какие столбцы индекса входят в условие запроса. Единственная причина иметь несколько индексов BRIN в одной таблице вместо одного многостолбцового заключается в возможности задавать разное значение параметра хранения pages_per_range.

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

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

Индексы и ORDER BY

В дополнение к простому поиску строк, удовлетворяющих запросу, индекс может выдать их отсортированными в определенном порядке. Это позволяет учесть в запросе указание ORDER BY без отдельного этапа сортировки. Из всех типов индексов, поддерживаемых в настоящее время QHB, только B-дерево умеет выдавать отсортированные данные — другие типы индексов возвращают строки в неопределенном, зависящем от реализации порядке.

Планировщик удовлетворит указание ORDER BY, либо просканировав доступный индекс, соответствующий этому указанию, либо просканировав таблицу в физическом порядке и выполнив явную сортировку. Для запроса, требующего сканирования большей части таблицы, явная сортировка скорее всего будет быстрее использования индекса, потому что для следования шаблону последовательного обращения ей требуется меньше дисковых операций ввода/вывода. Индексы более полезны, когда нужно извлечь только несколько строк. Важный особый случай представляет ORDER BY в сочетании с LIMIT n: при явной сортировке для определения первых n строк придется обработать все данные, но если есть индекс, соответствующий ORDER BY, первые n строк можно получить напрямую, вовсе не сканируя остальные.

По умолчанию индексы B-деревья хранят свои записи в порядке возрастания, причем значения NULL идут после всех остальных (при прочем равенстве записей они упорядочиваются по столбцу TID таблицы). Это означает, что при прямом сканировании индекса по столбцу x выдается результат, удовлетворяющий ORDER BY x (или точнее, ORDER BY x ASC NULLS LAST). Этот индекс можно просканировать и в обратном направлении, получив выходные данные, удовлетворяющие ORDER BY x DESC (или точнее, ORDER BY x DESC NULLS FIRST, поскольку NULLS FIRST подразумевается для ORDER BY DESC по умолчанию).

Можно настроить порядок индекса B-дерева, добавив параметры ASC, DESC, NULLS FIRST и/или NULLS LAST при создании индекса, например:

CREATE INDEX test2_info_nulls_low ON test2 (info NULLS FIRST);
CREATE INDEX test3_desc_index ON test3 (id DESC NULLS LAST);

Индекс, хранящий элементы в порядке возрастания, в котором NULL идут первыми, может удовлетворить либо предложению ORDER BY x ASC NULLS FIRST, либо предложению ORDER BY x DESC NULLS LAST, в зависимости от направления его сканирования.

Вы можете поинтересоваться, зачем предлагать все четыре параметра, когда два параметра вместе с возможностью обратного сканирования будут охватывать все варианты ORDER BY. Для одностолбцовых индексов этих параметров действительно многовато, но в многостолбцовых индексах они могут быть полезны. Рассмотрим индекс по двум столбцам (x, y): он удовлетворит ORDER BY x, y если сканировать вперед, или ORDER BY x DESC, y DESC, если сканировать назад. Но может случиться так, что приложению часто понадобится применять ORDER BY x ASC, y DESC. С простым индексом такой порядок получить никак не удастся, но он возможен, если индекс определен как (x ASC, y DESC) или (x DESC, y ASC).

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

Объединение нескольких индексов

При простом сканировании индекса могут применяться только те предложения запроса, которые используют столбцы индекса с операторами его класса операторов и соединяются AND. Например, для данного индекса (a, b) условие запроса WHERE a = 5 AND b = 6 может использовать этот индекс напрямую, но запрос вроде WHERE a = 5 OR b = 6 — не может.

К счастью, QHB имеет возможность объединять несколько индексов (включая многократное использование одного индекса) для обработки случаев, которые невозможно реализовать при простых сканированиях индекса. Система может сформировать условия AND и OR за несколько сканирований индекса. Например, запрос типа WHERE x = 42 OR x = 47 OR x = 53 OR x = 99 можно разбить на четыре отдельных сканирования индекса по x, по одному для каждого предложения. Результаты этих сканирований затем логически складываются посредством OR для получения итогового результата. Другой пример: если у нас есть отдельные индексы по x и y, одна из возможных реализаций запроса WHERE x = 5 AND y = 6 состоит в том, чтобы использовать каждый индекс для соответствующего предложения этого запроса, а затем логически умножить результаты индекса посредством AND для определения результирующих строк.

Чтобы объединить несколько индексов, система сканирует каждый необходимый индекс и подготавливает в памяти битовую карту, сохраняя в той местоположения строк таблицы, о которых известно, что они удовлетворяют условиям этого индекса. Затем битовые карты объединяются посредством AND и OR в соответствии с требованиями запроса. Наконец просматриваются и возвращаются фактические строки таблицы. Строки таблицы просматриваются в физическом порядке, потому что так они располагаются в битовой карте; это означает, что любой порядок исходных индексов потерян, и поэтому если в запросе есть предложение ORDER BY, то потребуется отдельный этап сортировки. По этой причине, а также потому, что на каждое дополнительное сканирование индекса тратится дополнительное время, планировщик иногда предпочитает использовать простое сканирование индекса, даже если доступны дополнительные индексы, которые тоже можно было бы использовать.

Во всех приложениях, кроме самых простых, могут быть полезны различные комбинации индексов, и, принимая решение о том, какие индексы вводить, разработчик базы данных должен найти компромисс. Иногда лучше использовать многостолбцовые индексы, а иногда лучше создавать отдельные индексы и полагаться на возможность объединения индексов. Например, если рабочая нагрузка включает в себя набор запросов, которые иногда содержат условие только по столбцу x, иногда только по y, а иногда по обоим столбцам, можно создать два отдельных индекса по x и y, полагаясь при обработке запросов, использующих оба столбца, на объединение этих индексов. Еще можно создать многостолбцовый индекс по (x, y). Обычно для запросов с условиями по обоим столбцам такой индекс эффективнее объединения индексов, но, как говорилось в разделе Многостолбцовые индексы, он почти бесполезен для запросов с условиями только по y, поэтому он не должен быть единственным индексом. В таком случае успешным будет сочетание многостолбцового индекса и отдельного индекса по y. Для запросов с условием только по x можно использовать многостолбцовый индекс, хотя он будет больше и, следовательно, медленнее, чем индекс только по x. Последний вариант заключается в создании всех трех индексов, но скорее всего это разумно, только если поиск в таблице происходит гораздо чаще, чем изменения, и частота всех трех типов запросов одинакова. Если один из типов запросов встречается значительно реже, чем другие, лучше создать только два индекса, которые наиболее соответствуют двум более частым запросам.

Уникальные индексы

Также индексы можно использовать для обеспечения уникальности значения столбца или комбинации значений из нескольких столбцов.

CREATE UNIQUE INDEX имя ON таблица (столбец [, ...]) [ NULLS [ NOT ] DISTINCT ];

В настоящее время только индексы типа B-дерево могут быть объявлены уникальными.

Когда индекс объявляется уникальным, наличие нескольких строк таблицы с одинаковыми индексируемыми значениями не допускаются. По умолчанию значения NULL одинаковыми не считаются, благодаря чему в одном столбце может быть несколько NULL. Параметр NULLS NOT DISTINCT изменяет это поведение и заставляет индексы воспринимать значения NULL как одинаковые. Уникальный многостолбцовый индекс будет отклонять только те случаи, когда все индексируемые столбцы одинаковы в нескольких строках.

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

Примечание
Не надо вручную создавать индексы по уникальным столбцам; это лишь продублирует автоматически созданный индекс.

Индексы по выражениям

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

Например, распространенный способ сравнения без учета регистра заключается в использовании функции lower:

SELECT * FROM test1 WHERE lower(col1) = 'value';

Этот запрос может использовать индекс, если таковой был определен по результату функции lower(col1):

CREATE INDEX test1_lower_col1_idx ON test1 (lower(col1));

Если объявить этот индекс уникальным (UNIQUE), это не позволит создать строки, в которых значения col1 отличаются только регистром, равно как и строки, в которых значения col1 действительно идентичны. Таким образом, индексы по выражениям можно применять для обеспечения выполнения ограничений, которые нельзя определить как простые ограничения уникальности.

Другой пример: если часто выполняются запросы вроде

SELECT * FROM people WHERE (first_name || ' ' || last_name) = 'John Smith';

тогда, возможно, стоит создать такой индекс:

CREATE INDEX people_names ON people ((first_name || ' ' || last_name));

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

Индексы по выражениям относительно дороги в обслуживании, поскольку производные выражения должны вычисляться после каждого добавления строки и каждого изменения без оптимизации HOT. Однако выражения индекса не вычисляются повторно во время поиска по индексу, поскольку результаты вычисления уже хранятся в индексе. В обоих приведенных выше примерах система видит запрос как просто WHERE индексированный_столбец = 'константа', поэтому скорость поиска такая же, как для любого другого простого запроса индекса. Таким образом, индексы по выражениям полезны, когда скорость поиска важнее скорости добавления и изменения.

Частичные индексы

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

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

Пример 1. Настройка частичного индекса для исключения распространенных значений

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

Предположим, имеется такая таблица:

CREATE TABLE access_log (
    url varchar,
    client_ip inet,
    ...
);

Чтобы создать частичный индекс, который соответствует нашему примеру, выполните следующую команду:

CREATE INDEX access_log_client_ip_ix ON access_log (client_ip)
WHERE NOT (client_ip > inet '192.168.100.0' AND
           client_ip < inet '192.168.100.255');

Типичный запрос, который может использовать этот индекс:

SELECT *
FROM access_log
WHERE url = '/index.html' AND client_ip = inet '212.78.10.32';

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

SELECT *
FROM access_log
WHERE url = '/index.html' AND client_ip = inet '192.168.100.23';

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

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

Пример 2. Настройка частичного индекса для исключения «неинтересных» значений

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

CREATE INDEX orders_unbilled_index ON orders (order_nr)
    WHERE billed is not true;

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

SELECT * FROM orders WHERE billed is not true AND order_nr < 10000;

Однако индекс также может использоваться в запросах, которые вообще не включают order_nr, например:

SELECT * FROM orders WHERE billed is not true AND amount > 5000.00;

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

Обратите внимание, что в таком запросе этот индекс не может использоваться:

SELECT * FROM orders WHERE order_nr = 3501;

Заказ 3501 может находиться как среди оплаченных, так и среди неоплаченных заказов.

Пример 2 также иллюстрирует, что индексированный столбец и столбец, используемый в предикате, не обязаны совпадать. QHB поддерживает частичные индексы с произвольными предикатами при условии, что задействованы только столбцы индексируемой таблицы. Однако имейте в виду, что предикат должен соответствовать условиям запросов, которые должны получить положительный результат от этого индекса. Точнее говоря, частичный индекс может использоваться в запросе, только если система может распознать, что из условия WHERE этого запроса математически вытекает предикат индекса. QHB не имеет сложного инструмента доказательства теорем, способного распознавать математически равнозначные выражения, написанные в разных формах. (Мало того что такой инструмент чрезвычайно трудно создать, он наверняка будет слишком медленным, чтобы применяться на практике.) Система может распознавать простые следствия из неравенств, например, что из «x < 1» следует «x < 2»; в противном случае условие предиката должно точно соответствовать условия WHERE данного запроса, иначе индекс не будет распознаваться как подходящий. Сопоставление происходит во время планирования запроса, а не во время выполнения. Как следствие, параметризованные предложения запросов не будут работать с частичным индексом. Например, подготовленный запрос с параметром может иметь условие «x < ?», из которого никогда не будет следовать «x < 2» при всех возможных значениях параметра.

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

Пример 3. Настройка частичного уникального индекса

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

CREATE TABLE tests (
    subject text,
    target text,
    success boolean,
    ...
);

CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target)
    WHERE success;

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

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

Имейте в виду, что настройка частичного индекса подразумевает, что вы знаете о данных как минимум столько же, сколько планировщик запросов; в частности, вы знаете, когда использование индекса может быть выгодным. Формирование этого знания требует опыта и понимания того, как работают индексы в QHB. В большинстве случаев преимущество частичного индекса над обычным будет минимальным. Бывают случаи, когда они даже контрпродуктивны, как показано в Примере 4.

Пример 4. Не используйте частичные индексы как замену партиционированию

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

CREATE INDEX mytable_cat_1 ON mytable (data) WHERE category = 1;
CREATE INDEX mytable_cat_2 ON mytable (data) WHERE category = 2;
CREATE INDEX mytable_cat_3 ON mytable (data) WHERE category = 3;
...
CREATE INDEX mytable_cat_N ON mytable (data) WHERE category = N;

Это плохая идея! Почти наверняка вам лучше обойтись одним не частичным индексом, объявленным следующим образом:

CREATE INDEX mytable_cat_data ON mytable (category, data);

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

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

Более подробную информацию о частичных индексах можно найти в следующих источниках: «The case for partial indexes» и «Generalized Partial Indexes».

Сканирование только по индексу и покрывающие индексы

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

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

Тип индекса должен поддерживать сканирование только по индексу. Индексы B-деревья поддерживают его всегда. Индексы GiST и SP-GiST поддерживают сканирование только по индексу для некоторых классов операторов, но не для всех. Остальные типы индексов это сканирование не поддерживают. Основным требованием является то, что индекс должен физически хранить или иметь возможность восстановить исходное значение данных для каждой записи индекса. В качестве контрпримера, индексы GIN не могут поддерживать сканирование только по индексу, поскольку обычно каждая запись индекса содержит только часть исходного значения данных.

Запрос должен ссылаться только на столбцы, хранящиеся в индексе. Например, если в таблице имеется индекс по столбцам x и y, и в ней также есть столбец z, сканирование только по индексу могут использовать следующие запросы:

SELECT x, y FROM tab WHERE x = 'key';
SELECT x FROM tab WHERE x = 'key' AND y < 42;

а такие запросы не могут:

SELECT x, z FROM tab WHERE x = 'key';
SELECT x FROM tab WHERE x = 'key' AND z < 42;

(Индексы по выражению и частичные индексы усложняет это правило, как рассказывается ниже.)

Если эти два фундаментальных требования выполнены, то все значения данных, требуемые запросу, доступны из индекса, поэтому сканирование только по индексу физически возможно. Но для любого сканирования таблицы в QHB есть дополнительное требование: система должна убедиться, что каждая извлеченная строка «видима» для MVCC-снимка запроса, как описано в главе Управление параллельным доступом. Информация о видимости хранится не в записях индекса, а только в записях кучи, так что на первый взгляд может показаться, что для любого извлечения строки в любом случае потребуется обращение к куче. И это действительно так, если строка таблицы была недавно изменена. Однако для редко меняющихся данных есть способ обойти эту проблему. QHB отслеживает для каждой страницы в куче таблицы, все ли строки, хранящиеся на этой странице, достаточно стары, чтобы быть видимыми для всех текущих и будущих транзакций. Эта информация сохраняется в битах в карте видимости таблицы. Сканирование только по индексу после нахождения подходящей записи индекса проверяет бит карты видимости для соответствующей страницы кучи. Если он установлен, строка определенно видимая, и поэтому данные могут быть возвращены без дополнительных операций. Если он не установлен, запись кучи придется посетить, чтобы выяснить, видима ли строка, поэтому никакого преимущества в производительности по сравнению со стандартным сканированием индекса не достигается. Даже в положительном случае этот подход заменяет обращение к куче на обращение к карте видимости; но поскольку карта видимости на четыре порядка меньше описываемой ей кучи, для доступа к ней требуется гораздо меньше операций ввода/вывода. В большинстве случаев карта видимости постоянно кэширована в памяти.

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

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

SELECT y FROM tab WHERE x = 'key';

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

CREATE INDEX tab_x_y ON tab(x) INCLUDE (y);

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

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

CREATE UNIQUE INDEX tab_x_y ON tab(x) INCLUDE (y);

условие уникальности применяется только к столбцу x, но не к комбинации x и y. (Также в ограничениях UNIQUE и PRIMARY KEY можно задать предложение INCLUDE, предоставив альтернативный синтаксис для настройки такого индекса.)

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

До того, как в QHB появилось свойство INCLUDE, люди иногда создавали покрывающие индексы, записывая столбцы дополнительной нагрузки как обычные столбцы индекса, то есть

CREATE INDEX tab_x_y ON tab(x, y);

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

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

В принципе сканирование только по индексу можно использовать с индексами по выражениям. Например, имея индекс по f(x), где x — это столбец таблицы, можно выполнить запрос

SELECT f(x) FROM tab WHERE f(x) < 1;

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

CREATE INDEX tab_f_x ON tab (f(x)) INCLUDE (x);

Дополнительный нюанс, если это делается, чтобы избежать повторного вычисления f(x), состоит в том, что планировщик необязательно сопоставит использование f(x) вне индексируемых предложений WHERE со столбцом индекса. Обычно он правильно понимает это в простых запросах наподобие показанных выше, но не в запросах с соединениями. Эти недостатки могут быть устранены в будущих версиях QHB.

Интересны также взаимодействия частичных индексов со сканированием только по индексу. Рассмотрим частичный индекс, показанный в Примере 3:

CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target)
    WHERE success;

В принципе, с этим индексом можно провести сканирование только по индексу для удовлетворения запроса вроде

SELECT target FROM tests WHERE subject = 'some-subject' AND success;

Но есть проблема: предложение WHERE ссылается на столбец success, который недоступен в качестве результирующего столбца индекса. Несмотря на это, сканирование только по индексу возможно, потому что плану не нужно перепроверять эту часть предложения WHERE во время выполнения: у всех записей, найденных в индексе, неизбежно будет значение success = true, так что это не нужно явно проверять в плане. QHB распознает такие случаи и позволит осуществить сканирование только по индексу.

Классы операторов и семейства операторов

В определении индекса можно указать класс операторов для каждого столбца индекса.

CREATE INDEX имя ON таблица (столбец класс_оп [ ( параметры_класса_оп ) ] [параметры сортировки] [, ...]);

Класс операторов определяет операторы, которые будут использоваться индексом для этого столбца. Например, индекс B-дерево по типу int4 будет использовать класс int4_ops; этот класс операторов включает функции сравнения для значений типа int4. На практике обычно бывает достаточно класса операторов по умолчанию для типа данных столбца. Основная причина указания классов операторов заключается в том, что для некоторых типов данных может быть более одного значимого варианта поведения индекса. Например, нам может понадобиться отсортировать тип данных комплексного числа по модулю или по действительной части. Это можно сделать, определив два класса операторов для этого типа данных, а затем выбрав подходящий класс при создании индекса. Класс оператора определяет основной порядок сортировки (который затем можно изменить, добавив параметры сортировки COLLATE, ASC/ DESC и/или NULLS FIRST/NULLS LAST).

Помимо классов операторов по умолчанию существует также несколько встроенных классов операторов:

Классы операторов text_pattern_ops, varchar_pattern_ops и bpchar_pattern_ops поддерживают индексы B-деревья для типов text, varchar и char соответственно. Отличие от классов операторов по умолчанию состоит в том, что значения сравниваются строго символ за символом, а не в соответствии с правилами сортировки, специфичными для локали. Это делает данные классы операторов пригодными для использования в запросах, включающих выражения сопоставления с шаблоном (регулярные выражения LIKE или POSIX), когда база данных использует локаль, отличную от стандартной «C». Например, можно проиндексировать столбец varchar следующим образом:

CREATE INDEX test_index ON test_table (col varchar_pattern_ops);

Обратите внимание, что если вы хотите, чтобы индекс применялся в запросах, включающих обычные сравнения <, <=, > или >=, придется также создать индекс с классом операторов по умолчанию. Такие запросы не могут использовать классы операторов xxx_pattern_ops. (Однако же обычные сравнения на равенство могут использовать эти классы операторов). Можно создать несколько индексов по одному столбцу, но с разными классами операторов. Если вы используете локаль C, вам не нужен индекс с классами операторов xxx_pattern_ops, поскольку в локали C индекс с операторами по умолчанию можно использовать и для запросов сопоставления с шаблоном.

Следующий запрос показывает все определенные классы операторов:

SELECT am.amname AS index_method,
       opc.opcname AS opclass_name,
       opc.opcintype::regtype AS indexed_type,
       opc.opcdefault AS is_default
    FROM pg_am am, pg_opclass opc
    WHERE opc.opcmethod = am.oid
    ORDER BY index_method, opclass_name;

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

Эта расширенная версия предыдущего запроса показывает семейство операторов, к которому принадлежит каждый класс операторов:

SELECT am.amname AS index_method,
       opc.opcname AS opclass_name,
       opf.opfname AS opfamily_name,
       opc.opcintype::regtype AS indexed_type,
       opc.opcdefault AS is_default
    FROM pg_am am, pg_opclass opc, pg_opfamily opf
    WHERE opc.opcmethod = am.oid AND
          opc.opcfamily = opf.oid
    ORDER BY index_method, opclass_name;

Этот запрос показывает все определенные семейства операторов и все операторы, включенные в каждое семейство:

SELECT am.amname AS index_method,
       opf.opfname AS opfamily_name,
       amop.amopopr::regoperator AS opfamily_operator
    FROM pg_am am, pg_opfamily opf, pg_amop amop
    WHERE opf.opfmethod = am.oid AND
          amop.amopfamily = opf.oid
    ORDER BY index_method, opfamily_name, opfamily_operator;

Совет
В psql имеются команды \dAc, \dAf и \dAo, которые предоставляют несколько более усовершенствованные версии этих запросов.

Индексы и правила сортировки

Индекс может поддерживать только одно правило сортировки на столбец индекса. Если есть необходимость в разных правилах сортировки, может потребоваться несколько индексов.

Рассмотрим следующие операторы:

CREATE TABLE test1c (
    id integer,
    content varchar COLLATE "x"
);

CREATE INDEX test1c_content_index ON test1c (content);

Индекс автоматически использует правило сортировки нижележащего столбца. Поэтому запрос вида

SELECT * FROM test1c WHERE content > константа;

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

SELECT * FROM test1c WHERE content > константа COLLATE "y";

тоже нужны, то можно создать дополнительный индекс, который будет поддерживать правило сортировки "y", например:

CREATE INDEX test1c_content_y_index ON test1c (content COLLATE "y");

Анализ использования индексов

Хотя индексы в QHB не нуждаются в обслуживании или настройке, все же важно проверить, какие индексы действительно используются реальной рабочей нагрузкой запросов. Изучение использования индекса для отдельного запроса выполняется командой EXPLAIN; ее применение для этой цели иллюстрируется в разделе Использование EXPLAIN. Также можно собрать общую статистику об использовании индекса на работающем сервере, как описано в разделе Система накопительной статистики.

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

Сначала всегда запускайте ANALYZE. Эта команда собирает статистику о распределении значений в таблице. Эта информация требуется для оценки количества строк, возвращаемых запросом, которое необходимо планировщику для реалистичной оценки затрат для каждого возможного плана выполнения запроса. При отсутствии какой-либо реальной статистики будут приняты некоторые значения по умолчанию, которые почти наверняка будут неточными. Поэтому изучить применение индекса приложением без запуска ANALYZE заведомо невозможно. Дополнительную информацию см. в подразделах Обновление статистики планировщика и Процесс «Автовакуум».

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

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

Если все же удалось заставить систему использовать индекс, то есть два варианта: либо система права, и использование индекса действительно не подходит, либо оценки стоимости планов запросов не отражают реальность. Поэтому необходимо замерить время выполнения запроса с индексами и без них. Здесь может пригодиться команда EXPLAIN ANALYZE.

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

Индексы B-деревья

Введение

QHB включает в себя реализацию стандартной индексной структуры данных — B-дерева (многонаправленного сбалансированного дерева, btree). Любой тип данных, который можно отсортировать в четко определенном линейном порядке, может быть загружен в индекс B-дерево. Единственное ограничение заключается в том, что запись индекса не может превышать приблизительно одной трети страницы (после сжатия TOAST, если применимо).

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

Поведение классов операторов B-деревьев

Как показано в таблице Стратегии B-дерева, класс операторов btree должен предоставлять пять операторов сравнения: <, <=, =, >= и >. Можно было бы ожидать, что в эти операторы также должен входить <>, но это не так, потому что практически никогда нет смысла использовать <> в предложении WHERE для поиска по индексу. (Для некоторых целей планировщик обрабатывает <> как связанный с классом операторов B-дерева; но он находит данный оператор через отрицание оператора =, а не обращаясь к pg_amop).

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

Существует несколько основных предположений, которым должно удовлетворять семейство операторов для B-деревьев:

Оператор = должен быть отношением эквивалентности, то есть для любых отличных от NULL значений A, B, C определенного типа данных:

A = A истинно (закон рефлексивности)

если A = B, то B = A (закон симметричности)

если A = B и B = C, то A = C (закон транзитивности)

Оператор < должен представлять из себя отношение строгого порядка, то есть для любых отличных от NULL значений A, B, C:

A < A ложно (закон антирефлексивности)

если A < B и B < C, то A < C (закон транзитивности)

Более того, порядок должен быть полным; то есть для любых отличных от NULL значений A, B:

истинно ровно одно утверждение: A < B, либо B < A, либо A = B (закон трихотомии) (Конечно, закон трихотомии обосновывает определение вспомогательной функции сравнения.)

Остальные три оператора определяются через операторы = и < очевидным образом и должны работать согласованно с последними.

Для семейства операторов, поддерживающих несколько типов данных, вышеуказанные законы должны выполняться, когда A, B, C берутся из любых типов данных в семействе. Закон транзитивности обеспечить сложнее всего, поскольку в ситуациях с разными типами это зависит от согласованности поведения двух или трех различных операторов. Для примера, нельзя поместить операторы для float8 и numeric в одно семейство, по крайней мере, не в сегодняшней ситуации, когда для сравнения с float8 значения numeric тоже преобразуются в тип float8. Из-за ограничения точности типа float8 различные значения numeric при приведении к float8 превращаются в одно и то же число, тем самым нарушая закон транзитивности.

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

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

Вспомогательные функции B-деревьев

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

order

Для каждой комбинации типов данных, для которых семейство операторов btree предоставляет операторы сравнения, оно должно предоставить и вспомогательную функцию сравнения, зарегистрированную в pg_amproc как вспомогательная функция номер 1 со свойствами amproclefttype/amprocrighttype, равными левому и правому типу данных сравнения (т. е. тем же типам данных, с которыми зарегистрированы соответствующие операторы в pg_amop). Функция сравнения должна принимать отличные от NULL значения A и B и возвращать значение int32, которое будет < 0, 0 или > 0, когда A < B, A = B или A > B соответственно. Результат NULL не допускается: все значения типа данных должны быть сравнимы.

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

sortsupport

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

in_range

Дополнительно семейство операторов btree может предоставлять вспомогательные функции in_range, зарегистрированные как вспомогательная функция номер 3. Такие функции не используется при операциях с индексами B-деревьями; они расширяют семантику семейства операторов, чтобы то могло поддерживать оконные предложения, содержащие типы границ рамки RANGE смещение PRECEDING и RANGE смещение FOLLOWING (см. подраздел Вызовы оконных функций). По сути, дополнительная информация позволяет добавлять или вычитать значение смещения способом, соответствующим принятому в семействе порядку сортировки.

Функция in_range должна иметь сигнатуру:

in_range(значение type1, база type1, смещение type2, вычитание bool, меньше bool)
returns bool

Значение и база должны быть одинакового типа, одного из поддерживаемых семейством операторов (т. е. типом, для которого предоставляется упорядочивание). Однако смещение может быть другого типа, который никаким другим образом данным семейством может и не поддерживаться. Например, встроенное семейство операторов time_ops предоставляет функцию in_range которая имеет параметр смещение типа interval. Семейство может предоставлять функцию in_range для любого поддерживаемого типа и одного или нескольких типов смещения. Каждая вспомогательная функция in_range должна быть зарегистрирована в pg_amproc с amproclefttype, равным type1, и amprocrighttype, равным type2.

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

если !вычитание и !меньше, возвращается значение >= (база + смещение)

если !вычитание и меньше, возвращается значение <= (база + смещение)

если вычитание и !меньше, возвращается значение >= (база - смещение)

если вычитание и меньше, возвращается значение <= (база - смещение)

Прежде чем делать это, функция должна проверить знак смещения и, если оно отрицательное, выдавать ошибку ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE (22013) с текстом ошибки «invalid preceding or following size in window function» (неверный предшествующий или последующий размер в оконной функции). (Этого требует стандарт SQL, хотя нестандартные семейства операторов, вероятно, могут проигнорировать это ограничение, т. к., по-видимому, в нем нет особой семантической необходимости.) Эта проверка возложена на in_range, чтобы коду ядра не нужно было понимать, что для конкретного типа данных означает «меньше нуля».

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

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

Если in_range с меньше = true возвращает true для некоторого значения1 и базы, true должно возвращаться для каждого значения2 <= значению1 с той же базой.

Если in_range с меньше = true возвращает false для некоторого значения1 и базы, false должно возвращаться для каждого значения2 >= значению1 с той же базой.

Если in_range с меньше = true возвращает true для некоторого значения и базы1, true должно возвращаться для каждой базы2 >= базе1 с тем же значением.

Если in_range с меньше = true возвращает false для некоторого значения и базы1, false должно возвращаться для каждой базы2 <= базе1 с тем же значением.

При меньше = false должны выполнятся аналогичные утверждения с противоположными условиями.

Если упорядочиваемый тип (type1) является сортируемым, функции in_range с помощью стандартного механизма PG_GET_COLLATION() будет передан OID соответствующего правила сортировки.

Функции in_range не обязаны обрабатывать NULL в аргументах и обычно помечаются как строгие.

equalimage

Дополнительно семейство операторов btree может предоставить вспомогательные функции equalimage («равенство подразумевает равенство образов»), зарегистрированные как вспомогательная функция номер 4. Эти функции позволяют основному коду определять, безопасно ли применять оптимизацию с дедупликацией в В-дереве. На данный момент функции equalimage вызываются только при построении или перестроении индекса.

Функция equalimage должна иметь сигнатуру

equalimage(тип_инд_классом_оп oid) returns bool

Возвращаемое значение является статической информацией о классе операторов и правиле сортировки. Результат true показывает, что функция order для класса операторов будет гарантированно возвращать 0 («аргументы равны»), только когда его аргументы А и В также взаимозаменяемы без потери семантической информации. Если функция equalimage не обнаружена или возвращает false, это означает, что нельзя предполагать выполнение данного условия.

В аргументе тип_инд_классом_оп передается pg_type.oid типа данных, который индексируется данным классом операторов. Это удобство позволяет повторно использовать в разных классах операторов одну и ту же нижележащую функцию equalimage. Если тип_инд_классом_оп относится к сортируемому типу данных, функции equalimage с помощью стандартного механизма PG_GET_COLLATION() будет передан OID соответствующего правила сортировки.

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

Равенство образов является условием, почти равнозначным простому битовому равенству. Есть лишь одно небольшое различие: при индексировании типа данных valerna представление двух равных образов на диске может отличаться в битовом отношении вследствие несогласованного применения сжатия TOAST к входным данным. Строго говоря, когда функция equalimage класса операторов возвращает true, безопасно предположить, что функция на С datum_image_eq() всегда будет согласована с функцией order класса операторов (при условии, что обеим функциям передан одинаковый OID правила сортировки).

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

Для классов операторов, включенных в базовый продукт QHB, принято соглашение регистрировать стандартную универсальную функцию equalimage. Большинство классов операторов регистрирует функцию btequalimage(), которая указывает, что дедупликация безопасна без каких-либо условий. Классы операторов для сортируемых типов данных, таких как text, регистрируют функцию btvarstrequalimage(), которая указывает, что дедупликация безопасна с детерминированными правилами сортировки. Для сохранения контроля в сторонних расширениях наилучшим решением будет регистрировать их собственные специальные функции equalimage.

options

Дополнительно семейство операторов B-деревьев может предоставлять вспомогательные функции options («параметры класса операторов»), зарегистрированные как вспомогательная функция номер 5. Эти функции определяют набор видимых пользователю параметров, которые управляют поведением класса операторов.

Вспомогательная функция options должна иметь сигнатуру

options(параметры_отн local_relopts *) returns void

Эта функция передает указатель на структуру local_relopts, в которую нужно внести набор параметров класса операторов. К этим параметрам можно обращаться из других вспомогательных функций при помощи макросов PG_HAS_OPCLASS_OPTIONS() и PG_GET_OPCLASS_OPTIONS().

На данный момент ни у одного класса операторов B-деревьев нет вспомогательной функции options. В отличие от GiST, SP-GiST, GIN и BRIN, В-дерево не допускает гибкое представление ключей. Так что, вероятно, в актуальном методе доступа по индексу В-дереву у функции options нет практического применения. Тем не менее эта вспомогательная функция была добавлена в В-дерево в целях единообразия и, возможно, станет полезной при дальнейшем развитии реализации В-дерева в QHB.

Реализация

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

Структура В-дерева

В QHB индексы В-деревья — это многоуровневые древовидные структуры, где каждый уровень дерева можно использовать в качестве двусвязного списка страниц. Единственная метастраница хранится в фиксированном положении в начале первого файла сегмента индекса. Все остальные страницы делятся на листовые и внутренние. Листовые страницы находятся на самом нижнем уровне дерева. Все остальные уровни состоят из внутренних страниц. Каждая листовая страница содержит кортежи, которые указывают на строки таблицы. Каждая внутренняя страница содержит кортежи, которые указывают на следующий уровень вниз по дереву. Обычно листовые страницы составляют более 99% всех страниц. Как внутренние, так и листовые страницы используют стандартный формат страницы, описанный в разделе Структура страницы базы данных.

Новые листовые страницы добавляются в индекс В-дерево, когда существующая листовая страница не может вместить поступающий кортеж. Операция разделения страницы освобождает место для элементов, которые изначально принадлежали переполнившейся странице, перенося какую-то их часть на новую страницу. Разделение страницы также вставляет в родительскую страницу новую ссылку вниз, на новую страницу, что, в свою очередь, может вызвать разделение родительской страницы. Страницы разделяются «каскадно вверх» рекурсивным образом. Когда же в итоге и корневая страница не может вместить новую ссылку вниз, выполняется операция разделения корневой страницы. Это добавляет к структуре дерева новый уровень путем создания новой корневой страницы, которая находится на уровень выше исходной корневой страницы.

Восходящее удаление индексных кортежей

В индексах B-деревьях не учитывается напрямую, что в среде MVCC может быть несколько действующих версий одной логической строки таблицы; для индекса каждый кортеж является независимым объектом, требующим отдельной записи в индексе. Кортежи «отработанных версий» иногда могут накапливаться и отрицательно влиять на время отклика и скорость обработки запросов. Обычно это происходит при нагрузках с преобладанием UPDATE, когда для большинства отдельных операций изменения данных нельзя применить оптимизацию HOT. Изменение значения даже одного столбца, покрываемого одним индексом, во время UPDATE всегда требует наличия нового набора индексных кортежей — по одному для каждого индекса в таблице. В частности, обратите внимание, что сюда входят и те индексы, которые не были «логически изменены» командой UPDATE. Во всех индексах будут нужны последующие физические индексные кортежи, указывающие на последнюю версию строки в таблице. Каждый новый кортеж в каждом индексе, как правило, должен сосуществовать с исходным «измененным» кортежем в течение короткого периода времени (обычно недолго после фиксации транзакции UPDATE).

В индексах B-деревьях постепенно удаляются индексные кортежи отработанных версий посредством выполнения процедуры восходящего удаления индексных кортежей. Каждый проход процедуры удаления запускается, реагируя на ожидаемое «разделение страниц из-за отрабатывания версий». Это касается только тех индексов, которые не были логически изменены операторами UPDATE, где в противном случае на определенных страницах произошло бы концентрированное накопление устаревших версий. Реализация обычно старается избежать разделения страниц, хотя возможно, что определенные эвристические алгоритмы на ее уровне не сумеют идентифицировать и удалить даже один мусорный индексный кортеж (в этом случае проблема с новым входящим кортежем, не помещающимся на листовой странице, решается путем разделения страницы или в результате дедупликации). Наибольшее количество версий, которое нужно пройти при каждом сканировании индекса (для каждой отдельной логической строки), является важным отрицательным фактором общей производительности и скорости отклика системы. Процедура восходящего удаления индексных кортежей выбирает на листовой странице предположительно мусорные кортежи на основании качественных характеристик, включающих логические строки и версии. Это диаметрально отличает ее от «нисходящей» уборки индекса, выполняемой рабочими процессами автовакуума, которая запускается при превышении определенных количественных пороговых значений на уровне таблицы (см. подраздел Процесс «Автовакуум»).

Примечание
Не все операции удаления, проводимые в индексах B-деревьях, относятся к операциям восходящего удаления. Существует другая категория таких операций: простое удаление индексных кортежей. Это отложенная операция обслуживания, удаляющая индексные кортежи, которые считаются безопасными для удаления (те, для идентификаторов которых уже установлен бит LP_DEAD). Как и восходящее удаление индексных кортежей, простое удаление происходит в тот момент, когда ожидается разделение страницы, для предотвращения этого разделения.
Простое удаление является спонтанным, в том смысле, что оно может произойти только тогда, когда при недавних сканированиях индекса для обработанных элементов были установлены биты LP_DEAD. Ранее единственной категорией удаления в B-дереве было простое удаление. Главное различие между ним и восходящим удалением состоит в том, что только первое спонтанно обусловлено активностью сканирований индекса, тогда как второе предметно нацелено на отработанные версии, оставляемые командами UPDATE, которые не изменяют логически индексированные столбцы.

Восходящее удаление выполняет основную работу по уборке всех мусорных кортежей из определенных индексов с определенной нагрузкой. Такой эффект ожидается для любого индекса B-дерева, который в значительной степени затрагивается отрабатыванием версий из-за команд UPDATE, редко или вообще никогда не изменяющих логически столбцы, покрываемые индексом. Среднее и наибольшее количество версий для логической строки может поддерживаться на низком уровне исключительно за счет целенаправленных инкрементных проходов удаления. Вполне возможно, что размер определенных индексов на диске никогда не увеличится ни на одну страницу/блок, несмотря на постоянное отрабатывание версий командами UPDATE. Но даже тогда в итоге понадобится полная «зачистка» индекса операцией VACUUM (обычно выполняемой в рабочем процессе autovacuum) как часть совокупной уборки таблицы и всех ее индексов.

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

Дедупликация

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

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

Примечание
Дедупликация в В-дереве также эффективна при работе с «дубликатами», которые содержат значение NULL, несмотря на то, что значения NULL, согласно операторам = из любого класса операторов В-дерева, не считаются равными друг другу. С точки зрения любой части реализации, которая понимает дисковую структуру В-дерева, NULL — это просто еще одно значение из домена значений индекса.

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

Команды CREATE INDEX и REINDEX используют дедупликацию, чтобы создать кортежи со списком идентификаторов, хотя применяемая ими стратегия слегка отличается. Каждая группа обычных дублирующихся кортежей, обнаруженных во взятых из таблицы отсортированных входных данных, объединяется в кортеж со списком идентификаторов до того, как добавляется на текущую ожидающую листовую страницу. В каждый такой кортеж упаковывается максимально возможное количество TID. Листовые страницы записываются обычным способом, без каких-либо отдельных проходов для исключения дубликатов. Эта стратегия очень подходит командам CREATE INDEX и REINDEX, поскольку они относятся к разовым групповым операциям.

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

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

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

Из-за ограничений на уровне реализации дедупликацию можно использовать не везде. Безопасность ее применения определяется при выполнении команды CREATE INDEX или REINDEX.

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

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

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

Дедупликация не может применяться с типом jsonb, поскольку внутри класса операторов В-дерева jsonb используется тип numeric.

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

Есть еще одно дополнительное ограничение на уровне реализации, которое может быть убрано в будущих версиях QHB:

Дедупликация не может применяться с типами-контейнерами (такими как составные или диапазонные типы или массивы).

Есть еще одно дополнительное ограничение на уровне реализации, которое действует независимо от используемого класса оператора или правила сортировки:

Дедупликация не может применяться в индексах с INCLUDE.

Индексы GiST

Введение

GiST расшифровывается как Generalized Search Tree (обобщенное поисковое дерево). Это сбалансированный древовидный метод доступа, работающий как базовый шаблон, в котором реализуется произвольная схема индексирования. С помощью GiST можно реализовать B-деревья, R-деревья и многие другие схемы индексирования.

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

Часть представленной здесь информации взята с сайта Проекта индексации GiST Калифорнийского университета в Беркли и из тезисов диссертации Марселя Корнакера (Marcel Kornacker) «Access Methods for Next-Generation Database Systems».

Встроенные классы операторов

В основной дистрибутив QHB включены классы операторов GiST, перечисленные в Таблице 1. (Некоторые из специализированных модулей, описанных в главе Расширения, предоставляют дополнительные классы операторов GiST.)

Таблица 1. Встроенные классы операторов GiST

Имя

Операторы упорядочивания

Индексируемые операторы

box_ops
<-> (box, point)
<< (box, box)

&< (box, box)

&& (box, box)

&> (box, box)

>> (box, box)

~= (box, box)

@> (box, box)

<@ (box, box)

&<| (box, box)

<<| (box, box)

|>> (box, box)

|&> (box, box)

~ (box, box)

@ (box, box)

circle_ops
<-> (circle, point)
<< (circle, circle)

&< (circle, circle)

&> (circle, circle)

>> (circle, circle)

<@ (circle, circle)

@> (circle, circle)

~= (circle, circle)

&& (circle, circle)

|>> (circle, circle)

<<| (circle, circle)

&<| (circle, circle)

|&> (circle, circle)

@ (circle, circle)

~ (circle, circle)

inet_ops

<< (inet, inet)

<<= (inet, inet)

>> (inet, inet)

>>= (inet, inet)

= (inet, inet)

<> (inet, inet)

< (inet, inet)

<= (inet, inet)

> (inet, inet)

>= (inet, inet)

&& (inet, inet)

multirange_ops

= (anymultirange, anymultirange)

&& (anymultirange, anymultirange)

&& (anymultirange, anyrange)

@> (anymultirange, anyelement)

@> (anymultirange, anymultirange)

@> (anymultirange, anyrange)

<@ (anymultirange, anymultirange)

<@ (anymultirange, anyrange)

<< (anymultirange, anymultirange)

<< (anymultirange, anyrange)

>> (anymultirange, anymultirange)

>> (anymultirange, anyrange)

&< (anymultirange, anymultirange)

&< (anymultirange, anyrange)

&> (anymultirange, anymultirange)

&> (anymultirange, anyrange)

-|- (anymultirange, anymultirange)

-|- (anymultirange, anyrange)

point_ops
<-> (point, point)
|>> (point, point)

<< (point, point)

>> (point, point)

<<| (point, point)

~= (point, point)

<@ (point, box)

<@ (point, polygon)

<@ (point, circle)

poly_ops
<-> (polygon, point)
<< (polygon, polygon)

&< (polygon, polygon)

&> (polygon, polygon)

>> (polygon, polygon)

<@ (polygon, polygon)

@> (polygon, polygon)

~= (polygon, polygon)

&& (polygon, polygon)

<<| (polygon, polygon)

&<| (polygon, polygon)

|&> (polygon, polygon)

|>> (polygon, polygon)

@ (polygon, polygon)

~ (polygon, polygon)

range_ops

= (anyrange, anyrange)

&& (anyrange, anyrange)

&& (anyrange, anymultirange)

@> (anyrange, anyelement)

@> (anyrange, anyrange)

@> (anyrange, anymultirange)

<@ (anyrange, anyrange)

<@ (anyrange, anymultirange)

<< (anyrange, anyrange)

<< (anyrange, anymultirange)

>> (anyrange, anyrange)

>> (anyrange, anymultirange)

&< (anyrange, anyrange)

&< (anyrange, anymultirange)

&> (anyrange, anyrange)

&> (anyrange, anymultirange)

-|- (anyrange, anyrange)

-|- (anyrange, anymultirange)

tsquery_ops

<@ (tsquery, tsquery)

@> (tsquery, tsquery)

tsvector_ops

@@ (tsvector, tsquery)

По историческим причинам класс операторов inet_ops не является классом по умолчанию для типов inet и cidr. Чтобы использовать его, нужно явно указать имя класса в CREATE INDEX, например:

CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops);

Расширяемость

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

Эту расширяемость не следует путать с расширяемостью других стандартных поисковых деревьев относительно типов данных, которые они могут обрабатывать. Например, QHB поддерживает расширяемые B-деревья и хеш-индексы. Это означает, что в QHB можно построить B-дерево или хеш-индекс по любому желаемому типу данных. Но B-деревья поддерживают только предикаты диапазона (<, =, >), а хеш-индексы — только равенства.

Таким образом, если вы проиндексируете, скажем, коллекцию изображений с помощью B-дерева QHB, то сможете делать только запросы вида «изображение А равно изображению Б?», «изображение А меньше изображения Б?» и т. п. В зависимости от того, как вы определяете «равно», «меньше» и «больше» в этом контексте, это может быть полезно. Однако с помощью индекса на основе GiST можно найти способы задавать специфичные для предметной области вопросы, например «найти все изображения лошадей» или «найти все засвеченные фотографии».

Все, что требуется для получения работающего метода доступа GiST, — это реализовать несколько пользовательских методов, определяющих поведение ключей в этом дереве. Конечно, эти методы должны быть довольно замысловатыми, чтобы поддерживать замысловатые запросы, но для всех стандартных запросов (B-деревья, R-деревья и т. д.) они относительно прямолинейны. Другими словами, GiST сочетает в себе расширяемость с универсальностью, повторным использованием кода и простым интерфейсом.

Существует пять методов, которые класс операторов индекса обязан предоставить для GiST, и шесть необязательных. Корректность индекса обеспечивается правильной реализацией методов same, consistent и union, а эффективность (размер и скорость работы) определяется методами penalty и picksplit. Два необязательных метода, compress и decompress, позволяют индексу хранить во внутренних (т.е. не листовых) вершинах дерева данные другого типа, нежели индексируемые. В листьях должны храниться данные индексируемого типа, тогда как в других узлах может храниться любая структура C/RUST (но все же в рамках общих правил QHB, касающихся типов данных; про данные переменной длины говорится в описании varlena). Если внутренний тип данных дерева существует на уровне SQL, в команде CREATE OPERATOR CLASS можно указать параметр STORAGE. Необязательный восьмой метод distance необходимо реализовывать, если классу операторов понадобится поддерживать упорядоченные сканирования (поиски ближайших соседей). Необязательный девятый метод fetch необходимо реализовывать, если классу операторов понадобится поддерживать сканирования только по индексу, за исключением случаев, когда метод compress опущен. Необязательный десятый метод options необходимо реализовывать, если класс операторов содержит параметры, определяемые пользователем. Необязательный одиннадцатый метод sortsupport используется для ускорения построения индекса GiST.

consistent

Для переданной записи индекса p и значения запроса q эта функция определяет, является ли запись индекса «согласованной» с запросом; то есть может ли предикат «индексированный_столбец индексируемый_оператор q» равняться true для какой-либо строки, представленной данной записью индекса? Для листовых записей индекса это равнозначно проверке индексируемого условия, в то время как для внутреннего узла дерева это определяет, необходимо ли сканировать поддерево индекса, представленного этим узлом дерева. Когда результат равен true, также должен вернуться флаг recheck. Это показывает, является ли предикат безусловно подходящим или только возможно подходящим. Если recheck = false, то индекс полностью проверил условие предиката, а если recheck = true, то эта строка является только кандидатом на соответствие. В этом случае система будет автоматически вычислять индексируемый_оператор по фактическому значению строки, чтобы точно узнать, действительно ли оно соответствует запросу. Это соглашение позволяет GiST поддерживать как точные, так и неточные индексы.

В SQL объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal)
RETURNS bool
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

А соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_consistent);

Datum
my_consistent(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    data_type  *query = PG_GETARG_DATA_TYPE_P(1);
    StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
    /* Oid subtype = PG_GETARG_OID(3); */
    bool       *recheck = (bool *) PG_GETARG_POINTER(4);
    data_type  *key = DatumGetDataType(entry->key);
    bool        retval;

    /*
     * определить возвращаемое значение как функцию стратегии, ключа и запроса.
     *
     * использовать GIST_LEAF(entry), чтобы понять, в каком месте дерева произошел
     * вызов, что может быть полезно, например, при реализации оператора =
     * (можно проверить непустое union() в нелистовых узлах и равенство в листовых).
     */

    *recheck = true;        /* или false, если проверка точная */

    PG_RETURN_BOOL(retval);
}

Здесь key — это элемент в индексе, а query — значение, которое ищут в индексе. Параметр StrategyNumber указывает, какой оператор из класса операторов применяется, — он соответствует номеру одного из операторов в команде CREATE OPERATOR CLASS.

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

union

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

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_union(internal, internal)
RETURNS storage_type
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

А соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_union);

Datum
my_union(PG_FUNCTION_ARGS)
{
    GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
    GISTENTRY  *ent = entryvec->vector;
    data_type  *out,
            *tmp,
            *old;
    int         numranges,
                i = 0;

    numranges = entryvec->n;
    tmp = DatumGetDataType(ent[0].key);
    out = tmp;

    if (numranges == 1)
    {
        out = data_type_deep_copy(tmp);

        PG_RETURN_DATA_TYPE_P(out);
    }

    for (i = 1; i < numranges; i++)
    {
        old = out;
        tmp = DatumGetDataType(ent[i].key);
        out = my_union_implementation(out, tmp);
    }

    PG_RETURN_DATA_TYPE_P(out);
}

Как можно видеть, в этой заготовке мы имеем дело с типом данных, где union (X, Y, Z) = union(union(X, Y), Z). Но поддержать типы данных, для которых это не так, тоже будет несложно, если реализовать соответствующий алгоритм объединения в этом вспомогательном методе GiST.

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

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

compress

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

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_compress(internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

А соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_compress);

Datum
my_compress(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    GISTENTRY  *retval;

    if (entry->leafkey)
    {
        /* заменить entry->key заархивированной версией */
        compressed_data_type *compressed_data = palloc(sizeof(compressed_data_type));

        /* заполнить *compressed_data из entry->key ... */

        retval = palloc(sizeof(GISTENTRY));
        gistentryinit(*retval, PointerGetDatum(compressed_data),
                    entry->rel, entry->page, entry->offset, FALSE);
    }
    else
    {
        /* Как правило, для не листовых записей ничего делать не надо */
        retval = entry;
    }

    PG_RETURN_POINTER(retval);
}

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

decompress

Преобразует сохраненное представление элемента данных формат, который могут обрабатывать другие методы GiST в этом классе операторов. Если метод decompress опущен, предполагается, что другие методы GiST могут работать непосредственно с сохраненным форматом данных. (Метод decompress необязательно противоположен методу compress; в частности, если compress неточный, decompress не в состоянии точно восстановить исходные данные. Также decompress не обязательно равнозначен fetch, поскольку другим методам GiST может не требоваться полное восстановление данных.)

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_decompress(internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

А соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_decompress);

Datum
my_decompress(PG_FUNCTION_ARGS)
{
    PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}

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

penalty

Возвращает значение, показывающее «стоимость» добавления новой записи в определенную ветвь дерева. Элементы будут добавлять по пути с наименьшим значением penalty в дереве. Значения, возвращаемые penalty, должны быть неотрицательными. Если возвращается отрицательное значение, оно будет восприниматься как ноль.

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;  -- в некоторых случаях функциям стоимости необязательно быть строгими

А соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_penalty);

Datum
my_penalty(PG_FUNCTION_ARGS)
{
    GISTENTRY  *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
    GISTENTRY  *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
    float      *penalty = (float *) PG_GETARG_POINTER(2);
    data_type  *orig = DatumGetDataType(origentry->key);
    data_type  *new = DatumGetDataType(newentry->key);

    *penalty = my_penalty_implementation(orig, new);
    PG_RETURN_POINTER(penalty);
}

По историческим причинам функция penalty не просто возвращает результат типа float; вместо этого она должна сохранить его значение в расположении, указанном в третьем аргументе. Собственно возвращаемое значение игнорируется, хотя в нем принято возвращать адрес этого аргумента.

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

picksplit

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

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_picksplit(internal, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

А соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_picksplit);

Datum
my_picksplit(PG_FUNCTION_ARGS)
{
    GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
    GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
    OffsetNumber maxoff = entryvec->n - 1;
    GISTENTRY  *ent = entryvec->vector;
    int         i,
                nbytes;
    OffsetNumber *left,
            *right;
    data_type  *tmp_union;
    data_type  *unionL;
    data_type  *unionR;
    GISTENTRY **raw_entryvec;

    maxoff = entryvec->n - 1;
    nbytes = (maxoff + 1) * sizeof(OffsetNumber);

    v->spl_left = (OffsetNumber *) palloc(nbytes);
    left = v->spl_left;
    v->spl_nleft = 0;

    v->spl_right = (OffsetNumber *) palloc(nbytes);
    right = v->spl_right;
    v->spl_nright = 0;

    unionL = NULL;
    unionR = NULL;

    /* Инициализировать чистый вектор записи. */
    raw_entryvec = (GISTENTRY **) palloc(entryvec->n * sizeof(void *));
    for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
        raw_entryvec[i] = &(entryvec->vector[i]);

    for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
    {
        int         real_index = raw_entryvec[i] - entryvec->vector;

        tmp_union = DatumGetDataType(entryvec->vector[real_index].key);
        Assert(tmp_union != NULL);

        /*
         * Выбрать, куда поместить записи индекса и изменить unionL и unionR
         * соответственно. Добавить эти записи либо в v->spl_left, либо в
         * v->spl_right и увеличить счетчики.
         */

        if (my_choice_is_left(unionL, curl, unionR, curr))
        {
            if (unionL == NULL)
                unionL = tmp_union;
            else
                unionL = my_union_implementation(unionL, tmp_union);

            *left = real_index;
            ++left;
            ++(v->spl_nleft);
        }
        else
        {
            /*
             * То же самое для правой стороны
             */
        }
    }

    v->spl_ldatum = DataTypeGetDatum(unionL);
    v->spl_rdatum = DataTypeGetDatum(unionR);
    PG_RETURN_POINTER(v);
}

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

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

same

Возвращает true, если две записи индекса идентичны; в противном случае — false. («Запись индекса» является значением хранимого типа индекса, а не обязательно исходного типа индексируемого столбца.)

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_same(storage_type, storage_type, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

А соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_same);

Datum
my_same(PG_FUNCTION_ARGS)
{
    prefix_range *v1 = PG_GETARG_PREFIX_RANGE_P(0);
    prefix_range *v2 = PG_GETARG_PREFIX_RANGE_P(1);
    bool       *result = (bool *) PG_GETARG_POINTER(2);

    *result = my_eq(v1, v2);
    PG_RETURN_POINTER(result);
}

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

distance

Для записи индекса p и значения запроса q эта функция определяет «расстояние» между записью индекса и значением запроса. Эту функцию необходимо представлять, если класс операторов содержит какие-либо операторы упорядочивания. Запрос, использующий оператор упорядочивания, будет реализован путем возвращения записей индекса с наименьшими значениями «расстояния» первыми, поэтому результат должен быть согласован с семантикой оператора. Для листовой записи индекса результат просто представляет расстояние до записи индекса; для внутреннего узла дерева результат должен быть наименьшим расстоянием, которое может быть получено среди дочерних записей.

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_distance(internal, data_type, smallint, oid, internal)
RETURNS float8
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

А соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_distance);

Datum
my_distance(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    data_type  *query = PG_GETARG_DATA_TYPE_P(1);
    StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
    /* Oid subtype = PG_GETARG_OID(3); */
    /* bool *recheck = (bool *) PG_GETARG_POINTER(4); */
    data_type  *key = DatumGetDataType(entry->key);
    double      retval;

    /*
     * определить возвращаемое значение как функцию стратегии, ключа и запроса.
     */

    PG_RETURN_FLOAT8(retval);
}

Аргументы у функции distance такие же, как аргументы функции consistent.

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

Если функция расстояния возвращает *recheck = true для любого листового узла, то возвращаемый тип исходного оператора упорядочивания должен быть float8 или float4, а результирующие значения функции расстояния должны быть совместимы со значениями исходного оператора упорядочивания, поскольку исполнитель будет проводить сортировку, используя как результаты функции расстояния, так и пересчитанные результаты оператора упорядочивания. В противном случае результирующие значения функции расстояния могут быть любыми конечными значениями float8, при условии, что относительный порядок этих результирующих значений соответствует порядку, возвращенному оператором упорядочивания. (Бесконечность и минус бесконечность применяются внутри для работы с особыми случаями, например с NULL, поэтому возвращать такие значения из функций distance не рекомендуется.)

fetch

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

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_fetch(internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

Аргумент является указателем на структуру GISTENTRY. В записи поле key содержит отличные от NULL листовые данные в сжатом виде. Возвращаемое значение — это еще одна структура GISTENTRY, чье поле key содержит те же данные в исходном, несжатом виде. Если функция сжатия класса операторов ничего не делает для листовых записей, то метод fetch может возвращать этот аргумент как есть. Или, если у класса операторов нет функции сжатия, метод fetch тоже можно опустить, поскольку он точно не будет ничего делать.

Соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_fetch);

Datum
my_fetch(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    input_data_type *in = DatumGetPointer(entry->key);
    fetched_data_type *fetched_data;
    GISTENTRY  *retval;

    retval = palloc(sizeof(GISTENTRY));
    fetched_data = palloc(sizeof(fetched_data_type));

    /*
     * Преобразовать 'fetched_data' в Datum исходного типа данных.
     */

    /* заполнить *retval из fetched_data. */
    gistentryinit(*retval, PointerGetDatum(converted_datum),
                entry->rel, entry->page, entry->offset, FALSE);

    PG_RETURN_POINTER(retval);
}

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

options

Позволяет определить видимые пользователю параметры, управляющие поведением класса операторов.

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_options(internal)
RETURNS void
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

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

Ниже показан пример реализации функции my_options() и использования параметров из других вспомогательных функций:

typedef enum MyEnumType
{
    MY_ENUM_ON,
    MY_ENUM_OFF,
    MY_ENUM_AUTO
} MyEnumType;

typedef struct
{
    int32   vl_len_;    /* заголовок varlena (не затрагивайте его напрямую!) */
    int     int_param;  /* целочисленный параметр */
    double  real_param; /* параметр с плавающей запятой */
    MyEnumType enum_param; /* перечислимый параметр */
    int     str_param;  /* строковый параметр */
} MyOptionsStruct;

/* Строковое представление перечислимых значений */
static relopt_enum_elt_def myEnumValues[] =
{
    {"on", MY_ENUM_ON},
    {"off", MY_ENUM_OFF},
    {"auto", MY_ENUM_AUTO},
    {(const char *) NULL}   /* завершающий элемент списка */
};

static char *str_param_default = "default";

/*
 * Пример функции проверки допустимости: проверяет, что строка не длиннее 8 байт.
 */
static void
validate_my_string_relopt(const char *value)
{
    if (strlen(value) > 8)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("str_param must be at most 8 bytes")));
}

/*
 * Пример функции-заполнителя: переводит символы в нижний регистр.
 */
static Size
fill_my_string_relopt(const char *value, void *ptr)
{
    char   *tmp = str_tolower(value, strlen(value), DEFAULT_COLLATION_OID);
    int     len = strlen(tmp);

    if (ptr)
        strcpy((char *) ptr, tmp);

    pfree(tmp);
    return len + 1;
}

PG_FUNCTION_INFO_V1(my_options);

Datum
my_options(PG_FUNCTION_ARGS)
{
    local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0);

    init_local_reloptions(relopts, sizeof(MyOptionsStruct));
    add_local_int_reloption(relopts, "int_param", "integer parameter",
                            100, 0, 1000000,
                            offsetof(MyOptionsStruct, int_param));
    add_local_real_reloption(relopts, "real_param", "real parameter",
                             1.0, 0.0, 1000000.0,
                             offsetof(MyOptionsStruct, real_param));
    add_local_enum_reloption(relopts, "enum_param", "enum parameter",
                             myEnumValues, MY_ENUM_ON,
                             "Valid values are: \"on\", \"off\" and \"auto\".",
                             offsetof(MyOptionsStruct, enum_param));
    add_local_string_reloption(relopts, "str_param", "string parameter",
                               str_param_default,
                               &validate_my_string_relopt,
                               &fill_my_string_relopt,
                               offsetof(MyOptionsStruct, str_param));

    PG_RETURN_VOID();
}

PG_FUNCTION_INFO_V1(my_compress);

Datum
my_compress(PG_FUNCTION_ARGS)
{
    int     int_param = 100;
    double  real_param = 1.0;
    MyEnumType enum_param = MY_ENUM_ON;
    char   *str_param = str_param_default;

    /*
     * Обычно когда класс операторов содержит метод 'options', параметры всегда
     * передаются вспомогательным функциям. Однако если вы добавите метод 'options'
     * в существующий класс операторов, в ранее определенных индексах параметров
     * не будет, поэтому необходима эта проверка.
     */
    if (PG_HAS_OPCLASS_OPTIONS())
    {
        MyOptionsStruct *options = (MyOptionsStruct *) PG_GET_OPCLASS_OPTIONS();

        int_param = options->int_param;
        real_param = options->real_param;
        enum_param = options->enum_param;
        str_param = GET_STRING_RELOPTION(options, str_param);
    }

    /* продолжение реализации вспомогательной функции */
}

Поскольку в GiST представление ключа гибкое, оно может зависеть от параметров, указанных пользователем. Например, можно задать длину ключа подписи. В качестве примера рассмотрите функцию gtsvector_options().

sortsupport

Возвращает функцию-компаратор для сортировки данных способом, сохраняющим локальность. Она используется командами CREATE INDEX и REINDEX. Качество построенного индекса зависит от того, насколько хорошо порядок сортировки, определенный функцией-компаратором, сохраняет локальность входных данных.

Метод sortsupport является необязательным. Если он не предоставляется, CREATE INDEX создает индекс путем добавления каждого кортежа в дерево с помощью функций penalty и picksplit, что происходит гораздо медленнее.

SQL-объявление функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_sortsupport(internal)
RETURNS void
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

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

Соответствующий код в модуле C можно реализовать по этой заготовке:

PG_FUNCTION_INFO_V1(my_sortsupport);

static int
my_fastcmp(Datum x, Datum y, SortSupport ssup)
{
  /* установить порядок между x и y, вычислив некое сортирующее значение z */

  int z1 = ComputeSpatialCode(x);
  int z2 = ComputeSpatialCode(y);

  return z1 == z2 ? 0 : z1 > z2 ? 1 : -1;
}

Datum
my_sortsupport(PG_FUNCTION_ARGS)
{
  SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);

  ssup->comparator = my_fastcmp;
  PG_RETURN_VOID();
}

Все вспомогательные методы GiST обычно вызываются в кратковременных контекстах памяти, то есть CurrentMemoryContext сбрасывается после обработки каждого кортежа. Таким образом, необязательно беспокоиться об освобождении с помощью pfree любых блоков памяти, выделенных функцией palloc. Однако в некоторых случаях вспомогательному методу полезно кэшировать данные между вызовами. Для этого нужно разместить долгоживущие данные в контексте fcinfo->flinfo->fn_mcxt и хранить указатель на них в fcinfo->flinfo->fn_extra. Время жизни таких данных — одна операция с индексом (например одно сканирование индекса GiST, построение индекса или добавление индексного кортежа). Заменяя значение fn_extra, не забудьте освободить предыдущее значение, иначе в ходе операции накопятся утечки памяти.

Реализация

Способы построения индексов GiST

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

Метод с сортировкой доступен, только если все классы операторов, используемые индексом, предоставляют функцию sortsupport, как описано в разделе Расширяемость. В таком случае этот метод обычно наиболее оптимален, поэтому используется по умолчанию.

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

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

Если сортировка невозможна, по умолчанию построение индекса GiST переключается на метод с буферизацией, когда размер индекса достигает значения effective_cache_size. Буферизацию можно принудительно включить или выключить вручную с помощью параметра buffering команды CREATE INDEX. Поведение по умолчанию подходит в большинстве случаев, но если входные данные упорядочены, выключение буферизации может некоторым образом ускорить построение индекса.

Примеры

Пакет QHB содержит несколько примеров индексных методов, реализованных на основе GiST. В настоящее время базовая система предоставляет поддержку полнотекстового поиска (индексирование типов tsvector и tsquery), а также функциональность, равнозначную R-дереву, для некоторых встроенных геометрических типов данных.

Классы операторов GiST содержатся также в следующих модулях из share/extension:

btree_gist
Функциональность, равнозначная B-дереву, для нескольких типов данных

cube
Индексирование для многомерных кубов

hstore
Модуль для хранения пар (ключ, значение)

intarray
RD-дерево для одномерных массивов значений int4

ltree
Индексирование для древовидных структур

pg_trgm
Схожесть текстов с помощью сопоставления триграмм

seg
Индексирование «диапазонов чисел с плавающей запятой»

Индексы SP-GiST

Введение

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

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

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

Часть представленной здесь информации взята с сайта Проекта индексации SP-GiST Университета Пердью.

Встроенные классы операторов

В основной дистрибутив QHB включены классы операторов SP-GiST, перечисленные в Таблице 1.

Таблица 1. Встроенные классы операторов SP-GiST

Имя

Операторы упорядочивания

Индексируемые операторы

box_ops
<-> (box, point)
<< (box, box)

&< (box, box)

&> (box, box)

>> (box, box)

<@ (box, box)

@> (box, box)

~= (box, box)

&& (box, box)

<<| (box, box)

&<| (box, box)

|&> (box, box)

|>> (box, box)

kd_point_ops
<-> (point,point)
|>> (point,point)

<< (point,point)

>> (point,point)

<<| (point,point)

~= (point,point)

<@ (point,box)

network_ops

<< (inet, inet)

<<= (inet, inet)

>> (inet, inet)

>>= (inet, inet)

= (inet, inet)

<> (inet, inet)

< (inet, inet)

<= (inet, inet)

> (inet, inet)

>= (inet, inet)

&& (inet, inet)

poly_ops
<-> (polygon,point)
<< (polygon,polygon)

&< (polygon,polygon)

&> (polygon,polygon)

>> (polygon,polygon)

<@ (polygon,polygon)

@> (polygon,polygon)

~= (polygon,polygon)

&& (polygon,polygon)

<<| (polygon,polygon)

&<| (polygon,polygon)

|>> (polygon,polygon)

|&> (polygon,polygon)

quad_point_ops
<-> (point, point)
|>> (point, point)

<< (point, point)

>> (point, point)

<<| (point, point)

~= (point, point)

<@ (point, box)

range_ops

= (anyrange,anyrange)

&& (anyrange,anyrange)

@> (anyrange,anyelement)

@> (anyrange,anyrange)

<@ (anyrange,anyrange)

<< (anyrange,anyrange)

>> (anyrange,anyrange)

&< (anyrange,anyrange)

&> (anyrange,anyrange)

-|- (anyrange,anyrange)

text_ops

= (text,text)

< (text,text)

<= (text,text)

> (text,text)

>= (text,text)

~<~ (text,text)

~<=~ (text,text)

~>=~ (text,text)

~>~ (text,text)

^@ (text,text)

Из двух классов операторов для типа point классом по умолчанию является quad_point_ops. Класс kd_point_ops поддерживает те же операторы, но использует другую структуру данных индекса, которая может обеспечить лучшую производительность в некоторых приложениях.

Классы операторов quad_point_ops, kd_point_ops и poly_ops поддерживают оператор упорядочения <->, который позволяет выполнять поиск k ближайших соседей (k-NN) по индексированному набору данных точек или многоугольников.

Расширяемость

SP-GiST предлагает интерфейс с высоким уровнем абстракции, требующий от разработчика методов доступа реализовать только методы, специфичные для указанного типа данных. Ядро SP-GiST отвечает за эффективное сопоставление с данными на диске и поиск в древовидной структуре. Кроме того он отвечает за конкурентный доступ и вопросы протоколирования.

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

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

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

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

Примечание
Код ядра SP-GiST отвечает за записи NULL. Хотя индексы SP-GiST и хранят записи для NULL в индексируемых столбцах, это скрыто от кода классов операторов индекса: записи индекса или условия поиска с NULL никогда не будут передаваться методам класса операторов. (Предполагается, что операторы SP-GiST строгие и поэтому не могут выдавать положительный результат для значений NULL.) В связи с этим значения NULL здесь больше рассматриваться не будут.

Существует пять пользовательских методов, которые класс операторов индекса должен предоставить для SP-GiST, и еще два необязательных. Все пять обязательных методов следуют соглашению о принятии двух аргументов internal, первый из которых является указателем на структуру C/RUST, содержащую входные значения для вспомогательного метода, тогда как второй аргумент является указателем на структуру C/RUST, в которую следует поместить выходные значения. Четыре из обязательных методов просто возвращают void, поскольку все их результаты находятся в выходной структуре; но метод leaf_consistent возвращает результат boolean. Эти методы не должны изменять никакие поля своих входных структур. Во всех случаях выходная структура обнуляется перед вызовом пользовательского метода. Необязательный шестой метод compress принимает единственный аргумент datum, который индексируется и возвращает значение, подходящее для физического хранения в листовом кортеже. Необязательный седьмой метод options принимает указатель internal на структуру C/RUST, в которую должны помещаться параметры, специфичные для класса операторов, и возвращает void.

Пять обязательных пользовательских методов:

config

Возвращает статическую информацию о реализации индекса, включая OID типов данных префикса и метки узла.

SQL-объявление этой функции должно выглядеть следующим образом:

CREATE FUNCTION my_config(internal, internal) RETURNS void ...

Первый аргумент является указателем на структуру на C spgConfigIn, содержащую входные данные для функции. Второй аргумент является указателем на структуру на C spgConfigOut, которую функция должна заполнить результирующими данными.

typedef struct spgConfigIn
{
    Oid         attType;        /* Индексируемый тип данных */
} spgConfigIn;

typedef struct spgConfigOut
{
    Oid         prefixType;     /* Тип данных префикса внутреннего кортежа */
    Oid         labelType;      /* Тип данных метки внутреннего кортежа */
    Oid         leafType;       /* Тип данных значений листового кортежа */
    bool        canReturnData;  /* Класс операторов умеет реконструировать исходные данные */
    bool        longValuesOK;   /* Класс операторов умеет обрабатывать значения объемом > 1 страницы */
} spgConfigOut;

Поле attType передается для поддержки полиморфных классов операторов индекса; для обычных классов операторов с фиксированным типом данных оно всегда будет иметь одно и то же значение и поэтому может игнорироваться.

Для классов операторов, не использующих префиксы, в prefixType можно задать VOIDOID. Аналогично для классов операторов, не использующих метки узлов, в labelType можно задать VOIDOID. Флаг canReturnData следует установить в true, если класс операторов способен восстанавливать изначально предоставленное индексу значение. Флаг longValuesOK следует установить в true, только если attType имеет переменную длину и класс оператора умеет нарезать длинные значения, нарезая суффиксы (см. подраздел Ограничения SP-GiST).

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

Когда attType и leafType различаются, должен предоставляться необязательный метод compress. Метод compress отвечает за преобразование индексируемых данных из типа attType в тип leafType.

choose

Выбирает метод добавления нового значения во внутренний кортеж.

SQL-объявление этой функции должно выглядеть следующим образом:

CREATE FUNCTION my_choose(internal, internal) RETURNS void ...

Первый аргумент является указателем на структуру на C spgChooseIn, содержащую входные данные для функции. Второй аргумент является указателем на структуру на C spgChooseOut, которую функция должна заполнить результирующими данными.

typedef struct spgChooseIn
{
    Datum       datum;          /* исходное значение, которое должно индексироваться */
    Datum       leafDatum;      /* текущие данные, которые должны сохраниться в листе */
    int         level;          /* текущий уровень (считая с нуля) */

    /* Данные из текущего внутреннего кортежа */
    bool        allTheSame;     /* кортеж помечен как имеющий признак все-равны? */
    bool        hasPrefix;      /* у кортежа есть префикс? */
    Datum       prefixDatum;    /* если есть, то значение префикса */
    int         nNodes;         /* количество узлов в этом внутреннем кортеже */
    Datum      *nodeLabels;     /* значения меток узлов (NULL, если меток нет) */
} spgChooseIn;

typedef enum spgChooseResultType
{
    spgMatchNode = 1,           /* спуститься в существующий узел */
    spgAddNode,                 /* добавить узел во внутренний кортеж */
    spgSplitTuple               /* разбить внутренний кортеж (изменить его префикс) */
} spgChooseResultType;

typedef struct spgChooseOut
{
    spgChooseResultType resultType;     /* код действия, см. выше */
    union
    {
        struct                  /* результаты для spgMatchNode */
        {
            int         nodeN;      /* спуститься до этого узла (считая от 0) */
            int         levelAdd;   /* увеличить уровень на это число */
            Datum       restDatum;  /* новое листовое значение */
        }           matchNode;
        struct                  /* результаты для spgAddNode */
        {
            Datum       nodeLabel;  /* метка нового узла */
            int         nodeN;      /* куда ее добавить (считая от 0) */
        }           addNode;
        struct                  /* результаты для spgSplitTuple */
        {
            /* Информация для формирования нового внутреннего кортежа верхнего
             * уровня с одним дочерним кортежем */
            bool        prefixHasPrefix;    /* кортеж должен иметь префикс? */
            Datum       prefixPrefixDatum;  /* если да, то его значение */
            int         prefixNNodes;       /* число узлов */
            Datum      *prefixNodeLabels;   /* их метки (или NULL, если меток нет) */
            int         childNodeN;         /* какой узел получит дочерний кортеж */

            /* Информация для формирования нового внутреннего кортежа нижнего
             * уровня со всеми старыми узлами */
            bool        postfixHasPrefix;   /* кортеж должен иметь префикс? */
            Datum       postfixPrefixDatum; /* если да, то его значение */
        }           splitTuple;
    }           result;
} spgChooseOut;

В поле datum передаются исходные данные spgConfigIn.attType, которые должны были быть вставлены в индекс. В поле leafDatum передается значение типа spgConfigOut.leafType, которое изначально является результатом метода compress, примененного к datum, когда этот метод предоставляется, или, в противном случае, — самим значением datum. leafDatum может меняться на нижних уровнях дерева, если его меняют методы choose или picksplit. Когда поиск места добавления достигает листовой страницы, текущим значением leafDatum становятся данные, которые будут храниться во вновь созданном листовом кортеже. В level передается текущий уровень листового кортежа, начиная с нуля для уровня корня. allTheSame имеет значение true, если текущий внутренний кортеж помечен как содержащий несколько равнозначных узлов (см. подраздел Внутренние кортежи «все-равны»). Флаг hasPrefix имеет значение true, если текущий внутренний кортеж содержит префикс; и если содержит, то его значением является prefixDatum. В nNodes передается число дочерних узлов, содержащихся во внутреннем кортеже, а в nodeLabels — массив значений их меток или NULL, если меток нет.

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

Если новое значение соответствует одному из существующих дочерних узлов, задайте в resultType значение spgMatchNode. Задайте в nodeN номер этого узла (начиная с нуля) в массиве узлов. Задайте в levelAdd шаг увеличения уровня (level), вызванного спуском через этот узел, или оставьте ноль, если класс операторов не использует уровни. Установите restDatum равным leafDatum, если класс операторов не меняет данные от уровня к уровню, или задайте в нем измененное значение, которое на следующем уровне будет использовано в качестве значения leafDatum.

Если необходимо добавить новый дочерний узел, установите resultType равным spgAddNode. Задайте в nodeLabel метку, которая будет использоваться для нового узла, а в nodeN — номер (начиная с нуля), под которым этот узел будет вставлен в массив узлов. После добавления узла снова будет вызвана функция choose с измененным внутренним кортежем; этот вызов должен привести к результату spgMatchNode.

Если новое значение не соответствует префиксу кортежа, установите resultType равным spgSplitTuple. Это действие перемещает все существующие узлы в новый внутренний кортеж нижнего уровня и заменяет текущий внутренний кортеж на кортеж с единственной нисходящей ссылкой, указывающей на новый внутренний кортеж нижнего уровня. Установите prefixHasPrefix, чтобы указать, должен ли новый верхний кортеж иметь префикс, и если должен, то задайте в prefixPrefixDatum значение этого префикса. Это новое значение префикса должно быть гораздо менее строгим, чем исходное, чтобы принять новое индексируемое значение. Задайте в prefixNNodes число узлов, необходимых в новом кортеже, а в prefixNodeLabels — указатель на выделенный с помощью palloc массив, содержащий их метки, или NULL, если метки узлов не требуются. Обратите внимание, что общий размер нового верхнего кортежа не должен превышать общий размер кортежа, который он замещает; это ограничивает длину нового префикса и новых меток. Задайте в childNodeN номер узла (начиная с нуля), который будет ссылаться на новый внутренний кортеж нижнего уровня. Установите postfixHasPrefix, чтобы указать, должен ли новый внутренний кортеж нижнего уровня иметь префикс, и если должен, задайте значение этого префикса в postfixPrefixDatum. Сочетание этих двух префиксов и метки узла (если таковая имеется) должно иметь то же семантическое значение, что и исходный префикс, потому что нет возможности изменить ни метки узлов, перемещенных в новый внутренний кортеж нижнего уровня, ни дочерние записи индекса. После разбиения узла снова будет вызвана функция choose с заменяемым внутренним кортежем. Этот вызов может вернуть результат spgAddNode, если действием spgSplitTuple не было создано ни одного подходящего узла. В конце концов choose должна вернуть spgMatchNode, чтобы позволить операции добавления спуститься на следующий уровень.

picksplit

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

SQL-объявление этой функции должно выглядеть следующим образом:

CREATE FUNCTION my_picksplit(internal, internal) RETURNS void ...

Первый аргумент является указателем на структуру на C spgPickSplitIn, содержащую входные данные для функции. Второй аргумент является указателем на структуру на C spgPickSplitOut, которую функция должна заполнить результирующими данными.

typedef struct spgPickSplitIn
{
    int         nTuples;        /* число листовых кортежей */
    Datum      *datums;         /* их данные (массив длиной nTuples) */
    int         level;          /* текущий уровень (считая от 0) */
} spgPickSplitIn;

typedef struct spgPickSplitOut
{
    bool        hasPrefix;      /* должен ли новый кортеж иметь префикс? */
    Datum       prefixDatum;    /* если должен, значение этого префикса */

    int         nNodes;         /* число узлов для нового внутреннего кортежа */
    Datum      *nodeLabels;     /* их метки (или NULL, если меток нет) */

    int        *mapTuplesToNodes;   /* номер узла для каждого листового кортежа */
    Datum      *leafTupleDatums;    /* данные, помещаемые в каждый новый листовой кортеж */
} spgPickSplitOut;

В nTuples передается число предоставленных листовых кортежей. В datums — массив значений их данных типа spgConfigOut.leafType. В level — текущий уровень, общий для всех листовых кортежей, который станет уровнем нового внутреннего кортежа.

Установите hasPrefix, чтобы указать, должен ли новый внутренний кортеж иметь префикс, и если должен, то задайте в prefixDatum значение этого префикса. Установите nNodes, чтобы указать количество узлов, которое будет содержать новый внутренний кортеж, и задайте в nodeLabels массив значений их меток или NULL, если метки узлов не требуются. Укажите в mapTuplesToNodes массив, назначающий номера (считая от нуля) узлов каждому листовому кортежу. Задайте в leafTupleDatums массив значений, которые будут храниться в новых листовых кортежах (они будут совпадать со входными данными (datums), если класс операторов не изменяет данные от уровня к уровню). Обратите внимание, что функция picksplit сама отвечает за выделение памяти под массивы nodeLabels, mapTuplesToNodes и leafTupleDatums с помощью функции palloc.

Если предоставляется больше одного листового кортежа, то ожидается, что функция picksplit классифицирует их и разделит на несколько узлов; иначе невозможно разнести листовые кортежи на несколько страниц, что является конечной целью этой операции. Таким образом, если в итоге функция picksplit помещает все листовые кортежи в один узел, код ядра SP-GiST переопределит это решение и сгенерирует внутренний кортеж, в котором листовые кортежи будут случайным образом связываются с несколькими узлами с одинаковыми метками. Такой кортеж помечается флагом allTheSame, показывающим, что все узлы равны. Функции choose и inner_consistent должны обращаться с такими внутренними кортежами надлежащим образом. Подробную информацию см. в подразделе Внутренние кортежи «все-равны».

Функцию picksplit можно применить к одному листовому кортежу только в том случае, если функция config установила в longValuesOK значение true и на вход было передано значение, не помещающееся на одной странице. Тогда смысл этой операции состоит в том, чтобы убрать префикс и создать новое, более короткое значение листовых данных. Этот вызов будет повторяться, пока листовое значение не станет достаточно коротким, чтобы поместиться на странице. Подробную информацию см. в подразделе Ограничения SP-GiST.

inner_consistent

Возвращает набор узлов (ветвей), по которым надо следовать при поиске по дереву.

SQL-объявление этой функции должно выглядеть следующим образом:

CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ...

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

typedef struct spgInnerConsistentIn
{
    ScanKey     scankeys;       /* массив операторов и сравниваемых значений */
    ScanKey     orderbys;       /* массив операторов упорядочивания и сравниваемых значений */
    int         nkeys;          /* длина массива scankeys */
    int         norderbys;      /* длина массива orderbys */

    Datum       reconstructedValue;     /* значение, восстановленное для родителя */
    void       *traversalValue; /* перемещаемое значение, специфичное для класса операторов */
    MemoryContext traversalMemoryContext;   /* поместить перемещаемые значения сюда */
    int         level;          /* текущий уровень (считая от 0) */
    bool        returnData;     /* нужно ли возвращать исходные данные? */

    /* Данные из текущего внутреннего кортежа */
    bool        allTheSame;     /* кортежа помечен как имеющий признак все-равны? */
    bool        hasPrefix;      /* кортеж имеет префикс? */
    Datum       prefixDatum;    /* если имеет, значение этого префикса */
    int         nNodes;         /* число узлов во внутреннем кортеже */
    Datum      *nodeLabels;     /* значения меток узлов (NULL, если меток нет) */
} spgInnerConsistentIn;

typedef struct spgInnerConsistentOut
{
    int         nNodes;         /* число дочерних узлов, которые нужно посетить */
    int        *nodeNumbers;    /* их номера в массиве узлов */
    int        *levelAdds;      /* увеличения уровня на это число для этих узлов */
    Datum      *reconstructedValues;    /* связанные восстановленные значения */
    void      **traversalValues;        /* перемещаемые значения, специфичные для класса операторов */
    double    **distances;      /* связанные расстояния */
} spgInnerConsistentOut;

Массив scankeys длины nkeys описывает условия поиска по индексу. Эти условия объединяются операцией И — только записи индекса, удовлетворяющие им всем, считаются подходящими. (Обратите внимание, что вариант nkeys = 0 означает, что запросу удовлетворяют все записи индекса). Обычно функция, оценивающая согласованность, обрабатывает только поля sk_strategy и sk_argument каждой записи массива, которые выдают индексируемый оператор и сравниваемое значение соответственно. В частности, нет необходимости проверять sk_flags на предмет, равно ли сравниваемое значение NULL, потому что код ядра SP-GiST отфильтрует такие условия. Массив orderbys длины norderbys аналогичным образом описывает операторы упорядочивания (если таковые имеются). В reconstructedValue передается значение, восстановленное для родительского кортежа; оно равно (Datum) 0 на уровне корня или если функция inner_consistent не предоставила значение на уровне родителя. В traversalValue передается указатель на все перемещаемые данные, спущенные при предыдущем вызове inner_consistent для родительского кортежа индекса, или NULL для уровня корня. В traversalMemoryContext передается контекст памяти, в котором будут храниться выходные перемещаемые значения (см. ниже). В level задается текущий уровень внутреннего кортежа, начиная с нуля для уровня корня. Флаг returnData имеет значение true, если для этого запроса требуются восстановленные данные; они понадобятся, только если функция config установила флаг canReturnData. Флаг allTheSame имеет значение true, если текущий внутренний кортеж помечен как имеющий свойство «все равны»; в этом случае у всех узлов будет одинаковая метка (если эта метка есть), и поэтому либо все, либо ни один из них не соответствует запросу (см. подраздел Внутренние кортежи «все-равны»). Флаг hasPrefix имеет значение true, если текущий внутренний кортеж содержит префикс; если это так, значение этого префикса задается в prefixDatum. В nNodes передается число дочерних узлов, содержащихся во внутреннем кортеже, а в nodeLabels — массив значений их меток или NULL, если у этих узлов нет меток.

В поле nNodes должно быть задано число дочерних узлов, которые нужно посетить при поиске, а в nodeNumbers — массив их номеров. Если класс операторов отслеживает уровни, задайте в levelAdds массив с шагами увеличения уровня, требуемыми при спуске к каждому узлу, который нужно посетить. (Зачастую эти шаги будут одинаковы для всех узлов, но не всегда, поэтому применяется массив.) Если требуется восстановление значения, задайте в reconstructedValues массив значений, восстановленных для каждого узла, который нужно посетить; в противном случае оставьте reconstructedValues равным NULL. Предполагается, что восстановленные значения имеют тип spgConfigOut.leafType. (Однако, поскольку базовая система ничего не будет с ними делать, кроме как, возможно, копировать, им достаточно иметь те же свойства typlen и typbyval, что и leafType.) Если проводится упорядоченный поиск, задайте в distances массив значений расстояний в соответствии с массивом orderbys (узлы с наименьшими расстояниями будут обрабатываться первыми). В противном случае оставьте это поле равным NULL. Если есть желание при поиске по дереву передать на нижние уровни дополнительную вспомогательную информацию («перемещаемые значения»), задайте в traversalValues массив соответствующих перемещаемых значений, по одному для каждого дочернего узла, который нужно посетить; в противном случае оставьте traversalValues равным NULL. Обратите внимание, что функция inner_consistent сама отвечает за выделение памяти с помощью функции palloc под массивы nodeNumbers, levelAdds, distances, reconstructedValues и traversalValues в текущем контексте памяти. Однако любые выходные перемещаемые значения, на которые указывает массив traversalValues, должны размещаться в контексте traversalMemoryContext. Каждое перемещаемое значение должно находиться в отдельном блоке памяти, выделенном palloc.

leaf_consistent

Возвращает true, если листовой кортеж удовлетворяет запросу.

SQL-объявление этой функции должно выглядеть следующим образом:

CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ...

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

typedef struct spgLeafConsistentIn
{
    ScanKey     scankeys;       /* массив операторов и сравниваемых значений */
    ScanKey     orderbys;       /* массив операторов упорядочивания и сравниваемых значений */
    int         nkeys;          /* длина массива scankeys */
    int         norderbys;      /* длина массива orderbys */

    Datum       reconstructedValue;     /* значение, восстановленное для родителя */
    void       *traversalValue; /* перемещаемое значение, специфичное для класса операторов */
    int         level;          /* текущий уровень (считая от нуля) */
    bool        returnData;     /* нужно ли возвращать исходные данные? */

    Datum       leafDatum;      /* данные в листовом кортеже */
} spgLeafConsistentIn;

typedef struct spgLeafConsistentOut
{
    Datum       leafValue;        /* восстановленные исходные данные, если имеются */
    bool        recheck;          /* true, если надо перепроверить оператор */
    bool        recheckDistances; /* true, если надо перепроверить расстояния */
    double     *distances;        /* связанные расстояния  */
} spgLeafConsistentOut;

Массив scankeys длины nkeys описывает условия поиска в индексе. Эти условия объединяются операцией логического умножения — только записи индекса, удовлетворяющие им всем, считаются подходящими. (Обратите внимание, что вариант nkeys = 0 означает, что запросу удовлетворяют все записи индекса.) Обычно функция, оценивающая согласованность, обрабатывает только поля sk_strategy и sk_argument каждой записи массива, которые выдают индексируемый оператор и сравниваемое значение соответственно. В частности, нет необходимости проверять sk_flags на предмет, равно ли сравниваемое значение NULL, потому что код ядра SP-GiST отфильтрует такие условия. Массив orderbys длины norderbys аналогичным образом описывает операторы упорядочивания. В reconstructedValue передается значение, восстановленное для родительского кортежа; оно равно (Datum) 0 на уровне корня или если функция inner_consistent не предоставила значение на уровне родителя. В traversalValue передается указатель на все перемещаемые данные, спущенные при предыдущем вызове inner_consistent для родительского кортежа индекса, или NULL для уровня корня. В level задается текущий уровень листового кортежа, начиная с нуля для уровня корня. Флаг returnData равен true, если для этого запроса требуются восстановленные данные; они понадобятся, только если функция config установила флаг canReturnData. В leafDatum передается значение ключа spgConfigOut.leafType, хранящееся в текущем листовом кортеже.

Эта функция должна возвращать true, если листовой кортеж соответствует запросу, иначе false. Если результат равен true и returnData имеет значение true, то в leafValue должно быть задано значение (типа spgConfigIn.attType), изначально переданное для индексации в этот кортеж. Кроме того, в recheck можно установить true, если соответствие не точное, и поэтому для подтверждения соответствия к фактическому кортежу кучи надо повторно применить оператор(ы). Если выполняется упорядоченный поиск, задайте в distances массив значений расстояний в соответствии с массивом orderbys. В противном случае оставьте его равным NULL. Если хотя бы одно из возвращенных расстояний не точное, установите в recheckDistances значение true. В этом случае исполнитель вычислит точные расстояния после извлечения кортежа из кучи и при необходимости переупорядочит кортежи.

Необязательные пользовательские методы:

Datum compress(Datum in)

Преобразует элемент данных в формат, подходящий для физического хранения в листовом кортеже индекса. Эта функция принимает значение типа spgConfigIn.attType и возвращает значение типа spgConfigOut.leafType. Выходное значение не должно содержать указатель на внешние TOAST-данные.

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

options

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

SQL-объявление этой функции должно выглядеть следующим образом:

CREATE OR REPLACE FUNCTION my_options(internal)
RETURNS void
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

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

Поскольку представление ключа в SP-GiST гибкое, оно может зависеть от параметров, определяемых пользователем.

Все вспомогательные методы SP-GiST обычно вызываются в кратковременных контекстах памяти, то есть CurrentMemoryContext сбрасывается после обработки каждого кортежа. Таким образом, необязательно беспокоиться об освобождении с помощью pfree любых блоков памяти, выделенных функцией palloc. (Метод config является исключением: в нем следует избегать утечек памяти. Но обычно метод config не делает ничего, помимо присвоения констант переданной структуре параметров.)

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

Реализация

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

Ограничения SP-GiST

Отдельные листовые и внутренние кортежи должны помещаться на одной странице индекса (по умолчанию 8кБ). Поэтому при индексировании значений типов данных переменной длины длинные значения могут поддерживаться только такими методами, как префиксные деревья, в которых каждый уровень дерева включает префикс, достаточно короткий, чтобы уместиться на странице, а конечный уровень листьев включает аналогично короткий суффикс, который тоже помещается на странице. Класс операторов должен устанавливать в longValuesOK значение true, только если он готов это организовать. В противном случае ядро SP-GiST отклонит любой запрос на индексацию значения, которое слишком велико, чтобы поместиться на странице индекса.

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

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

Когда longValuesOK равен true, ожидается, что последующие уровни дерева SP-GiST будут включать все больше и больше информации в префиксы и метки узлов внутренних кортежей, все уменьшая и уменьшая требуемое листовое значение, так что в итоге оно умещается на странице. Чтобы возможные ошибки в классах операторов не вызывали бесконечные циклы при добавлении записи, ядро SP-GiST выдаст ошибку, если листовое значение не уменьшится за десять циклов вызовов метода choose.

SP-GiST без меток узлов

Некоторые древовидные алгоритмы используют фиксированный набор узлов для каждого внутреннего кортежа; например, в дереве квадрантов всегда есть ровно четыре узла, соответствующих четырем квадрантам вокруг центральной точки внутреннего кортежа. В таком случае код обычно работает с узлами согласно их номерам, и в явных метках узлов нет необходимости. Чтобы убрать метки узлов (и тем самым сэкономить немного места), функция picksplit может возвращать NULL вместо массива nodeLabels, а функция choose аналогично может возвращать NULL вместо массива prefixNodeLabels во время действия spgSplitTuple. Это, в свою очередь, приведет к тому, что nodeLabels будет равен NULL во время последующих вызовов choose и inner_consistent. В принципе, в пределах одного индекса метки узлов можно использовать для некоторых внутренних кортежей, а для остальных — опускать.

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

Внутренние кортежи «все-равны»

Ядро SP-GiST может переопределять результаты вызова функции picksplit класса операторов, когда той не удается разделить переданные ей листовые значения как минимум на две категории узлов. Когда это происходит, создается новый внутренний кортеж с несколькими узлами, каждый из которых имеет ту же метку (если вообще имеет), как та, которую picksplit дала использованному ей одному узлу, а листовые значения делятся случайным образом между этими равнозначными узлами. Во внутреннем кортеже устанавливается флаг allTheSame, предупреждающий функции choose и inner_consistent о том, что у этого кортежа нет набора узлов, который они могли бы ожидать в ином случае.

При работе с кортежем allTheSame результат spgMatchNode функции choose воспринимается как то, что новое значение можно связать с любым из равнозначных узлов; код ядра проигнорирует переданное значение nodeN и спустится в один из узлов, выбранный случайно (чтобы сохранить дерево сбалансированным). Для choose будет ошибкой вернуть spgAddNode, поскольку это сделало бы узлы неравнозначными; если добавляемое значение не соответствует существующим узлам, следует применить действие spgSplitTuple.

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

Индексы GIN

Введение

GIN расшифровывается как Generalized Inverted Index (обобщенный инвертированный индекс). GIN предназначен для случаев, когда индексируемые значения являются составными, а запросы, на обработку которых рассчитан индекс, ищут значения элементов в этих составных объектах. Например, такими объектами могут быть документы, а запросы могут выполнять поиск документов, содержащих определенные слова.

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

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

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

Одним из преимуществ GIN является то, что он позволяет разрабатывать специальные типы данных с соответствующими методами доступа экспертам в области типов данных, а не экспертам по СУБД. Это практически то же самое преимущество, что есть у GiST.

Встроенные классы операторов

Базовый дистрибутив QHB включает классы операторов GIN, перечисленные в Таблице 1. (Некоторые из специализированных модулей, описанных в главе Расширения, предоставляют дополнительные классы операторов GIN.)

Таблица 1. Встроенные классы операторов GIN

Имя

Индексируемые операторы

array_ops
&& (anyarray,anyarray)

@> (anyarray,anyarray)

<@ (anyarray,anyarray)

= (anyarray,anyarray)

jsonb_ops
@> (jsonb,jsonb)

@? (jsonb,jsonpath)

@@ (jsonb,jsonpath)

? (jsonb,text)

?| (jsonb,text[])

?& (jsonb,text[])

jsonb_path_ops
@> (jsonb,jsonb)

@? (jsonb,jsonpath)

@@ (jsonb,jsonpath)

tsvector_ops
@@ (tsvector,tsquery)

@@@ (tsvector,tsquery)

Из двух классов операторов для типа jsonb классом по умолчанию является jsonb_ops. jsonb_path_ops поддерживает меньше операторов, но обеспечивает для них более высокую производительность. Дополнительную информацию см. в подразделе Индексация jsonb.

Расширяемость

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

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

Существует два метода, которые класс операторов должен предоставить для GIN:

extractValue

Datum *extractValue(Datum itemValue, int32 *nkeys, bool **nullFlags)

Возвращает выделенный с помощью palloc массив ключей индексируемого объекта. Число возвращаемых ключей должно записываться в *nkeys. Если какой-то из ключей может быть NULL, то необходимо также выделить память для массива из *nkeys полей bool, сохранить его адрес в *nullFlags и при необходимости установить эти флаги NULL. Если все ключи отличны от NULL, то можно оставить в *nullFlags значение NULL (это его исходное значение). Возвращаемое значение может быть NULL, если объект вообще не содержит ключей.

extractQuery

Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode)

Возвращает выделенный с помощью palloc массив ключей для запроса; то есть в query* передается значение правого операнда индексируемого оператора, у которого в левом операнде передается индексируемый столбец. В n задается номер стратегии оператора в классе операторов (см. подраздел Стратегии индексного метода). Зачастую функции extractQuery требуется проанализировать n, чтобы определить тип данных query и метод, который нужно использовать для извлечения значений ключей. Число возвращаемых ключей должно сохраняться в *nkeys. Если какой-то из ключей может быть NULL, то необходимо также выделить память для массива из *nkeys полей bool, сохранить его адрес в *nullFlags и при необходимости установить эти флаги NULL. Если все ключи отличны от NULL, то можно оставить в *nullFlags значение NULL (это его исходное значение). Возвращаемое значение может быть NULL, если объект вообще не содержит ключей.

Выходной аргумент searchMode позволяет функции extractQuery выбрать режим, в котором должен выполняться поиск. Если в *searchMode задать GIN_SEARCH_MODE_DEFAULT (это значение устанавливается перед вызовом), подходящими кандидатами считаются объекты, которые соответствуют хотя бы одному из возвращенных ключей. Если в *searchMode задать GIN_SEARCH_MODE_INCLUDE_EMPTY, то в дополнение к объектам с минимум одним совпадением ключа подходящими кандидатами будут считаться и объекты, вообще не содержащие ключей. (Этот режим полезен для реализации, например, операторов «является-подмножеством»). Если в *searchMode задать GIN_SEARCH_MODE_ALL, подходящими кандидатами считаются все отличные от NULL объекты в индексе, независимо от того, соответствуют ли они какому-либо из возвращаемых ключей. (Этот режим намного медленнее других, так как он по сути требует сканирования всего индекса, но он может быть необходим для корректной обработки крайних случаев. Оператор, который часто выбирает этот режим, скорее всего не подходит для реализации в классе операторов GIN).

Выходной аргумент pmatch используется, когда поддерживается частичное соответствие. Чтобы использовать его, extractQuery должна выделить массив из *nkeys элементов типа bool и сохранить его адрес в *pmatch. Если соответствующему ключу требуется частичное соответствие, каждый элемент этого массива должен быть равен true, если нет — false. Если *pmatch равна NULL, GIN предполагает, что частичное соответствие не требуется. В этой переменной NULL устанавливается перед вызовом, поэтому этот аргумент можно просто игнорировать в классах операторов, не поддерживающих частичное соответствие.

Выходной аргумент extra_data позволяет функции extractQuery передать методам consistent и comparePartial дополнительные данные. Чтобы использовать его, extractQuery должна выделить массив из *nkeys указателей и сохранить его адрес в *extra_data, а затем сохранять в отдельные указатели все, что ей нужно. В этой переменной NULL устанавливается перед вызовом, поэтому этот аргумент можно просто игнорировать в классах операторов, которым не требуются дополнительные данные. Если массив *extra_data задан, то он передается в метод consistent целиком, а в comparePartial передается соответствующий его элемент.
.

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

consistent

bool consistent(bool check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], bool *recheck, Datum queryKeys[], bool nullFlags[])

Возвращает true, если индексированный объект удовлетворяет оператору запроса со стратегией под номером n (или может удовлетворять, если возвращается указание перепроверки). У этой функции нет прямого доступа к значению индексированного объекта, поскольку GIN не хранит объекты явно. Скорее, доступно знание о том, какие значения ключа, извлеченные из запроса, находятся в заданном индексированной объекте. Массив check имеет длину nkeys, равную числу ключей, ранее возвращенных функцией extractQuery для этого значения query. Каждый элемент массива check равен true, если индексированный объект содержит соответствующий ключ запроса, т. е. если (check[i] == true), то i-ый ключ результата массива queryKeys представлен в индексированном объекте. Исходное значение query передается в том случае, если методу consistent нужно его проанализировать, и то же касается массивов queryKeys[] и nullFlags[], ранее возвращенных функцией extractQuery. В extra_data передается массив дополнительных данных, возвращенный функцией extractQuery, или NULL, если такого массива нет.

Когда extractQuery возвращает ключ NULL в queryKeys[], соответствующий элемент check[] равен true, если индексированный объект содержит ключ NULL; то есть семантика check[] подобна IS NOT DISTINCT FROM. Функция consistent может проверить соответствующий элемент nullFlags[], если ей нужно различать соответствие с обычным значением и соответствие с NULL.

В случае успеха в *recheck следует установить true, если кортеж кучи нужно перепроверить с оператором запроса, или false, если проверка по индексу точная. То есть возвращенное значение false гарантирует, что кортеж кучи не соответствует запросу; возвращенное значение true вместе с *recheck, установленным в false, означает, кортеж кучи может соответствовать запросу, поэтому его нужно выбрать и перепроверить путем вычисления оператора запроса напрямую по исходному индексированному объекту.

triConsistent

GinTernaryValue triConsistent(GinTernaryValue check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], Datum queryKeys[], bool nullFlags[])

Функция triConsistent подобна consistent, но вместо логических значений в векторе check ей передаются три возможных значения для каждого ключа: GIN_TRUE, GIN_FALSE и GIN_MAYBE. У GIN_FALSE и GIN_TRUE тот же смысл, что и у обычных логических значений, тогда как GIN_MAYBE означает, что присутствие этого ключа неизвестно. Когда присутствуют значения GIN_MAYBE, эта функция должна возвращать GIN_TRUE, только если объект точно удовлетворяет запросу, независимо от того, содержит ли объект индекса соответствующие ключи запроса. Аналогичным образом, функция должна возвращать GIN_FALSE, только если объект точно не удовлетворяет запросу, независимо того, содержи ли он ключи GIN_MAYBE. Если результат зависит от записей GIN_MAYBE, т. е. соответствие нельзя подтвердить или опровергнуть, основываясь на известных ключах запроса, функция должна вернуть GIN_MAYBE.

Когда в векторе check нет значений GIN_MAYBE, возвращаемое значение GIN_MAYBE равнозначно установке флага recheck в логической функции consistent.
.

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

compare

int compare(Datum a, Datum b)

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

Или же, если класс операторов не предоставляет метод compare, GIN будет искать класс операторов B-дерева по умолчанию для типа данных ключа индекса и воспользуется его функцией сравнения. Рекомендуется указывать функцию сравнения в классе операторов GIN, предназначенном только для одного типа данных, поскольку поиск класса операторов B-дерева занимает несколько циклов. Однако для полиморфных классов операторов GIN (например для array_ops) обычно нельзя указать одну функцию сравнения.

Дополнительно класс операторов для GIN может предоставить следующие методы:

comparePartial

int comparePartial(Datum partial_key, Datum key, StrategyNumber n, Pointer extra_data)

Сравнивает ключ запроса с частичным соответствием с ключом индекса. Возвращает целое, знак которого отражает результат: отрицательное число означает, что ключ индекса не соответствует запросу, но сканирование индекса должно продолжаться; ноль означает, что ключ индекса не соответствует запросу; положительное число указывает на то, что сканирование индекса должно остановиться, потому что больше соответствий не будет. Функции предоставляется номер стратегии n оператора, сгенерировавшего запрос частичного соответствия, на случай, если его семантика понадобится, чтобы определить, когда прекратить сканирование. Кроме того, в extra_data передается соответствующий элемент массива дополнительных данных, созданного функцией extractQuery, или NULL, если такого массива нет. Ключи NULL этой функции никогда не передаются.

options

void options(local_relopts *relopts)

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

Функции options передается указатель на структуру local_relopts, которую нужно заполнить набором параметров, специфичных для класса операторов. К этим параметрам можно обратиться из других вспомогательных функций с помощью макросов PG_HAS_OPCLASS_OPTIONS() и PG_GET_OPCLASS_OPTIONS().

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

Для поддержки запросов на «частичное соответствие» класс операторов должен предоставить метод comparePartial, а его метод extractQuery должен устанавливать параметр pmatch, когда встречается запрос на частичное соответствие. Дополнительную информацию см. в подразделе Алгоритм частичного соответствия.

Фактические типы данных различных значений Datum, упоминаемых выше, варьируются в зависимости от класса операторов. Значения объектов, передаваемые функции extractValue, всегда имеет входной тип класса операторов, а все значения ключей имеют тип, заданный параметром STORAGE для этого класса. Тип аргумента query, передаваемого функциям extractQuery, consistent и triConsistent, будет совпадать с входным типом правого операнда оператора-члена класса, определяемого по номеру стратегии. Он не обязательно должен совпадать с индексируемым типом, главное, чтобы из него можно было извлечь значения ключей правильного типа. Однако в SQL-объявлениях этих трех вспомогательных функций рекомендуется использовать для аргумента query индексируемый тип данных класса операторов, даже если фактический тип может быть другим, в зависимости от оператора.

Реализация

Внутри индекс GIN содержит индекс B-дерево, построенный по ключам, где каждый ключ является элементом одного или нескольких индексированных объектов (например членом массива), и где каждый кортеж в листовой странице содержит либо указатель на B-дерево указателей на кучу («дерево указателей»), либо простой список указателей на кучу («список указателей»), когда этот список достаточно мал, чтобы поместиться в одном кортеже индекса вместе со значением ключа. На Рисунке 1 показаны эти компоненты индекса GIN.

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

Многостолбцовые индексы GIN реализуются путем построения одного B-дерева по составным значениям (номер столбца, значение ключа). Значения ключей для разных столбцов могут быть разных типов.

Рисунок 1. Внутренняя структура GIN

Методика быстрого обновления GIN

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

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

Если согласуемое время отклика важнее, чем скорость изменений, использование отложенных записей можно отключить, выключив параметр хранения fastupdate индекса GIN. Дополнительную информацию см. на справочной странице команды CREATE INDEX.

Алгоритм частичного соответствия

GIN может поддерживать проверки «частичного соответствия», в которых запрос выявляет не точное соответствие одному и более ключам, а возможные соответствия, попадающие в достаточно узкий диапазон значений ключей (при порядке сортировки ключей, определенном вспомогательным методом compare). Метод extractQuery, вместо того, чтобы вернуть значение ключа, которое соответствует точно, возвращает значение ключа, равное нижней границе сканируемого диапазона, и устанавливает флаг pmatch. Затем этот диапазон ключей сканируется методом comparePartial. Метод comparePartial должен вернуть ноль для соответствующего ключа индекса, отрицательное значение, если соответствия нет, но оно все еще внутри диапазона, поэтому нужно продолжать поиск, или положительное значение, если ключ индекса выходит за пределы диапазона, в котором может быть соответствие.

Советы и приемы для индексов GIN

Создание или добавление

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

Когда для GIN включен режим fastupdate (подробную информацию см. в подразделе Методика быстрого обновления GIN), издержки меньше, чем когда он выключен. Но для очень больших объемов изменений, возможно, все же будет лучше удалить и пересоздать индекс.

maintenance_work_mem

Время построения индекса GIN сильно зависит от параметра maintenance_work_mem; не стоит экономить на рабочей памяти во время создания индекса.

gin_pending_list_limit

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

gin_fuzzy_search_limit

Основной целью разработки индексов GIN было создать поддержку отлично расширяемого полнотекстового поиска в QHB, и при этом нередко возникают ситуации, когда полнотекстовый поиск возвращает очень большой набор результатов. Более того, это зачастую происходит, когда запрос содержит очень часто встречающиеся слова, так что огромный результат даже не будет полезен. Поскольку чтение множества кортежей с диска и их сортировка могут занять много времени, это неприемлемо в среде промышленной эксплуатации. (Обратите внимание, что сам по себе поиск по индексу крайне быстрый.)
Чтобы облегчить управляемое выполнение таких запросов, у GIN имеется настраиваемый мягкий верхний предел числа возвращаемых строк: параметр конфигурации gin_fuzzy_search_limit. По умолчанию он равен 0 (то есть предела нет). Если установлено ненулевое значение, то возвращаемый набор является случайно выбранным подмножеством общего результирующего набора.
«Мягкий» означает, что фактическое число возвращаемых результатов может немного отличаться от заданного предела, в зависимости от запроса и качества системного генератора случайных чисел.
Из опыта: значения в несколько тысяч (например 5000 — 20000) работают нормально.

Ограничения

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

Примеры

Основной дистрибутив QHB включает классы операторов GIN, ранее перечисленные в Таблице 1. Классы операторов GIN также содержат следующие модули из share/extension:

btree_gin
Функциональность, равнозначная B-дереву, для некоторых типов данных

hstore
Модуль для хранения пар (ключ, значение)

intarray
Расширенная поддержка для int[]

pg_trgm
Похожесть текста, основанная на сопоставлении триграмм

Индексы BRIN

Введение

BRIN расшифровывается как Block Range Index (индекс диапазона блоков). BRIN предназначен для обработки очень больших таблиц, в которых определенные столбцы имеют некую естественную корреляцию с их физическим расположением в таблице.

BRIN работает в категориях диапазонов блоков (или «диапазонов страниц»). Диапазон блоков — это группа страниц, которые физически соседствуют в таблице; для каждого диапазона блоков в индексе сохраняется некоторая сводная информация. Например, таблица, хранящая заказы на поставку, может иметь столбец даты добавления каждого заказа, и в большинстве случаев записи для более ранних заказов в таблице будут размещены выше; в таблице со столбцом почтового индекса могут храниться все коды городов, сгруппированные естественным образом.

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

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

Размер диапазона блоков определяется во время создания индекса параметром хранения pages_per_range. Количество записей индекса будет равно размеру отношения в страницах, деленному на выбранное значение pages_per_range. Таким образом, чем меньше это число, тем больше размер индекса (из-за необходимости хранить больше записей), но в то же время может повыситься точность сохраняемой сводной информации, а во время сканирования можно будет пропустить больше блоков данных.

Обслуживание индекса

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

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

Наконец, можно использовать следующие функции:

brin_summarize_new_values(regclass), рассчитывающую сводку для всех неучтенных диапазонов;

brin_summarize_range(regclass, bigint), рассчитывающую сводку только для диапазона, содержащего заданную страницу, если он неучтенный.

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

LOG:  request for BRIN range summarization for index "brin_wi_idx" page 128 was not recorded
-- ЗАПИСЬ: запрос на сводку диапазона BRIN для страницы 128 индекса "brin_wi_idx" не был записан

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

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

Встроенные классы операторов

Основной дистрибутив QHB включает классы операторов BRIN, показанные в Таблице 1.

Классы операторов minmax хранят минимальное и максимальное значения, встречающиеся в индексированном столбце в пределах определенного диапазона. Классы операторов inclusion хранят значение, в котором содержатся значения индексированного столбца в пределах определенного диапазона. Классы операторов bloom строят фильтр Блума для всех значений в определенном диапазоне. Классы операторов minmax-multi хранят несколько минимальных и максимальных значений, представляющих значения, встречающиеся в индексированном столбце в пределах определенного диапазона.

Таблица 1. Встроенные классы операторов BRIN

Имя

Индексируемые операторы

bit_minmax_ops
= (bit,bit)

< (bit,bit)

> (bit,bit)

<= (bit,bit)

>= (bit,bit)

box_inclusion_ops
@> (box,point)

<< (box,box)

&< (box,box)

&> (box,box)

>> (box,box)

<@ (box,box)

@> (box,box)

~= (box,box)

&& (box,box)

<<| (box,box)

&<| (box,box)

|&> (box,box)

|>> (box,box)

bpchar_bloom_ops
= (character,character)

bpchar_minmax_ops
= (character,character)

< (character,character)

<= (character,character)

> (character,character)

>= (character,character)

bytea_bloom_ops
= (bytea,bytea)

bytea_minmax_ops
= (bytea,bytea)

< (bytea,bytea)

<= (bytea,bytea)

> (bytea,bytea)

>= (bytea,bytea)

char_bloom_ops
= ("char","char")

char_minmax_ops
= ("char","char")

< ("char","char")

<= ("char","char")

> ("char","char")

>= ("char","char")

date_bloom_ops
= (date,date)

date_minmax_ops
= (date,date)

< (date,date)

<= (date,date)

> (date,date)

>= (date,date)

date_minmax_multi_ops
= (date,date)

< (date,date)

<= (date,date)

> (date,date)

>= (date,date)

float4_bloom_ops
= (float4,float4)

float4_minmax_ops
= (float4,float4)

< (float4,float4)

<= (float4,float4)

> (float4,float4)

>= (float4,float4)

float4_minmax_multi_ops
= (float4,float4)

< (float4,float4)

<= (float4,float4)

> (float4,float4)

>= (float4,float4)

float8_bloom_ops
= (float8,float8)

float8_minmax_ops
= (float8,float8)

< (float8,float8)

<= (float8,float8)

> (float8,float8)

>= (float8,float8)

float8_minmax_multi_ops
= (float8,float8)

< (float8,float8)

<= (float8,float8)

> (float8,float8)

>= (float8,float8)

inet_inclusion_ops
<< (inet,inet)

<<= (inet,inet)

>> (inet,inet)

>>= (inet,inet)

= (inet,inet)

&& (inet,inet)

inet_bloom_ops
= (inet,inet)

inet_minmax_ops
= (inet,inet)

< (inet,inet)

<= (inet,inet)

> (inet,inet)

>= (inet,inet)

inet_minmax_multi_ops
= (inet,inet)

< (inet,inet)

<= (inet,inet)

> (inet,inet)

>= (inet,inet)

int2_bloom_ops
= (int2,int2)

int2_minmax_ops
= (int2,int2)

< (int2,int2)

<= (int2,int2)

> (int2,int2)

>= (int2,int2)

int2_minmax_multi_ops
= (int2,int2)

< (int2,int2)

<= (int2,int2)

> (int2,int2)

>= (int2,int2)

int4_bloom_ops
= (int4,int4)

int4_minmax_ops
= (int4,int4)

< (int4,int4)

<= (int4,int4)

> (int4,int4)

>= (int4,int4)

int4_minmax_multi_ops
= (int4,int4)

< (int4,int4)

<= (int4,int4)

> (int4,int4)

>= (int4,int4)

int8_bloom_ops
= (bigint,bigint)

int8_minmax_ops
= (bigint,bigint)

< (bigint,bigint)

<= (bigint,bigint)

> (bigint,bigint)

>= (bigint,bigint)

int8_minmax_multi_ops
= (bigint,bigint)

< (bigint,bigint)

<= (bigint,bigint)

> (bigint,bigint)

>= (bigint,bigint)

interval_bloom_ops
= (interval,interval)

interval_minmax_ops
= (interval,interval)

< (interval,interval)

<= (interval,interval)

> (interval,interval)

>= (interval,interval)

interval_minmax_multi_ops
= (interval,interval)

< (interval,interval)

<= (interval,interval)

> (interval,interval)

>= (interval,interval)

macaddr_bloom_ops
= (macaddr,macaddr)

macaddr_minmax_ops
= (macaddr,macaddr)

< (macaddr,macaddr)

<= (macaddr,macaddr)

> (macaddr,macaddr)

>= (macaddr,macaddr)

macaddr_minmax_multi_ops
= (macaddr,macaddr)

< (macaddr,macaddr)

<= (macaddr,macaddr)

> (macaddr,macaddr)

>= (macaddr,macaddr)

macaddr8_bloom_ops
= (macaddr8,macaddr8)

macaddr8_minmax_ops
= (macaddr8,macaddr8)

< (macaddr8,macaddr8)

<= (macaddr8,macaddr8)

> (macaddr8,macaddr8)

>= (macaddr8,macaddr8)

macaddr8_minmax_multi_ops
= (macaddr8,macaddr8)

< (macaddr8,macaddr8)

<= (macaddr8,macaddr8)

> (macaddr8,macaddr8)

>= (macaddr8,macaddr8)

name_bloom_ops
= (name,name)

name_minmax_ops
= (name,name)

< (name,name)

<= (name,name)

> (name,name)

>= (name,name)

numeric_bloom_ops
= (numeric,numeric)

numeric_minmax_ops
= (numeric,numeric)

< (numeric,numeric)

<= (numeric,numeric)

> (numeric,numeric)

>= (numeric,numeric)

numeric_minmax_multi_ops
= (numeric,numeric)

< (numeric,numeric)

<= (numeric,numeric)

> (numeric,numeric)

>= (numeric,numeric)

oid_bloom_ops
= (oid,oid)

oid_minmax_ops
= (oid,oid)

< (oid,oid)

<= (oid,oid)

> (oid,oid)

>= (oid,oid)

oid_minmax_multi_ops
= (oid,oid)

< (oid,oid)

<= (oid,oid)

> (oid,oid)

>= (oid,oid)

pg_lsn_bloom_ops
= (pg_lsn,pg_lsn)

pg_lsn_minmax_ops
= (pg_lsn,pg_lsn)

< (pg_lsn,pg_lsn)

<= (pg_lsn,pg_lsn)

> (pg_lsn,pg_lsn)

>= (pg_lsn,pg_lsn)

pg_lsn_minmax_multi_ops
= (pg_lsn,pg_lsn)

< (pg_lsn,pg_lsn)

<= (pg_lsn,pg_lsn)

> (pg_lsn,pg_lsn)

>= (pg_lsn,pg_lsn)

range_inclusion_ops
= (anyrange,anyrange)

< (anyrange,anyrange)

<= (anyrange,anyrange)

> (anyrange,anyrange)

>= (anyrange,anyrange)

&& (anyrange,anyrange)

@> (anyrange,anyelement)

@> (anyrange,anyrange)

<@ (anyrange,anyrange)

<< (anyrange,anyrange)

>> (anyrange,anyrange)

&< (anyrange,anyrange)

&> (anyrange,anyrange)

-|- (anyrange,anyrange)

text_bloom_ops
= (text,text)

text_minmax_ops
= (text,text)

< (text,text)

<= (text,text)

> (text,text)

>= (text,text)

tid_bloom_ops
= (tid,tid)

tid_minmax_ops
= (tid,tid)

< (tid,tid)

<= (tid,tid)

> (tid,tid)

>= (tid,tid)

tid_minmax_multi_ops
= (tid,tid)

< (tid,tid)

<= (tid,tid)

> (tid,tid)

>= (tid,tid)

timestamp_bloom_ops
= (timestamp,timestamp)

timestamp_minmax_ops
= (timestamp,timestamp)

< (timestamp,timestamp)

<= (timestamp,timestamp)

> (timestamp,timestamp)

>= (timestamp,timestamp)

timestamp_minmax_multi_ops
= (timestamp,timestamp)

< (timestamp,timestamp)

<= (timestamp,timestamp)

> (timestamp,timestamp)

>= (timestamp,timestamp)

timestamptz_bloom_ops
= (timestamptz,timestamptz)

timestamptz_minmax_ops
= (timestamptz,timestamptz)

< (timestamptz,timestamptz)

<= (timestamptz,timestamptz)

> (timestamptz,timestamptz)

>= (timestamptz,timestamptz)

timestamptz_minmax_multi_ops
= (timestamptz,timestamptz)

< (timestamptz,timestamptz)

<= (timestamptz,timestamptz)

> (timestamptz,timestamptz)

>= (timestamptz,timestamptz)

time_bloom_ops
= (time,time)

time_minmax_ops
= (time,time)

< (time,time)

<= (time,time)

> (time,time)

>= (time,time)

time_minmax_multi_ops
= (time,time)

< (time,time)

<= (time,time)

> (time,time)

>= (time,time)

timetz_bloom_ops
= (timetz,timetz)

timetz_minmax_ops
= (timetz,timetz)

< (timetz,timetz)

<= (timetz,timetz)

> (timetz,timetz)

>= (timetz,timetz)

timetz_minmax_multi_ops
= (timetz,timetz)

< (timetz,timetz)

<= (timetz,timetz)

> (timetz,timetz)

>= (timetz,timetz)

uuid_bloom_ops
= (uuid,uuid)

uuid_minmax_ops
= (uuid,uuid)

< (uuid,uuid)

<= (uuid,uuid)

> (uuid,uuid)

>= (uuid,uuid)

uuid_minmax_multi_ops
= (uuid,uuid)

< (uuid,uuid)

<= (uuid,uuid)

> (uuid,uuid)

>= (uuid,uuid)

varbit_minmax_ops
= (varbit,varbit)

< (varbit,varbit)

<= (varbit,varbit)

> (varbit,varbit)

>= (varbit,varbit)

Параметры класса операторов

Для некоторых встроенных классов операторов разрешено задавать параметры, влияющие на их поведение. У каждого класса операторов имеется собственный набор допустимых операторов. Указывать параметры можно только для классов операторов bloom и minmax-multi.

Классы операторов bloom принимают следующие параметры:

n_distinct_per_range
Определяет предположительное количество уникальных значений, отличных от NULL, в диапазоне блоков, используемых индексами BRIN типа bloom для установки размера фильтра Блума. Его поведение аналогично таковому у параметра n_distinct команды ALTER TABLE. Когда задается положительное значение, считается, что каждый диапазон блоков содержит именно это количество уникальных значений, отличных от NULL. Когда задается отрицательное значение, которое должно быть больше либо равно -1, считается, что количество уникальных значений, отличных от NULL, линейно возрастает в зависимости от максимально возможного количества кортежей в диапазоне блоков (примерно 290 строк на блок). По умолчанию установлено значение -0.1, а минимальное количество уникальных значений, отличных от NULL, которое можно задать, равно 16.

false_positive_rate
Определяет целевую долю ложноположительных срабатываний, используемых индексами BRIN типа bloom для установки размера фильтра Блума. Значение должно находиться в пределах от 0.0001 до 0.25. Значение по умолчанию равно 0.01, т. е. доля ложноположительных срабатываний составляет 1%.

Классы операторов minmax-multi принимают следующие параметры:

values_per_range
Определяет максимальное количество значений, хранимых индексами BRIN типа minmax для создания сводки по диапазону блоков. Каждое значение может представлять либо точку, либо границу интервала. Значения должны находиться в пределах от 8 до 256, а значение по умолчанию равно 32.

Расширяемость

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

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

Существует четыре метода, которые должен предоставить класс операторов для BRIN:

opcInfo

BrinOpcInfo *opcInfo(Oid type_oid)

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

typedef struct BrinOpcInfo
{
    /* Количество столбцов, хранящихся в столбце индекса этого класса операторов */
    uint16      oi_nstored;

    /* Непрозрачный указатель для внутреннего использования классом операторов */
    void       *oi_opaque;

    /* Записи кэша типов для сохраненных столбцов */
    TypeCacheEntry *oi_typcache[FLEXIBLE_ARRAY_MEMBER];
} BrinOpcInfo;

Brinpcinfo.oi_opaque могут использовать процедуры класса операторов для передачи информации между вспомогательными функциями при сканировании индекса.

consistent

bool consistent(BrinDesc *bdesc, BrinValues *column, ScanKey *keys, int nkeys)

Показывает, все ли записи ScanKey согласуются с заданными индексированными значениями некоторого диапазона. Номер целевого атрибута передается как часть ключа сканирования. Для одного атрибута может передаваться несколько ключей сканирования; количество записей определяется параметром nkeys.

bool consistent(BrinDesc *bdesc, BrinValues *column, ScanKey key)

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

addValue

bool addValue(BrinDesc *bdesc, BrinValues *column, Datum newval, bool isnull)

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

unionTuples

bool unionTuples(BrinDesc *bdesc, BrinValues *a, BrinValues *b)

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

Дополнительно класс операторов для BRIN может задать следующий метод:

options

void options(local_relopts *relopts)

Определяет набор видимых пользователю параметров, управляющих поведением класса операторов.
Функции options передается указатель на структуру local_relopts, которую нужно заполнить набором параметров, специфичных для класса операторов. К этим параметрам можно обращаться из других вспомогательных функций с помощью макросов PG_HAS_OPCLASS_OPTIONS() и PG_GET_OPCLASS_OPTIONS().
Поскольку в BRIN извлечение ключа из индексируемых значений и представление этого ключа допускают гибкость, они могут зависеть от параметров, заданных пользователем.
.

Основной дистрибутив включает поддержку четырех типов классов операторов: minmax, minmax-multi, inclusion и bloom. Использующие их определения классов операторов поставляются для резидентных типов данных по мере необходимости. Для других типов данных пользователь может определить дополнительные классы операторов с помощью равнозначных определений без необходимости писать какой-либо исходный код; достаточно объявить подходящие записи в каталоге. Обратите внимание, что предположения о семантике стратегий операторов зашиты в исходном коде вспомогательных функций.

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

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

Таблица 2. Номера стратегий и вспомогательных функций для классов операторов minmax

Член класса операторов | Объект
Вспомогательная функция 1 | внутренняя функция brin_minmax_opcinfo()
Вспомогательная функция 2 | внутренняя функция brin_minmax_add_value()
Вспомогательная функция 3 | внутренняя функция brin_minmax_consistent()
Вспомогательная функция 4 | внутренняя функция brin_minmax_union()
Стратегия оператора 1 | оператор меньше
Стратегия оператора 2 | оператор меньше-или-равно
Стратегия оператора 3 | оператор равно
Стратегия оператора 4 | оператор больше-или-равно
Стратегия оператора 5 | оператор больше

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

Таблица 3. Номера стратегий и вспомогательных функций для классов операторов inclusion

Член класса операторов | Объект | Зависимость
Вспомогательная функция 1 | внутренняя функция brin_inclusion_opcinfo() |
Вспомогательная функция 2 | внутренняя функция brin_inclusion_add_value() |
Вспомогательная функция 3 | внутренняя функция brin_inclusion_consistent() |
Вспомогательная функция 4 | внутренняя функция brin_inclusion_union() |
Вспомогательная функция 11 | функция для слияния двух элементов |
Вспомогательная функция 12 | необязательная функция для проверки возможности слияния двух элементов |
Вспомогательная функция 13 | необязательная функция для проверки, содержится ли один элемент в другом |
Вспомогательная функция 14 | необязательная функция для проверки, является ли элемент пустым |
Стратегия оператора 1 | оператор левее | Стратегия оператора 4
Стратегия оператора 2 | оператор не-простирается-правее | Стратегия оператора 5
Стратегия оператора 3 | оператор перекрывается |
Стратегия оператора 4 | оператор не-простирается-левее | Стратегия оператора 1
Стратегия оператора 5 | оператор правее | Стратегия оператора 2
Стратегия оператора 6, 18 | оператор то-же-или-равно | Стратегия оператора 7
Стратегия оператора 7, 16, 24, 25 | оператор содержит-или-равно |
Стратегия оператора 8, 26, 27 | оператор содержится-в-или-равно | Стратегия оператора 3
Стратегия оператора 9 | оператор не-простирается-выше | Стратегия оператора 11
Стратегия оператора 10 | оператор ниже | Стратегия оператора 12
Стратегия оператора 11 | оператор выше | Стратегия оператора 9
Стратегия оператора 12 | оператор не-простирается-ниже | Стратегия оператора 10
Стратегия оператора 20 | оператор меньше | Стратегия оператора 5
Стратегия оператора 21 | оператор меньше-или-равно | Стратегия оператора 5
Стратегия оператора 22 | оператор больше | Стратегия оператора 1
Стратегия оператора 23 | оператор больше-или-равно | Стратегия оператора 1

Номера вспомогательных функций от 1 до 10 зарезервированы для внутренних функций BRIN, поэтому функции уровня SQL начинаются с номера 11. Вспомогательная функция номер 11 является основной функцией, требуемой для постройки индекса. Она должна принимать два аргумента с тем же типом данных, что и у класса операторов, и возвращать их объединение. Класс операторов inclusion может сохранять объединенные значения в различных типах данных, если это определено в параметре STORAGE. Значение, возвращаемое функцией объединения, должно соответствовать типу данных STORAGE.

Вспомогательные функции номер 12 и номер 14 предоставляются для поддержки нерегулярностей встроенных типов данных. Функция номер 12 используется для поддержки сетевых адресов из различных семейств, которые нельзя объединять. Функция номер 14 используется для поддержки пустых диапазонов. Функция номер 13 является необязательной, но рекомендуемой; она позволяет проверить новое значение, прежде чем оно будет передано функции объединения. Поскольку инфраструктура BRIN может упростить некоторые операции, когда объединение не меняется, использование этой функции может повысить производительность индекса.

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

Таблица 4. Номера стратегий и вспомогательных процедур для классов операторов bloom

Член класса операторов | Объект
Вспомогательная процедура 1 | внутренняя функция brin_bloom_opcinfo()
Вспомогательная процедура 2 | внутренняя функция brin_bloom_add_value()
Вспомогательная процедура 3 | внутренняя функция brin_bloom_consistent()
Вспомогательная процедура 4 | внутренняя функция brin_bloom_union()
Вспомогательная процедура 5 | внутренняя функция brin_bloom_options()
Вспомогательная процедура 11 | функция для вычисления хеша элемента
Стратегия оператора 1 | оператор равно

Номера вспомогательных процедур 1-10 зарезервированы для внутренних функций BRIN, поэтому функции уровня SQL начинаются с номера 11. Вспомогательная функция номер 11 является основной функцией, требуемой для построения индекса. Она должна принимать один аргумент с тем же типом данных, что и у класса операторов, и возвращать хеш этого значения.

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

Таблица 5. Номера стратегий и вспомогательных процедур для классов операторов minmax-multi

Член класса операторов | Объект
Вспомогательная процедура 1 | внутренняя функция brin_minmax_multi_opcinfo()
Вспомогательная процедура 2 | внутренняя функция brin_minmax_multi_add_value()
Вспомогательная процедура 3 | внутренняя функция brin_minmax_multi_consistent()
Вспомогательная процедура 4 | внутренняя функция brin_minmax_multi_union()
Вспомогательная процедура 5 | внутренняя функция brin_minmax_multi_options()
Вспомогательная процедура 11 | функция для вычисления расстояния между двумя значениями (длины диапазона)
Стратегия оператора 1 | оператор меньше
Стратегия оператора 2 | оператор меньше-или-равно
Стратегия оператора 3 | оператор равно
Стратегия оператора 4 | оператор больше-или-равно
Стратегия оператора 5 | оператор больше

Классы операторов minmax и inclusion поддерживают операторы с разными типами данных, хотя с ними зависимости становятся более сложными. Классу операторов minmax требуется, чтобы для двух аргументов одного типа определялся полный набор операторов. Это позволяет поддерживать дополнительные типы данных, определяя дополнительные наборы операторов. Стратегии класса операторов inclusion зависят от других стратегий, как показано в Таблице 3, или от своих собственных стратегий оператора. Для них требуется, чтобы оператор зависимости был определен с типом данных STORAGE в качестве левого аргумента и другим поддерживаемым типом в качестве правого аргумента поддерживаемого оператора. См. определение float4_minmax_ops в качестве примера minmax и box_inclusion_ops в качестве примера inclusion.

Хеш-индексы

Обзор

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

Хеш-индексы поддерживают только построение индекса по одному столбцу и не допускают проверок на уникальность.

Хеш-индексы поддерживают только оператор =, поэтому для предложений WHERE, в которых задаются операции с диапазонами, хеш-индексы будут бесполезны.

Каждый кортеж хеш-индекса хранит только 4-байтовое хеш-значение, а не фактическое значение столбца. Как следствие, хеш-индексы могут быть гораздо меньше B-деревьев при индексировании более длинных объектов данных, например UUID, URL-адресов и т. д. Из-за отсутствия значения столбцов все сканирования хеш-индекса являются неточными. Хеш-индексы могут принимать участие в сканированиях индекса по битовой карте и обратных сканированиях.

Хеш-индексы лучше всего оптимизированы для нагрузок с большим количеством операций SELECT и UPDATE, выполняющих сканирование с проверкой равенства на больших таблицах. В индексе B-дереве поиск должен спускаться по дереву, пока не найдется листовая страница. В таблицах с миллионами строк такой спуск может увеличить время обращения к данным. Аналог листовой страницы называется в хеш-индексе страницей сегментов. В отличие от B-деревьев, хеш-индекс позволяет обращаться к страницам сегментов напрямую, тем самым потенциально сокращая время доступа по индексу в больших таблицах. Это сокращение «логического ввода/вывода» становится еще более выраженным для индексов/данных, не помещающихся в общих буферах/ОЗУ.

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

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

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

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

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

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

Реализация

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

Как для сканирования индекса, так и для добавления кортежей требуется определение местоположения сегмента, где должен располагаться данный кортеж. Для этого нужно знать число сегментов и значения highmask и lowmask из метастраницы; однако с точки зрения производительности нежелательно блокировать и закреплять метастраницу для каждой такой операции. Вместо этого в записи кэша отношений каждого внутреннего сервера сохраняется кэшированная копия метастраницы. Это приводит к правильному сопоставлению сегментов при условии, что целевой сегмент не был разделен с момента последнего обновления кэша.

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

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

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

Полнотекстовый поиск

Введение

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

Операторы текстового поиска существуют в базах данных уже многие годы. В QHB для текстовых типов данных имеются операторы ~, ~*, LIKE и ILIKE, но у них отсутствуют многие существенные свойства, требуемые для современных информационных систем:

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

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

Как правило, они выполняются медленно, поскольку не поддерживают индексы и при каждом поиске должны обрабатывать все документы.

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

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

Преобразование синтаксических единиц в лексемы. Лексема, как и синтаксическая единица, является строкой, но только нормализованная, чтобы разные формы одного слова стали одной. К примеру, нормализация почти всегда включает приведение букв верхнего регистра к нижнему и зачастую удаление суффиксов/окончаний (например, s или es в английском). Это позволяет находить при поиске различные формы одного слова без трудоемкого ввода всех возможных вариантов. Кроме того, на этом шаге обычно исключаются стоп-слова, то есть слова настолько распространенные, что искать их бесполезно. (Иначе говоря, синтаксические единицы являются исходными фрагментами текста документа, тогда как лексемы — это слова, считающиеся полезными для индексации и поиска.) Для выполнения этого шага в QHB используются словари. Предоставляются различные стандартные словари, но для особых нужд можно создать и специализированные.

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

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

Определять стоп-слова, которые не должны индексироваться.

Сопоставлять синонимы с единственным словом, используя Ispell.

Сопоставлять фразы с единственным словом, используя тезаурус.

Сопоставлять различные вариации слова с канонической формой, используя словарь Ispell.

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

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

Что такое документ?

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

Для поиска в QHB документ обычно представляет собой текстовое поле в строке таблицы базы данных или, возможно, сочетание (конкатенация) таких полей, предположительно хранящихся в нескольких таблицах или полученных динамически. Другими словами, для индексации документ может быть собран из нескольких частей и не храниться где-либо как единое целое. Например:

SELECT title || ' ' ||  author || ' ' ||  abstract || ' ' || body AS document
FROM messages
WHERE mid = 12;

SELECT m.title || ' ' || m.author || ' ' || m.abstract || ' ' || d.body AS document
FROM messages m, docs d
WHERE m.mid = d.did AND m.mid = 12;

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

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

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

Простое сопоставление текста

В QHB полнотекстовый поиск работает на основе оператора сопоставления @@, который возвращает true, если tsvector (документ) соответствует tsquery (запросу). Какой тип данных записан первым, значения не имеет:

SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;
 ?column?
----------
 t

SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector;
 ?column?
----------
 f

Как подсказывает пример выше, tsquery — это не просто необработанный текст, как и tsvector. Тип tsquery содержит ключевые слова, которые должны быть уже нормализованными лексемами и могут объединяться по несколько штук с помощью операторов И, ИЛИ, НЕ и ПРЕДШЕСТВУЕТ. (Подробно синтаксис описан в подразделе tsquery.) Также существуют функции to_tsquery, plainto_tsquery и phraseto_tsquery, помогающие преобразовать заданный пользователем текст в требуемое значение tsquery, в первую очередь нормализуя имеющиеся в этом тексте слова. Аналогичным образом функция to_tsvector применяется для анализа и нормализации строки документа. Поэтому на практике текстовый поиск с сопоставлением будет выглядеть скорее так:

SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat');
 ?column?
----------
 t

Обратите внимание, что это сопоставление не сработает, если будет записано как

SELECT 'fat cats ate fat rats'::tsvector @@ to_tsquery('fat & rat');
 ?column?
----------
 f

поскольку слово rats не будет нормализовано. Элементами tsvector являются лексемы, предположительно уже нормализованные, поэтому rats не соответствует rat.

Оператор @@ также поддерживает входные данные типа text, позволяя в простых случаях пропускать явное преобразование текстовой строки в tsvector или tsquery. Имеющиеся варианты этого оператора:

tsvector @@ tsquery
tsquery  @@ tsvector
text @@ tsquery
text @@ text

Первые два из них мы уже видели. Форма text @@ tsquery равнозначнаto_tsvector(x) @@ y, а форма text @@ text — to_tsvector(x) @@ plainto_tsquery(y).

В значении tsquery оператор & (И) указывает, что для соответствия оба его аргумента должны присутствовать в документе. Аналогичным образом оператор | (ИЛИ) указывает, что должен присутствовать хотя бы один из его аргументов, тогда как оператор ! (НЕ) указывает, что для соответствия ни один из его аргументов в документе присутствовать не должен. Например, запрос fat & ! rat соответствует документам, содержащим fat, но не содержащим rat.

Поиск по фразам возможен с помощью оператора <-> (ПРЕДШЕСТВУЕТ) типа tsquery, который находит совпадение, только если в документе его аргументы находятся рядом и в заданном порядке. Например:

SELECT to_tsvector('fatal error') @@ to_tsquery('fatal <-> error');
 ?column?
----------
 t

SELECT to_tsvector('error is not fatal') @@ to_tsquery('fatal <-> error');
 ?column?
----------
 f

Более общая версия оператора ПРЕДШЕСТВУЕТ имеет форму <N>, где N — целое число, означающее разность между позициями сопоставляемых лексем. Запись <1> равнозначна <->, тогда как <2> допускает существование ровно одной отличной лексемы между заданными, и т. д. Функция phraseto_tsquery задействует этот оператор для создания tsquery, который можно сопоставлять с многословной фразой, включающей в себя стоп-слова. Например:

SELECT phraseto_tsquery('cats ate rats');
       phraseto_tsquery
-------------------------------
 'cat' <-> 'ate' <-> 'rat'

SELECT phraseto_tsquery('the cats ate the rats');
       phraseto_tsquery
-------------------------------
 'cat' <-> 'ate' <2> 'rat'

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

Для управления операторами tsquery можно применять скобки. Без скобок | имеет наименьшую степень привязки, за ним идет &, затем <->, а привязка ! максимальна.

Стоит отметить, что операторы И/ИЛИ/НЕ имеют слегка иное значение, когда находятся в аргументах оператора ПРЕДШЕСТВУЕТ, поскольку в нем важна точная позиция совпадения. Например, обычному !x соответствуют только документы, нигде не содержащие x. Но условию !x <-> y соответствует y, если оно не идет сразу за x; вхождение x в любом другом месте документа не исключает сопоставления. Другой пример: обычному x & y требуется только, чтобы x и y фигурировали где-то в документе, но (x & y) <-> z требуется, чтобы x и y находились в одном месте, непосредственно перед z. Следовательно, этот запрос ведет себя по-другому, нежели x <-> z & y <-> z, которому будет соответствовать документ, содержащий две отдельные последовательности x z и y z. (Этот конкретный запрос в данном виде неприменим, поскольку x и y не могут находиться в одном месте, но в более сложных ситуациях, например, с шаблонами сопоставления по префиксу, запрос в такой форме может оказаться полезен.)

Конфигурации

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

Подходящая конфигурация выбирается во время установки и записывается в параметре default_text_search_config в файле qhb.conf. Если вы используете для всего кластера одну конфигурацию текстового поиска, достаточно будет этого значения в qhb.conf. Чтобы использовать в кластере разные конфигурации, но для каждой базы данных какую-то одну, выполните ALTER DATABASE ... SET. В противном случае можно устанавливать default_text_search_config в рамках каждого сеанса.

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

Для упрощения создания специальных конфигураций текстового поиска они строятся из более простых объектов базы данных. Механизм текстового поиска QHB предоставляет четыре типа объектов базы данных, связанных с конфигурацией:

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

Словари текстового поиска приводят эти единицы в нормализованную форму и отбрасывают стоп-слова.

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

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

Анализаторы и шаблоны текстового поиска строятся из низкоуровневых функций на C/RUST; следовательно, чтобы разработать новые, требуется умение программировать на C/RUST, а чтобы подключить их к базе данных — права суперпользователя. (В каталоге share/extension установки QHB можно найти примеры дополнительных анализаторов и шаблонов.) Поскольку словари и конфигурации просто параметризуют и соединяют вместе некоторые из нижележащих анализаторов и шаблонов, для их создания не требуется специальных прав. Далее в этой главе приводятся примеры создания пользовательских словарей и конфигураций.

Таблицы и индексы

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

Поиск в таблице

Полнотекстовый поиск можно выполнить и без индекса. Следующий простой запрос выводит заголовок (title) каждой строки, содержащей слово friend в поле body:

SELECT title
FROM pgweb
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'friend');

Также он найдет связанные слова, такие как friends и friendly, поскольку все они сводятся к одной нормализованной лексеме.

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

SELECT title
FROM pgweb
WHERE to_tsvector(body) @@ to_tsquery('friend');

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

В следующем, более сложном примере выбирается десять последних документов, содержащих слова create и table в полях title или body:

SELECT title
FROM pgweb
WHERE to_tsvector(title || ' ' || body) @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC
LIMIT 10;

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

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

Создание индексов

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

CREATE INDEX qhbweb_idx ON qhbweb USING GIN (to_tsvector('english', body));

Обратите внимание, что здесь используется версия to_tsvector с двумя аргументами. В выражениях индексов (см. раздел Индексы по выражениям) могут применяться только те функции текстового поиска, в которых задается имя конфигурации. Это связано с тем, что на содержимое индекса не должно влиять значение параметра default_text_search_config. Иначе содержимое индекса может быть несогласованным вследствие того, что разные записи могут содержать значения tsvector, которые были созданы с разными конфигурациями текстового поиска, и нельзя будет отличить одну от другой. Выгрузить и восстановить такой индекс корректно будет невозможно.

Поскольку в индексе выше применялась версия to_tsvector с двумя аргументами, этот индекс будет использоваться только в тех запросах, где функция to_tsvector вызывается с двумя аргументами и с тем же именем конфигурации. То есть WHERE to_tsvector('english', body) @@ 'a & b' может использовать этот индекс, но WHERE to_tsvector(body) @@ 'a & b' — нет. Это гарантирует, что индекс будет использоваться только с той конфигурацией, с которой создавались его записи.

Можно устанавливать и более сложные выражения индекса, где имя конфигурации задается в другом столбце, например:

CREATE INDEX qhbweb_idx ON qhbweb USING GIN (to_tsvector(config_name, body));

где config_name — это столбец в таблице qhbweb. Это позволяет смешивать в одном индексе несколько конфигураций, записывая при этом, какая конфигурация использовалась для каждой записи индекса. Это может быть полезно, например, если коллекция документов содержит документы на разных языках. Опять же, запросы, в которых предполагается использовать этот индекс, должны быть составлены соответствующим образом, например, WHERE to_tsvector(config_name, body) @@ 'a & b'.

В индексах можно даже конкатенировать столбцы:

CREATE INDEX qhbweb_idx ON qhbweb USING GIN (to_tsvector('english', title || ' ' || body));

Еще один способ — создать отдельный столбец tsvector для сохранения вывода функции to_tsvector. Чтобы этот столбец автоматически синхронизировался с исходными данными, создайте его как сохраненный генерируемый столбец. В следующем примере используется конкатенация столбцов title и body, и вызывается функция coalesce, чтобы гарантировать, что даже если один из столбцов равен NULL, другой по-прежнему будет индексироваться:

ALTER TABLE qhbweb
    ADD COLUMN textsearchable_index_col tsvector
               GENERATED ALWAYS AS (to_tsvector('english', coalesce(title, '') || ' ' || coalesce(body, ''))) STORED;

Затем мы создаем индекс GIN для ускорения поиска:

CREATE INDEX textsearch_idx ON pgweb USING GIN (textsearchable_index_col);

Теперь мы готовы выполнить быстрый полнотекстовый поиск:

SELECT title
FROM qhbweb
WHERE textsearchable_index_col @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC
LIMIT 10;

Одно из преимуществ подхода с хранением выражения индекса в отдельном столбце заключается в том, что для использования индекса нет необходимости явно указывать в запросах конфигурацию текстового поиска. Как показано в примере выше, запрос может зависеть от параметра default_text_search_config. Еще одно преимущество состоит в том, что поиск будет проходить быстрее, так как повторно вызывать to_tsvector для проверки соответствия данных индексу не понадобится. (Это более актуально для индексов GiST, чем для GIN; см. раздел Предпочтительные типы индексов для текстового поиска.) Тем не менее подход с индексом по выражению проще реализовать, и для него требуется меньше места на диске, так как представление tsvector не хранится явно.

Управление текстовым поиском

Для реализации полнотекстового поиска требуется функция, чтобы создавать tsvector из документа и tsquery из пользовательского запроса. Кроме того, нам нужно возвращать результаты в удобном порядке, поэтому нам требуется функция, сравнивающая документы с учетом их релевантности для этого запроса. Также важно иметь возможность правильно отображать результаты. QHB предоставляет поддержку для всех этих функций.

Анализ документов

Для преобразования документа в тип данных tsvector QHB предоставляет функцию to_tsvector.

to_tsvector([ конфигурация regconfig, ] документ text) returns tsvector

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

SELECT to_tsvector('english', 'a fat  cat sat on a mat - it ate a fat rats');
                  to_tsvector
-----------------------------------------------------
 'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4

В примере выше мы видим, что результирующий tsvector не содержит слова a, on и it, слово rats превратилось в rat, а знак препинания «-» был проигнорирован.

Функция to_tsvector внутри вызывает синтаксический анализатор, который разбивает текст документа на синтаксические единицы и присваивает тип каждой из них. Для каждой единицы проверяется список словарей (раздел Словари), который может варьировать в зависимости от типа единицы. Первый же словарь, распознавший единицу, выдает одну или несколько представляющих ее нормализованных лексем. Например, rats превращается в rat, потому что один из словарей распознает, что слово rats — это форма множественного числа от rat. Некоторые слова распознаются как стоп-слова (подраздел Стоп-слова) и поэтому игнорируются, так как они встречаются слишком часть, чтобы использоваться в поиске. В нашем примере это a, on и it. Если ни один словарь в списке не распознает синтаксическую единицу, она тоже игнорируется. В данном примере это произошло со знаком препинания «-», поскольку в действительности для его типа единицы (символы-разделители) не назначен ни один словарь, то есть единицы разделителей никогда не будут индексироваться. Выбор анализатора, словарей и индексируемых типов единиц определяется выбранной конфигурацией текстового поиска (раздел Пример конфигурации). В одной базе данных можно иметь много разных конфигураций, и для разных языков доступны предопределенные конфигурации. В нашем примере мы использовали english, конфигурацию по умолчанию для английского языка.

Для назначения записям tsvector заданного веса (где вес задается буквой A, B, C или D) можно применять функцию setweight. Обычно это используется для маркировки записей, находящихся в разных частях документа, например в заголовке или теле. Затем эту информацию можно использовать для ранжирования результатов поиска.

Поскольку to_tsvector(NULL) вернет NULL, рекомендуется вызывать coalesce везде, где поле может содержать NULL. Вот рекомендуемый метод создания tsvector из структурированного документа:

UPDATE tt SET ti =
    setweight(to_tsvector(coalesce(title,'')), 'A')    ||
    setweight(to_tsvector(coalesce(keyword,'')), 'B')  ||
    setweight(to_tsvector(coalesce(abstract,'')), 'C') ||
    setweight(to_tsvector(coalesce(body,'')), 'D');

Здесь мы использовали setweight, чтобы отметить происхождение каждой лексемы в итоговых значениях tsvector, а затем объединили помеченные значения с помощью оператора конкатенации типов tsvector ||. (Подробную информацию об этих операциях см. в подразделе Обработка документов.)

Анализ запросов

Для преобразования запроса в тип данных tsquery QHB предоставляет функции to_tsquery, plainto_tsquery, phraseto_tsquery и websearch_to_tsquery. Функция to_tsquery дает доступ к более широким возможностям, чем plainto_tsquery или phraseto_tsquery, но более строга к входным данным. Функция websearch_to_tsquery является упрощенной версией to_tsquery с альтернативным синтаксисом, похожим на тот, что применяется в поисковых системах в Интернете.

to_tsquery([ конфигурация regconfig, ] текст_запроса text) returns tsquery

Функцияto_tsquery создает значение tsquery из текста_запроса, который должен состоять из одиночных синтаксических единиц, разделенных операторами типов tsquery & (И), | (ИЛИ), ! (НЕ) и <-> (ПРЕДШЕСТВУЕТ), возможно, сгруппированных с помощью скобок. Другими словами, входное значение для to_tsquery уже должно следовать общим правилам для входных значений tsquery, описанным в подразделе tsquery. Разница состоит в том, что в то время как в простом входном значении tsquery синтаксические единицы воспринимаются буквально, функция to_tsquery нормализует каждую единицу в лексему, используя конфигурацию по умолчанию или заданную, и отбрасывает те единицы, которые согласно этой конфигурации являются стоп-словам. Например:

SELECT to_tsquery('english', 'The & Fat & Rats');
  to_tsquery
---------------
 'fat' & 'rat'

Как и для простого входного значения tsquery, каждой лексеме можно назначить вес(а), чтобы они строго соответствовали только лексемами tsvector с такими же весами. Например:

SELECT to_tsquery('english', 'Fat | Rats:AB');
    to_tsquery
------------------
 'fat' | 'rat':AB

Также к лексеме можно добавить *, чтобы задать сопоставление по префиксу:

SELECT to_tsquery('supern:*A & star:A*B');
        to_tsquery
--------------------------
 'supern':*A & 'star':*AB

Такая лексема будет соответствовать любому слову в tsvector, начинающемуся с заданной строки.

Кроме того, to_tsquery может принимать фразы в апострофах. Это полезно в первую очередь когда конфигурация включает тезаурус, который может среагировать на такие фразы. В примере ниже тезаурус содержит правило supernovae stars : sn:

SELECT to_tsquery('''supernovae stars'' & !crab');
  to_tsquery
---------------
 'sn' & !'crab'

Без апострофов to_tsquery будет выдавать синтаксическую ошибку для единиц, не разделенных оператором И, ИЛИ или ПРЕДШЕСТВУЕТ.

plainto_tsquery([ конфигурация regconfig, ] текст_запроса text) returns tsquery

Функция plainto_tsquery преобразует неотформатированный текст_запроса в значение tsquery. Текст анализируется и нормализуется почти так же, как для to_tsvector, затем между оставшимися словами вставляется оператор tsquery & (И).

Пример:

SELECT plainto_tsquery('english', 'The Fat Rats');
 plainto_tsquery
-----------------
 'fat' & 'rat'

Обратите внимание, что plainto_tsquery не будет распознавать во входном значении операторы tsquery и метки весов и префиксов:

SELECT plainto_tsquery('english', 'The Fat & Rats:C');
   plainto_tsquery
---------------------
 'fat' & 'rat' & 'c'

В данном случае были отброшены все пунктуационные знаки.

phraseto_tsquery([ конфигурация regconfig, ] текст_запроса text) returns tsquery

Функция phraseto_tsquery ведет себя подобно plainto_tsquery, за исключением того, что вместо оператора & (И) она вставляет между оставшимися словами оператор <-> (ПРЕДШЕСТВУЕТ). Кроме того, стоп-слова не просто отбрасываются, а подсчитываются путем вставления операторов <N> вместо <->. Эта функция полезна при поиске точных последовательностей лексем, поскольку операторы ПРЕДШЕСТВУЕТ проверяют не только наличие всех лексем, но и их порядок.

Пример:

SELECT phraseto_tsquery('english', 'The Fat Rats');
 phraseto_tsquery
------------------
 'fat' <-> 'rat'

Как и plainto_tsquery, функция phraseto_tsquery не будет распознавать во входном значении операторы tsquery и метки весов и префиксов:

SELECT phraseto_tsquery('english', 'The Fat & Rats:C');
      phraseto_tsquery
-----------------------------
 'fat' <-> 'rat' <-> 'c'

websearch_to_tsquery([ конфигурация regconfig, ] текст_запроса text) returns tsquery

Функция websearch_to_tsquery создает значение tsquery из текста_запроса, используя альтернативный синтаксис, в котором простой неформатированный текст является допустимым запросом. В отличие от plainto_tsquery и phraseto_tsquery, она также распознает определенные операторы. Более того, эта функция никогда не будет выдавать синтаксические ошибки, что позволяет использовать для поиска необработанное значение, введенное пользователем. Поддерживается следующий синтаксис:

текст не в кавычках: текст, не заключенный в кавычки, будет преобразован в ключевые слова, разделенные операторами &, как если бы его обрабатывала plainto_tsquery.

"текст в кавычках": текст, заключенный в кавычки, будет преобразован в ключевые слова, разделенные операторами <->, как если бы его обрабатывала phraseto_tsquery.

OR: слово «or» будет преобразовано в оператор |.

-: минус будет преобразован в оператор !.

Другая пунктуация игнорируется. Поэтому, как и plainto_tsquery и phraseto_tsquery, функция websearch_to_tsquery не будет распознавать во входном значении операторы tsquery и метки весов и префиксов.

Примеры:

SELECT websearch_to_tsquery('english', 'The fat rats');
 websearch_to_tsquery
----------------------
 'fat' & 'rat'
(1 row)

SELECT websearch_to_tsquery('english', '"supernovae stars" -crab');
       websearch_to_tsquery
----------------------------------
 'supernova' <-> 'star' & !'crab'
(1 row)

SELECT websearch_to_tsquery('english', '"sad cat" or "fat rat"');
       websearch_to_tsquery
-----------------------------------
 'sad' <-> 'cat' | 'fat' <-> 'rat'
(1 row)

SELECT websearch_to_tsquery('english', 'signal -"segmentation fault"');
         websearch_to_tsquery
---------------------------------------
 'signal' & !( 'segment' <-> 'fault' )
(1 row)

SELECT websearch_to_tsquery('english', '""" )( dummy \\ query <->');
 websearch_to_tsquery
----------------------
 'dummi' & 'queri'
(1 row)

Ранжирование результатов поиска

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

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

ts_rank([ веса float4[], ] вектор tsvector, запрос tsquery [, нормализация integer ]) returns float4

Ранжирует векторы, основываясь на частоте соответствующих им лексем.

ts_rank_cd([ веса float4[], ] вектор tsvector, запрос tsquery [, нормализация integer ]) returns float4

Эта функция вычисляет плотность покрытия для данного вектора аргумента и запроса, как описано в статье Кларка, Кормана и Тадхоупа «Relevance Ranking for One to Three Term Queries» в журнале «Information Processing and Management», 1999 г. Плотность покрытия схожа с ранжированием ts_rank, но учитывается еще и близость соответствующих лексем друг к другу.

Для проведения расчетов этой функции требуется информация о позиции лексем. Поэтому она игнорирует все «очищенные» (от информации) лексемы в значении tsvector. Если во входных данных нет неочищенных лексем, результатом будет ноль. (Более подробную информацию о функции strip и позиционной информации в значениях tsvector см. в подразделе Обработка документов.)

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

{D-weight, C-weight, B-weight, A-weight}

Если веса не предоставляются, то используются эти значения по умолчанию:

{0.1, 0.2, 0.4, 1.0}

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

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

0 (по умолчанию): размер документа игнорируется

1: ранг делится на 1 + логарифм размера документа

2: ранг делится на размер документа

4: ранг делится на среднее гармоническое расстояние между областями (это реализовано только в ts_rank_cd)

8: ранг делится на количество уникальных слов в документе

16: ранг делится на 1 + логарифм количества уникальных слов в документе

32: ранг делится на свое же значение + 1

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

Следует отметить, что функции ранжирования не используют никакую глобальную информацию, поэтому добиться чистовой нормализации до 1% или 100% невозможно, хотя иногда и желательно. Можно применить параметр нормализации 32 (rank/(rank+1)), чтобы свести все ранги к диапазону от нуля до единицы, но, разумеется, это будет всего лишь косметическое изменение; оно не повлияет на порядок сортировки результатов поиска.

Вот пример, где выбираются только десять соответствий с наивысшим рангом:

SELECT title, ts_rank_cd(textsearch, query) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE query @@ textsearch
ORDER BY rank DESC
LIMIT 10;
                     title                     |   rank
-----------------------------------------------+----------
 Neutrinos in the Sun                          |      3.1
 The Sudbury Neutrino Detector                 |      2.4
 A MACHO View of Galactic Dark Matter          |  2.01317
 Hot Gas and Dark Matter                       |  1.91171
 The Virgo Cluster: Hot Plasma and Dark Matter |  1.90953
 Rafting for Solar Neutrinos                   |      1.9
 NGC 4650A: Strange Galaxy and Dark Matter     |  1.85774
 Hot Gas and Dark Matter                       |   1.6123
 Ice Fishing for Cosmic Neutrinos              |      1.6
 Weak Lensing Distorts the Universe            | 0.818218

Тот же пример с нормализованным ранжированием:

SELECT title, ts_rank_cd(textsearch, query, 32 /* rank/(rank+1) */ ) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE  query @@ textsearch
ORDER BY rank DESC
LIMIT 10;
                     title                     |        rank
-----------------------------------------------+-------------------
 Neutrinos in the Sun                          | 0.756097569485493
 The Sudbury Neutrino Detector                 | 0.705882361190954
 A MACHO View of Galactic Dark Matter          | 0.668123210574724
 Hot Gas and Dark Matter                       |  0.65655958650282
 The Virgo Cluster: Hot Plasma and Dark Matter | 0.656301290640973
 Rafting for Solar Neutrinos                   | 0.655172410958162
 NGC 4650A: Strange Galaxy and Dark Matter     | 0.650072921219637
 Hot Gas and Dark Matter                       | 0.617195790024749
 Ice Fishing for Cosmic Neutrinos              | 0.615384618911517
 Weak Lensing Distorts the Universe            | 0.450010798361481

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

Выделение результатов

Чтобы представить результаты поиска, в идеале нужно отобразить часть каждого документа и показать, как она связана с запросом. Обычно поисковые системы отображают фрагменты документа с отмеченными ключевыми словами. Для реализации этой функциональности QHB предоставляет функцию ts_headline.

ts_headline([ конфигурация regconfig, ] документ text, запрос tsquery [, параметры text ]) returns text

ts_headline принимает документ вместе с запросом и возвращает отрывок из документа, в котором выделены ключевые слова из запроса. Конфигурацию, используемую для синтаксического анализа документа, можно указать в параметре конфигурация; если он опущен, применяется конфигурация default_text_search_config.

Если задается строка параметры, она должна состоять из списка разделенных запятыми пар параметр=значение (их может быть одна или несколько). Доступные параметры:

MaxWords, MinWords (целочисленные): эти числа определяют максимальную и минимальную длину отображаемой выдержки. Значения по умолчанию — 35 и 15 соответственно.

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

HighlightAll (логический): при значении true в качестве выдержки будет использован весь документ, и предыдущие три параметра игнорируются. Значение по умолчанию — false.

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

StartSel, StopSel (строки): строки, которые будут разграничивать имеющиеся в документе слова запроса, чтобы выделить их среди остальных слов в выдержке. Значения по умолчанию — «<b>» и «</b>», что может быть удобно для вывода HTML.

FragmentDelimiter (строка): При отображении нескольких фрагментов они будут разделяться этой строкой. Значение по умолчанию — « ... ».

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

При генерировании выдержек без фрагментов функция ts_headline находит вхождения, соответствующие заданному запросу и выбирает одно из них для отображения, предпочитая те, что содержат в пределах допустимой длины выдержки больше слов из запроса. При генерировании выдержек с фрагментами функция ts_headline находит вхождения, соответствующие запросу, и разделяет их на «фрагменты», состоящие не более чем из MaxWords слов каждый, предпочитая фрагменты, содержащие больше слов из запроса, а затем может «растянуть» фрагменты, включив в них окружающие слова. Таким образом, режим с фрагментами более полезен, когда вхождения слов разбросаны по большим разделам документа или когда желательно отобразить несколько вхождений. Если ни одного соответствия выявить не удается, в обоих режимах будет выведен один фрагмент с первыми MinWords словами из документа.

Например:

SELECT ts_headline('english',
  'The most common type of search
is to find all documents containing given query terms
and return them in order of their similarity to the
query.',
  to_tsquery('english', 'query & similarity'));
                        ts_headline
------------------------------------------------------------
 containing given <b>query</b> terms                       +
 and return them in order of their <b>similarity</b> to the+
 <b>query</b>.

SELECT ts_headline('english',
  'Search terms may occur
many times in a document,
requiring ranking of the search matches to decide which
occurrences to display in the result.',
  to_tsquery('english', 'search & term'),
  'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<, StopSel=>>');
                        ts_headline
------------------------------------------------------------
 <<Search>> <<terms>> may occur                            +
 many times ... ranking of the <<search>> matches to decide

Функция ts_headline использует оригинальный документ, а не сжатый конспект tsvector, поэтому она может быть медленной, и применять ее следует с осторожностью.

Дополнительная функциональность

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

Обработка документов

В подразделе Анализ документов описывалось, как исходные текстовые документы можно преобразовать в значения типа tsvector. QHB предоставляет также функции и операторы, которые можно использовать для обработки документов, уже представленных в виде tsvector.

tsvector || tsvector

Оператор конкатенации значений tsvector возвращает вектор, объединяющий лексемы и позиционную информацию двух векторов, заданных в аргументах. Позиции и метки весов во время конкатенации сохраняются. Позиции, находящиеся в правом векторе, сдвигаются на максимальное значение позиции, указанной в левом векторе, так что результат почти равнозначен применению to_tsvector к результату конкатенации двух исходных строк документа. (Идентичность не полная, поскольку стоп-слова, удаленные в конце левого аргумента, не повлияют на результат, тогда как при использовании текстовой конкатенации они бы повлияли на позиции лексем в правом аргументе.)

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

setweight(вектор tsvector, вес "char") returns tsvector

Функция setweight возвращает копию входного вектора, в котором каждая позиция была помечена заданным весом: A, B, C или D. (D является меткой по умолчанию для новых векторов, поэтому не отображается в выводе.) Эти метки сохраняются при конкатенации векторов, позволяя функциям ранжировать слова из разных частей документа по-разному.

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

length(вектор tsvector) returns integer

Возвращает количество лексем, сохраненных в векторе.

strip(вектор tsvector) returns tsvector

Возвращает вектор, в котором перечисляются те же лексемы, что и в заданном, но без информации о позиции и весе. Обычно результат гораздо меньше неочищенного вектора, но при этом он менее полезен. С очищенными векторами хуже работает ранжирование по релевантности. Кроме того, оператор tsquery <-> (ПРЕДШЕСТВУЕТ) никогда не найдет соответствие в очищенном входном значении, так как не сможет определить расстояние между вхождениями лексем.

Полный список связанных с tsvector функций приведен в таблице Функции текстового поиска.

Обработка запросов

В подразделе Анализ запросов описывалось, как исходные текстовые запросы можно преобразовать в значения типа tsquery. QHB предоставляет также функции и операторы, которые можно использовать для обработки запросов, уже представленных в виде tsquery.

tsquery && tsquery

Возвращает логическое произведение (И) двух данных запросов.

tsquery || tsquery

Возвращает логическое объединение (ИЛИ) двух данных запросов.

!! tsquery

Возвращает логическое отрицание (НЕ) данного запроса.

tsquery <-> tsquery

Возвращает запрос, который ищет соответствие первому данному запросу, сразу за которым следует соответствие второму данному запросу, используя оператор tsquery <-> (ПРЕДШЕСТВУЕТ). Например:

SELECT to_tsquery('fat') <-> to_tsquery('cat | rat');
          ?column?
----------------------------
 'fat' <-> ( 'cat' | 'rat' )

tsquery_phrase(запрос1 tsquery, запрос2 tsquery [, расстояние integer ]) returns tsquery

Возвращает запрос, который ищет соответствие первому данному запросу, за которым точно на заданном расстоянии (указывается в лексемах) следует соответствие второму данному запросу, используя оператор tsquery <N>. Например:

SELECT tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'), 10);
  tsquery_phrase
------------------
 'fat' <10> 'cat'

numnode(запрос tsquery) returns integer

Возвращает количество узлов (лексемы плюс операторы) в значении tsquery. Эта функция помогает определить, имеет ли запрос смысл (возвращает значение > 0), или он содержит только стоп-слова (возвращает 0). Примеры:

SELECT numnode(plainto_tsquery('the any'));
NOTICE:  query contains only stopword(s) or does not contain lexeme(s), ignored
-- ЗАМЕЧАНИЕ:  запрос содержит только стоп-слово(а) или не содержит лексем, поэтому игнорируется
 numnode
---------
       0

SELECT numnode('foo & bar'::tsquery);
 numnode
---------
       3

querytree(запрос tsquery) returns text

Возвращает часть значения tsquery, которую можно использовать для поиска по индексу. Эта функция помогает выявить неиндексируемые запросы, например такие, которые содержат только стоп-слова или только ключевые слова отрицания. Например:

SELECT querytree(to_tsquery('defined'));
 querytree
-----------
 'defin'

SELECT querytree(to_tsquery('!defined'));
 querytree
-----------
 T

Перезапись запросов

Семейство функций ts_rewrite ищет в данном значении tsquery вхождения целевого подзапроса и заменяет каждое из них указанным подставляемым подзапросом. По сути эта операция является специфичной для tsquery версией замены подстроки в строке. Сочетание цели и подстановки можно считать правилом перезаписи запросов. Коллекция таких правил перезаписи может быть полезным средством поиска. Например, можно расширить поиск с помощью синонимов (например, new york, big apple, nyc, gotham) или сузить область поиска, чтобы нацелить пользователя на некоторую актуальную тему. Эта функциональность в некотором смысле пересекается с функциональностью тезаурусов (см. подраздел Тезаурус). Однако при этом можно изменять набор правил перезаписи на лету, тогда как для вступления в силу обновления тезауруса требуется переиндексация.

ts_rewrite (запрос tsquery, цель tsquery, подстановка tsquery) returns tsquery

Эта форма ts_rewrite просто применяет одно правило перезаписи: цель заменяется подстановкой везде, где она встречается в запросе. Например:

SELECT ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'c'::tsquery);
 ts_rewrite
------------
 'b' & 'c'

ts_rewrite (запрос tsquery, select text) returns tsquery

Эта форма ts_rewrite принимает начальный запрос и команду SQL select, которая задается текстовой строкой. Команда select должна выдавать два столбца типа tsquery. Для каждой строки результата select вхождения значения первого столбца (цели) заменяются значением второго столбца (подстановкой) в текущем значении запроса. Например:

CREATE TABLE aliases (t tsquery PRIMARY KEY, s tsquery);
INSERT INTO aliases VALUES('a', 'c');

SELECT ts_rewrite('a & b'::tsquery, 'SELECT t,s FROM aliases');
 ts_rewrite
------------
 'b' & 'c'

Обратите внимание, что когда таким способом применяются несколько правил перезаписи, порядок их применения может быть важен, поэтому на практике рекомендуется в исходном запросе добавить ORDER BY с каким-либо ключом упорядочивания.

Давайте рассмотрим практический пример, касающийся астрономии. Мы развернем запрос supernovae, используя правила перезаписи в таблице:

CREATE TABLE aliases (t tsquery primary key, s tsquery);
INSERT INTO aliases VALUES(to_tsquery('supernovae'), to_tsquery('supernovae|sn'));

SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases');
           ts_rewrite
---------------------------------
 'crab' & ( 'supernova' | 'sn' )

Мы можем изменить правила перезаписи, просто изменив таблицу:

UPDATE aliases
SET s = to_tsquery('supernovae|sn & !nebulae')
WHERE t = to_tsquery('supernovae');

SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases');
                 ts_rewrite
---------------------------------------------
 'crab' & ( 'supernova' | 'sn' & !'nebula' )

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

SELECT ts_rewrite('a & b'::tsquery,
                  'SELECT t,s FROM aliases WHERE ''a & b''::tsquery @> t');
 ts_rewrite
------------
 'b' & 'c'

Триггеры для автоматических обновлений

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

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

tsvector_update_trigger(имя_столбца_tsvector,​ имя_конфигурации, имя_столбца_текста [, ... ])
tsvector_update_trigger_column(имя_столбца_tsvector,​ имя_столбца_конфигурации, имя_столбца_текста [, ... ])

Эти триггерные функции автоматически вычисляют значение для столбца tsvector из одного или нескольких текстовых столбцов под контролем параметров, заданных в команде CREATE TRIGGER. Пример их использования:

CREATE TABLE messages (
    title       text,
    body        text,
    tsv         tsvector
);

CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON messages FOR EACH ROW EXECUTE FUNCTION
tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);

INSERT INTO messages VALUES('title here', 'the body text is here');

SELECT * FROM messages;
   title    |         body          |            tsv
------------+-----------------------+----------------------------
 title here | the body text is here | 'bodi':4 'text':5 'titl':1

SELECT title, body FROM messages WHERE tsv @@ to_tsquery('title & body');
   title    |         body
------------+-----------------------
 title here | the body text is here

После создания этого триггера любое изменение в поле title или body будет автоматически отражаться в содержимом tsv, так что приложению не придется заниматься этим.

Первым документом триггера должно быть имя обновляемого столбца tsvector. Во втором аргументе задается конфигурация текстового поиска, которая будет применяться для преобразования. Для функции tsvector_update_trigger имя конфигурации передается просто как второй аргумент триггера. Оно должно быть дополнено схемой, как показано выше, чтобы поведение триггера не менялось при изменениях в пути поиска search_path. Для функции tsvector_update_trigger_column второй аргумент триггера представляет собой имя другого столбца таблицы, который должен иметь тип regconfig. Это позволяет выбирать для разных строк разные конфигурации. Оставшиеся аргументы являются именами текстовых столбцов (типа text, varchar или char). Их содержимое будет включено в документ в заданном порядке. Значения NULL будут пропущены (но другие столбцы при этом все равно будут индексироваться).

Ограничение этих встроенных триггеров состоит в том, что они обрабатывают все входные столбцы одинаково. Чтобы столбцы обрабатывались по-разному — например, чтобы заголовку присваивался вес, отличный от веса тела документа, — нужно будет написать специальный триггер. Вот пример того, как это можно сделать на языке PL/pgSQL:

CREATE FUNCTION messages_trigger() RETURNS trigger AS $$
begin
  new.tsv :=
     setweight(to_tsvector('pg_catalog.english', coalesce(new.title,'')), 'A') ||
     setweight(to_tsvector('pg_catalog.english', coalesce(new.body,'')), 'D');
  return new;
end
$$ LANGUAGE plpgsql;

CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
    ON messages FOR EACH ROW EXECUTE FUNCTION messages_trigger();

Учтите, что, создавая в триггерах значения tsvector, важно явно задавать имя конфигурации, чтобы на содержимое столбца не влияли изменения параметра default_text_search_config. Если этого не сделать, скорее всего возникнут проблемы; например, результаты поиска изменятся после выгрузки и восстановления данных.

Сбор статистики по документу

Функция ts_stat полезна для проверки конфигурации и нахождения потенциальных стоп-слов.

ts_stat(запрос_sql text, [ веса text, ]
        OUT слово text, OUT число_док integer,
        OUT число_вхожд integer) returns setof record

Здесь запрос_sql — это текстовое значение, содержащее запрос SQL, который должен возвращать единственный столбец tsvector. Функция ts_stat выполняет этот запрос и возвращает статистику по каждой отдельной лексеме (слову), содержащейся в данных tsvector. Возвращаются следующие столбцы:

слово text — значение лексемы

число_док integer — количество документов (значений tsvector), в которых встретилось слово

число_вхожд integer — общее количество вхождений слова

Если предоставляются веса, то подсчитываются только вхождения с одним из этих весов.

Например, чтобы найти десять наиболее часто встречающихся слов в коллекции документов, можно выполнить:

SELECT * FROM ts_stat('SELECT vector FROM apod')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;

То же самое, но подсчитываются только вхождения слов с весом A или B:

SELECT * FROM ts_stat('SELECT vector FROM apod', 'ab')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;

Синтаксические анализаторы

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

Этот встроенный синтаксический анализатор называется pg_catalog.default. Он распознает 23 типа синтаксических единиц, перечисленные в Таблице 1.

Таблица 1. Типы синтаксических единиц, выделяемых стандартным анализатором

Псевдоним | Описание | Пример
asciiword | Слово только из букв ASCII | elephant
word | Слово из любых букв | mañana
numword | Слово из букв и цифр | beta1
asciihword | Слово только из букв ASCII с дефисами | up-to-date
hword | Слово из любых букв с дефисами | lógico-matemática
numhword | Слово из букв и цифр с дефисами | qhb-beta1
hword_asciipart | Часть слова с дефисами, только из букв ASCII | qhb в контексте qhb-beta1
hword_part | Часть слова с дефисами, из любых букв | lógico или matemática в контексте lógico-matemática
hword_numpart | Часть слова с дефисами, из букв и цифр | beta1 в контексте qhb-beta1
email | Адрес электронной почты | foo@example.com
protocol | Префикс протокола | http://
url | URL | example.com/stuff/index.html
host | Имя хоста | example.com
url_path | Путь в URL | /stuff/index.html, в контексте URL
file | Путь или имя файла | /usr/local/foo.txt, если не входит в URL
sfloat | Научная запись числа | -1.234e56
float | Десятичная запись числа | -1.234
int | Целое число со знаком | -1234
uint | Целое число без знака | 1234
version | Номер версии | 1.5.1
tag | Тег XML | <a href="dictionaries.html">
entity | Сущность XML | &
blank | Символы-разделители | (любые пробельные символы или знаки препинания, не вошедшие в другие категории)

Примечание
Для анализатора понятие «буква» определяется настройками локали базы данных, а именно параметром lc_ctype. Слова, содержащие только буквы из ASCII распознаются как отдельный тип единиц, поскольку иногда бывает полезно их выделить. В большинстве европейских языков типы единиц word и asciiword должны обрабатываться одинаково.

Тип email поддерживает не все символы, считающиеся допустимыми согласно стандарту RFC 5322. В частности, для имен почтовых ящиков помимо буквенно- цифровых символов поддерживаются только точка, минус и подчеркивание.

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

SELECT alias, description, token FROM ts_debug('foo-bar-beta1');
      alias      |               description                |     token
-----------------+------------------------------------------+---------------
 numhword        | Hyphenated word, letters and digits      | foo-bar-beta1
 hword_asciipart | Hyphenated word part, all ASCII          | foo
 blank           | Space symbols                            | -
 hword_asciipart | Hyphenated word part, all ASCII          | bar
 blank           | Space symbols                            | -
 hword_numpart   | Hyphenated word part, letters and digits | beta1

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

SELECT alias, description, token FROM ts_debug('http://example.com/stuff/index.html');
  alias   |  description  |            token
----------+---------------+------------------------------
 protocol | Protocol head | http://
 url      | URL           | example.com/stuff/index.html
 host     | Host          | example.com
 url_path | URL path      | /stuff/index.html

Словари

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

Несколько примеров нормализации:

Лингвистическая — словари Ispell пытаются свести входные слова к нормализованной форме; стеммеры убирают окончания слов

Адреса URL могут быть канонизированы, чтобы равнозначные URL считались совпадающими:

http://www.pgsql.ru/db/mw/index.html

http://www.pgsql.ru/db/mw/

http://www.pgsql.ru/db/../db/mw/index.html

Названия цветов могут быть заменены их шестнадцатеричными значениями, например red, green, blue, magenta -> FF0000, 00FF00, 0000FF, FF00FF

При индексировании чисел мы можем отбросить несколько цифр в дробной части для сокращения диапазона возможных чисел, чтобы, например, 3.14159265359, 3.1415926 и 3.14 стали одинаковыми после нормализации, если после десятичной точки останутся только две цифры.

Словарь — это программа, которая принимает на вход синтаксическую единицу и возвращает:

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

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

пустой массив, если словарь узнает эту синтаксическую единицу, но она является стоп-словом

NULL, если словарь не распознает введенную синтаксическую единицу

QHB предоставляет предопределенные словари для многих языков. Кроме того, имеется несколько предопределенных шаблонов, которые можно использовать для создания новых словарей с индивидуальными параметрами. Все эти шаблоны описаны ниже. Если ни один из существующих шаблонов не подходит, можно создать новые; примеры см. в каталоге share/extension установки QHB.

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

Общее правило конфигурации списка словарей заключается в том, чтобы поместить в начало наиболее узкопрофильные и специфические словари, затем более общие и закончить самым общим словарем, например стеммером Snowball или словарем simple, распознающим все. Например, для поиска по теме астрономии (конфигурация astro_en) тип единиц asciiword (слово из букв ASCII) можно связать со словарем синонимов астрономических терминов, с обычным английским словарем и со стеммером английских окончаний Snowball:

ALTER TEXT SEARCH CONFIGURATION astro_en
    ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem;

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

Стоп-слова

Стоп-слова — это слова, которые встречаются очень часто, есть почти в каждом документе и не имеют различительной ценности. Поэтому при полнотекстовом поиске их можно игнорировать. К примеру, каждый английский текст содержит слова a и the, так что хранить их в индексе не имеет смысла. Тем не менее стоп-слова влияют на позиции лексем в значении tsvector, что, в свою очередь, влияет на ранжирование:

SELECT to_tsvector('english', 'in the list of stop words');
        to_tsvector
----------------------------
 'list':3 'stop':5 'word':6

Позиции 1,2,4 отсутствуют, поскольку являются стоп-словами. Ранги, вычисленные для документов со стоп-словами и без них, значительно различаются:

SELECT ts_rank_cd (to_tsvector('english', 'in the list of stop words'), to_tsquery('list & stop'));
 ts_rank_cd
------------
       0.05

SELECT ts_rank_cd (to_tsvector('english', 'list stop words'), to_tsquery('list & stop'));
 ts_rank_cd
------------
        0.1

Как именно обрабатывать стоп-слова, определяет конкретный словарь. Например, словари ispell сначала нормализуют слова, а потом просматривают список стоп-слов, тогда как стеммеры Snowball сначала просматривают список стоп-слов. Причина такого разного поведения объясняется стремлением снизить количество «фоновых» слов.

Простой словарь

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

Вот пример определения словаря с помощью шаблона simple:

CREATE TEXT SEARCH DICTIONARY public.simple_dict (
    TEMPLATE = pg_catalog.simple,
    STOPWORDS = english
);

Здесь english — это базовое имя файла стоп-слов. Полным именем файла будет $SHAREDIR/tsearch_data/english.stop, где $SHAREDIR означает каталог с разделяемыми данными установки QHB, часто это /usr/local/share/qhb (если вы в этом не уверены, точно узнать его имя можно с помощью команды pg_config --sharedir). Формат этого файла представляет собой просто список слов, по одному в строке. Пустые строки и завершающие пробелы игнорируются, а символы в верхнем регистре переводятся в нижний, но больше содержимое файла никак не обрабатывается.

Теперь мы можем протестировать наш словарь:

SELECT ts_lexize('public.simple_dict', 'YeS');
 ts_lexize
-----------
 {yes}

SELECT ts_lexize('public.simple_dict', 'The');
 ts_lexize
-----------
 {}

Также мы можем сделать так, чтобы словарь возвращал NULL вместо слова в нижнем регистре, если то не обнаруживается в файле стоп-слов. Это поведение выбирается установкой в параметре словаря Accept значения false. Продолжая наш пример:

ALTER TEXT SEARCH DICTIONARY public.simple_dict ( Accept = false );

SELECT ts_lexize('public.simple_dict', 'YeS');
 ts_lexize
-----------

SELECT ts_lexize('public.simple_dict', 'The');
 ts_lexize
-----------
 {}

Со значением Accept = true (по умолчанию) словарь simple имеет смысл помещать только в конец списка словарей, поскольку он никогда не передаст синтаксическую единицу следующему словарю. И наоборот, Accept = false имеет смысл, только когда за нашим словарем идет еще хотя бы один словарь.

ВНИМАНИЕ!
Большинство типов словарей полагаются на файлы конфигурации, например, на файлы стоп-слов. Эти файлы должны храниться в кодировке UTF-8. Если текущая кодировка базы данных отличается, они будут переведены в нее, когда их прочитает сервер.

ВНИМАНИЕ!
Обычно сеанс базы данных прочитывает конфигурационный файл словаря только один раз, при первом использовании. Если вы изменяете этот файл и хотите, чтобы существующие сеансы работали с новым содержимым, выполните для этого словаря команду ALTER TEXT SEARCH DICTIONARY. Это обновление может быть «пустышкой» и в действительности не менять значения никаких параметров.

Словарь синонимов

Этот шаблон словарей используется для создания словарей, заменяющих слова их синонимами. Словосочетания не поддерживаются (для этого воспользуйтесь шаблоном тезауруса (подраздел Тезаурус)). Словарь синонимов можно использовать для преодоления лингвистических проблем, например, не дав стеммеру английского сократить слово «Paris» до «pari». Для этого достаточно записать в словаре синонимов строку Paris paris и поместить его перед словарем english_stem. Например:

SELECT * FROM ts_debug('english', 'Paris');
   alias   |   description   | token |  dictionaries  |  dictionary  | lexemes
-----------+-----------------+-------+----------------+--------------+---------
 asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | {pari}

CREATE TEXT SEARCH DICTIONARY my_synonym (
    TEMPLATE = synonym,
    SYNONYMS = my_synonyms
);

ALTER TEXT SEARCH CONFIGURATION english
    ALTER MAPPING FOR asciiword
    WITH my_synonym, english_stem;

SELECT * FROM ts_debug('english', 'Paris');
   alias   |   description   | token |       dictionaries        | dictionary | lexemes
-----------+-----------------+-------+---------------------------+------------+---------
 asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | my_synonym | {paris}

Единственный параметр, который нужен шаблону synonym, это SYNONYMS, в котором задается базовое имя его файла конфигурации — в примере выше это my_synonyms. Полным именем этого файла будет $SHAREDIR/tsearch_data/my_synonyms.syn (где $SHAREDIR означает каталог с разделяемыми данными установки QHB). Формат этого файла представляет собой просто строки, в каждой из которых сначала идет заменяемое слово, а затем, после пробела, его синоним. Пустые строки и завершающие пробелы игнорируются.

Шаблон synonym также принимает необязательный параметр CaseSensitive, который по умолчанию имеет значение false. При CaseSensitive равном false слова в файле синонимов приводятся к нижнему регистру, равно как и входные синтаксические единицы. При значении true слова и синтаксические единицы не приводятся к нижнему регистру, а сравниваются как есть.

В конце синонима в этом файле конфигурации можно добавить звездочку (*). Она указывает на то, что синоним является префиксом. В функции to_tsvector() звездочка игнорируется, но при ее использовании в to_tsquery(), результатом будет запрос с маркером сопоставления префиксов (см. подраздел Анализ запросов). Например, предположим, что в нашем файле $SHAREDIR/tsearch_data/ synonym_sample.syn имеются следующие записи:

quantumhybrid      qhb
quantumhybridbase  qhb
quantum qhb
gogle   googl
indices index*

Тогда мы получим такие результаты:

mydb=# CREATE TEXT SEARCH DICTIONARY syn (template=synonym, synonyms='synonym_sample');
mydb=# SELECT ts_lexize('syn', 'indices');
 ts_lexize
-----------
 {index}
(1 row)

mydb=# CREATE TEXT SEARCH CONFIGURATION tst (copy=simple);
mydb=# ALTER TEXT SEARCH CONFIGURATION tst ALTER MAPPING FOR asciiword WITH syn;
mydb=# SELECT to_tsvector('tst', 'indices');
 to_tsvector
-------------
 'index':1
(1 row)

mydb=# SELECT to_tsquery('tst', 'indices');
 to_tsquery
------------
 'index':*
(1 row)

mydb=# SELECT 'indexes are very useful'::tsvector;
            tsvector
---------------------------------
 'are' 'indexes' 'useful' 'very'
(1 row)

mydb=# SELECT 'indexes are very useful'::tsvector @@ to_tsquery('tst', 'indices');
 ?column?
----------
 t
(1 row)

Тезаурус

Тезаурус (Thesaurus или сокращенно TZ) представляет собой коллекцию слов и включает информацию о взаимосвязях слов и словосочетаний, т. е. более широкие понятия (broader terms, BT), более узкие понятия (narrower terms, NT), предпочтительные термины, нежелательные термины, родственные термины и т. д.

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

# это комментарий
sample word(s) : indexed word(s)
more sample word(s) : more indexed word(s)
...

где символ двоеточия (:) служит разделителем между словосочетанием и его заменой.

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

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

Конкретные стоп-слова, распознанные внутренним словарем, указать нельзя; вместо этого используйте ?, чтобы пометить позицию, где может находиться стоп-слово. Например, предположим, что, согласно внутреннему словарю, a и the являются стоп-словами:

? one ? two : swsw

соответствует входным строкам a one the two и the one a two; обе эти строки будут заменены на swsw.

Поскольку тезаурус способен распознавать словосочетания, он должен запоминать свое состояние и взаимодействовать с синтаксическим анализатором. Тезаурус использует эти привязки для проверки того, должен ли он обработать следующее слово или прекратить накопление словосочетания. Поэтому конфигурировать тезаурус следует аккуратно. К примеру, если назначить тезаурусу обработку только единиц asciiword, тогда определение one 7 в нем не будет работать, поскольку тип uint этому тезаурусу не назначен.

ВНИМАНИЕ!
Тезаурусы используются при индексации, поэтому любое изменение его параметров требует переиндексации. Для большинства других типов словарей небольшие изменения, например добавление или удаление стоп-слов, переиндексации не требуют.

Конфигурирование тезауруса

Для определения нового тезауруса используется шаблон thesaurus. Например:

CREATE TEXT SEARCH DICTIONARY thesaurus_simple (
    TEMPLATE = thesaurus,
    DictFile = mythesaurus,
    Dictionary = pg_catalog.english_stem
);

Здесь:

thesaurus_simple — это имя нового словаря

mythesaurus — это базовое имя файла конфигурации тезаурусы. (Полным именем будет $SHAREDIR/tsearch_data/mythesaurus.ths, где $SHAREDIR означает каталог разделяемых данных этой установки.)

pg_catalog.english_stem — это внутренний словарь (в данном случае это английский стеммер Snowball), который будет использоваться для нормализации тезауруса. Обратите внимание, что внутренний словарь будет иметь собственную конфигурацию (например, стоп-слова), которая здесь не показана.

Теперь можно связать тезаурус thesaurus_simple с желаемыми типами синтаксических единиц в конфигурации, например:

ALTER TEXT SEARCH CONFIGURATION russian
    ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
    WITH thesaurus_simple;

Пример тезауруса

Рассмотрим простой астрономический тезаурус thesaurus_astro, содержащий несколько комбинаций астрономических терминов:

supernovae stars : sn
crab nebulae : crab

Ниже мы создаем словарь и привязываем некоторые типы синтаксических единиц к астрономическому тезаурусу и английскому стеммеру:

CREATE TEXT SEARCH DICTIONARY thesaurus_astro (
    TEMPLATE = thesaurus,
    DictFile = thesaurus_astro,
    Dictionary = english_stem
);

ALTER TEXT SEARCH CONFIGURATION russian
    ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
    WITH thesaurus_astro, english_stem;

Теперь можно посмотреть, как он работает. Функция ts_lexize не очень полезна для проверки тезауруса, поскольку она обрабатывает входное значение как одну синтаксическую единицу. Вместо нее можно вызвать функции plainto_tsquery и to_tsvector, которые разобьют свои входные данные на несколько синтаксических единиц:

SELECT plainto_tsquery('supernova star');
 plainto_tsquery
-----------------
 'sn'

SELECT to_tsvector('supernova star');
 to_tsvector
-------------
 'sn':1

В принципе, можно воспользоваться и to_tsquery, если заключить аргумент в кавычки:

SELECT to_tsquery('''supernova star''');
 to_tsquery
------------
 'sn'

Обратите внимание, что supernova star совпадает с supernovae stars в thesaurus_astro, поскольку в определении тезауруса мы указали стеммер english_stem. Этот стеммер удалил окончания e и s.

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

supernovae stars : sn supernovae stars

SELECT plainto_tsquery('supernova star');
       plainto_tsquery
-----------------------------
 'sn' & 'supernova' & 'star'

Словарь Ispell

Шаблон словарей Ispell поддерживает морфологические словари, которые могут нормализовать множество разных лингвистических форм слова, сведя их к одной лексеме. Например, английский словарь Ispell может сопоставить все склонения и спряжения ключевого слова bank, например banking, banked, banks, banks' и bank's.

Стандартный дистрибутив QHB не включает файлы конфигурации Ispell. Словари для огромного числа языков можно загрузить со страницы Ispell. Кроме того, поддерживаются и некоторые другие современные форматы словарей — MySpell (OO < 2.0.1) и Hunspell (OO >= 2.0.2). Большой список словарей можно найти на странице OpenOffice Wiki.

Чтобы создать словарь Ispell, выполните следующие операции:

загрузите файлы конфигурации словаря. Пакет OpenOffice имеет расширение .oxt. Из него необходимо извлечь файлы .aff и .dic и поменять их расширения на .affix и .dict соответственно. Для некоторых файлов словарей также нужно преобразовать символы в кодировку UTF-8 с помощью команд (например, для словаря норвежского языка):

iconv -f ISO_8859-1 -t UTF-8 -o nn_no.affix nn_NO.aff
iconv -f ISO_8859-1 -t UTF-8 -o nn_no.dict nn_NO.dic

скопируйте файлы в каталог $SHAREDIR/tsearch_data

загрузите файлы в QHB с помощью следующей команды:

CREATE TEXT SEARCH DICTIONARY english_hunspell (
    TEMPLATE = ispell,
    DictFile = en_us,
    AffFile = en_us,
    Stopwords = english);

Здесь в параметрах DictFile, AffFile и StopWords задаются базовые имена файлов словаря, аффиксов и стоп-слов. Файл стоп-слов имеет тот же формат, что описывался выше для типа словаря simple. Формат остальных файлов здесь не описывается, но его можно посмотреть на указанных выше сайтах.

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

Файл .affix для Ispell имеет такую структуру:

prefixes
flag *A:
    .           >   RE      # As in enter > reenter
suffixes
flag T:
    E           >   ST      # As in late > latest
    [^AEIOU]Y   >   -Y,IEST # As in dirty > dirtiest
    [AEIOU]Y    >   EST     # As in gray > grayest
    [^EY]       >   EST     # As in small > smallest

А файл .dict — такую:

lapse/ADGRS
lard/DGRS
large/PRTY
lark/MRS

Формат файла .dict:

basic_form/affix_class_name

В файле .affix каждый флаг аффиксов описывается в следующем формате:

условие > [-отсекаемые_буквы,] добавляемый_аффикс

Здесь условие имеет формат, схожий с форматом регулярных выражений. В нем можно использовать группирование [...] и [^...]. Например, [AEIOU]Y означает, что последняя буква слова "y", а предпоследняя может быть "a", "e", "i", "o" или "u". [^EY] означает, что последняя буква не "e" и "y".

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

compoundwords  controlled z

Вот несколько примеров для норвежского языка:

SELECT ts_lexize('norwegian_ispell', 'overbuljongterningpakkmesterassistent');
   {over,buljong,terning,pakk,mester,assistent}
SELECT ts_lexize('norwegian_ispell', 'sjokoladefabrikk');
   {sjokoladefabrikk,sjokolade,fabrikk}

Формат MySpell является подмножеством формата Hunspell. Файл .affix словаря Hunspell имеет следующую структуру:

PFX A Y 1
PFX A   0     re         .
SFX T N 4
SFX T   0     st         e
SFX T   y     iest       [^aeiou]y
SFX T   0     est        [aeiou]y
SFX T   0     est        [^ey]

Первая строка класса аффиксов — заголовок. Поля правил аффиксов перечисляются после этого заголовка:

имя параметра (PFX или SFX)

флаг (имя класса префиксов)

отсекаемые символы в начале (в префиксе) или в конце (в суффиксе) слова

добавляемый аффикс

условие в формате, схожем с форматом регулярных выражений.

Файл .dict похож на файл .dict словаря Ispell:

larder/M
lardy/RT
large/RSPMYT
largehearted

Примечание
Словарь MySpell не поддерживает составные слова. В словаре Hunspell имеется полноценная поддержка этих слов. В настоящее время QHB реализует только наиболее простые операции с составными словами из предлагаемых Hunspell.

Словарь Snowball

Шаблон словарей Snowball основан на проекте Мартина Портера, изобретателя популярного алгоритма стемминга для английского языка. Сейчас Snowball предлагает стемминговые алгоритмы и для многих других языков (подробную информацию см. на сайте Snowball). Каждый алгоритм понимает, как выделить основу слова (т. е. свести распространенные словоформы к начальной форме) в своем языке. Для словаря Snowball задается обязательный параметр language, определяющий, какой именно стеммер использовать, и может задаваться необязательный параметр stopword, где указывается имя файла со списком исключаемых слов. (Стандартные списки стоп-слов QHB тоже используются в проекте Snowball.) Например, имеется встроенное определение, равнозначное выполнению

CREATE TEXT SEARCH DICTIONARY english_stem (
    TEMPLATE = snowball,
    Language = english,
    StopWords = english
);

Формат файла стоп-слов такой же, как у описанного выше.

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

Пример конфигурации

В конфигурации текстового поиска задаются все параметры, необходимые для преобразования документа в формат tsvector: синтаксический анализатор, который будет разбивать текст на синтаксические единицы, и словари, которые будут преобразовывать их в лексемы. При каждом вызове to_tsvector или to_tsquery для выполнения операций требуется конфигурация текстового поиска. В параметре конфигурации default_text_search_config задается имя конфигурации текстового поиска по умолчанию, которая будет использоваться функциями текстового поиска, если явный параметр конфигурации опущен. Этот параметр можно задать в qhb.conf, или установить в рамках отдельного сеанса с помощью команды SET.

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

В качестве примера мы создадим конфигурацию qhb, начав с копирования встроенной конфигурации english:

CREATE TEXT SEARCH CONFIGURATION public.qhb ( COPY = pg_catalog.english );

Мы будем использовать список синонимов, специфичных для QHB, который сохраним в файле $SHAREDIR/tsearch_data/qhb_dict.syn. Содержимое файла выглядит так:

QHBase             qhb
QHB                qhb
QuantumHybridBase  qhb

Мы определяем словарь синонимов следующим образом:

CREATE TEXT SEARCH DICTIONARY qhb_dict (
    TEMPLATE = synonym,
    SYNONYMS = qhb_dict
);

Затем мы регистрируем словарь Ispell english_ispell, имеющий собственные файлы конфигурации:

CREATE TEXT SEARCH DICTIONARY english_ispell (
    TEMPLATE = ispell,
    DictFile = english,
    AffFile = english,
    StopWords = english
);

Теперь мы можем настроить сопоставления для слов в конфигурации qhb:

ALTER TEXT SEARCH CONFIGURATION qhb
    ALTER MAPPING FOR asciiword, asciihword, hword_asciipart,
                      word, hword, hword_part
    WITH qhb_dict, english_ispell, english_stem;

Мы решаем не индексировать и не искать некоторые типы синтаксических единиц, которые обрабатывает встроенная конфигурация:

ALTER TEXT SEARCH CONFIGURATION qhb
    DROP MAPPING FOR email, url, url_path, sfloat, float;

Теперь мы можем протестировать нашу конфигурацию:

SELECT * FROM ts_debug('public.qhb', '
QHB, the highly scalable, SQL compliant, object-relational
database management system, is now undergoing beta testing of the next
version of our software.
');

Следующий шаг — настроить текущий сеанс на использование новой конфигурации, которая была создана в схеме public:

=> \dF
   List of text search configurations
 Schema  | Name | Description
---------+------+-------------
 public  | qhb   |

SET default_text_search_config = 'public.qhb';
SET

SHOW default_text_search_config;
 default_text_search_config
----------------------------
 public.qhb

Тестирование и отладка текстового поиска

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

Тестирование конфигурации

Функция ts_debug позволяет с легкостью протестировать конфигурацию текстового поиска.

ts_debug([ конфигурация regconfig, ] документ text,
         OUT псевдоним text,
         OUT описание text,
         OUT синтаксическая_единица text,
         OUT словари regdictionary[],
         OUT словарь regdictionary,
         OUT лексемы text[])
         returns setof record

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

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

псевдоним text — краткое имя типа синтаксической единицы

описание text — описание типа синтаксической единицы

синтаксическая_единица text — текст синтаксической единицы

словари regdictionary[] — словари, выбранные конфигурацией для этого типа синтаксической единицы

словарь regdictionary — словарь, распознавший эту синтаксическую единицу, или NULL, если ее не распознал ни один словарь

лексемы text[] — лексемы, выданные словарем, распознавшим синтаксическую единицу, или NULL, если ее не распознал ни один словарь; пустой массив ({}) означает, что единица была распознана как стоп-слово

Простой пример:

SELECT * FROM ts_debug('english', 'a fat  cat sat on a mat - it ate a fat rats');
   alias   |   description   | token |  dictionaries  |  dictionary  | lexemes
-----------+-----------------+-------+----------------+--------------+---------
 asciiword | Word, all ASCII | a     | {english_stem} | english_stem | {}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | fat   | {english_stem} | english_stem | {fat}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | cat   | {english_stem} | english_stem | {cat}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | sat   | {english_stem} | english_stem | {sat}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | on    | {english_stem} | english_stem | {}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | a     | {english_stem} | english_stem | {}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | mat   | {english_stem} | english_stem | {mat}
 blank     | Space symbols   |       | {}             |              |
 blank     | Space symbols   | -     | {}             |              |
 asciiword | Word, all ASCII | it    | {english_stem} | english_stem | {}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | ate   | {english_stem} | english_stem | {ate}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | a     | {english_stem} | english_stem | {}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | fat   | {english_stem} | english_stem | {fat}
 blank     | Space symbols   |       | {}             |              |
 asciiword | Word, all ASCII | rats  | {english_stem} | english_stem | {rat}

Для более полной демонстрации мы сначала создадим конфигурацию public.english и словарь Ispell для английского языка:

CREATE TEXT SEARCH CONFIGURATION public.english ( COPY = pg_catalog.english );

CREATE TEXT SEARCH DICTIONARY english_ispell (
    TEMPLATE = ispell,
    DictFile = english,
    AffFile = english,
    StopWords = english
);

ALTER TEXT SEARCH CONFIGURATION public.english
   ALTER MAPPING FOR asciiword WITH english_ispell, english_stem;

SELECT * FROM ts_debug('public.english', 'The Brightest supernovaes');
   alias   |   description   |    token    |         dictionaries          |   dictionary   |   lexemes
-----------+-----------------+-------------+-------------------------------+----------------+-------------
 asciiword | Word, all ASCII | The         | {english_ispell,english_stem} | english_ispell | {}
 blank     | Space symbols   |             | {}                            |                |
 asciiword | Word, all ASCII | Brightest   | {english_ispell,english_stem} | english_ispell | {bright}
 blank     | Space symbols   |             | {}                            |                |
 asciiword | Word, all ASCII | supernovaes | {english_ispell,english_stem} | english_stem   | {supernova}

В этом примере слово Brightest было распознано анализатором как слово из символов ASCII (псевдоним asciiword). Для этого типа синтаксических единиц список словарей включает english_ispell и english_stem. Слово было распознано english_ispell, который свел его к существительному bright. Слово supernovaes незнакомо словарю english_ispell поэтому оно было передано следующему словарю и, к счастью, было распознано (на самом деле english_stem является словарем Snowball, который распознает все; вот почему он был помещен в конец списка словарей).

Слово The было распознано словарем english_ispell как стоп-слово (подраздел Стоп-слова) и не будет индексироваться. Пробелы тоже отбрасываются, поскольку в этой конфигурации для них нет словарей.

Вы можете уменьшить ширину вывода, явно указав, какие столбцы вы хотите видеть:

SELECT alias, token, dictionary, lexemes
FROM ts_debug('public.english', 'The Brightest supernovaes');
   alias   |    token    |   dictionary   |   lexemes
-----------+-------------+----------------+-------------
 asciiword | The         | english_ispell | {}
 blank     |             |                |
 asciiword | Brightest   | english_ispell | {bright}
 blank     |             |                |
 asciiword | supernovaes | english_stem   | {supernova}

Тестирование синтаксического анализатора

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

ts_parse(имя_анализатора text, документ text,
         OUT код_единицы integer, OUT синтаксическая_единица text) returns setof record
ts_parse(oid_анализатора oid, документ text,
         OUT код_единицы integer, OUT синтаксическая_единица text) returns setof record

Функция ts_parse анализирует данный документ и возвращает последовательность записей, по одной для каждой синтаксической единицы, выделенной анализом. Каждая запись включает код_единицы, показывающий тип связанной синтаксической единицы и собственно синтаксическую_единицу в текстовом виде. Например:

SELECT * FROM ts_parse('default', '123 - a number');
 tokid | token
-------+--------
    22 | 123
    12 |
    12 | -
     1 | a
    12 |
     1 | number

ts_token_type(имя_анализатора text, OUT код_единицы integer,
              OUT псевдоним text, OUT описание text) returns setof record
ts_token_type(oid_анализатора oid, OUT код_единицы integer,
              OUT псевдоним text, OUT описание text) returns setof record

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

SELECT * FROM ts_token_type('default');
 tokid |      alias      |               description
-------+-----------------+------------------------------------------
     1 | asciiword       | Word, all ASCII
     2 | word            | Word, all letters
     3 | numword         | Word, letters and digits
     4 | email           | Email address
     5 | url             | URL
     6 | host            | Host
     7 | sfloat          | Scientific notation
     8 | version         | Version number
     9 | hword_numpart   | Hyphenated word part, letters and digits
    10 | hword_part      | Hyphenated word part, all letters
    11 | hword_asciipart | Hyphenated word part, all ASCII
    12 | blank           | Space symbols
    13 | tag             | XML tag
    14 | protocol        | Protocol head
    15 | numhword        | Hyphenated word, letters and digits
    16 | asciihword      | Hyphenated word, all ASCII
    17 | hword           | Hyphenated word, all letters
    18 | url_path        | URL path
    19 | file            | File or path name
    20 | float           | Decimal notation
    21 | int             | Signed integer
    22 | uint            | Unsigned integer
    23 | entity          | XML entity

Тестирование словаря

Функция ts_lexize обеспечивает тестирование словаря.

ts_lexize(словарь regdictionary, синтаксическая_единица text) returns text[]

ts_lexize возвращает массив лексем, если входная синтаксическая_единица известна словарю, или пустой массив, если единица известна словарю, но является стоп-словом, или NULL, если это незнакомое слово.

Примеры:

SELECT ts_lexize('english_stem', 'stars');
 ts_lexize
-----------
 {star}

SELECT ts_lexize('english_stem', 'a');
 ts_lexize
-----------
 {}

Примечание
Функция ts_lexize принимает одиночную синтаксическую единицу, а не текст. Вот пример возможной путаницы:

SELECT ts_lexize('thesaurus_astro', 'supernovae stars') is null;
 ?column?
----------
 t

Тезаурусу thesaurus_astro известно словосочетание supernovae stars, но функция ts_lexize не работает, поскольку она не анализирует входной текст, а воспринимает его как одну синтаксическую единицу. Поэтому для проверки тезаурусов воспользуйтесь функциями plainto_tsquery или to_tsvector, например:

SELECT plainto_tsquery('supernovae stars');
 plainto_tsquery
-----------------
 'sn'

Предпочтительные типы индексов для текстового поиска

Существует два вида индексов, которые можно применять для ускорения полнотекстового поиска: GIN и GiST. Обратите внимание, что индексы необязательны для выполнения полнотекстового поиска, но в случаях, когда по какому-либо столбцу регулярно проводится поиск, индексы обычно желательны.

Чтобы создать такой индекс, выполните одну из следующих команд:

CREATE INDEX имя ON таблица USING GIN (столбец);

Создает индекс на основе GIN (Generalized Inverted Index, обобщенный обратный индекс). Столбец должен иметь тип tsvector.

CREATE INDEX имя ON таблица USING GIST (столбец [ { DEFAULT | tsvector_ops } (siglen = число) ] );

Создает индекс на основе GiST (Generalized Search Tree, обобщенное дерево поиска). Столбец может иметь тип tsvector или tsquery. Необязательный целочисленный параметр siglen определяет длину сигнатуры в байтах (подробную информацию см. ниже).

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

Индекс GiST является неточным, то есть он может выдавать ложные соответствия, и для их исключения нужно проверить фактические строки таблицы. (при необходимости QHB делает это автоматически.) Индексы GiST неточные, поскольку каждый документ представляется в них сигнатурой фиксированной длины. Длина сигнатуры в байтах определяется значением необязательного целочисленного параметра siglen. По умолчанию (когда siglen не указан) длина сигнатуры равна 124 байтам, а максимальная длина составляет 2024 байта. Сигнатура генерируется путем хеширования каждого слова в один бит в строке из n-бит, а затем логического объединения всех этих битов в n-битовую сигнатуру документа. Когда два слова хешируются в один бит, появляется ложное соответствие. Если для всех слов в запросе обнаруживаются соответствия (фактические или ложные), для проверки их корректности следует извлечь строку таблицы. При увеличении длины сигнатуры поиск становится более точным (сканируется меньшая часть индекса и меньше страниц кучи), но за счет увеличения размера самого индекса.

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

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

Обратите внимание, что время построения индекса GIN можно сократить, увеличив значение maintenance_work_mem, тогда как на время построения индекса GiST этот параметр не влияет.

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

Поддержка psql

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

\dF{d,p,t}[+] [ШАБЛОН]

При добавлении необязательного + выводятся более подробные сведения.

Необязательный параметр ШАБЛОН может быть именем объекта текстового поиска, возможно, дополненным схемой. Если ШАБЛОН опущен, будет отображаться информация обо всех видимых объектах. ШАБЛОН может быть регулярным выражением и предоставлять отдельные шаблоны для схемы и объекта. Это иллюстрируют следующие примеры:

=> \dF *fulltext*
       List of text search configurations
 Schema |  Name        | Description
--------+--------------+-------------
 public | fulltext_cfg |

=> \dF *.fulltext*
       List of text search configurations
 Schema   |  Name        | Description
----------+----------------------------
 fulltext | fulltext_cfg |
 public   | fulltext_cfg |

Доступные команды:

\dF[+] [ШАБЛОН]

Список конфигураций текстового поиска (добавьте + для дополнительных сведений).

=> \dF russian
            List of text search configurations
   Schema   |  Name   |            Description
------------+---------+------------------------------------
 pg_catalog | russian | configuration for russian language

=> \dF+ russian
Text search configuration "pg_catalog.russian"
Parser: "pg_catalog.default"
      Token      | Dictionaries
-----------------+--------------
 asciihword      | english_stem
 asciiword       | english_stem
 email           | simple
 file            | simple
 float           | simple
 host            | simple
 hword           | russian_stem
 hword_asciipart | english_stem
 hword_numpart   | simple
 hword_part      | russian_stem
 int             | simple
 numhword        | simple
 numword         | simple
 sfloat          | simple
 uint            | simple
 url             | simple
 url_path        | simple
 version         | simple
 word            | russian_stem

\dFd[+] [ШАБЛОН]

Список словарей текстового поиска (добавьте + для дополнительных сведений).

=> \dFd
                             List of text search dictionaries
   Schema   |      Name       |                        Description
------------+-----------------+-----------------------------------------------------------
 pg_catalog | arabic_stem     | snowball stemmer for arabic language
 pg_catalog | armenian_stem   | snowball stemmer for armenian language
 pg_catalog | basque_stem     | snowball stemmer for basque language
 pg_catalog | catalan_stem    | snowball stemmer for catalan language
 pg_catalog | danish_stem     | snowball stemmer for danish language
 pg_catalog | dutch_stem      | snowball stemmer for dutch language
 pg_catalog | english_stem    | snowball stemmer for english language
 pg_catalog | finnish_stem    | snowball stemmer for finnish language
 pg_catalog | french_stem     | snowball stemmer for french language
 pg_catalog | german_stem     | snowball stemmer for german language
 pg_catalog | greek_stem      | snowball stemmer for greek language
 pg_catalog | hindi_stem      | snowball stemmer for hindi language
 pg_catalog | hungarian_stem  | snowball stemmer for hungarian language
 pg_catalog | indonesian_stem | snowball stemmer for indonesian language
 pg_catalog | irish_stem      | snowball stemmer for irish language
 pg_catalog | italian_stem    | snowball stemmer for italian language
 pg_catalog | lithuanian_stem | snowball stemmer for lithuanian language
 pg_catalog | nepali_stem     | snowball stemmer for nepali language
 pg_catalog | norwegian_stem  | snowball stemmer for norwegian language
 pg_catalog | portuguese_stem | snowball stemmer for portuguese language
 pg_catalog | romanian_stem   | snowball stemmer for romanian language
 pg_catalog | russian_stem    | snowball stemmer for russian language
 pg_catalog | serbian_stem    | snowball stemmer for serbian language
 pg_catalog | simple          | simple dictionary: just lower case and check for stopword
 pg_catalog | spanish_stem    | snowball stemmer for spanish language
 pg_catalog | swedish_stem    | snowball stemmer for swedish language
 pg_catalog | tamil_stem      | snowball stemmer for tamil language
 pg_catalog | turkish_stem    | snowball stemmer for turkish language
 pg_catalog | yiddish_stem    | snowball stemmer for yiddish language

\dFp[+] [ШАБЛОН]

Список синтаксических анализаторов текстового поиска (добавьте + для дополнительных сведений).

=> \dFp
        List of text search parsers
   Schema   |  Name   |     Description
------------+---------+---------------------
 pg_catalog | default | default word parser
=> \dFp+
    Text search parser "pg_catalog.default"
     Method      |    Function    | Description
-----------------+----------------+-------------
 Start parse     | prsd_start     |
 Get next token  | prsd_nexttoken |
 End parse       | prsd_end       |
 Get headline    | prsd_headline  |
 Get token types | prsd_lextype   |

        Token types for parser "pg_catalog.default"
   Token name    |               Description
-----------------+------------------------------------------
 asciihword      | Hyphenated word, all ASCII
 asciiword       | Word, all ASCII
 blank           | Space symbols
 email           | Email address
 entity          | XML entity
 file            | File or path name
 float           | Decimal notation
 host            | Host
 hword           | Hyphenated word, all letters
 hword_asciipart | Hyphenated word part, all ASCII
 hword_numpart   | Hyphenated word part, letters and digits
 hword_part      | Hyphenated word part, all letters
 int             | Signed integer
 numhword        | Hyphenated word, letters and digits
 numword         | Word, letters and digits
 protocol        | Protocol head
 sfloat          | Scientific notation
 tag             | XML tag
 uint            | Unsigned integer
 url             | URL
 url_path        | URL path
 version         | Version number
 word            | Word, all letters
(23 rows)

\dFt[+] [ШАБЛОН]

Список шаблонов текстового поиска (добавьте + для дополнительных сведений).

=> \dFt
                           List of text search templates
   Schema   |   Name    |                        Description
------------+-----------+-----------------------------------------------------------
 pg_catalog | ispell    | ispell dictionary
 pg_catalog | simple    | simple dictionary: just lower case and check for stopword
 pg_catalog | snowball  | snowball stemmer
 pg_catalog | synonym   | synonym dictionary: replace word by its synonym
 pg_catalog | thesaurus | thesaurus dictionary: phrase by phrase substitution

Ограничения

Функциональность текстового поиска в QHB имеет следующие ограничения:

Длина каждой лексемы должна быть меньше 2 килобайт

Длина значения tsvector (лексемы + их позиции) должна быть меньше 1 мегабайта

Количество лексем должно быть меньше 2⁶⁴

Значения позиций в tsvector должны находиться в диапазоне от 0 до 16 383 (включая верхнюю границу)

Расстояние между вхождениями соответствий в операторе <N> (ПРЕДШЕСТВУЕТ) типа tsquery не может превышать 16 384

Не больше 256 позиций для одной лексемы

Количество узлов (лексемы + операторы) в значении tsquery должно быть меньше 32 768

Управление параллельным доступом

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

Введение

QHB предоставляет разработчикам богатый набор инструментов для управления параллельным доступом к данным. Внутренняя согласованность данных поддерживается с помощью многоверсионной модели (Multiversion Concurrency Control, MVCC). Она устроена таким образом, что каждая команда SQL видит снимок данных (версию базы данных), сделанный некоторое время назад, независимо от текущего состояния нижележащих данных. Благодаря этому команды не видят несогласованных данных, появившихся в результате параллельных транзакций, вносящих изменения в одни и те же строки данных, тем самым обеспечивая изоляцию транзакций для каждого сеанса баз данных. MVCC, отказываясь от методологий блокировки традиционных СУБД, сводит к минимуму конфликты блокировок, чтобы обеспечить разумную производительность в многопользовательских средах.

Основное преимущество использования модели управления параллельным доступом MVCC вместо блокировок состоит в том, что в MVCC блокировки, полученные для запроса (чтения) данных, не конфликтуют с блокировками, полученными для записи данных, а потому чтение и запись никогда не блокируют друг друга. QHB гарантирует это даже при самом строгом уровне изоляции транзакций за счет использования инновационного уровня изоляции Сериализуемая изоляция снимков (Serializable Snapshot Isolation, SSI).

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

Изоляция транзакций

Стандарт SQL определяет четыре уровня изоляции транзакций. Наиболее строгим является сериализуемый, который определяется стандартом в параграфе, гласящим, что любое одновременное выполнение набора из нескольких сериализуемых транзакций должно гарантированно давать тот же эффект, что и запуск их по очереди в некотором порядке. Три других уровня определяются в терминах явлений, возникающих в результате взаимодействия между параллельными транзакциями, которые не должны происходить на каждом уровне. В стандарте отмечается, что из определения сериализуемого уровня вытекает, что на этом уровне невозможно ни одно из этих явлений. (В этом нет ничего удивительного — если эффект транзакций должен быть таким же, как и при выполнении их по очереди, как можно увидеть какие-либо явления, вызванные их взаимодействием?)

Явления, которые запрещены на разных уровнях:

«Грязное» чтение (dirty read)

Транзакция считывает данные, записанные параллельной незафиксированной транзакцией.

Неповторяемое чтение (nonrepeatable read)

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

Фантомное чтение (phantom read)

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

Аномалии сериализации (serialization anomaly)

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

Уровни изоляции транзакций, описанные в стандарте SQL и реализованные в QHB, описаны в таблице ниже.

Таблица 1. Уровни изоляции транзакций

Уровень изоляции | Грязное чтение | Неповторяемое чтение | Фантомное чтение | Аномалия сериализации
Read uncommitted (Чтение незафиксированных данных) | Разрешено, но не в QHB | Возможно | Возможно | Возможно
Read committed (Чтение зафиксированных данных) | Невозможно | Возможно | Возможно | Возможно
Repeatable read (Повторяемое чтение) | Невозможно | Невозможно | Разрешено, но не в QHB | Возможно
Serializable (Сериализуемость) | Невозможно | Невозможно | Невозможно | Невозможно

В QHB вы можете запросить любой из четырех стандартных уровней изоляции транзакций, но внутренне реализованы только три различных уровня, то есть режим Read Uncommitted в QHB ведет себя как Read Committed. Причиной является то, что это единственный разумный способ сопоставить стандартные уровни изоляции с архитектурой многоверсионного управления QHB.

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

Чтобы установить уровень изоляции транзакции, используйте команду SET TRANSACTION.

ВАЖНО
Для некоторых типов данных и функций QHB имеются специальные правила, касающиеся поведения транзакций. В частности, изменения, внесенные в последовательность (и, следовательно, в счетчик столбца, объявленного как serial), сразу видны всем другим транзакциям и не откатываются назад, если транзакция, которая внесла изменения, прерывается. См. раздел Функции для управления последовательностями и подраздел Последовательные типы.

Уровень изоляции Read Committed

Read Committed — уровень изоляции транзакций, выбираемый в QHB по умолчанию. Когда транзакция использует этот уровень изоляции, запрос SELECT (без предложения FOR UPDATE/SHARE) видит только данные, зафиксированные до начала запроса; он никогда не видит ни незафиксированные данные, ни изменения, зафиксированные во время выполнения запроса параллельными транзакциями. По сути, запрос SELECT видит снимок базы данных на момент начала выполнения запроса. Однако при этом SELECT видит и результаты предыдущих изменений, произошедших в его собственной транзакции, даже если они еще не зафиксированы. Также обратите внимание, что две последовательные команды SELECT могут видеть разные данные (даже если они выполняются в рамках одной транзакции), если другие транзакции фиксируют изменения данных после запуска первой SELECT и до запуска второй.

Относительно поиска целевых строк команды UPDATE, DELETE, SELECT FOR UPDATE и SELECT FOR SHARE ведут себя так же, как и SELECT: они будут находить только те целевые строки, которые были зафиксированы на момент запуска команды. Однако к моменту ее обнаружения такая целевая строка может быть уже изменена, удалена или заблокирована другой параллельной транзакцией. В этом случае запланированная изменяющая данные транзакция будет дожидаться фиксации или отката первой изменяющей данные транзакции (если та еще выполняется). Если первая транзакция откатывается, результат ее действий нивелируется, и вторая транзакция может продолжить изменение изначально обнаруженной строки. Если первая транзакция фиксируется и при этом удаляет строку, то вторая эту строку проигнорирует, а в противном случае попытается выполнить свою операцию с измененной версией строки. Условие поиска в команде (предложение WHERE) вычисляется повторно для проверки, по-прежнему ли измененная версия строки ему соответствует. Если да, то вторая изменяющая данные транзакция продолжает свою операцию с измененной версией строки. Применительно к SELECT FOR UPDATE и SELECT FOR SHARE это означает, что измененная версия строки блокируется и возвращается клиенту.

Команда INSERT с предложением ON CONFLICT DO UPDATE ведет себя схожим образом. В режиме Read Commited каждая строка, предлагаемая для добавления, будет либо добавлена, либо изменена. Если не возникает несвязанных ошибок, гарантируется один из этих двух исходов. Если конфликт вызван другой транзакцией, результат который пока невидим для INSERT, то предложение UPDATE подействует на эту строку, даже несмотря на то, что обычным путем данная команда может не видеть ни одной версии этой строки.

Выполнение INSERT с предложением ON CONFLICT DO NOTHING может привести к тому, что добавление строки не продолжиться из-за результата действий другой транзакции, эффекты которой не видны для снимка INSERT. Опять же, это характерно только для режима Read Committed.

MERGE позволяет пользователю указывать различные комбинации подкоманд INSERT, UPDATE и DELETE. Команда MERGE с подкомандами INSERT и UPDATE похожа на INSERT с предложением ON CONFLICT DO UPDATE, но не гарантирует, что команда INSERT или UPDATE будет осуществлена. Если MERGE пытается выполнить UPDATE или DELETE, и строка параллельно изменяется, но условие соединения по-прежнему выполняется для текущего целевого и текущего исходного кортежа, то MERGE будет вести себя так же, как команды UPDATE или DELETE, и произведет свои действия с измененной версией строки. Однако поскольку в MERGE можно задать несколько действий, и они могут быть условными, условия для всех действий вычисляются заново для измененной версии строки, начиная с первого действия, даже если действие, которое подходило изначально, фигурирует в списке действий позже. С другой стороны, если строка параллельно изменяется или удаляется так, что условие соединения не выполняется, тогда MERGE будет вычислять условия действий NOT MATCHED и выполнит действие с первым подходящим условием. Если MERGE пытается выполнить INSERT при наличии уникального индекса, и параллельно добавляется строка, то возникает ошибка нарушения уникальности; MERGE не пытается избежать таких ошибок, заново вычисляя условия MATCHED.

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

BEGIN;
UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534;
COMMIT;

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

В более сложных операциях режим Read Committed может привести к нежелательным результатам. Например, рассмотрим команду DELETE, работающую с данными, которые добавляются и удаляются из ее ограничительных критериев другой командой. Например, предположим, что website — это таблица из двух строк, где website.hits равны 9 и 10:

BEGIN;
UPDATE website SET hits = hits + 1;
-- выполняется параллельно в другом сеансе: DELETE FROM website WHERE hits = 10;
COMMIT;

Команда DELETE не сделает ничего, даже несмотря на то, что строка website.hits = 10 была в таблице как до, так и после выполнения UPDATE. Это связано с тем, что строка со значением 9 до изменения пропускается, а когда UPDATE завершается и DELETE получает блокировку, новым значением строки становится уже не 10, а 11, которое больше не соответствует критериям.

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

Частичной изоляции транзакций, обеспечиваемой режимом Read Committed, достаточно для многих приложений, и этот режим быстр и прост в использовании; однако он подходит не для всех случаев. Приложениям, которые выполняют сложные запросы и обновления, может потребоваться более строго согласованное представление базы данных, чем то, которое дает режим Read Committed.

Уровень изоляции Repeatable Read

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

Этот уровень отличается от Read Committed тем, что запрос в транзакции повторяемого чтения видит снимок состояния на момент начала первой команды в транзакции (не считая команд управления транзакцией), а не начала текущей команды в рамках транзакции. Таким образом, последовательные команды SELECT в одной транзакции видят одни и те же данные, т. е. они не видят изменений, внесенных другими транзакциями, зафиксированными после запуска их собственной транзакции.

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

Относительно поиска целевых строк команды UPDATE, DELETE, MERGE, SELECT FOR UPDATE и SELECT FOR SHARE ведут себя так же, как и SELECT: они будут находить только те целевые строки, которые были зафиксированы на момент запуска команды. Однако к моменту ее обнаружения такая целевая строка может быть уже изменена, удалена или заблокирована другой параллельной транзакцией. В этом случае транзакция в режиме Repeatable Read будет дожидаться фиксации или отката первой изменяющей данные транзакции (если та еще выполняется). Если первая транзакция откатывается, результат ее действий нивелируется, и текущая транзакция может продолжить изменение изначально обнаруженной строки. Но если первая транзакция фиксируется (и не просто блокирует строку, а на самом деле изменяет или удаляет ее), то произойдет откат текущей транзакции с сообщением

ERROR:  could not serialize access due to concurrent update
-- ОШИБКА: не удалось сериализовать доступ вследствие параллельного изменения

потому что транзакция в режиме Repeatable Read не может изменять или блокировать строки, измененные другими транзакциями с момента ее начала.

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

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

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

Для реализации уровня изоляции Repeatable Read применяется подход, который называется в академической литературе по базам данных и в других СУБД Изоляция снимков (Snapshot Isolation). По сравнению с системами, использующими традиционный метод блокировок, затрудняющий параллельное выполнение, при этом подходе наблюдается другое поведение и другая производительность. В некоторых СУБД могут существовать даже два отдельных уровня Repeatable Read и Snapshot Isolation с различным поведением. Допускаемые особые условия, представляющие отличия двух этих подходов, не были формализованы разработчиками теории БД до развития стандарта SQL, и их рассмотрение выходит за рамки данного руководства. Полностью эта тема рассматривается в техническом докладе «A Critique of ANSI SQL Isolation Levels».

Уровень изоляции Serializable

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

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

class | value
------+-------
    1 |    10
    1 |    20
    2 |   100
    2 |   200

Предположим, что сериализуемая транзакция A вычисляет:

SELECT SUM(value) FROM mytab WHERE class = 1;

а затем вставляет результат (30) в качестве value в новую строку с class = 2. Одновременно сериализуемая транзакция B вычисляет:

SELECT SUM(value) FROM mytab WHERE class = 2;

и получает результат 300, который вставляет в новую строку с class = 1. Затем обе транзакции пытаются зафиксироваться. Если какая-либо транзакция выполняется на уровне изоляции Repeatable Read, обеим будет разрешено зафиксироваться; но поскольку результат не соответствует последовательному порядку, использование транзакций в режиме Serializable позволит зафиксировать одну транзакцию и откатит другую с этим сообщением:

ERROR:  could not serialize access due to read/write dependencies among transactions
-- ОШИБКА: не удалось сериализовать доступ вследствие зависимостей чтения/записи между транзакциями

Это связано с тем, что если бы A выполнилась до B, то B вычислила бы сумму 330, а не 300, и точно так же при выполнении в обратном порядке А вычислила бы другую сумму.

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

Для гарантии истинной сериализуемости QHB использует предикатную блокировку, т. е. сохраняет блокировки, которые позволяют ей определять, когда запись могла бы повлиять на результат предыдущего чтения из параллельной транзакции, если бы та выполнялась первой. В QHB эти блокировки не приводят к реальной блокировке данных и, следовательно, никак не могут вызвать взаимоблокировку транзакций. Они используются для идентификации и пометки зависимостей между параллельными транзакциями уровня Serializable, которые в определенных комбинациях могут привести к аномалиям сериализации. Напротив, транзакции уровня Read Committed или Repeatable Read для обеспечения согласованности данных придется либо блокировать таблицу полностью, что может помешать другим пользователям ее использовать, либо применить команды SELECT FOR UPDATE или SELECT FOR SHARE, которые могут не только заблокировать другие транзакции, но и открыть доступ к диску.

Предикатные блокировки в QHB, как и в большинстве других СУБД, основаны на данных, фактически доступных транзакции. Они будут отображаться в системном представлении pg_locks со значением mode равным SIReadLock. Какие именно блокировки будут получены во время выполнения запроса, зависит от плана, используемого этим запросом, при этом несколько детализированных блокировок (например блокировки кортежей) в процессе транзакции могут объединяться в более общие (например блокировки страниц) для экономии памяти, затрачиваемой на отслеживание блокировок. На самом деле транзакция READ ONLY способна освободить свои блокировки SIRead до завершения, если обнаружит, что конфликты, которые могут привести к аномалии сериализации, по-прежнему исключены. Фактически транзакции READ ONLY зачастую могут установить этот факт еще при запуске и тем самым избежать каких-либо предикатных блокировок. Если явно запросить транзакцию SERIALIZABLE READ ONLY DEFERRABLE, она будет блокироваться, пока не сможет установить этот факт. (Это единственный случай, когда транзакции уровня Serializable блокируются, а транзакции уровня Repeatable Read — нет.) С другой стороны, блокировки SIRead часто должны удерживаться и после фиксации транзакции, пока не завершатся перекрывающие ее транзакции чтения-записи.

Последовательное использование сериализуемых транзакций может упростить разработку. Гарантия того, что любая совокупность успешно зафиксированных параллельных сериализуемых транзакций приведет к тому же результату, как если бы они выполнялись по очереди, означает, что если вы сумеете доказать, что одна транзакция определенного содержания будет работать правильно при отдельном запуске, то можете быть уверены, что эта транзакция будет работать правильно в любой комбинации сериализуемых транзакций, даже не имея никакой информации о том, что они могут делать, либо же она не будет успешно зафиксирована. При этом важно, чтобы в среде, в которой используется этот метод, был реализован общий способ обработки ошибок сериализации (которые всегда возвращаются со значением SQLSTATE равным '40001'), поскольку будет очень сложно заранее предсказать, какие именно транзакции могут стать жертвами зависимостей чтения/записи и потребуют отката во избежание аномалий сериализации. Мониторинг зависимостей чтения/записи создает нагрузку, как и перезапуск транзакций, которые прерываются с ошибкой сериализации, но если сравнить ее с затратами и блокированием, связанными с использованием явных блокировок и SELECT FOR UPDATE или SELECT FOR SHARE, то для некоторых сред сериализуемые транзакции гораздо выгоднее в плане производительности.

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

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

По возможности объявляйте транзакции как READ ONLY.

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

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

Не оставляйте соединения висеть «простаивающими в транзакции» дольше необходимого. Для автоматического отключения длительных сеансов можно использовать параметр конфигурации idle_in_transaction_session_timeout.

Исключите явные блокировки SELECT FOR UPDATE и SELECT FOR SHARE там, где они больше не нужны благодаря защите, автоматически предоставляемой сериализуемыми транзакциями.

Когда система вынуждена объединять несколько предикатных блокировок уровня страниц в одну предикатную блокировку уровня отношений, поскольку в таблице предикатных блокировок не хватает памяти, может возрасти частота сбоев сериализации. Этого можно избежать, увеличив параметры max_pred_locks_per_transaction, max_pred_locks_per_relation и/или max_pred_locks_per_page.

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

Для реализации уровня изоляции Serializable применяется подход, который называется в академической литературе по базам данных Сериализуемая изоляция снимков (Serializable Snapshot Isolation), который построен на основе обычной изоляции снимков путем добавления дополнительных проверок на предмет аномалий сериализации. По сравнению с другими системами, использующими традиционный метод блокировок, при этом подходе наблюдается другое поведение и другая производительность. Подробнее это рассматривается в статье «Serializable Snapshot Isolation in PostgreSQL».

Явная блокировка

Для управления параллельным доступом к данным в таблицах QHB предоставляет различные режимы блокировки. Эти режимы можно использовать для блокировки со стороны приложения в ситуациях, когда MVCC не вызывает желаемого поведения. Кроме того, большинство команд QHB автоматически получают блокировки соответствующих режимов для гарантии того, что во время выполнения команды ссылочные таблицы не будут несовместимым образом удалены или изменены. (Например, TRUNCATE не может безопасно выполняться параллельно с другими операциями в той же таблице, поэтому во избежание конфликтов эта команда получает эксклюзивную блокировку (ACCESS EXCLUSIVE) для таблицы.)

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

Блокировки на уровне таблицы

В приведенном ниже списке показаны доступные режимы блокировки и контексты, в которых они автоматически используются QHB. Кроме того, любую из этих блокировок можно получить явно с помощью команды LOCK. Помните, что все эти режимы блокировки являются блокировками на уровне таблицы, даже если имя режима содержит слово «строка»; такие имена сложились исторически. В некоторой степени имена отражают типичное использование каждого режима блокировки — но семантика у них одна. Единственное, что на самом деле отличает один режим блокировки от другого, это набор режимов блокировки, с которыми конфликтует каждый из них (см. Таблицу 2). Две транзакции не могут одновременно владеть блокировками конфликтующих режимов на одной и той же таблице. (Однако транзакция никогда не конфликтует сама с собой. Например, она может запросить блокировку ACCESS EXCLUSIVE а затем ACCESS SHARE для той же таблицы.) При этом многие транзакции могут одновременно владеть бесконфликтными режимами блокировки. Обратите внимание, в частности, на то, что некоторые режимы блокировки конфликтуют сами с собой (например, блокировкой ACCESS EXCLUSIVE в один момент времени может владеть только одна транзакция), тогда как другие этого не делают (например, блокировкой ACCESS SHARE могут одновременно владеть несколько транзакций).

Режимы блокировки на уровне таблицы

ACCESS SHARE (AccessShareLock)
Конфликтует только с режимом блокировки ACCESS EXCLUSIVE.
Команда SELECT запрашивает этот режим блокировки для ссылочных таблиц. В целом, любой запрос, который только читает таблицу и не изменяет ее, получит этот режим блокировки.

ROW SHARE (RowShareLock)
Конфликтует с режимами блокировки EXCLUSIVE и ACCESS EXCLUSIVE.
Команда SELECT получает блокировку в этом режиме для всех таблиц, для которых заданы параметры FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE или FOR KEY SHARE (в дополнение к блокировкам ACCESS SHARE для любых других ссылочных таблиц без явного параметра блокировки FOR ...).

ROW EXCLUSIVE (RowExclusiveLock)
Конфликтует с режимами блокировки SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE.
Команды UPDATE, DELETE, INSERT и MERGE запрашивают этот режим блокировки для целевой таблицы (в дополнение к блокировкам ACCESS SHARE для любых других ссылочных таблиц). В целом, этот режим блокировки получит любая команда, которая изменяет данные в таблице.

SHARE UPDATE EXCLUSIVE (ShareUpdateExclusiveLock)
Конфликтует с режимами блокировки SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE. Этот режим защищает таблицу от параллельного запуска изменений схемы и команды VACUUM.
Запрашивается командами VACUUM (без FULL), ANALYZE, CREATE INDEX CONCURRENTLY, REINDEX CONCURRENTLY, CREATE STATISTICS, COMMENT ON, REINDEX CONCURRENTLY, а также некоторыми вариантами ALTER INDEX и ALTER TABLE (подробную информацию см. на справочных страницах этих команд).

SHARE (ShareLock)
Конфликтует с режимами блокировки ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE. Этот режим защищает таблицу от параллельных изменений данных.
Запрашивается командой CREATE INDEX (без CONCURRENTLY).

SHARE ROW EXCLUSIVE (ShareRowExclusiveLock)
Конфликтует с режимами блокировки ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE. Этот режим защищает таблицу от параллельных изменений данных и является самоисключающим, поэтому получить его может только один сеанс за раз.
Запрашивается командой CREATE TRIGGER и некоторыми формами ALTER TABLE.

EXCLUSIVE (ExclusiveLock)
Конфликтует с режимами блокировки ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE. Этот режим допускает только одновременные блокировки ACCESS SHARE, т. к. параллельно с транзакцией, владеющей этим режимом блокировки, может выполняться только чтение из таблицы.
Запрашивается командой REFRESH MATERIALIZED VIEW CONCURRENTLY.

ACCESS EXCLUSIVE (AccessExclusiveLock)
Конфликтует со всеми режимами блокировки (ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE). Этот режим гарантирует, что только транзакция, владеющая этой блокировкой, имеет доступ к таблице (любым способом).
Запрашивается командами DROP TABLE, TRUNCATE, REINDEX, CLUSTER, VACUUM FULL и REFRESH MATERIALIZED VIEW (без CONCURRENTLY). Многие формы ALTER INDEX и ALTER TABLE также запрашивают блокировку на этом уровне. Это также режим блокировки по умолчанию для операторов LOCK TABLE, которые не указывают режим явно.

Подсказка
Только блокировка ACCESS EXCLUSIVE блокирует оператор SELECT (без FOR UPDATE/SHARE).

После получения блокировка обычно удерживается до конца транзакции. Но если блокировка получена после установления точки сохранения, то при откате к этой точке блокировка немедленно снимается. Это согласуется с принципом, что команда ROLLBACK отменяет все эффекты команд после точки сохранения. То же самое относится и к блокировкам, полученным в блоке исключений PL/pgSQL: при выходе из блока с ошибкой эти блокировки освобождаются.

Таблица 2. Конфликтующие режимы блокировки

Запраши
ваемый
режим бло
кировки
Текущий режим блокировки

ACCESS
SHARE
ROW
SHARE
ROW
EXCLUSIVE
SHARE
UPDATE
EXCLUSIVE
SHARE
SHARE
ROW
EXCLUSIVE
EXCLUSIVE
ACCESS
EXCLUSIVE

ACCESS SHARE

X

ROW SHARE

X
X

ROW EXCLUSIVE

X
X
X
X

SHARE UPDATE EXCLUSIVE

X
X
X
X
X

SHARE

X
X

X
X
X

SHARE ROW EXCLUSIVE

X
X
X
X
X
X

EXCLUSIVE

X
X
X
X
X
X
X

ACCESS EXCLUSIVE
X
X
X
X
X
X
X
X

Блокировки на уровне строк

В дополнение к блокировкам на уровне таблиц существуют блокировки на уровне строк, которые перечислены ниже с контекстами, в которых они автоматически используются QHB. Полный список конфликтов между блокировками уровня строк см. в Таблице 3. Обратите внимание, что одна транзакция может владеть конфликтующими блокировками одной строки, даже в разных субтранзакциях, но две разные транзакции не могут владеть конфликтующими блокировками одной и той же строки. Блокировки на уровне строк не влияют на запросы данных; они блокируют только записи и блокировки в одну и ту же строку. Блокировки уровня строк, как и блокировки уровня таблицы, освобождаются в конце транзакции или при откате к точке сохранения.

Режимы блокировки на уровне строк

FOR UPDATE
Режим FOR UPDATE блокирует извлеченные командой SELECT строки как для изменения. Это предотвращает их блокировку, изменение или удаление другими транзакциями до завершения текущей транзакции. То есть другие транзакции, пытающиеся выполнить с этими строками UPDATE, DELETE, SELECT FOR UPDATE, SELECT FOR NO KEY UPDATE, SELECT FOR SHARE или SELECT FOR KEY SHARE, будут заблокированы до завершения текущей транзакции; и наоборот, SELECT FOR UPDATE будет ожидать параллельную транзакцию, которая выполнила любую из этих команд в той же строке, а затем заблокирует и вернет измененную строку (или не вернет, если та была удалена). Однако в транзакции REPEATABLE READ или SERIALIZABLE возникнет ошибка, если строка, подлежащая блокировке, изменилась с момента запуска транзакции. Подробнее это рассматривается в разделе Проверка согласованности данных на уровне приложения.
Режим блокировки FOR UPDATE также запрашивается на уровне строки любой командой DELETE, а также командой UPDATE, изменяющей значения в определенных столбцах. В настоящее время вариант с UPDATE предусмотрен для столбцов с уникальным индексом, который можно использовать во внешнем ключе (поэтому частичные индексы и индексы выражений не учитываются), но в будущем это может измениться.

FOR NO KEY UPDATE
Ведет себя аналогично FOR UPDATE, за исключением того, что полученная блокировка слабее: она не будет блокировать команды SELECT FOR KEY SHARE, которые пытаются получить блокировку в тех же строках. Также этот режим блокировки активируется любой командой UPDATE, которая не запрашивает блокировку FOR UPDATE.

FOR SHARE
Ведет себя аналогично FOR NO KEY UPDATE, за исключением того, что для каждой извлеченной строки запрашивается совместная, а не эксклюзивная блокировка. Совместная блокировка блокирует в этих строках выполнение другими транзакциями команд UPDATE, DELETE, SELECT FOR UPDATE или SELECT FOR NO KEY UPDATE, но не препятствует выполнению SELECT FOR SHARE или SELECT FOR KEY SHARE.

FOR KEY SHARE
Ведет себя аналогично FOR SHARE, за исключением того, что эта блокировка слабее: она блокирует SELECT FOR UPDATE, но не SELECT FOR NO KEY UPDATE. Блокировка совместным ключом не дает другим транзакциям выполнить команду DELETE или любую команду UPDATE, изменяющую значения ключа, но не другие UPDATE, а также не препятствует выполнению SELECT FOR NO KEY UPDATE, SELECT FOR SHARE или SELECT FOR KEY SHARE.

QHB не хранит в памяти никакой информации об измененных строках, поэтому ограничений на количество блокируемых за раз строк нет. Однако блокировка строки может привести к записи на диск, например, SELECT FOR UPDATE изменяет выбранные строки, чтобы пометить их как заблокированные, и в результате происходит запись на диск.

Таблица 3. Конфликтующие блокировки на уровне строк

Запрашиваемый режим блокировки | Текущий режим блокировки
FOR KEY SHARE | FOR SHARE | FOR NO KEY UPDATE | FOR UPDATE
FOR KEY SHARE |  |  |  | X
FOR SHARE |  |  | X | X
FOR NO KEY UPDATE |  | X | X | X
FOR UPDATE | X | X | X | X

Блокировки на уровне страницы

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

Взаимные блокировки

Использование явной блокировки может увеличить вероятность взаимных блокировок, при которых каждая из двух (или более) транзакций удерживает блокировки, которые нужны другой. Например, если транзакция 1 получает эксклюзивную блокировку таблицы A, а затем пытается получить эксклюзивную блокировку таблицы B, которую до этого уже получила транзакция 2, в настоящий момент желающая получить эксклюзивную блокировку таблицы A, ни одна из этих транзакций не сможет продолжить работу. QHB автоматически выявляет случаи взаимоблокировки и разрешает их, прерывая одну из задействованных транзакций и позволяя другой (или другим) завершить работу. (Какая именно транзакция будет прервана, предсказать трудно, и на это не стоит полагаться.)

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

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;

Она получает блокировку на уровне строк в строке с указанным номером счета. Затем вторая транзакция выполняет:

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;

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

UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;

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

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

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

Рекомендательные блокировки

QHB предоставляет средства для создания блокировок, смысл которых определяет приложение. Они называются рекомендательными блокировками, поскольку система не навязывает их использование — правильное их применение зависит от самого приложения. Рекомендательные блокировки могут быть полезны для стратегий блокировки, которые плохо подходят для модели MVCC. Например, рекомендательные блокировки часто применяются для эмулирования стратегий пессимистической блокировки, типичных для так называемых систем управления данными «плоский файл». Хотя для той же цели можно использовать хранящийся в таблице флаг, рекомендательные блокировки работают быстрее, не раздувают таблицы и автоматически удаляются сервером в конце сеанса.

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

Как и все остальные блокировки в QHB, полный список рекомендательных блокировок, которыми в настоящее время владеют любые сеансы, можно найти в системном представлении pg_locks.

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

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

SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ок
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- опасно!
SELECT pg_advisory_lock(q.id) FROM
(
  SELECT id FROM foo WHERE id > 12345 LIMIT 100
) q; -- ок

В приведенных выше запросах вторая форма опасна, поскольку LIMIT не обязательно будет применен до выполнения функции блокировки. Это может привести к получению некоторых блокировок, которые приложение не ожидает и, следовательно, не сможет освободить (пока не завершится сеанс). С точки зрения приложения такие блокировки оказались бы в подвешенном состоянии, хоть и остались бы видимыми в pg_locks.

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

Проверка согласованности данных на уровне приложения

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

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

Как упоминалось в подразделе Уровень изоляции Serializable, транзакции Serializable — это те же транзакции Repeatable Read, только дополненные неблокирующим мониторингом опасных шаблонов конфликтов чтения/записи. При обнаружении шаблона, который может вызвать цикл в видимом порядке выполнения, одна из вовлеченных транзакций откатывается, чтобы прервать цикл.

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

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

Использование этого метода позволит программистам приложений избежать ненужной нагрузки, если программное обеспечение приложения проходит через инфраструктуру, которая автоматически повторяет транзакции, откатывающиеся с ошибкой сериализации. Может быть полезно задать параметру default_transaction_isolation значение serializable (т. е. сделать Serializable уровнем изоляции по умолчанию). Также было бы разумно предпринять некоторые действия для предотвращения использования других уровней изоляции (непреднамеренно или для саботирования проверок согласованности), проверяя уровень изоляции транзакции в триггерах.

Рекомендации по увеличению производительности см. в подразделе Уровень изоляции Serializable.

ПРЕДУПРЕЖДЕНИЕ
Уровень защиты согласованности с использованием сериализуемых транзакций пока не поддерживается в режиме горячего резерва (см. раздел Горячий резерв). Из-за этого там, где используется горячий резерв, может понадобиться применить уровень Repeatable Read и явную блокировку на главном сервере.

Обеспечение согласованности с помощью явных блокировок

Когда возможны несериализуемые записи, для обеспечения текущей достоверности строки и ее защиты от параллельных изменений, необходимо использовать SELECT FOR UPDATE, SELECT FOR SHARE или соответствующую команду LOCK TABLE. (SELECT FOR UPDATE и SELECT FOR SHARE блокируют от параллельных изменений только возвращенные строки, тогда как LOCK TABLE блокирует всю таблицу.) Это следует учитывать при переносе приложений в QHB из других сред.

Для тех, кто конвертирует из других сред, также следует отметить тот факт, что применение SELECT FOR UPDATE не гарантирует, что параллельная транзакция не станет изменять или удалять выбранную строку. Для получения такой гарантии в QHB нужно изменить строку, даже если нет необходимости менять ее значение. SELECT FOR UPDATE временно блокирует другие транзакции, не давая им получить ту же блокировку или выполнить UPDATE или DELETE, которые могут повлиять на заблокированную строку, но как только транзакция, удерживающая эту блокировку, фиксируется или откатывается, заблокированная транзакция продолжит выполнять конфликтующую операцию, если только во время удержания блокировки для данной строки действительно не был выполнен UPDATE.

Глобальные проверки достоверности с использованием несериализуемых MVCC требуют большей продуманности. Например, банковскому приложению может понадобиться проверить, что сумма всех расходов в одной таблице равна сумме приходов в другой, во время активного изменения обеих таблиц. Сравнение результатов двух последовательных команд SELECT sum(...) в режиме Read Committed не будет достоверным, поскольку второй запрос, скорее всего, будет включать результаты транзакций, не учитываемые первой. Подсчет двух сумм в одной транзакции Repeatable Read даст точную картину результатов действий только тех транзакций, которые были зафиксированы до начала данной — но можно с полным основанием задаться вопросом, будет ли ответ по-прежнему актуален на момент его выдачи. Если транзакция Repeatable Read сама внесла какие-то изменения, прежде чем попыталась проверить согласованность, полезность этой проверки становится еще более сомнительной, поскольку теперь она включает некоторые, но не все изменения, случившиеся после запуска транзакции. В таких случаях осмотрительный человек может заблокировать все задействованные в проверке таблицы, чтобы получить бесспорную картину реальной действительности. Блокировка в режиме SHARE (или более строгая) гарантирует отсутствие незафиксированных изменений в заблокированной таблице, за исключением тех, что внесла текущая транзакция.

Также обратите внимание, что при использовании явных блокировок для предотвращения параллельных изменений следует либо применять режим Read Committed, либо в режиме Repeatable Read обязательно получить блокировки перед выполнением запросов. Блокировка, полученная транзакцией Repeatable Read, гарантирует, что никакие другие транзакции, изменяющие таблицу не выполняются, но если снимок состояния, видимый транзакцией, предшествует получению блокировки, в нем могут отсутствовать некоторые уже зафиксированные на данный момент изменения в таблице. Снимок транзакции Repeatable Read фактически создается в начале ее первой команды запроса или изменения данных (SELECT, INSERT, UPDATE, DELETE или MERGE), поэтому получить явные блокировки можно до его создания.

Обработка сбоев сериализации

На уровнях изоляции Repeatable Read и Serializable могут выдаваться ошибки, разработанные для предотвращения аномалий сериализации. Как уже говорилось ранее, приложения, использующие эти уровни, должны быть готовы повторить транзакции, завершившиеся сбоем вследствие ошибок сериализации. Текст такого сообщения об ошибке будет варьироваться в зависимости от конкретных обстоятельств, но он всегда будет иметь код SQLSTATE 40001 (serialization_failure).

Также можно порекомендовать повторять запросы при ошибках взаимоблокировки. Их код SQLSTATE 40P01 (deadlock_detected).

В некоторых случаях также имеет смысл повторять запросы при ошибках нарушения уникальности ключа, которые имеют код SQLSTATE 23505 (unique_violation), и ошибках нарушения исключающего ограничения, которые имеют код SQLSTATE 23P01 (exclusion_violation). К примеру, если приложение выбирает новое значение для столбца первичного ключа после проверки сохраненных на данным момент ключей, оно может получить ошибку нарушения уникальности ключа, поскольку другой экземпляр этого же приложения параллельно выбрал такой же новый ключ. По сути, это сбой сериализации, но сервер не обнаруживает его как таковой, поскольку не может «видеть» связь между добавляемым значением и предыдущими чтениями. Есть также некоторые патологические случаи, когда сервер выдает ошибку нарушения уникальности ключа или исключающего ограничения, несмотря на то, что у него в принципе достаточно информации, чтобы определить, что первопричиной является проблема сериализации. Хотя запросы с ошибками serialization_failure рекомендуется просто безусловно повторять, при повторении запросов с другими кодами ошибок следует соблюдать осторожность, поскольку они могут указывать на присутствие постоянных состояний ошибки, а не временных сбоях.

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

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

Ограничения применимости

Некоторые команды DDL (в настоящее время это только TRUNCATE и формы ALTER TABLE, перезаписывающие таблицу) небезопасны с точки зрения MVCC. Это означает, что после фиксации опустошения или перезаписи таблица отобразится пустой для параллельных транзакций, если они используют снимок, сделанный перед фиксацией команды DDL. Это станет проблемой только для транзакции, которая не обращалась к рассматриваемой таблице до запуска команды DDL — любая транзакция, которая обращалась к ней раньше, получила бы по меньшей мере блокировку таблицы ACCESS SHARE, которая заблокировала бы эту команду DDL до завершения данной транзакции. Таким образом, эти команды не повлекут за собой каких-либо явных несоответствий с содержанием таблицы для последовательных запросов к целевой таблице, но могут вызвать видимое несоответствие между содержимым целевой таблицы и другими таблицами в базе данных.

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

Внутреннее обращение к системным каталогам проводится за рамками уровня изоляции текущей транзакции. Это означает, что вновь созданные объекты базы данных, например таблицы, являются видимыми для параллельных транзакций Repeatable Read и Serializable, несмотря на то, что строки в них не видны. Напротив, запросы, которые просматривают системные каталоги напрямую, не видят строки, представляющие параллельно созданные объекты базы данных, если используют более высокие уровни изоляции.

Блокировка и индексы

Хотя QHB обеспечивает неблокирующий доступ на чтение/запись к данным таблицы, на данный момент такой доступ поддерживается не для всех методов индексного доступа, реализованных в QHB. Различные типы индексов обрабатываются следующим образом:

Индексы B-деревья, GiST и SP-GiST

Для доступа на чтение/запись используются краткосрочные совместные/эксклюзивные блокировки на уровне страницы. Блокировки освобождаются сразу после извлечения или добавления каждой строки индекса. Эти типы индексов обеспечивают максимальный параллелизм операций без возникновения условий взаимоблокировки.

Хеш-индексы

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

Индексы GIN

Для доступа на чтение/запись используются краткосрочные совместные/эксклюзивные блокировки на уровне страницы. Блокировки освобождаются сразу после извлечения или добавления каждой строки индекса. Но обратите внимание, что добавление значения в поле с индексом GIN обычно приводит к нескольким добавлениям ключа индекса в каждую строку, поэтому GIN может проделывать значительную работу для вставки всего одного значения.
.

В настоящее время для параллельных приложений наилучшую производительность обеспечивают индексы B-деревья; поскольку они вдобавок обладают большими функциональными возможностями, чем хеш-индексы, именно их рекомендуется использовать для параллельных приложений, которым необходимо индексировать скалярные данные. При работе с нескалярными данными B-деревья бесполезны, и вместо них следует использовать индексы GiST, SP-GiST или GIN.

libpq — библиотека для языка C

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

Кроме того, libpq является компонентом, лежащим в основе нескольких других прикладных интерфейсов QHB, включая написанные для C++, Perl, Python, Tcl и ECPG. Поэтому некоторые аспекты поведения libpq будут важны для использующих один из этих пакетов. В частности, в разделах Переменные среды, Файл паролей и Поддержка SSL описывается поведение, видимое пользователю любого приложения, использующего libpq.

В конце этой главы (раздел Примеры программ) приведены короткие примеры, показывающие, как писать программы, использующие libpq.

Клиентские программы, использующие libpq, должны включать файл заголовка libpq-fe.h и должны быть связаны с библиотекой libpq.

Содержание

Функции управления подключением к базе данных

Строки подключения

Ключевые слова-параметры

Функции, показывающие состояние подключения

Функции для выполнения команд

Основные функции

Извлечение информации о результате запроса

Извлечение другой информации о результате

Экранирование строк для включения в команды SQL

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

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

Применение конвейерного режима

Функции, связанные с конвейерным режимом

Когда использовать конвейерный режим

Построчное извлечение результатов запроса

Отмена запросов в процессе выполнения

Интерфейс быстрого пути

Асинхронное уведомление

Функции, связанные с командой COPY

Функции для передачи данных COPY

Функции для приема данных COPY

Устаревшие функции для COPY

Функции управления

Прочие функции

Обработка замечаний

Система событий

Типы событий

Процедура обратного вызова для событий

Вспомогательные функции событий

Пример события

Переменные среды

Файл паролей

Файл подключений служб

Поиск параметров подключения через LDAP

Поддержка SSL

Проверка сертификатов сервера на стороне клиента

Сертификаты клиента

Защита, обеспечиваемая в различных режимах

Файлы, используемые клиентом SSL

Инициализация библиотеки SSL

Поведение в многопоточных программах

Сборка программ с libpq

Примеры программ

Функции управления подключением к базе данных

Следующие функции имеют дело с установкой подключения к внутреннему серверу QHB. У прикладной программы может быть открыто несколько подключений к серверу одновременно. (Одна из причин этого состоит в том, чтобы иметь доступ к нескольким базам данных.) Каждое подключение представляется объектом PGconn, получаемым из функции PQconnectdb, PQconnectdbParams или PQsetdbLogin. Обратите внимание, что эти функции всегда будут возвращать непустой указатель на объект, возможно, за исключением случая, когда свободной памяти так мало, что ее не удастся выделить даже для объекта PGconn. Прежде чем передавать запросы через объект подключения, нужно вызвать функцию PQstatus для проверки возвращаемого значения при успешном подключении.

ПРЕДУПРЕЖДЕНИЕ
Если к базе данных, в которой не был внедрен шаблон безопасного использования схем, имеют доступ недоверенные пользователи, начинайте каждый сеанс с удаления общедоступных для записи схем из пути поиска (search_path). Для этого можно установить в ключевом слове-параметре options значение -csearch_path=. Или, как вариант, выполнить PQexec(соединение, "SELECT pg_catalog.set_config('search_path', '', false)") после подключения. Это касается не только libpq, но и всех остальных интерфейсов для выполнения произвольных команд SQL.

ПРЕДУПРЕЖДЕНИЕ
В системе Unix клонирование процесса с открытыми подключениями libpq может привести к непредсказуемым результатам, так как родительский и дочерний процессы совместно используют одни и те же сокеты и ресурсы операционной системы. По этой причине подобная практика не рекомендуется, хотя вызов exec из дочернего процесса для загрузки нового исполняемого файла является безопасным.

PQconnectdbParams

Создает новое подключение к серверу баз данных.

PGconn *PQconnectdbParams(const char * const *keywords,
                          const char * const *values,
                          int expand_dbname);

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

Распознаваемые в настоящее время ключевые слова-параметры перечислены в подразделе Ключевые слова-параметры.

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

Когда expand_dbname имеет ненулевое значение, значение первого ключевого слова dbname проверяется на предмет, не является ли оно строкой подключения. Если это так, она «разворачивается» в отдельные параметры подключения, извлеченные из этой строки. Значение считается строкой подключения, а не просто именем базы данных, если оно содержит знак равенства (=) или начинается с обозначения схемы URI. (Более подробно форматы строки подключения описываются в подразделе Строки подключения.) Таким способом обрабатывается только первое вхождение dbname; все последующие параметры dbname обрабатываются как просто имя базы данных.

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

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

PQconnectdb

Создает новое подключение к серверу баз данных.

PGconn *PQconnectdb(const char *conninfo);

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

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

PQsetdbLogin

Создает новое подключение к серверу баз данных.

PGconn *PQsetdbLogin(const char *pghost,
                     const char *pgport,
                     const char *pgoptions,
                     const char *pgtty,
                     const char *dbName,
                     const char *login,
                     const char *pwd);

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

Если параметр dbName содержит знак = или имеет допустимый префикс URI для подключения, то он воспринимается как строка conninfo точно таким же образом, как если бы он был передан функции PQconnectdb, а затем оставшиеся параметры применяются, как указано для функции PQconnectdbParams.

Параметр pgtty больше не применяется, и любое переданное ему значение будет игнорироваться.

PQsetdb

Создает новое подключение к серверу баз данных.

PGconn *PQsetdb(char *pghost,
                char *pgport,
                char *pgoptions,
                char *pgtty,
                char *dbName);

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

PQconnectStartParams
PQconnectStart
PQconnectPoll

Создают подключение к серверу баз данных неблокирующим способом.

PGconn *PQconnectStartParams(const char * const *keywords,
                             const char * const *values,
                             int expand_dbname);

PGconn *PQconnectStart(const char *conninfo);

PostgresPollingStatusType PQconnectPoll(PGconn *conn);

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

С функцией PQconnectStartParams подключение к базе данных производится с использованием параметров, взятых из массивов keywords и values, и управляется expand_dbname, как описано выше для функции PQconnectdbParams.

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

Ни PQconnectStartParams, ни PQconnectStart, ни PQconnectPoll не заблокируются, пока соблюдается ряд ограничений:

Параметр hostaddr должен использоваться соответствующим образом, чтобы предотвратить выполнение запросов DNS. Подробную информацию по этому параметру см. в подразделе Ключевые слова-параметры.

Если вы вызываете функцию PQtrace, позаботьтесь о том, чтобы объект-поток, в который производится трассировка, не заблокировался.

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

Чтобы начать неблокирующий запрос на подключение, вызовите PQconnectStart или PQconnectStartParams. Если результат равен NULL, значит libpq не смогла выделить память для новой структуры PGconn. В противном случае возвращается допустимый указатель PGconn (хотя он еще не представляет рабочее подключение к базе данных). Затем вызовите PQstatus(conn). Если результатом является CONNECTION_BAD, значит попытка подключения уже провалилась, скорее всего, из-за недопустимых параметров подключения.

Если вызов PQconnectStart или PQconnectStartParams успешен, следующим шагом нужно опросить libpq, чтобы она могла продолжить процедуру подключения. Воспользуйтесь PQsocket(conn) для получения дескриптора сокета, лежащего в основе подключения к базе данных. (Внимание: не стоит полагать, что этот сокет остается неизменным от вызова к вызову PQconnectPoll.) Организуйте цикл таким образом: если PQconnectPoll(conn) при последнем вызове вернула PGRES_POLLING_READING, ожидайте, пока сокет не будет готов для чтения (что показывает select(), poll() или похожая системная функция). Затем снова вызовите PQconnectPoll(conn). Если же PQconnectPoll(conn) при последнем вызове вернула PGRES_POLLING_WRITING, ожидайте, пока сокет не будет готов для записи, а затем снова вызовите PQconnectPoll(conn). На первой итерации, т. е. когда вы еще не вызвали PQconnectPoll, реализуйте то же поведение, как если бы она вернула PGRES_POLLING_WRITING. Продолжайте этот цикл, пока PQconnectPoll(conn) не вернет PGRES_POLLING_FAILED, показывая, что процедура подключения окончилась неудачей, или PGRES_POLLING_OK, показывая, что соединение было успешно установлено.

В любое время в процессе подключения его состояние можно проверить, вызвав функцию PQstatus. Если этот вызов возвращает CONNECTION_BAD, значит процедура подключения окончилась неудачей; если же вызов возвращает CONNECTION_OK, значит соединение установлено. Оба эти состояния одинаково определяются на основе возвращаемого значения функции PQconnectPoll, описанной выше. Кроме того, во время асинхронной процедуры подключения (и только во время нее) могут встречаться и другие состояния. Они показывают текущую стадию процедуры подключения и могут быть полезны, например, для предоставления обратной связи пользователю. Вот эти состояния:

CONNECTION_STARTED
Ожидание установки соединения.

CONNECTION_MADE
Соединение установлено; ожидание отправки.

CONNECTION_AWAITING_RESPONSE
Ожидание ответа от сервера.

CONNECTION_AUTH_OK
Получена аутентификация; ожидание завершения запуска серверной части.

CONNECTION_SSL_STARTUP
Согласование шифрования SSL.

CONNECTION_SETENV
Согласование значений параметров, зависящих от среды.

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

CONNECTION_CONSUME
Прочтение всех оставшихся ответных сообщений через соединение.

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

switch(PQstatus(conn))
{
        case CONNECTION_STARTED:
            feedback = "Connecting...";
            break;

        case CONNECTION_MADE:
            feedback = "Connected to server...";
            break;
.
.
.
        default:
            feedback = "Connecting...";
}

При использовании PQconnectPoll параметр подключения connect_timeout игнорируется; именно приложение должно решать, не истекло ли чрезмерно много времени. В противном случае цикл с вызовом PQconnectStart и последующим вызовом PQconnectPoll равнозначны вызову функции PQconnectdb.

Обратите внимание, что когда функция PQconnectStart или PQconnectStartParams возвращает непустой указатель, то, закончив его использование, следует вызвать функцию PQfinish, чтобы освободить эту структуру и все связанные с ней блоки памяти. Это нужно сделать, даже если попытка подключения провалилась или прекратилась.

PQconndefaults

Возвращает значения по умолчанию для параметров подключения.

PQconninfoOption *PQconndefaults(void);

typedef struct
{
    char   *keyword;   /* Ключевое слово этого параметра */
    char   *envvar;    /* Имя резервной переменной среды */
    char   *compiled;  /* Скомпилированное резервное значение по умолчанию */
    char   *val;       /* Текущее значение параметра или NULL */
    char   *label;     /* Метка этого поля в диалоге подключения */
    char   *dispchar;  /* Показывает, как отображать это поле
                          в диалоге подключения. Значения следующие:
                          ""        Отображать введенное значение как есть
                          "*"       Поле пароля — скрывать значение
                          "D"       Параметр отладки — не показывать по умолчанию */
    int     dispsize;  /* Размер поля в символах для диалога */
} PQconninfoOption;

Возвращает массив параметров подключения. Его можно использовать для определения всех возможных параметров функции PQconnectdb и их текущих значений по умолчанию. Возвращаемое значение указывает на массив структур PQconninfoOption, который заканчивается записью, имеющей пустой указатель keyword. Если память выделить не удалось, возвращается пустой указатель. Обратите внимание, что текущие значения по умолчанию (поля val) будут зависеть от переменных среды и другого контекста. Отсутствующий или недействительный файл служб будет молча игнорироваться. Вызывающие функции должны рассматривать данные параметров подключения как доступные только для чтения.

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

PQconninfo

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

PQconninfoOption *PQconninfo(PGconn *conn);

Возвращает массив параметров подключения. Его можно использовать для определения всех возможных параметров функции PQconnectdb и значений, которые использовались для подключения к серверу. Возвращаемое значение указывает на массив структур PQconninfoOption, который заканчивается записью, имеющей пустой указатель keyword. Все примечания, приведенные выше для функции PQconndefaults, применимы также и к результату функции PQconninfo.

PQconninfoParse

Возвращает синтаксически проанализированные параметры подключения из предоставленной строки подключения.

PQconninfoOption *PQconninfoParse(const char *conninfo, char **errmsg);

Синтаксически анализирует строку подключения и возвращает результирующие параметры в виде массива или возвращает NULL, если при анализе строки подключения возникает. Эту функцию можно использовать для извлечения параметров функции PQconnectdb из предоставленной строки подключения. Возвращаемое значение указывает на массив структур PQconninfoOption, который заканчивается записью, имеющей пустой указатель keyword.

Все допустимые параметры будут присутствовать в результирующем массиве, но для всех параметров, отсутствующих в строке подключения, PQconninfoOption будет иметь в поле val значение NULL; значения по умолчанию не подставляются.

Если поле errmsg не равно NULL, то в случае успеха в поле *errmsg устанавливается NULL, а в противном случае — выделенная malloc строка с сообщением об ошибке, объясняющем проблему. (Также возможен вариант, когда в поле *errmsg устанавливается NULL, и при этом функция возвращает NULL; это указывает на нехватку памяти.)

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

PQfinish

Закрывает соединение с сервером. Также освобождает память, используемую объектом PGconn.

void PQfinish(PGconn *conn);

Обратите внимание, что даже если попытка подключения к серверу провалилась (как показывает функция PQstatus), приложение все равно должно вызвать PQfinish, чтобы освободить память, используемую объектом PGconn. Указатель PGconn не должен использоваться повторно после того, как была вызвана функция PQfinish.

PQreset

Переустанавливает канал связи с сервером.

void PQreset(PGconn *conn);

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

PQresetStart
PQresetPoll

Переустанавливает канал связи с сервером неблокирующим способом.

int PQresetStart(PGconn *conn);

PostgresPollingStatusType PQresetPoll(PGconn *conn);

Эти функции закроют соединение с сервером и попытаются установить новое подключение, используя все те же параметры, что использовались раньше. Это может быть полезно для восстановления после ошибки при потере работающего соединения с сервером. Они отличаются от функции PQreset (см. выше) тем, что действуют неблокирующим способом. На эти функции наложены те же ограничения, что и на PQconnectStartParams, PQconnectStart и PQconnectPoll.

Чтобы запустить переподключение, вызовите функцию PQresetStart. Если она вернет 0, значит переподключение не удалось. Если же она вернет 1, опросите результат переподключения, используя функцию PQresetPoll точно таким же образом, как если бы вы проводили подключение с помощью функции PQconnectPoll.

PQpingParams

Функция PQpingParams отображает состояние сервера. Она принимает параметры подключения, идентичные параметрам описанной выше функции PQconnectdbParams. Чтобы получить состояние сервера, нет необходимости предоставлять корректные значения имени пользователя, пароля или имени базы данных; однако если будут предоставлены некорректные значения, сервер запротоколирует в журнал провалившуюся попытку подключения.

PGPing PQpingParams(const char * const *keywords,
                    const char * const *values,
                    int expand_dbname);

Эта функция возвращает одно из следующих значений:

PQPING_OK
Сервер работает и предположительно принимает подключения.

PQPING_REJECT
Сервер работает, но находится в состоянии, не допускающем подключения (запуск, завершение работы или восстановление после сбоя).

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

PQPING_NO_ATTEMPT
Попытка связаться с сервером не проводилась, потому что предоставленные параметры были очевидно неправильными или имела места какая-то проблема на стороне клиента (например, нехватка памяти).

PQping

Функция PQping отображает состояние сервера. Она принимает параметры подключения, идентичные параметрам описанной выше функции PQconnectdb. Чтобы получить состояние сервера, нет необходимости предоставлять корректные значения имени пользователя, пароля или имени базы данных; однако если будут предоставлены некорректные значения, сервер запротоколирует в журнал провалившуюся попытку подключения.

PGPing PQping(const char *conninfo);

Возвращаемые значения такие же, как и для функции PQpingParams.

PQsetSSLKeyPassHook_OpenSSL

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

void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);

Приложение передает указатель функции обратного вызова с сигнатурой:

int callback_fn(char *buf, int size, PGconn *conn);

которую libpq потом будет вызывать вместо своего стандартного обработчика PQdefaultSSLKeyPassHook_OpenSSL. Эта функция обратного вызова должна идентифицировать пароль для ключа и скопировать его в результирующий буфер buf размера size. Строка в buf должна завершаться NULL. Функция обратного вызова должна вернуть длину пароля, сохраненного в buf, не считая завершающего NULL. В случае неудачи она должна установить buf[0] = '\0' и вернуть 0. В качестве примера см. реализацию PQdefaultSSLKeyPassHook_OpenSSL в исходном коде libpq.

Если пользователь задал местоположение ключа явно, то при вызове функции обратного вызова его путь будет передан в conn->sslkey. Это поле будет пустым, если используется путь к ключу по умолчанию. Что касается ключей, специфичных для криптомодулей, именно реализации криптомодулей должны решать, использовать ли функцию-обработчик паролей OpenSSL или определить собственный обработчик.

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

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

PQgetSSLKeyPassHook_OpenSSL

Функция PQgetSSLKeyPassHook_OpenSSL возвращает текущий обработчик пароля для ключа клиентского сертификата или NULL, если таковой не был установлен.

PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);

Строки подключения

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

Строки подключения вида «ключ/значение»

В формате ключ/значение установка каждого параметра происходит в форме ключ = значение, с пробелами между параметрами. Пробелы вокруг знака равенства являются необязательными. Чтобы записать пустое значение или значение с пробелами, нужно заключить его в апострофы, например, keyword = 'a value'. Апострофы и обратные слэши внутри значения следует экранировать обратным слэшем, т. е. \' и \\.

Пример:

host=localhost port=5432 dbname=mydb connect_timeout=10

Распознаваемые ключевые слова-параметры перечислены в подразделе Ключевые слова-параметры.

URI подключения

Общая форма URI подключения:

qhb://[указание_пользователя@][указание_хоста][/имя_бд][?указание_параметра]

где указанием_пользователя является:

пользователь[:пароль]

и указанием_хоста является:

[хост][:порт][,...]

и указанием_параметра является:

имя=значение[&...]

В качестве обозначения схемы URI используется qhb://. Все остальные части URI являются необязательными. Следующие примеры иллюстрируют допустимый синтаксис URI:

qhb://
qhb://localhost
qhb://localhost:5433
qhb://localhost/mydb
qhb://user@localhost
qhb://user:secret@localhost
qhb://other@localhost/otherdb?connect_timeout=10&application_name=myapp
qhb://host1:123,host2:456/somedb?target_session_attrs=any&application_name=myapp

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

qhb:///mydb?host=localhost&port=5433

Все именованные параметры должны соответствовать ключевым словам, перечисленным в подразделе Ключевые слова-параметры, за исключением того, что для совместимости с URI подключения JDBC вхождения ssl=true заменяются на sslmode=require.

Если URI подключения содержит в какой-либо из частей символы со специальным значением, он должен кодироваться в формате с процентами. Вот пример, где знак равенства (=) заменяется на %3D, а знак пробела — на %20:

qhb://user@localhost:5433/mydb?options=-c%20synchronous_commit%3Doff

Часть строки с хостом можно представить либо именем хоста, либо IP-адресом. Чтобы задать адрес IPv6, нужно заключить его в квадратные скобки:

qhb://[2001:db8::1234]/database

Часть строки с хостом интерпретируется в соответствии с описанием параметра host. В частности, если часть строки с хостом пуста или выглядит как абсолютный путь, выбирается соединение через сокеты домена Unix; в противном случае инициируется подключение по TCP/IP. Однако обратите внимание, что слэш в иерархической части URI является зарезервированным символом. Поэтому чтобы задать нестандартный каталог сокета домена Unix, нужно либо опустить в URI часть с хостом и задать сервер в именованном параметре, либо закодировать путь в этой части процентами:

qhb:///dbname?host=/var/lib/qhb
qhb://%2Fvar%2Flib%2Fqhb/dbname

В одном URI можно задать несколько компонентов хостов, каждый с необязательным компонентом порта. URI вида qhb://host1:port1,host2:port2,host3:port3/ равнозначен строке подключения вида host=host1,host2,host3 port=port1,port2,port3. Как описывается ниже, все хосты будут перебираться по очереди, пока не будет установлено соединение.

Указание нескольких хостов

В строке подключения можно указать несколько хостов, и тогда клиент будет пытаться подключиться к ним в заданном порядке. В формате ключ/значение параметры host, hostaddr и port принимают списки значений, разделенных запятыми. В каждом указанном параметре должно быть задано одинаковое число элементов, чтобы, например, первый элемент hostaddr соответствовал имени первого хоста, второй — имени второго хоста и т. д. В порядке исключения, если указывается только один элемент port, он применяется ко всем хостам.

В формате URI подключения в компоненте host можно перечислить несколько пар host:port, разделенных запятыми.

В любом формате одно имя хоста может переводиться в несколько сетевых адресов. Типичным примером этого является хост, имеющий как адрес IPv4, так и адрес IPv6.

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

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

Ключевые слова-параметры

В настоящее время распознаются следующие ключевые слова-параметры:

host

Имя хоста для подключения. Если имя хоста выглядит как абсолютный путь, оно задает связь через домен Unix, а не через TCP/IP; значение является именем каталога, в котором хранится файл сокета. (В системах Unix абсолютный путь начинается со слэша.) Если имя хоста начинается с @, оно воспринимается как сокет домена Unix в абстрактном пространстве имен (в настоящее время поддерживается в Linux). Поведение по умолчанию, когда параметр host не задан или пуст, заключается в подключении к сокету домена Unix в /tmp (или в том каталоге сокетов, который был указан при сборке QHB). На машинах без сокетов домена Unix по умолчанию выполняется подключение к localhost.

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

hostaddr

Числовой IP-адрес хоста для подключения. Он должен иметь стандартный формат адресов IPv4, например 172.28.40.9. Если ваша машина поддерживает IPv6, можно использовать и такие адреса. Когда в этом параметре задается непустая строка, всегда используется связь через TCP/IP. Если этот параметр не задан, соответствующий IP-адрес будет взят из значения host — или, если в host задан IP-адрес, это значение будет использоваться напрямую.

Использование hostaddr позволяет приложению обойтись без розыска имени хоста, что может быть важно для приложений с временными ограничениями. Однако имя хоста требуется для методов аутентификации GSSAPI или SSPI, а также для проверки сертификатов SSL в режиме verify-full. Применяются следующие правила:

Если host задается без hostaddr, ищется имя хоста. (При использовании функции PQconnectPoll поиск производится, когда PQconnectPoll впервые изучает имя этого хоста, и это может надолго заблокировать PQconnectPoll.)

Если hostaddr задается без host, значение hostaddr дает сетевой адрес сервера. Если для метода аутентификации требуется имя хоста, попытка подключения провалится.

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

Обратите внимание, что аутентификация, вероятно, завершится неудачей, если host не является именем сервера с сетевым адресом hostaddr. Кроме того, когда задаются и host, и hostaddr, то значение host используется для идентификации подключения в файле паролей (см. раздел Файл паролей).

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

В отсутствие имени и адреса хоста libpq произведет подключение, используя локальный сокет домена Unix, а на машинах без сокетов домена Unix попытается подключиться к localhost.

port

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

dbname

Имя базы данных. По умолчанию будет совпадать с именем пользователя. В определенных контекстах это значение проверяется на соответствие расширенным форматам; более подробную информацию см. в подразделе Строки подключения.

user

Имя пользователя QHB, под которым будет производиться подключение. По умолчанию будет совпадать с тем именем пользователя в операционной системе, от лица которого выполняется приложение.

password

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

passfile

Задает имя файла, используемого для хранения паролей (см. раздел Файл паролей). По умолчанию это ~/.pgpass. (Если этот файл не существует, это не будет ошибкой.)

channel_binding

Этот параметр управляет использованием связывания каналов клиентом. Значение require означает, что соединение должно задействовать связывание каналов, prefer — что клиент выберет связывание каналов, если оно доступно, а disable предотвращает использование связывания каналов. Если QHB компилируется с поддержкой SSL значением по умолчанию будет prefer, в противном случае — disable.

Связывание каналов — это метод, позволяющий серверу подтвердить свою подлинность клиенту. Он поддерживается только для подключений поверх SSL с серверами QHB, использующими метод аутентификации SCRAM.

connect_timeout

Максимальное время ожидания подключения в секундах (записывается в виде десятичного целого числа, например 10). Ноль, отрицательное или не заданное значение означают бесконечное ожидание. Минимально допустимое время ожидания составляет 2 секунды, поэтому значение 1 интерпретируется как 2. Это время ожидания применяется по отдельности для каждого имени хоста или IP-адреса. Например, если задать два хоста и установить в connect_timeout значение 5, для каждого хоста время ожидания при отсутствии подключения истечет через 5 секунд, так что общее время, затраченное на ожидание подключения, может составить до 10 секунд.

client_encoding

Здесь устанавливается параметр конфигурации client_encoding для данного подключения. Помимо значений, принимаемых соответствующим параметром сервера, можно использовать значение auto, чтобы определить верную кодировку на основе текущей локали на стороне клиента (в системах Unix это переменная среды LC_CTYPE).

options

Задает параметры командной строки для передачи серверу при установлении соединения. Например, значение -c geqo=off установит для параметра сеанса geqo значение off. Пробелы в этой строки считаются разделяющими аргументы командной строки, если только они не экранированы обратным слэшем (\); чтобы отобразить собственно обратный слэш, нужно написать \\. Более подробное описание доступных параметров см. в главе Конфигурация сервера.

application_name

Задает значение для параметра конфигурации application_name.

fallback_application_name

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

keepalives

Определяет, будут ли использоваться keepalive-сообщения протокола TCP на стороне клиента. Значение 1 (по умолчанию) означает, что использование включено, но его можно изменить на 0 (выключено), если keepalive-сообщения не нужны. Этот параметр игнорируется для соединений, установленных через сокет домена Unix.

keepalives_idle

Управляет продолжительностью периода бездействия (в секундах), по истечении которого TCP должен отправить keepalive-сообщение на сервер. При нулевом значении используется системное значение по умолчанию. Этот параметр игнорируется для соединений, установленных через сокет домена Unix, или при отключенных keepalive-сообщениях. Он поддерживается только в системах, где доступен TCP_KEEPIDLE или равнозначный ему параметр сокета; в других системах он не оказывает никакого эффекта.

keepalives_interval

Управляет продолжительностью периода бездействия (в секундах), по истечении которого keepalive-сообщение TCP, получение которого не подтверждено сервером, следует отправить повторно. При нулевом значении используется системное значение по умолчанию. Этот параметр игнорируется для соединений, установленных через сокет домена Unix, или при отключенных keepalive-сообщениях. Он поддерживается только в системах, где доступен TCP_KEEPINTVL или равнозначный ему параметр сокета; в других системах он не оказывает никакого эффекта.

keepalives_count

Определяет количество keepalive-сообщений TCP, которые могут быть потеряны, прежде чем соединение клиента с сервером будет признано неработающим. При нулевом значении используется системное значение по умолчанию. Этот параметр игнорируется для соединений, установленных через сокет домена Unix, или при отключенных keepalive- сообщениях. Он поддерживается только в системах, где доступен TCP_KEEPCNT или равнозначный ему параметр сокета; в других системах он не оказывает никакого эффекта.

tcp_user_timeout

Управляет продолжительностью периода (в миллисекундах), в течение которого отправляемые данные могут оставаться неподтвержденными, прежде чем соединение будет принудительно закрыто. При нулевом значении используется системное значение по умолчанию. Этот параметр игнорируется для соединений, установленных через сокет домена Unix. Он поддерживается только в системах, где доступен параметр сокета TCP_USER_TIMEOUT; в других системах он не оказывает никакого эффекта.

replication

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

Поддерживаются следующие значения, без учета регистра:

true, on, yes, 1
Подключение производится в режиме физической репликации.

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

false, off, no, 0
Подключение производится в обычном режиме, что является поведением по умолчанию.

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

gssencmode

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

disable
пытаться установить только соединение без шифрования GSSAPI

prefer (по умолчанию)
если присутствуют учетные данные GSSAPI (т. е. имеются в кэше учетных данных), сначала попытаться установить зашифрованное GSSAPI соединение; если это не получится или учетных данных нет, попытаться установить соединение без шифрования GSSAPI. Это значение по умолчанию, если QHB была скомпилирована с поддержкой GSSAPI.

require
пытаться установить только зашифрованное GSSAPI соединение

Параметр gssencmode игнорируется при подключении через сокет домена Unix. Если QHB компилируется без поддержки GSSAPI, использование режима require вызовет ошибку, тогда как prefer будет принят, но на самом деле libpq не будет пытаться установить зашифрованное GSSAPI соединение.

sslmode

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

disable
пытаться установить только соединение без использования SSL

allow
сначала попытаться установить соединение без использования SSL; если это не получится, попытаться установить соединение SSL

prefer (по умолчанию)
сначала попытаться установить соединение SSL; если это не получится, попытаться установить соединение без использования SSL

require
пытаться установить только соединение SSL. Если присутствует файл корневого центра сертификации (CA), проверить сертификат тем же способом, как если бы был задан режим verify-ca

verify-ca
пытаться установить только соединение SSL и проверить, что сертификат сервера был выпущен доверенным CA

verify-full
пытаться установить только соединение SSL, проверить, что сертификат сервера был выпущен доверенным CA и что имя хоста запрошенного сервера соответствует указанному в сертификате

Подробное описание того, как работают эти режимы, см. в разделе Поддержка SSL.

Параметр sslmode игнорируется при подключении через сокет домена Unix. Если QHB компилируется без поддержки SSL, использование режимов require, verify-ca или verify-full вызовет ошибку, тогда как режимы allow и prefer будут приняты, но на самом деле libpq не будет пытаться установить соединение SSL.

Обратите внимание, что если возможно шифрование GSSAPI, оно будет предпочитаться шифрованию SSL вне зависимости от значения параметра sslmode. Чтобы принудительно использовать шифрование SSL в среде с рабочей инфраструктурой GSSAPI (например, на сервере Kerberos), нужно дополнительно установить в параметре gssencmode значение disable.

requiressl

Этот параметр считается устаревшим; рекомендуется вместо него установить sslmode.

При значении 1 требуется соединение SSL с сервером (это равнозначно значению require в sslmode). Тогда libpq откажется подключаться, если сервер не принимает соединений SSL. При значении 0 (по умолчанию) libpq будет согласовывать тип соединения с сервером (равнозначно значению prefer в sslmode). Этот параметр доступен, только если QHB компилируется с поддержкой SSL.

sslcompression

При значении 1 данные, передаваемые через соединения SSL, будут сжиматься. При значении 0 сжатие будет выключено. Значение по умолчанию — 0. Этот параметр игнорируется, если соединение SSL не установлено.

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

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

sslcert

Этот параметр задает имя файла для SSL-сертификата клиента, заменяющего файл по умолчанию ~/.qhb/qhb.crt. Этот параметр игнорируется, если соединение SSL не установлено.

sslkey

Этот параметр задает расположение секретного ключа, используемого для клиентского сертификата. Он может задать либо имя файла, который будет использоваться вместо имени по умолчанию ~/.qhb/qhb.key, либо ключ, полученный из внешнего «криптомодуля» (криптомодули — это загружаемые модули OpenSSL). Спецификация внешнего криптомодуля должна состоять из имени криптомодуля и специфичного для криптомодуля идентификатора ключа, разделенных двоеточием. Этот параметр игнорируется, если соединение SSL не установлено.

sslpassword

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

Любое непустое значение, заданное для этого параметра, подавляет вывод приглашения OpenSSL Enter PEM pass phrase: (введите пароль для PEM:), которое по умолчанию будет выводиться, если в libpq передается зашифрованный ключ клиентского сертификата.

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

Для этого параметра не существует равнозначной переменной среды, а также нет средства его поиска в .pgpass. Его можно использовать в определении подключения в служебном файле. Пользователям, тяготеющим к более сложным способам, следует рассмотреть возможность использования криптомодулей и инструментов OpenSSL, например PKCS#11 или криптографических USB-устройств.

sslrootcert

Этот параметр задает имя файла, содержащего SSL-сертификат(ы) центра сертификации (CA). Если такой файл существует, сертификат сервера будет проверен на предмет подписания одним из этих центров. Имя по умолчанию — ~/.qhb/root.crt.

sslcrl

Этот параметр задает имя файла со списком отозванных SSL-сертификатов сервера (CRL). Сертификаты, перечисленные в этом файле, если таковой существует, будут отвергаться при попытке установить подлинность сертификата сервера. Если ни sslcrl, ни sslcrldir не установлены, используется файл ~/.qhb/root.crl.

sslcrldir

Этот параметр задает имя каталога со списком отозванных SSL-сертификатов сервера (CRL). Сертификаты, перечисленные в этом каталоге, если таковой существует, будут отвергаться при попытке установить подлинность сертификата сервера.

Этот каталог нужно подготовить с помощью команды OpenSSL openssl rehash или c_rehash. Подробную информацию см. в документации OpenSSL.

Параметры sslcrl и sslcrldir можно указывать вместе.

sslsni

При значении 1 (по умолчанию) libpq включает расширение TLS «Server Name Indication» (указание имени сервера, SNI) для соединений с шифрованием SSL. При установке в этом параметре значения 0 это расширение выключается.

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

requirepeer

Этот параметр задает имя пользователя операционной системы для сервера, например, requirepeer=qhb. При установке соединения через сокет домена Unix, если этот параметр установлен, клиент проверяет в начале подключения, что серверный процесс запущен от имени указанного пользователя; если это не так, соединение прерывается с ошибкой. Этот параметр можно использовать для обеспечения аутентификации сервера, схожей с той, которая доступна с SSL-сертификатами при соединениях по TCP/IP. (Обратите внимание, что если сокет домена Unix находится в каталоге /tmp или ином расположении, запись в котором разрешена всем пользователям, любой пользователь может запустить там прослушивающий сервер. Используйте этот параметр, чтобы гарантировать, что вы подключены к серверу, запущенному доверенным пользователем.) Этот параметр поддерживается только на платформах, для которых реализован метод аутентификации peer; см. раздел Аутентификация peer.

ssl_min_protocol_version

Этот параметр задает минимальную версию протокола SSL/TLS, допустимую для этого соединения. Допустимые значения: TLSv1, TLSv1.1, TLSv1.2 и TLSv1.3. Набор поддерживаемых протоколов зависит от используемой версии OpenSSL; более старые версии не поддерживают самые последние версии протоколов. Если значение не задано, минимальная версия по умолчанию — TLSv1.2, что удовлетворяет передовым рекомендациям индустрии на момент написания этой документации.

ssl_max_protocol_version

Этот параметр задает максимальную версию протокола SSL/TLS, допустимую для этого соединения. Допустимые значения: TLSv1, TLSv1.1, TLSv1.2 и TLSv1.3. Набор поддерживаемых протоколов зависит от используемой версии OpenSSL; более старые версии не поддерживают самые последние версии протоколов. Если значение не задано, этот параметр игнорируется, и при подключении будет использоваться верхняя граница, определенная сервером (если она установлена). Установка максимальной версии протокола полезна в основном для тестирования или если у какого-то компонента возникают затруднения при работе с новым протоколом.

krbsrvname

Имя службы Kerberos для использования при аутентификации с GSSAPI. Чтобы аутентификация Kerberos прошла успешно, оно должно соответствовать имени службы, заданному в конфигурации сервера. (См. также раздел Аутентификация GSSAPI.) По умолчанию обычно устанавливается имя qhb, но это можно изменить при сборке QHB посредством параметра --with-krb-srvnam скрипта configure. В большинстве сред никогда не возникает необходимости менять этот параметр. Некоторым реализациям Kerberos может потребоваться другое имя службы.

service

Имя службы, используемое для задания дополнительных параметров. Оно задает имя службы в файле pg_service.conf, где хранятся дополнительные параметры подключения. Это позволяет приложениям указывать только имя службы, так как параметры подключения могут поддерживаться централизованно. См. раздел Файл подключений служб.

target_session_attrs

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

any (по умолчанию)
любое успешное подключение приемлемо

read-write
сеанс должен по умолчанию принимать транзакции чтения-записи (то есть сервер не должен находиться в режиме горячего резерва, а параметр default_transaction_read_only должен иметь значение off (выключен))

read-only
сеанс не должен по умолчанию принимать транзакции чтения-записи (противоположный случай)

primary
сервер не должен находиться в режиме горячего резерва

standby
сервер должен находиться в режиме горячего резерва

prefer-standby
сначала попытаться найти резервный сервер, но если ни один из перечисленных хостов таковым не является, попробовать снова в режиме any

Функции, показывающие состояние подключения

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

Совет
Разработчики приложений libpq должны тщательно поддерживать абстракцию PGconn. Чтобы изучить содержимое PGconn, используйте описанные ниже функции доступа. Обращение к внутренним полям PGconn через libpq-int.h не рекомендуется, поскольку они могут измениться в будущем.

Следующие функции возвращают значения параметров, установленных при подключении. Эти значения фиксируются на время существования соединения. Если используется строка подключения с несколькими хостами, значения PQhost, PQport и PQpass могут измениться, если производится новое подключение с тем же объектом PGconn. Другие значения фиксируются на время существования объекта PGconn.

PQdb

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

char *PQdb(const PGconn *conn);

PQuser

Возвращает имя пользователя, установившего соединение.

char *PQuser(const PGconn *conn);

PQpass

Возвращает пароль, использованный при подключении.

char *PQpass(const PGconn *conn);

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

PQhost

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

char *PQhost(const PGconn *conn);

Если в параметрах подключения был задан и host, и hostaddr, то PQhost вернет информацию из host. Если был задан только hostaddr, то вернется его значение. если в параметрах подключения было задано несколько хостов, PQhost возвращает хост, с которым фактически было установлено соединение.

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

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

PQhostaddr

Возвращает IP-адрес сервера активного соединение. Это может быть адрес, в который разрешилось имя хоста, или IP-адрес, предоставленный посредством параметра hostaddr.

char *PQhostaddr(const PGconn *conn);

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

PQport

Возвращает порт активного соединения.

char *PQport(const PGconn *conn);

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

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

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

PQtty

Сейчас эта функция ничего не делает, но оставлена для обратной совместимости. Она всегда возвращает пустую строку или NULL, если аргумент conn равен NULL.

char *PQtty(const PGconn *conn);

PQoptions

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

char *PQoptions(const PGconn *conn);

.

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

PQstatus

Возвращает статус подключения.

ConnStatusType PQstatus(const PGconn *conn);

Статус может принимать одно из ряда значений. Однако только два из них видны извне процедуры асинхронного подключения: CONNECTION_OK and CONNECTION_BAD. Успешное подключение к базе данных имеет статус CONNECTION_OK. О неудавшейся попытке подключения сигнализирует статус CONNECTION_BAD. Обычно статус OK остается до вызова функции PQfinish, но ошибка связи может привести к преждевременной смене статуса на CONNECTION_BAD. В таком случае приложение может попытаться восстановить подключение, вызвав функцию PQreset.

О других кодах статуса, которые могут быть возвращены, можно узнать в описании функций PQconnectStartParams, PQconnectStart и PQconnectPoll.

PQtransactionStatus

Возвращает текущий статус сервера, касающийся транзакций.

PGTransactionStatusType PQtransactionStatus(const PGconn *conn);

Статус может быть PQTRANS_IDLE (в настоящее время простаивает), PQTRANS_ACTIVE (команда в процессе обработки), PQTRANS_INTRANS (простаивает, но находится в блоке действительной транзакции) или PQTRANS_INERROR (простаивает, но находится в блоке неудавшейся транзакции). Статус PQTRANS_UNKNOWN выводится, если соединение не работает. Статус PQTRANS_ACTIVE выводится, только когда запрос был отправлен серверу и еще не завершился.

PQparameterStatus

Отыскивает текущее значение параметра сервера.

const char *PQparameterStatus(const PGconn *conn, const char *paramName);

Значения определенных параметров выводятся сервером автоматически в начале процесса подключения или при изменении их значений. Функцию PQparameterStatus можно использовать для запроса этих значений. Она возвращает текущее значение параметра, если оно известно, или NULL, если параметр неизвестен.

Параметры, выводимые в текущей версии, включают 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. Обратите внимание, что параметры server_version, server_encoding и integer_datetimes нельзя изменить после запуска.

Если для standard_conforming_strings не выводится никакого значения, приложения могут предположить, что оно равно off, то есть что обратные слэши в строковых литералах воспринимаются как управляющие символы. Кроме того, присутствие этого параметра можно считать указанием на то, что синтаксис строки с экранированием (E'...') является приемлемым.

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

PQprotocolVersion

Запрашивает используемый клиент-серверный протокол.

int PQprotocolVersion(const PGconn *conn);

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

PQserverVersion

Возвращает целое число, представляющее версию сервера.

int PQserverVersion(const PGconn *conn);

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

Если номер версии состоит из трех чисел, то основную версию представляют первые два числа. Для таких версий функция PQserverVersion использует для каждой части по две цифры; например, версия 1.4.1 будет возвращена как 10401, а версия 1.5.0 — как 10500.

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

PQerrorMessage

Возвращает последнее сообщение об ошибке, сгенерированное операцией в данном соединении.

char *PQerrorMessage(const PGconn *conn);

Почти все функции libpq в случае сбоя будут передавать сообщение для функции PQerrorMessage. Обратите внимание, что по соглашению libpq непустой результат PQerrorMessage может состоять из нескольких строк и будет включать завершающий символ перевода строки. Вызывающая функция не должна напрямую освобождать память, выделенную под результат. Она освободится, когда связанный с ней маркер PGconn будет передан функции PQfinish. Не следует ожидать, что результирующая строка останется той же самой после нескольких операций со структурой PGconn.

PQsocket

Получает номер дескриптора файла сокета соединения с сервером. Допустимый дескриптор будет больше или равен 0; результат -1 показывает, что в данный момент не открыто ни одно соединение с сервером. (Это значение не изменится во время обычной работы, но может измениться во время установки или переустановки подключения.)

int PQsocket(const PGconn *conn);

PQbackendPID

Возвращает идентификатор серверного процесса (PID), обрабатывающего это соединение.

int PQbackendPID(const PGconn *conn);

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

PQconnectionNeedsPassword

Возвращает true (1), если методу аутентификации соединения требовался пароль, но тот не был предоставлен. Возвращает false (0), если пароль не требовался.

int PQconnectionNeedsPassword(const PGconn *conn);

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

PQconnectionUsedPassword

Возвращает true (1), если метод аутентификации соединения использовал пароль. Возвращает false (0), если это не так.

int PQconnectionUsedPassword(const PGconn *conn);

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

Следующие функции возвращают информацию, связанную с SSL. Обычно эта информация не меняется после установки соединения.

PQsslInUse

Возвращает true (1), если для подключения используется SSL, и false (0), если не используется.

int PQsslInUse(const PGconn *conn);

PQsslAttribute

Возвращает связанную с SSL информацию о подключении.

const char *PQsslAttribute(const PGconn *conn, const char *attribute_name);

Список доступных атрибутов зависит от использованной библиотеки SSL и типа подключения. Возвращает NULL, если подключение не использует SSL или заданное имя атрибута не определено для используемой библиотеки.

Как правило, доступны следующие атрибуты:

library
Имя используемой реализации SSL. (В настоящее время реализуется только "OpenSSL")

protocol
Используемая версия SSL/TLS. Принятые значения: "TLSv1", "TLSv1.1" и "TLSv1.2", но реализация может возвращать и другие строки, если используется какой-то другой протокол.

key_bits
Количество битов в ключе, используемом алгоритмом шифрования.

cipher
Краткое имя используемой комбинации шифров, например, "DHE-RSA-DES-CBC3-SHA". Эти имена у каждой реализации SSL свои.

compression
Возвращает «on», если используется сжатие SSL; в противном случае возвращает «off».

В виде исключения атрибут library можно запросить без подключения, передав NULL в качестве аргумента conn. Результатом будет имя библиотеки SSL, используемой по умолчанию, или NULL, если libpq была скомпилирована без поддержки SSL. (В версиях QHB ниже 1.5.2 при передаче NULL в аргументе conn всегда возвращался NULL. Если в этом случае клиентским программам нужно отличить старую реализацию от новой, можно проверить макрос LIBPQ_HAS_SSL_LIBRARY_DETECTION.)

PQsslAttributeNames

Возвращает массив имен доступных атрибутов SSL. Этот массив завершается указателем NULL.

const char * const * PQsslAttributeNames(const PGconn *conn);

PQsslStruct

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

void *PQsslStruct(const PGconn *conn, const char *struct_name);

Доступная структура (или структуры) зависит от используемой реализации SSL. Для OpenSSL существует одна структура, доступная под именем «OpenSSL», и эта функция возвращает указатель на структуру OpenSSL SSL. Чтобы применить эту функцию, можно воспользоваться кодом со следующими строками:

#include <libpq-fe.h>
#include <openssl/ssl.h>

...

    SSL *ssl;

    dbconn = PQconnectdb(...);
    ...

    ssl = PQsslStruct(dbconn, "OpenSSL");
    if (ssl)
    {
        /* использовать функции OpenSSL для обращения к ssl */
    }

Эту структуру можно использовать для проверки уровней шифрования, проверки сертификатов сервера и т. д. Информацию об этой структуре см. в документации по OpenSSL.

PQgetssl

Возвращает структуру SSL, использовавшуюся при подключении, или NULL, если SSL не используется.

void *PQgetssl(const PGconn *conn);

Эта функция равнозначна PQsslStruct(conn, "OpenSSL"). Ее не следует применять в новых приложениях, поскольку возвращаемая структура специфична для OpenSSL и не будет доступна в случае использования другой реализации SSL. Чтобы проверить, использует ли подключение SSL, вызовите вместо нее PQsslInUse, а чтобы получить более подробную информацию о подключении — PQsslAttribute.

Функции для выполнения команд

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

Основные функции

PQexec

Передает команду серверу и ожидает результата.

PGresult *PQexec(PGconn *conn, const char *command);

Возвращает указатель на PGresult или, возможно, пустой указатель. Обычно возвращается непустой указатель, за исключением ситуаций нехватки памяти или серьезных ошибок, например невозможности отправить команду серверу. Для проверки возвращаемого значения на ошибки нужно вызвать функцию PQresultStatus (в т. ч. в случае пустого указателя — тогда она возвратит PGRES_FATAL_ERROR). Для получения дополнительной информации о таких ошибках воспользуйтесь функцией PQerrorMessage.

Командная строка может включать в себя несколько команд SQL (разделяемых точкой с запятой). Несколько запросов, отправленных в одном вызове PQexec, обрабатываются в рамках одной транзакции, если только в строку запроса не включены явно команды BEGIN/COMMIT, чтобы разделить его на несколько транзакций. (Подробную информацию о том, как сервер обрабатывает строки с несколькими операторами, см. в подразделе Несколько операторов в простом запросе.) Однако обратите внимание, что возвращаемая структура PGresult описывает только результат последней команды, выполненной из этой строки. Если одна из команд завершится сбоем, обработка строки на этом останавливается, и возвращаемая PGresult описывает состояние ошибки.

PQexecParams

Передает команду серверу и ожидает результата, имея при этом возможность передавать параметры отдельно от текста команды SQL.

PGresult *PQexecParams(PGconn *conn,
                       const char *command,
                       int nParams,
                       const Oid *paramTypes,
                       const char * const *paramValues,
                       const int *paramLengths,
                       const int *paramFormats,
                       int resultFormat);

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

Эта функция имеет следующие аргументы:

conn
Объект, описывающий соединение, через которое передается команда.

command
Строка выполняемой команды SQL. Если используются параметры, то в командной строке на них ссылаются как на $1, $2 и т. д.

nParams
Количество предоставляемых параметров; оно равно длине массивов paramTypes[], paramValues[], paramLengths[] и paramFormats[]. (Указатели на массивы могут равняться NULL, когда nParams равен нулю.)

paramTypes[]
Задает, посредством OID, типы данных, которые должны быть назначены символам параметров. Если paramTypes имеет значение NULL или какой-либо отдельный элемент в этом массиве равен нулю, сервер логически выводит тип данных для символа параметра таким же образом, как сделал бы это для нетипизированной литеральной строки.

paramValues[]
Задает фактические значения параметров. Пустой указатель в этом массиве означает, что соответствующий параметр имеет значение NULL; в противном случае указатель указывает на текстовую строку, завершающуюся нулем (для текстового формата), или двоичные данные в формате, ожидаемом сервером (для двоичного формата).

paramLengths[]
Задает фактические длины данных параметров в двоичном формате. Он игнорируется для параметров со значением NULL и параметров в текстовом формате. Указатель на массив может быть пустым, если двоичные параметры отсутствуют.

paramFormats[]
Определяет, являются ли параметры текстовыми (поместите ноль в запись массива для соответствующего параметра) или двоичными (поместите единицу в запись массива для соответствующего параметра). Если указатель на массив является пустым, предполагается, что все параметры являются текстовыми строками.
Значения, переданные в двоичном формате, требуют знания внутреннего представления, ожидаемого сервером. Например, целые числа должны передаваться в сетевом порядке байтов. Передача значений numeric требует знания формата, в котором их хранит сервер.

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

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

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

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

SELECT * FROM mytable WHERE x = $1::bigint;

Благодаря этому параметр $1 будет восприниматься как имеющий тип bigint, тогда как по умолчанию ему был бы назначен тот же тип, что и у x. Вмешательство в принятие решение о типе параметра либо данным способом, либо путем указания числового OID типа настоятельно рекомендуется при передаче значений параметров в двоичном формате, поскольку двоичный формат имеет меньшую избыточность, чем текстовый, и поэтому куда менее вероятно, что сервер обнаружит ошибку несоответствия типов, допущенную вами.

PQprepare

Отправляет запрос на создание подготовленного оператора с заданными параметрами и ожидает завершения.

PGresult *PQprepare(PGconn *conn,
                    const char *stmtName,
                    const char *query,
                    int nParams,
                    const Oid *paramTypes);

Функция PQprepare создает подготовленный оператор для последующего его выполнения с помощью функции PQexecPrepared. Эта функциональность позволяет командам выполняться многократно без необходимости в повторном синтаксическом анализе и планировании; подробную информацию см. на справочной странице команды PREPARE.

Эта функция создает подготовленный оператор с именем stmtName из строки query, которая должна содержать одну команду SQL. stmtName может быть пустой строкой "" для создания неименованного оператора, и тогда любой уже существующий неименованный оператор автоматически заменяется им; в противном случае, если имя оператора уже определено в текущем сеансе, возникает ошибка. Если используются какие-либо параметры, то в запросе на них них ссылаются как на $1, $2 и т. д. Аргумент nParams — это количество параметров, типы для которых заранее указаны в массиве paramTypes[]. (Указатель на массив может быть равен NULL, когда nParams имеет нулевое значение.) Массив paramTypes[] задает, посредством OID, типы данных, которые должны быть назначены символам параметров. Если paramTypes имеет значение NULL или какой-либо отдельный элемент в этом массиве равен нулю, сервер логически выводит тип данных для символа параметра таким же образом, как сделал бы это для нетипизированной литеральной строки. Кроме того, запрос может использовать символы параметров с номерами большими, чем nParams; для этих символов типы данных тоже будут логически выведены. (Способы определить, какие типы данных были выведены, см. в описании функции PQdescribePrepared.)

Как и у PQexec, результатом обычно является объект PGresult, содержимое которого показывает успех или сбой на стороне сервера. Результат NULL показывает нехватку памяти или невозможность вообще отправить команду. Для получения дополнительной информации о таких ошибках используйте функцию PQerrorMessage.

Подготовленные операторы для использования с функцией PQexecPrepared также можно создать, выполнив операторы SQL PREPARE. Кроме того, хотя функции libpq для удаления подготовленного оператора не существует, для этой цели можно воспользоваться оператором SQL DEALLOCATE.

PQexecPrepared

Отправляет запрос на создание подготовленного оператора с заданными параметрами и ожидает результата.

PGresult *PQexecPrepared(PGconn *conn,
                         const char *stmtName,
                         int nParams,
                         const char * const *paramValues,
                         const int *paramLengths,
                         const int *paramFormats,
                         int resultFormat);

Функция PQexecPrepared похожа на функцию PQexecParams, но выполняемая команда задается путем указания имени ранее подготовленного оператора, а не строки запроса. Эта возможность позволяет командам, которые будут вызываться многократно, подвергаться синтаксическому анализу и планированию всего один раз, а не при каждом их выполнении. Оператор должен быть предварительно подготовлен в рамках текущего сеанса.

Параметры идентичны таковым функции PQexecParams, за исключением того, что вместо строки запроса задается имя подготовленного оператора и отсутствует параметр paramTypes[] (он не нужен, поскольку типы параметров подготовленного оператора были определены при его создании).

PQdescribePrepared

Передает запрос на получение информации о заданном подготовленном операторе и ожидает завершения.

PGresult *PQdescribePrepared(PGconn *conn, const char *stmtName);

Функция PQdescribePrepared позволяет приложению получить информацию о ранее подготовленном операторе.

Аргумент stmtName может быть пустой строкой "" или NULL для ссылки на неименованный оператор, в противном случае он должен быть именем существующего подготовленного оператора. При успехе возвращается объект PGresult со статусом PGRES_COMMAND_OK. Для получения информации о параметрах подготовленного оператора к этому PGresult можно применить функции PQnparams и PQparamtype, а функции PQnfields, PQfname, PQftype и т. п. предоставляют информацию о результирующих столбцах этого оператора (если таковые имеются).

PQdescribePortal

Передает запрос на получение информации о заданном портале и ожидает завершения.

PGresult *PQdescribePortal(PGconn *conn, const char *portalName);

Функция PQdescribePortal позволяет приложению получить информацию о ранее созданном портале. (libpq не предоставляет прямого доступа к порталам, но эту функцию можно использовать для просмотра свойств курсора, созданного командой SQL DECLARE CURSOR.)

Аргумент portalName может быть пустой строкой "" или NULL для ссылки на неименованный портал, в противном случае это должно быть имя существующего портала. При успехе возвращается структура PGresult со статусом PGRES_COMMAND_OK. Для получения информации о результирующих столбцах портала (если таковые имеются) к PGresult можно применить функции PQnfields, PQfname, PQftype и т. п.

Структура PGresult содержит результат, возвращенный сервером. Разработчики приложений libpq должны тщательно поддерживать абстракцию PGresult. Для получения доступа к содержимому PGresult следует использовать описанные ниже функции доступа. Избегайте непосредственного обращения к полям структуры PGresult, так как они могут измениться в будущем.

PQresultStatus

Возвращает статус результата команды.

ExecStatusType PQresultStatus(const PGresult *res);

Функция PQresultStatus может вернуть одно из следующих значений:

PGRES_EMPTY_QUERY
Строка, переданная серверу, была пустой.

PGRES_COMMAND_OK
Успешное завершение команды, не возвращающей никаких данных.

PGRES_TUPLES_OK
Успешное завершение команды, возвращающей данные (например, SELECT или SHOW).

PGRES_COPY_OUT
Начался перенос данных Copy Out (с сервера).

PGRES_COPY_IN
Начался перенос данных Copy In (на сервер).

PGRES_BAD_RESPONSE
Ответ сервера не был распознан.

PGRES_NONFATAL_ERROR
Произошла некритическая ошибка (замечание или предупреждение).

PGRES_FATAL_ERROR
Произошла критическая ошибка.

PGRES_COPY_BOTH
Начался перенос данных Copy In/Out (на сервер и с сервера). В настоящий момент эту функциональность используется только для потоковой репликации, так что этот статус не должен появляться в обычных приложениях.

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

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

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

При статусе результата PGRES_TUPLES_OK или PGRES_SINGLE_TUPLE для извлечения строк, возвращенных запросом, можно применить функции, описанные ниже. Обратите внимание, что команда SELECT, которая извлекла ноль строк, все равно показывает PGRES_TUPLES_OK. Статус PGRES_COMMAND_OK предназначен для команд, которые могут никогда не возвращать строк (INSERT или UPDATE без предложения RETURNING и т. п.). Ответ PGRES_EMPTY_QUERY может указывать на ошибку в программном обеспечении клиента.

Результат со статусом PGRES_NONFATAL_ERROR никогда не будет возвращен напрямую PQexec или другими функциями выполнения запросов; вместо этого результаты такого рода передаются обработчику замечаний (см. раздел Обработка замечаний).

PQresStatus

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

char *PQresStatus(ExecStatusType status);

PQresultErrorMessage

Возвращает сообщение об ошибке, связанное с командой, или пустую строку, если ошибки не произошло.

char *PQresultErrorMessage(const PGresult *res);

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

Если сразу после вызова функции PQexec или PQgetResult вызвать функцию PQerrorMessage (для данного соединения), она вернет ту же строку, что и функция PQresultErrorMessage (для данного результата). Однако структура PGresult будет хранить свое сообщение об ошибке, пока не будет уничтожена, тогда как сообщение об ошибке для данного соединения будет изменяться при выполнении последующих операций. Используйте функцию PQresultErrorMessage, когда хотите узнать статус, связанный с конкретным объектом PGresult; используйте функцию PQerrorMessage, когда хотите узнать статус, возвращенный последней операцией на данном соединении.

PQresultVerboseErrorMessage

Возвращает переформатированную версию сообщения об ошибке, связанного с объектом PGresult.

char *PQresultVerboseErrorMessage(const PGresult *res,
                                  PGVerbosity verbosity,
                                  PGContextVisibility show_context);

В некоторых ситуациях клиент может пожелать получить более подробную версию выданного ранее сообщения об ошибке. Функция PQresultVerboseErrorMessage удовлетворяет эту потребность, составляя сообщение, которое было бы выдано функцией PQresultErrorMessage, если бы заданный уровень детализации действовал для соединения на момент генерирования данного PGresult. Если PGresult не является результатом с ошибкой, вместо этого выдается сообщение «PGresult is not an error result». Возвращаемая строка включает завершающий символ перевода строки.

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

При нехватке памяти может быть возвращен NULL.

PQresultErrorField

Возвращает индивидуальное поле отчета об ошибке.

char *PQresultErrorField(const PGresult *res, int fieldcode);

Аргумент fieldcode — это идентификатор поля ошибки; см. символы, перечисленные ниже. Если PGresult не является результатом с ошибкой или предупреждением или не включает заданное поле, возвращается NULL. Обычно значения полей не включают завершающий символ перевода строки. Вызывающая функция не должна напрямую освобождать память, выделенную под результат. Она будет освобождена, когда связанный маркер PGresult будет передан функции PQclear.

Доступны следующие коды полей:

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

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

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

PG_DIAG_MESSAGE_PRIMARY
Основное сообщение об ошибке, удобное для восприятия человеком (как правило, состоит из одной строки). Присутствует всегда.

PG_DIAG_MESSAGE_DETAIL
Подробности: необязательное дополнительное сообщение об ошибке, содержащее больше сведений о проблеме. Может состоять из нескольких строк.

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

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

PG_DIAG_INTERNAL_POSITION
Это поле определяется так же, как и поле PG_DIAG_STATEMENT_POSITION, но используется, когда позиция курсора относится к команде, сгенерированной внутренне, а не к команде, предложенной клиентом. Когда появляется это поле, всегда будет появляться и поле PG_DIAG_INTERNAL_QUERY.

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

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

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

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

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

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

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

PG_DIAG_SOURCE_FILE
Имя файла, содержащего расположение в исходном коде, для которого было выдано сообщение об ошибке.

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

PG_DIAG_SOURCE_FUNCTION
Имя функции в исходном коде, сообщающей об ошибке.

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

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

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

Обратите внимание, что поля ошибки доступны только из объектов PGresult, но не из объектов PGconn; не существует функции PQerrorField.

PQclear

Освобождает память, связанную с PGresult. Результат каждой команды должен быть освобожден функцией PQclear, когда он больше не нужен.

void PQclear(PGresult *res);

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

Извлечение информации о результате запроса

Эти функции применяются для извлечения информации из объекта PGresult, который представляет результат успешного запроса (то есть запроса со статусом PGRES_TUPLES_OK или PGRES_SINGLE_TUPLE). Также их можно использовать для извлечения информации из успешной операции DESCRIBE: ее результат содержит все ту же информацию о столбцах, которую предоставило бы фактическое выполнение запроса, но не содержит ни одной строки. Для объектов с другими значениями статуса эти функции будут вести себя так, словно результат не содержит ни строк, ни столбцов.

PQntuples

Возвращает количество строк (кортежей) в результате запроса. (Обратите внимание, что объекты PGresult не могут содержать больше INT_MAX строк, так что достаточно результата типа int.)

int PQntuples(const PGresult *res);

PQnfields

Возвращает количество столбцов (полей) в каждой строке результата запроса.

int PQnfields(const PGresult *res);

PQfname

Возвращает имя столбца, соответствующего данному номеру столбца. Номера столбцов начинаются с 0. Вызывающая функция не должна напрямую освобождать память, выделенную под результат. Она будет освобождена, когда связанный маркер PGresult будет передан функции PQclear.

char *PQfname(const PGresult *res,
              int column_number);

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

PQfnumber

Возвращает номер столбца, соответствующего данному имени столбца.

int PQfnumber(const PGresult *res,
              const char *column_name);

Если заданное имя не соответствует ни одному столбцу, возвращается -1.

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

SELECT 1 AS FOO, 2 AS "BAR";

мы бы получили такие результаты:

PQfname(res, 0)              foo
PQfname(res, 1)              BAR
PQfnumber(res, "FOO")        0
PQfnumber(res, "foo")        0
PQfnumber(res, "BAR")        -1
PQfnumber(res, "\"BAR\"")    1

PQftable

Возвращает OID таблицы, из которой был получен данный столбец. Номера столбцов начинаются с 0.

Oid PQftable(const PGresult *res,
             int column_number);

Если номер столбца выходит за пределы допустимого диапазона или указанный столбец не является простой ссылкой на столбец таблицы, то возвращается InvalidOid. Чтобы точно определить, к какой таблице происходит обращение, можно сделать запрос к системной таблице pg_class.

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

PQftablecol

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

int PQftablecol(const PGresult *res,
                int column_number);

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

PQfformat

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

int PQfformat(const PGresult *res,
              int column_number);

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

PQftype

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

Oid PQftype(const PGresult *res,
            int column_number);

Чтобы получить имена и свойства различных типов данных, можно сделать запрос к системной таблице pg_type. OID встроенных типов данных определяются в файле catalog/pg_type_d.h каталога include установки QHB.

PQfmod

Возвращает модификатор типа для столбца, соответствующего данному номеру столбца. Номера столбцов начинаются с 0.

int PQfmod(const PGresult *res,
           int column_number);

Интерпретация значений модификаторов является типоспецифичной; обычно они показывают точность или пределы размеров. Значение -1 используется, чтобы указать «нет доступной информации». Большинство типов данных не применяют модификаторы, и в этом случае значение всегда будет -1.

PQfsize

Возвращает размер (в байтах) столбца, соответствующего данному номеру столбца. Номера столбцов начинаются с 0.

int PQfsize(const PGresult *res,
            int column_number);

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

PQbinaryTuples

Возвращает 1, если PGresult содержит двоичные данные, и 0, если он содержит текстовые данные.

int PQbinaryTuples(const PGresult *res);

Эта функция считается устаревшей (за исключением использования совместно с командой COPY), поскольку в одном объекте PGresult могут в одних столбцах содержаться текстовые данные, а в других — двоичные. Предпочтительнее использовать функцию PQfformat. Функция PQbinaryTuples возвращает 1, только если все столбцы результата являются двоичными (формат 1).

PQgetvalue

Возвращает значение одного поля из одной строки в PGresult. Номера строк и столбцов начинаются с 0. Вызывающая функция не должна напрямую освобождать память, выделенную под результат. Она будет освобождена, когда связанный маркер PGresult будет передан функции PQclear.

char *PQgetvalue(const PGresult *res,
                 int row_number,
                 int column_number);

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

Если значение поля равно NULL, возвращается пустая строка. Чтобы отличить значения NULL от пустых строковых значений, используйте функцию PQgetisnull.

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

PQgetisnull

Проверяет поле на отсутствие значения NULL. Номера строк и столбцов начинаются с 0.

int PQgetisnull(const PGresult *res,
                int row_number,
                int column_number);

Эта функция возвращает 1, если поле равно NULL, и 0, если оно содержит значение, отличное от NULL. (Обратите внимание, что для поля с NULL функция PQgetvalue вернет пустую строку, а не пустой указатель.)

PQgetlength

Возвращает фактическую длину значения поля в байтах. Номера строк и столбцов начинаются с 0.

int PQgetlength(const PGresult *res,
                int row_number,
                int column_number);

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

PQnparams

Возвращает количество параметров подготовленного оператора.

int PQnparams(const PGresult *res);

Эта функция полезна только при изучении результатов функции PQdescribePrepared. Для остальных типов запросов она вернет ноль.

PQparamtype

Возвращает тип данных указанного параметра оператора. Номера параметров начинаются с 0.

Oid PQparamtype(const PGresult *res, int param_number);

Эта функция полезна только при изучении результатов функции PQdescribePrepared. Для остальных типов запросов она вернет ноль.

PQprint

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

void PQprint(FILE *fout,      /* поток вывода */
             const PGresult *res,
             const PQprintOpt *po);
typedef struct
{
    pqbool  header;      /* вывести заголовки полей и счетчик строк */
    pqbool  align;       /* выравнивать поля */
    pqbool  standard;    /* старый неудобный формат */
    pqbool  html3;       /* выводить HTML-таблицы */
    pqbool  expanded;    /* расширять таблицы */
    pqbool  pager;       /* при необходимости использовать скрипт постраничного вывода */
    char    *fieldSep;   /* разделитель полей */
    char    *tableOpt;   /* атрибуты для элемента HTML-таблицы */
    char    *caption;    /* подпись к HTML-таблице */
    char    **fieldName; /* массив заменителей для имен полей с завершающим нулем */
} PQprintOpt;

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

Извлечение другой информации о результате

Эти функции применяются для извлечения из объектов PGresult остальной информации.

PQcmdStatus

Возвращает тег статуса из команды SQL, сгенерировавшей PGresult.

char *PQcmdStatus(PGresult *res);

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

PQcmdTuples

Возвращает количество строк, затронутых командой SQL.

char *PQcmdTuples(PGresult *res);

Эта функция возвращает сроку, содержащую количество строк, затронутых оператором SQL, который сгенерировал PGresult. Эту функцию можно использовать только после выполнения оператора SELECT, CREATE TABLE AS, INSERT, UPDATE, DELETE, MERGE, MOVE, FETCH или COPY либо после выполнения оператора EXECUTE с подготовленным запросом, содержащим оператор INSERT, UPDATE, DELETE или MERGE. Если PGresult был сгенерирован какой-то другой командой, функция PQcmdTuples возвращает пустую строку. Вызывающая функция не должна напрямую освобождать память, выделенную под результат. Она будет освобождена, когда связанный маркер PGresult будет передан функции PQclear.

PQoidValue

Возвращает OID добавленной строки, если командой SQL была INSERT, которая добавила ровно одну строку в таблицу, имеющую идентификаторы OID, или EXECUTE с подготовленным запросом, содержащим подходящий оператор INSERT. В противном случае эта функция возвращает InvalidOid. Кроме того, эта функция возвратит InvalidOid, если таблица, затронутая оператором INSERT не содержит идентификаторы OID.

Oid PQoidValue(const PGresult *res);

PQoidStatus

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

char *PQoidStatus(const PGresult *res);

Экранирование строк для включения в команды SQL

PQescapeLiteral

char *PQescapeLiteral(PGconn *conn, const char *str, size_t length);

Функция PQescapeLiteral экранирует строку для использования внутри команды SQL. Это полезно при добавлении в команды SQL значений данных в виде строковых констант. Определенные символы (например, кавычки и обратные слэши) следует экранировать, чтобы предотвратить их специальную интерпретацию синтаксическим анализатором SQL. Эту операцию выполняет функция PQescapeLiteral.

Функция PQescapeLiteral возвращает экранированную версию параметра str, размещенную в области памяти, выделенной с помощью функции malloc(). Эту память следует освободить с помощью функции PQfreemem(), когда результат будет уже не нужен. Завершающий нулевой байт не требуется и не должен учитываться в параметре length. (Если завершающий нулевой байт обнаруживается до обработки length байт, функция PQescapeLiteral останавливается на этом нулевом байте, так что ее поведение напоминает strncpy.) В возвращаемой строке все специальные символы заменяются таким образом, что их может адекватно обработать синтаксический анализатор строковых литералов QHB. Также добавляется завершающий нулевой байт. Апострофы, которые должны окружить строковые литералы QHB, включаются в результирующую строку.

При ошибке функция PQescapeLiteral возвращает NULL, а в объект conn сохраняется соответствующее сообщение.

Совет
Особенно важно выполнять надлежащее экранирование при обработке строк, полученных из ненадежного источника. Иначе возникает угроза безопасности: вы уязвимы к атакам с «инъекцией SQL», при которых в вашу базу данных поступают нежелательные команды SQL.

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

PQescapeIdentifier

char *PQescapeIdentifier(PGconn *conn, const char *str, size_t length);

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

Функция PQescapeIdentifier возвращает версию параметра str, экранированную как идентификатор SQL и размещенную в области памяти, выделенной с помощью функции malloc(). Эту память следует освободить с помощью функции PQfreemem(), когда результат будет уже не нужен. Завершающий нулевой байт не требуется и не должен учитываться в параметре length. (Если завершающий нулевой байт обнаруживается до обработки length байт, функция PQescapeIdentifier останавливается на этом нулевом байте, так что ее поведение напоминает strncpy.) В возвращаемой строке все специальные символы заменяются таким образом, что ее можно адекватно обработать как идентификатор SQL. Также добавляется завершающий нулевой байт. Кроме того, возвращаемая строка будет заключена в кавычки.

При ошибке функция PQescapeIdentifier возвращает NULL, а в объект conn сохраняется соответствующее сообщение.

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

PQescapeStringConn

size_t PQescapeStringConn(PGconn *conn,
                          char *to, const char *from, size_t length,
                          int *error);

Функция PQescapeStringConn экранирует строковые литералы подобно функции PQescapeLiteral. Однако, в отличие от PQescapeLiteral, за предоставление буфера надлежащего размера отвечает вызывающая функция. Более того, функция PQescapeStringConn не генерирует апострофы, которые должны окружать строковые литералы QHB; они должны предоставляться в команде SQL, в которую вставляется результат. Параметр from указывает на первый символ строки, который нужно экранировать, а параметр length задает количество байтов в этой строке. Завершающий нулевой байт не требуется и не должен учитываться в параметре length. (Если завершающий нулевой байт обнаруживается до обработки length байт, функция PQescapeStringConn останавливается на этом нулевом байте, так что ее поведение напоминает strncpy.) Параметр to должен указывать на буфер, способный вместить как минимум на один байт больше удвоенного значения length, в противном случае поведение функции не определено. Кроме того, поведение, скорее всего, будет неопределенным, если строки to и from перекрываются.

Если параметр error не равен NULL, то в *error в случае успеха устанавливается ноль, а в случае ошибки — ненулевое значение. В настоящее время единственным возможным условием ошибки является недопустимая многобайтовая кодировка в исходной строке. В случае ошибки выходная строка все равно генерируется, но можно ожидать, что сервер отвергнет ее как некорректную. При ошибке в объект conn сохраняется соответствующее сообщение, независимо от того, равен ли NULL параметр error.

Функция PQescapeStringConn возвращает количество байтов, записанных в to, не включая завершающий нулевой байт.

PQescapeString

Функция PQescapeString является более старой, выводимой из употребления версией функции PQescapeStringConn.

size_t PQescapeString (char *to, const char *from, size_t length);

Единственное отличие от функции PQescapeStringConn заключается в том, что функция PQescapeString не принимает параметры PGconn и error. Из-за этого она не может скорректировать свое поведение в зависимости от свойств подключения (например, кодировки символов) и, как следствие, может выдавать неверные результаты. Кроме того, она не способна сообщать об ошибках.

Функцию PQescapeString можно безопасно применять в клиентских программах, работающих только с одним соединением с QHB за раз (в этом случае она может найти то, что ей нужно знать, «за кадром»). В других контекстах она представляет собой угрозу безопасности, и следует предпочесть использование функции PQescapeStringConn.

PQescapeByteaConn

Экранирует двоичные данные для использования внутри команды SQL с типом bytea. Как и в случае с PQescapeStringConn, эта функция применяется, только когда данные добавляются в строку команды SQL напрямую.

unsigned char *PQescapeByteaConn(PGconn *conn,
                                 const unsigned char *from,
                                 size_t from_length,
                                 size_t *to_length);

Определенные значения байтов нужно экранировать при использовании в качестве части литерала типа bytea в операторе SQL. Функция PQescapeByteaConn экранирует байты с помощью шестнадцатеричного кодирования или обратного слэша. Подробную информацию см. в разделе Двоичные типы данных.

Параметр from указывает на первый байт строки, который нужно экранировать, а параметр from_length задает количество байтов в этой двоичной строке. (Завершающий нулевой байт не требуется и не должен учитываться.) Параметр to_length указывает на переменную, которая будет содержать длину результирующей экранированной строки. Эта длина включает завершающий нулевой байт результата.

Функция PQescapeByteaConn возвращает экранированную версию двоичной строки из параметра from, размещенную в области памяти, выделенной с помощью функции malloc(). Эту память следует освободить с помощью функции PQfreemem(), когда результат будет уже не нужен. В возвращаемой строке все специальные символы заменяются таким образом, что их могут адекватно обработать синтаксический анализатор строковых литералов QHB и функция ввода типа bytea. Также добавляется завершающий нулевой байт. Апострофы, которые должны окружать строковые литералы QHB, не являются частью результирующей строки.

При ошибке возвращается пустой указатель, а в объект conn сохраняется соответствующее сообщение. В настоящее время единственной возможной ошибкой является нехватка памяти для результирующей строки.

PQescapeBytea

Функция PQescapeBytea является более старой, выводимой из употребления версией функции PQescapeByteaConn.

unsigned char *PQescapeBytea(const unsigned char *from,
                             size_t from_length,
                             size_t *to_length);

Единственное отличие от функции PQescapeByteaConn заключается в том, что функция PQescapeBytea не принимает параметр PGconn. Из-за этого PQescapeBytea можно безопасно применять в клиентских программах, работающих только с одним соединением с QHB за раз (в этом случае она может найти то, что ей нужно знать, «за кадром»). Она может выдавать неверные результаты при использовании в программах, имеющих несколько соединений с базами данных (в таких случаях применяйте функцию PQescapeByteaConn).

PQunescapeBytea

Преобразует строковое представление двоичных данных в двоичные данные — обратно поведению функции PQescapeByteaConn. Она требуется при извлечении данных типа bytea в текстовом формате, но не при извлечении их в двоичном формате.

unsigned char *PQunescapeBytea(const unsigned char *from, size_t *to_length);

Параметр from указывает на такую строку, какую могла бы вернуть функция PQgetvalue, примененная к столбцу типа bytea. Функция PQunescapeBytea преобразует это строковое представление в его двоичное представление. Она возвращает указатель на буфер, выделенный с помощью функции malloc(), или NULL при ошибке и помещает размер буфера в параметр to_length. Результат следует освободить с помощью функции PQfreemem, когда он будет уже не нужен.

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

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

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

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

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

PQexec может вернуть только одну структуру PGresult. Если передаваемая серверу командная строка содержит несколько команд SQL, PQexec отбрасывает все PGresult, кроме последней.

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

Приложения, которых не устраивают эти ограничения, могут вместо этого воспользоваться нижележащими функциями, на которых построена PQexec: PQsendQuery и PQgetResult. Также имеются функции PQsendQueryParams, PQsendPrepare, PQsendQueryPrepared, PQsendDescribePrepared и PQsendDescribePortal, которые можно использовать с PQgetResult, чтобы повторить функциональность PQexecParams, PQprepare, PQexecPrepared, PQdescribePrepared и PQdescribePortal соответственно.

PQsendQuery

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

int PQsendQuery(PGconn *conn, const char *command);

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

В конвейерном режиме вызывать эту функцию запрещено.

PQsendQueryParams

Передает серверу команду и отдельно параметры, не ожидая результата(ов).

int PQsendQueryParams(PGconn *conn,
                      const char *command,
                      int nParams,
                      const Oid *paramTypes,
                      const char * const *paramValues,
                      const int *paramLengths,
                      const int *paramFormats,
                      int resultFormat);

Эта функция равнозначна функции PQsendQuery, за исключением того, что параметры запроса можно задать отдельно от строки запроса. Параметры функции обрабатываются идентично с функцией PQexecParams. Как и PQexecParams, она позволяет включить в строку запроса только одну команду.

PQsendPrepare

Посылает запрос на создание подготовленного оператора с данными параметрами, не ожидая завершения запроса.

int PQsendPrepare(PGconn *conn,
                  const char *stmtName,
                  const char *query,
                  int nParams,
                  const Oid *paramTypes);

Это асинхронная версия функции PQprepare: она возвращает 1, если ей удалось отправить запрос, и 0, если нет. После ее успешного вызова вызовите функцию PQgetResult, чтобы определить, создал ли сервер подготовленный оператор. Параметры функции обрабатываются идентично с функцией PQprepare.

PQsendQueryPrepared

Отправляет запрос на выполнение подготовленного оператора с данными параметрами, не ожидая результата(ов).

int PQsendQueryPrepared(PGconn *conn,
                        const char *stmtName,
                        int nParams,
                        const char * const *paramValues,
                        const int *paramLengths,
                        const int *paramFormats,
                        int resultFormat);

Эта функция схожа с функцией PQsendQueryParams, но выполняемая команда задается указанием на ранее подготовленный оператор, а не передачей строки запроса. Параметры функции обрабатываются идентично с функцией PQexecPrepared.

PQsendDescribePrepared

Отправляет запрос на получение информации о заданном подготовленном операторе, не ожидая завершения запроса.

int PQsendDescribePrepared(PGconn *conn, const char *stmtName);

Это асинхронная версия функции PQdescribePrepared: она возвращает 1, если ей удалось отправить запрос, и 0, если нет. После ее успешного вызова вызовите функцию PQgetResult, чтобы получить результаты. Параметры функции обрабатываются идентично с функцией PQdescribePrepared.

PQsendDescribePortal

Отправляет запрос на получение информации о заданном портале, не ожидая завершения запроса.

int PQsendDescribePortal(PGconn *conn, const char *portalName);

Это асинхронная версия функции PQdescribePortal: она возвращает 1, если ей удалось отправить запрос, и 0, если нет. После ее успешного вызова вызовите функцию PQgetResult, чтобы получить результаты. Параметры функции обрабатываются идентично с функцией PQdescribePortal.

PQgetResult

Ожидает следующего результата от предшествующего вызова PQsendQuery, PQsendQueryParams, PQsendPrepare, PQsendQueryPrepared, PQsendDescribePrepared, PQsendDescribePortal или PQpipelineSync и возвращает его. Когда команда завершается и результатов больше не будет, возвращается пустой указатель.

PGresult *PQgetResult(PGconn *conn);

Функцию PQgetResult нужно вызывать повторно, пока она не вернет пустой указатель, демонстрируя, что команда завершена. (Если PQgetResult вызвать, когда нет ни одной активной команды, она просто сразу вернет пустой указатель.) Каждый отличный от NULL результат, полученный от функции PQgetResult, должен обрабатываться с помощью тех же самых описанных выше функций доступа к PGresult. Не забывайте освобождать с помощью функции PQclear память, занятую каждым результирующим объектом, по окончании работы с ним. Обратите внимание, что функция PQgetResult заблокируется, только если команда активна, а требуемые ответные данные еще не были прочитаны функцией PQconsumeInput.

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

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

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

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

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

PQconsumeInput

Если доступны входные данные от сервера, принять их.

int PQconsumeInput(PGconn *conn);

Обычно функция PQconsumeInput возвращает 1, показывая, что «ошибки нет», но возвращает 0, если имелась какая-либо проблема (в этом случае можно обратиться к функции PQerrorMessage). Обратите внимание, что результат не говорит, были ли в действительности собраны какие-либо входные данные. После вызова функции PQconsumeInput приложение может проверить PQisBusy и/или PQnotifies, чтобы посмотреть, изменилось ли их состояние.

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

PQisBusy

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

int PQisBusy(PGconn *conn);

Функция PQisBusy сама не будет пытаться прочитать данные с сервера, поэтому сначала надо вызвать PQconsumeInput, или занятое состояние никогда не закончится.

Типичное приложение, использующее эти функции, будет иметь основной цикл, применяющий select() или poll(), чтобы дождаться всех условий, на которые оно должно реагировать. Одним из этих условий будут входные данные, поступающие с сервера, что в терминах функции select() означает удобочитаемые данные в файловом дескрипторе, идентифицируемом PQsocket. Когда основной цикл обнаруживает готовые входные данные, он должен вызвать PQconsumeInput, чтобы их прочитать. Затем он может вызвать PQisBusy, а потом PQgetResult, если PQisBusy возвращает false (0). Также он может вызвать PQnotifies, чтобы проверить сообщения NOTIFY (см. раздел Асинхронное уведомление).

Клиент, применяющий PQsendQuery/PQgetResult, может также попытаться отменить команду, которая все еще обрабатывается сервером; см. раздел Отмена запросов в процессе выполнения. Но независимо от значения, возвращаемого функцией PQcancel, приложение должно продолжать обычную последовательность операций чтения результата с помощью PQgetResult. При успешной отмене команда просто завершится раньше, чем она завершилась бы в обычных обстоятельствах.

Используя вышеописанные функции, можно избежать блокировки при ожидании входных данных от сервера баз данных. Однако все равно возможно, что приложение будет заблокировано, ожидая отправки на сервер выходных данных. Это происходит относительно нечасто, но может случиться, если отправляются очень длинные команды SQL или значения данных. (Однако вероятность резко повышается, если приложение посылает данные через команду COPY IN.) Чтобы предотвратить такую возможность и достичь совершенно неблокируемую операцию базы данных, можно воспользоваться следующими дополнительными функциями.

PQsetnonblocking

Устанавливает неблокирующий статус соединения.

int PQsetnonblocking(PGconn *conn, int arg);

Устанавливает состояние соединения в неблокирующее, если arg равен 1, или блокирующее, если arg равен 0. Возвращает 0 при успехе, -1 при ошибке.

В неблокирующем состоянии вызовы функций PQsendQuery, PQputline, PQputnbytes, PQputCopyData и PQendcopy не будут блокироваться, а вместо этого вернут ошибку, если их потребуется вызвать еще раз.

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

PQisnonblocking

Возвращает статус блокирования соединения базы данных.

int PQisnonblocking(const PGconn *conn);

Возвращает 1, если соединение установлено в неблокирующем режиме, и 0, если в блокирующем.

PQflush

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

int PQflush(PGconn *conn);

После отправки любой команды или данных через неблокирующее соединение вызовите функцию PQflush. Если она возвращает 1, подождите, пока сокет не станет готовым к чтению или записи. Если он станет готовым к записи, снова вызовите PQflush. Если он станет готовым к чтению, вызовите функцию PQconsumeInput, затем снова вызовите PQflush. Повторяйте, пока функция PQflush не вернет 0. (Необходимо проверить на готовность к чтению и забрать входные данные с помощью PQconsumeInput, поскольку сервер может заблокироваться, пытаясь отправить нам данные, например сообщения NOTICE, и не будет читать наши данные, пока мы не прочитаем его.) Как только функция PQflush вернет 0, подождите, пока сокет не станет готовым к чтению, а затем прочитайте ответ, как описано выше.

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

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

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

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

Конвейерный API libpq представляет собой функциональность на стороне клиента, которая не требует специальной серверной поддержки и работает с любым сервером, поддерживающим 3-ю версию протокола расширенных запросов. Подробную информацию см. в подразделе Конвейерный режим.

Применение конвейерного режима

Для запуска конвейеров приложение должно переключить соединение в конвейерный режим, что делается с помощью функции PQenterPipelineMode. Для проверки того, включен ли конвейерный режим, можно использовать функцию PQpipelineStatus. В конвейерном режиме разрешены только асинхронные операции, использующие расширенный протокол запросов; командные строки, содержащие несколько команд SQL, а также команда COPY запрещены. Использование функций синхронного выполнения команд, таких как PQfn, PQexec, PQexecParams, PQprepare, PQexecPrepared, PQdescribePrepared, PQdescribePortal, является условием ошибки. Функция PQsendQuery тоже запрещена, поскольку она использует простой протокол запросов. После обработки результатов всех отправленных команд и получения конечного результата конвейера приложение может вернуться в обычный режим с помощью функции PQexitPipelineMode.

Примечание
Лучше всего применять конвейерный режим с libpq в неблокирующем режиме. При использовании в блокирующем режиме возможны взаимоблокировки на уровне клиент/сервер.¹

Отправка запросов

Перейдя в конвейерный режим, приложение отправляет запросы с помощью функции PQsendQueryParams или родственной ей функции PQsendQueryPrepared, работающей с подготовленными запросами. Эти запросы стоят в очередь на стороне клиента, пока не сбрасываются на сервер; это происходит, когда с помощью функции PQpipelineSync в конвейере устанавливается точка синхронизации, или когда вызывается функция PQflush. В конвейерном режиме также работают функции PQsendPrepare, PQsendDescribePrepared и PQsendDescribePortal. Обработка результата описывается ниже.

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

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

Обработка результатов

Чтобы обработать результат одного запроса в конвейере, приложение многократно вызывает функцию PQgetResult и обрабатывает каждый результат, пока PQgetResult не вернет NULL. Затем может быть получен результат следующего запроса в конвейере, тоже с помощью PQgetResult, и цикл повторяется. Результаты отдельных операторов приложение обрабатывает как обычно. После того как будут возвращены результаты всех запросов в конвейере, PQgetResult вернет результат, содержащий значение статуса PGRES_PIPELINE_SYNC.

Клиент может предпочесть отложить обработку результатов до завершения отправки всего конвейера или чередовать ее с отправкой дальнейших запросов в конвейере; см. подраздел Чередование обработки результатов и отправки запросов.

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

Функция PQgetResult ведет себя так же, как и при обычной асинхронной обработке, за исключением того, что она может содержать новые типы PGresult: PGRES_PIPELINE_SYNC и PGRES_PIPELINE_ABORTED. Результат PGRES_PIPELINE_SYNC выдается ровно один раз для каждого вызова PQpipelineSync в соответствующей точке конвейера. Результат PGRES_PIPELINE_ABORTED выдается вместо обычного результата запроса для первой ошибки и всех последующих результатов до следующего PGRES_PIPELINE_SYNC; см. подраздел Обработка ошибок.

Функции PQisBusy, PQconsumeInput и т. п. при обработке результатов конвейера работают как обычно. В частности, вызов PQisBusy в середине конвейера возвращает 0, если были обработаны результаты всех запросов, выполненных на данный момент.

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

Обработка ошибок

С точки зрения клиента, после того как функция PQresultStatus возвращает PGRES_FATAL_ERROR, конвейер помечается как прерванный. PQresultStatus будет выдавать результат PGRES_PIPELINE_ABORTED для каждой оставшейся в очереди операции в прерванном конвейере. Результат для функции PQpipelineSync выдается в виде PGRES_PIPELINE_SYNC, сигнализируя об окончании прерванного конвейера и возобновлении обычной обработки результатов.

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

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

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

Чередование обработки результатов и отправки запросов

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

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

Функции, связанные с конвейерным режимом

PQpipelineStatus

Возвращает текущий статус конвейерного режима соединения libpq.

PGpipelineStatus PQpipelineStatus(const PGconn *conn);

Функция PQpipelineStatus может возвращать одно из следующих значений:

PQ_PIPELINE_ON
Соединение libpq находится в конвейерном режиме.

PQ_PIPELINE_OFF
Соединение libpq находится не в конвейерном режиме.

PQ_PIPELINE_ABORTED
Соединение libpq находится в конвейерном режиме, и при обработке текущего конвейера произошла ошибка. Флаг прерывания сбрасывается, когда функция PQgetResult возвращает результат типа PGRES_PIPELINE_SYNC.

PQenterPipelineMode

Заставляет соединение войти в конвейерный режим, если в настоящий момент оно бездействует или уже находится в конвейерном режиме.

int PQenterPipelineMode(PGconn *conn);

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

PQexitPipelineMode

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

int PQexitPipelineMode(PGconn *conn);

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

PQpipelineSync

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

int PQpipelineSync(PGconn *conn);

Возвращает 1 при успехе. Возвращает 0, если соединение не находится в конвейерном режиме или не удалось отправить сообщение синхронизации.

PQsendFlushRequest

Отправляет серверу команду сбросить его буфер вывода на диск.

int PQsendFlushRequest(PGconn *conn);

Возвращает 1 при успехе. Возвращает 0 при любом сбое.

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

Когда использовать конвейерный режим

Почти так же, как в случае с асинхронным режимом запросов, при использовании конвейерного режима нет значительных издержек производительности. Этот режим увеличивает сложность клиентского приложения и требует дополнительной осторожности во избежание взаимоблокировок на уровне клиент/сервер, может предложить значительное улучшение производительности в обмен на повышение объема используемой памяти из-за более длительного выхода из состояния.

Конвейерный режим наиболее полезен с удаленным сервером, т. е. когда сетевая задержка («время ответа») велика, а также когда много небольших операций выполняются в быстрой последовательности. Обычно применение конвейерных команд дает меньше преимуществ, когда выполнение каждого запроса занимает во много раз больше времени, чем задержка из-за передачи и подтверждения приема между клиентом и сервером. Операция из 100 операторов, выполняемая на сервере за 300 мс за вычетом этого времени, без конвейеризации заняла бы 30 секунд из-за одной только сетевой задержки; с конвейеризацией эта операция потратит не более 0,3 с на ожидание результатов от сервера.

Используйте конвейерные команды, когда ваше приложение выполняет много небольших операций INSERT, UPDATE и DELETE, которые нельзя легко преобразовать в наборы операций или в операцию COPY.

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

BEGIN;
SELECT x FROM mytable WHERE id = 42 FOR UPDATE;
-- результат: x=2
-- клиент прибавляет 1 к x:
UPDATE mytable SET x = 3 WHERE id = 42;
COMMIT;

можно гораздо эффективнее сделать с помощью:

UPDATE mytable SET x = x + 1 WHERE id = 42;

Конвейеризация менее полезна и более сложна, когда один конвейер содержит несколько транзакций (см. подраздел Обработка ошибок).

¹

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

Построчное извлечение результатов запроса

Обычно libpq собирает весь результат SQL и возвращает его приложению в виде единственного объекта PGresult. Это может быть неосуществимо для команд, возвращающих огромное количество строк. В таких случаях приложение может использовать функции PQsendQuery и PQgetResult в однострочном режиме. В этом режиме результирующие строки возвращаются приложению по одной за раз, по мере их получения от сервера.

Для входа в однострочный режим вызовите функцию PQsetSingleRowMode сразу после успешного вызова PQsendQuery (или родственной ей функции). Выбор этого режима действителен только для запроса, обрабатываемого в настоящий момент. Затем повторно вызывайте PQgetResult, пока та не вернет NULL, как описано в разделе Асинхронная обработка команд. Если запрос возвращает некоторое количество строк, они возвращаются в виде отдельных объектов PGresult, которые выглядят как обычные результаты запроса, но имеют код статуса PGRES_SINGLE_TUPLE вместо PGRES_TUPLES_OK. После последней строки или сразу, если запрос не возвращает ни одной строки, возвращается объект, не содержащий строк, со статусом PGRES_TUPLES_OK; это сигнал о том, что больше строки поступать не будут. (Но обратите внимание, что все равно нужно продолжать вызывать PQgetResult, пока та не вернет NULL.) Все эти объекты PGresult будут содержать те же самые данные, описывающие строки (имена столбцов, типы и т. д.), которые имел бы обычный объект PGresult для этого запроса. Память, занимаемую каждым объектом, следует освобождать с помощью функции PQclear, как обычно.

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

PQsetSingleRowMode

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

int PQsetSingleRowMode(PGconn *conn);

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

ВНИМАНИЕ!
Во время обработки запроса сервер может возвратить несколько строк, а затем столкнуться с ошибкой, из-за чего запрос прерывается. Обычно libpq отбрасывает такие строки и сообщает только об ошибке. Но в однострочном режиме эти строчки уже будут возвращены приложению. Как следствие, приложение увидит несколько объектов PGresult со статусом PGRES_SINGLE_TUPLE, а следом за ними — объект со статусом PGRES_FATAL_ERROR. Для обеспечения надлежащего поведения транзакций приложение должно быть разработано так, чтобы отбрасывать или отменять все операции, проведенные с ранее обработанными строками, если запрос в итоге завершается сбоем.

Отмена запросов в процессе выполнения

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

PQgetCancel

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

PGcancel *PQgetCancel(PGconn *conn);

Функция PQgetCancel создает объект PGcancel, получив объект PGconn, описывающий соединение. Она вернет NULL, если параметр conn равен NULL или в нем задано нерабочее соединение. Объект PGcancel является непрозрачной структурой, которая не предназначена для того, чтобы приложение обращалось к ней напрямую; ее можно только передать функции PQcancel или PQfreeCancel.

PQfreeCancel

Освобождает память, занятую структурой данных, созданную функцией PQgetCancel.

void PQfreeCancel(PGcancel *cancel);

Функция PQfreeCancel освобождает память, занятую объектом данных, ранее созданным функцией PQgetCancel.

PQcancel

Требует, чтобы сервер прекратил обработку текущей команды.

int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize);

Возвращаемое значение равно 1, если запрос на отмену был успешно отправлен, и 0, если этого не произошло. В случае неудачной отправки errbuf заполняется пояснительным сообщением об ошибке. Аргумент errbuf должен быть массивом символов с размером errbufsize (рекомендуемый размер — 256 байт).

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

Функцию PQcancel можно безопасно вызывать из обработчика сигнала, если errbuf является в нем локальной переменной. Относительно PQcancel объект PGcancel доступен только для чтения, поэтому ее также можно вызвать из потока, отдельного от того, который управляет объектом PGconn.

PQrequestCancel

Функция PQrequestCancel является устаревшим вариантом функции PQcancel.

int PQrequestCancel(PGconn *conn);

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

Интерфейс быстрого пути

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

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

Функция PQfn запрашивает выполнение серверной функции посредством интерфейса быстрого пути:

PGresult *PQfn(PGconn *conn,
               int fnid,
               int *result_buf,
               int *result_len,
               int result_is_int,
               const PQArgBlock *args,
               int nargs);

typedef struct
{
    int len;
    int isint;
    union
    {
        int *ptr;
        int integer;
    } u;
} PQArgBlock;

Аргумент fnid представляет собой OID выполняемой функции. Аргументы args и nargs определяют параметры, которые нужно передать этой функции; они должны соответствовать списку аргументов объявленной функции. Когда поле isint структуры параметра равно true, значение поля u.integer передается серверу в виде целого числа указанной длины (это должно быть 2 или 4 байта); при этом происходит перестановка байтов. Когда isint равно false, указанное количество байтов из поля *u.ptr передается без обработки; эти данные должны быть представлены в формате, ожидаемом сервером для передачи в двоичном виде типа данных аргументов функции. (Объявление поля u.ptr как имеющего тип int * является историческим; было бы лучше рассматривать его как тип void *.) Аргумент result_buf указывает на буфер, в который должно быть помещено возвращаемое значение функции. Вызывающая функция должна выделить достаточно места для сохранения возвращаемого значения. (Это не проверяется!) Фактическая длина результата в байтах будет возвращена в виде целого числа, на которое указывает result_len. Если ожидается двух- или четырехбайтовый целочисленный результат, установите в result_is_int значение 1; в противном случае установите там 0. Когда значение result_is_int равно 1, libpq при необходимости переставляет байты в передаваемом значении, чтобы оно доставлялось на клиентскую машину в виде правильного значения типа int; обратите внимание, что четырехбайтовое целое доставляется в *result_buf для любого допустимого размера результата. Когда значение result_is_int равно 0, строка байтов в двоичном формате, передаваемая сервером, возвращается немодифицированной. (В этом случае лучше рассматривать result_buf как имеющий тип void *.)

Функция PQfn всегда возвращает действительный указатель на PGresult со статусом PGRES_COMMAND_OK при успехе или PGRES_FATAL_ERROR при столкновении с какой-либо проблемой. Перед использованием результата обязательно нужно проверить его статус. Вызывающая функция отвечает за освобождение с помощью функции PQclear памяти, занимаемой PGresult, когда в этом объект больше нет необходимости.

Чтобы передать функции аргумент NULL, установите в поле len этой структуры параметра значение -1; при этом поля isint и u не важны.

Если функция возвращает NULL, в *result_len устанавливается значение -1, а *result_buf не модифицируется.

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

Асинхронное уведомление

QHB предлагает асинхронное уведомление посредством команд LISTEN и NOTIFY. Сеанс клиента регистрирует свою заинтересованность в конкретном канале уведомлений с помощью команды LISTEN (и может остановить прослушивание с помощью команды UNLISTEN). Все сеансы, прослушивающие конкретный канал, будут уведомляться асинхронно, когда в рамках любого сеанса выполняется команда NOTIFY с именем этого канала. Для передачи прослушивающим процессам дополнительных данных может передаваться строка «полезные данные».

Приложения с libpq отправляют команды LISTEN, UNLISTEN и NOTIFY как обычные команды SQL. Поступление сообщений от команды NOTIFY впоследствии можно отследить, вызвав функцию PQnotifies.

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

PGnotify *PQnotifies(PGconn *conn);

typedef struct pgNotify
{
    char *relname;              /* имя канала уведомлений */
    int  be_pid;                /* PID серверного процесса, передающего уведомление */
    char *extra;                /* строка полезных данных уведомления */
} PGnotify;

После обработки объекта PGnotify, возвращенного функцией PQnotifies, обязательно освободите занимаемую им память с помощью функции PQfreemem. Достаточно освободить указатель на PGnotify; поля relname и extra не представляют отдельных областей памяти. (Эти поля названы так по историческим причинам; в частности, имена каналов не обязаны иметь какое-либо отношение к именам отношений.)

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

На самом деле функция PQnotifies не читает данные с сервера; она просто возвращает сообщения, ранее собранные другой функцией libpq. В ранних выпусках libpq единственным способом обеспечить своевременное получение сообщений от команды NOTIFY была постоянная отправка команд, пусть даже пустых, с последующей проверкой PQnotifies после каждого вызова функции PQexec. Хотя этот метод все еще работает, он считается устаревшим из-за излишней траты вычислительной мощности.

Более продвинутым способом проверки наличия сообщений от NOTIFY, когда у вас нет полезных команд для выполнения, является вызов функции PQconsumeInput с последующей проверкой PQnotifies. Вы можете использовать select(), чтобы подождать прибытия данных от сервера, как следствие, тратя ресурсы центрального процессора, только если имеются операции для выполнения. (Как получить номер дескриптора для использования с select(), рассказывается в описании функции PQsocket.) Обратите внимание, что это будет работать независимо от того, отправляете ли вы команды с помощью функции PQsendQuery/PQgetResult или просто используете функцию PQexec. Однако нужно не забывать проверять PQnotifies после каждого вызова PQgetResult или PQexec, чтобы увидеть, не поступили ли во время обработки команды какие-либо уведомления.

Функции, связанные с командой COPY

Команда COPY в QHB имеет дополнительные возможности читать или записывать данные через сетевое подключение, используемое libpq. Функции, описанные в этом разделе, позволяют приложениям воспользоваться этим функционалом для предоставления или приема копируемых данных.

В целом этот процесс выглядит следующим образом: сначала приложение выполняет команду SQL COPY посредством вызова PQexec или одной из равнозначных ей функций. Ответом на это (если в команде нет ошибки) будет объект PGresult с кодом статуса PGRES_COPY_OUT или PGRES_COPY_IN (в зависимости от заданного направления копирования). Затем приложение должно использовать функции, описанные в этом разделе, чтобы получить или передать строки данных. По завершении передачи данных возвращается еще один объект PGresult, указывающий на успех или сбой передачи. В случае успеха его статус будет PGRES_COMMAND_OK, а при возникновении какой-либо проблемы — PGRES_FATAL_ERROR. Да этом этапе дальнейшие команды SQL можно запускать через функцию PQexec. (Пока производится операция COPY, выполнить другие команды SQL через то же подключение нельзя.)

Если команда COPY запускается через функцию PQexec в строке, которая может содержать дополнительные команды, приложение должно продолжать извлекать результаты через функцию PQgetResult после завершения последовательности COPY. Только когда PQgetResult возвращает NULL, можно утверждать, что командная строка PQexec выполнена, и можно с уверенностью передавать другие команды.

Функции из этого раздела следует выполнять только после получения статуса результата PGRES_COPY_OUT или PGRES_COPY_IN от функции PQexec или PQgetResult.

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

PQnfields
Возвращает количество копируемых столбцов (полей).

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

PQfformat
Возвращает код формата (0 для текстового, 1 для двоичного), связанный с каждым столбцом операции копирования. Коды форматов столбцов всегда будут нулевыми, если общий формат копирования текстовый, но двоичный формат может поддерживать и текстовые, и двоичные столбцы. (Однако в текущей реализации COPY в двоичной копии фигурируют только двоичные столбцы, поэтому в настоящее время форматы столбцов всегда соответствуют общему формату.)

Функции для передачи данных COPY

Эти функции используются для передачи данных во время COPY FROM STDIN. Они успешно выполнятся, только если при их вызове соединение находится в состоянии COPY_IN.

PQputCopyData

Отправляет данные на сервер при активном состоянии COPY_IN.

int PQputCopyData(PGconn *conn,
                  const char *buffer,
                  int nbytes);

Передает серверу данные COPY из заданного буфера buffer, длиной nbytes байт. Результат равен 1, если данные попали в очередь на передачу, ноль, если они не попали в очередь из-за переполненных буферов (это возможно только в неблокирующем режиме), или -1, если произошла ошибка. (Если возвращается значение -1, воспользуйтесь функцией PQerrorMessage для получения подробных сведений об ошибке. Если возвращается ноль, дождитесь готовности к записи и повторите попытку.)

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

PQputCopyEnd

Передает на сервер индикатор конца данных при активном состоянии COPY_IN.

int PQputCopyEnd(PGconn *conn,
                 const char *errormsg);

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

Результат равен 1, если было передано сообщение завершения; в неблокирующем режиме это может означать только, что сообщение завершения было успешно поставлено в очередь. (В неблокирующем режиме для того, чтобы удостовериться, что данные были отправлены, нужно дождаться готовности к записи и неоднократно вызвать функцию PQflush, пока та не вернет ноль.) Ноль показывает, что функция не смогла поставить сообщение завершения в очередь из-за переполненных буферов; это возможно только в неблокирующем режиме. (В этом случае дождитесь готовности к записи и снова попробуйте вызвать PQputCopyEnd.) Если происходит серьезная ошибка, возвращается -1; для получение подробных сведений об ошибке можно вызвать функцию PQerrorMessage.

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

Функции для приема данных COPY

Эти функции используются для получения данных во время COPY TO STDOUT. Они успешно выполнятся, только если при их вызове соединение находится в состоянии COPY_OUT.

PQgetCopyData

Получает данные от сервера при активном состоянии COPY_OUT.

int PQgetCopyData(PGconn *conn,
                  char **buffer,
                  int async);

Пытается получить следующую строку данных от сервера в процессе выполнения COPY. Данные всегда возвращаются по строке за раз; если доступна только часть строки, она не возвращается. Успешное возвращение строки данных включает выделение участка памяти для хранения этих данных. Параметр buffer должен быть отличным от NULL. В поле *buffer устанавливается указатель на выделенную память или NULL (в случаях, когда буфер не возвращается). Когда в буфере результата, отличном от NULL, отпадет необходимость, занимаемую им память следует освободить с помощью функции PQfreemem.

Когда строка возвращается успешно, возвращаемое значение представляет собой количество байтов данных в этой строке (оно всегда будет больше нуля). Возвращаемая строка всегда завершается нулевым байтом, хотя это полезно, вероятно, только для текстовой команды COPY. Нулевой результат показывает, что команда COPY еще выполняется, но ни одна строка еще не доступна (это возможно, только когда параметр async равен true). Результат -1 показывает, что команда COPY завершена. Результат -2 показывает, что произошла ошибка (ее причину можно узнать с помощью функции PQerrorMessage).

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

После того как PQgetCopyData вернет -1, вызовите функцию PQgetResult, чтобы получить окончательный статус результата команды COPY. Ожидать появления этого результата можно обычным образом. Затем вернитесь к обычной операции.

Устаревшие функции для COPY

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

PQgetline

Читает строку символов (передаваемую сервером), завершающуюся символом перевода строки, в буферную строку размера length.

int PQgetline(PGconn *conn,
              char *buffer,
              int length);

Эта функция копирует до length-1 символов в буфер и преобразует символ перевода строки в нулевой байт. PQgetline возвращает EOF в конце ввода, 0 если была прочитана вся строка, и 1, если буфер заполнен, но завершающий символ перевода строки еще не был прочитан.

Обратите внимание, что приложение должно проверить, не состоит ли новая строка ровно из двух символов \., показывая, что сервер закончил передачу результатов команды COPY. Если приложение способно получать строки длиннее length-1 символов, нужно позаботиться о том, чтобы оно корректно распознавало строку \. (а не принимало, например, конец длинной строки данных за завершающую строку).

PQgetlineAsync

Читает строку данных COPY (передаваемую сервером) в буфер без блокировки.

int PQgetlineAsync(PGconn *conn,
                   char *buffer,
                   int bufsize);

Эта функция схожа с функцией PQgetline, но может использоваться приложениями, которые должны читать данные COPY асинхронно, то есть без блокировки. Запустив команду COPY и получив ответ PGRES_COPY_OUT, приложение должно вызывать функции PQconsumeInput и PQgetlineAsync, пока не будет обнаружен сигнал конца данных.

В отличие от функции PQgetline, эта функция сама отвечает за обнаружение конца данных.

При каждом вызове PQgetlineAsync будет возвращать данные, если во входном буфере libpq доступна полная строка данных. В противном случае никакие данные не возвращаются, пока не поступит остальная часть строки. Функция возвращает -1, если был распознан маркер завершения копирования данных, или 0, если данные не получены, или положительное число, отражающее количество возвращенных байтов данных. Если возвращается -1, вызывающая функция должна затем вызвать функцию PQendcopy, а потом вернуться к обычной обработке.

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

PQputline

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

int PQputline(PGconn *conn,
              const char *string);

Поток данных COPY, передаваемых последовательностью вызовов функции PQputline, имеет тот же формат, что возвращает функция PQgetlineAsync, за исключением того, что приложения не обязаны передавать ровно по одной строке данных за вызов PQputline; они могут посылать часть строки или сразу несколько строк.

Примечание
С версиями протокола QHB ниже 3.0 приложению нужно было явно передать в качестве последней строки два символа (\.), чтобы показать серверу, что оно закончило передачу данных COPY. Хотя это по-прежнему работает, но считается устаревшим, и можно ожидать, что в будущих релизах специальное значение символов \. будет убрано. Сейчас после передачи фактических данных достаточно вызвать функцию PQendcopy.

PQputnbytes

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

int PQputnbytes(PGconn *conn,
                const char *buffer,
                int nbytes);

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

PQendcopy

Проводит синхронизацию с сервером.

int PQendcopy(PGconn *conn);

Эта функция ожидает, пока сервер завершит копирование. Она должна запускаться либо когда серверу была с помощью функции PQputline передана последняя строка, либо когда от сервера была с помощью функции PQgetline получена последняя строка. Ее необходимо запускать, иначе сервер «рассинхронизируется» с клиентом. После возвращения значения от этой функции сервер готов принять следующую команду SQL. При успешном завершении возвращается 0, в противном случае — ненулевое значение. (Если возвращаемое значение отлично от нуля, воспользуйтесь функцией PQerrorMessage для получения подробных сведений об ошибке.)

Применяя функцию PQgetResult, приложение должно ответить на результат PGRES_COPY_OUT, повторно вызывая функцию PQgetline, а затем, увидев завершающую строку, вызвать функцию PQendcopy. Затем оно должно вернуться к циклу PQgetResult и оставаться в нем, пока PQgetResult не вернет пустой указатель. Аналогичным образом обрабатывается результат PGRES_COPY_IN: серия вызовов функции PQputline, следом — вызов функции PQendcopy, а затем возвращение к циклу PQgetResult. Такая организация обработки гарантирует, что команда COPY будет выполняться корректно даже в составе последовательности команд SQL.

Более старые приложения, как правило, передают команду COPY через функцию PQexec и предполагают, что транзакция завершается после PQendcopy. Это будет работать корректно, только если COPY является единственной командой SQL в командной строке.

Функции управления

Эти функции управляют различными нюансами поведения libpq.

PQclientEncoding

Возвращает клиентскую кодировку.

int PQclientEncoding(const PGconn *conn);

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

char *pg_encoding_to_char(int encoding_id);

PQsetClientEncoding

Устанавливает клиентскую кодировку.

int PQsetClientEncoding(PGconn *conn, const char *encoding);

В аргументе conn передается подключение к серверу, а в аргументе encoding — желаемая кодировка. Если функция успешно устанавливает кодировку, она возвращает 0, а в противном случае — -1. Текущую кодировку для данного подключения можно определить с помощью функции PQclientEncoding.

PQsetErrorVerbosity

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

typedef enum
{
    PQERRORS_TERSE,
    PQERRORS_DEFAULT,
    PQERRORS_VERBOSE,
    PQERRORS_SQLSTATE
} PGVerbosity;

PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);

Функция PQsetErrorVerbosity устанавливает режим детализации, возвращая предыдущее его значение для подключения. В режиме TERSE (краткий) возвращаемые сообщения включают только уровень важности, основной текст и позицию; обычно это умещается в одной строке. В режиме DEFAULT (по умолчанию) создаются сообщения, включающие все вышеперечисленное плюс поля подробных сведений, советов или контекста (они могут занимать несколько строк). В режиме VERBOSE (подробный) включаются все доступные поля. В режиме SQLSTATE включаются только уровень важности и код ошибки SQLSTATE, если он доступен (если нет, вывод будет таким же, как в режиме TERSE).

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

PQsetErrorContextVisibility

Определяет режим обработки полей CONTEXT в сообщениях, возвращаемых функциями PQerrorMessage и PQresultErrorMessage.

typedef enum
{
    PQSHOW_CONTEXT_NEVER,
    PQSHOW_CONTEXT_ERRORS,
    PQSHOW_CONTEXT_ALWAYS
} PGContextVisibility;

PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibility show_context);

Функция PQsetErrorContextVisibility устанавливает режим отображения контекста, возвращая его предыдущее значение для подключения. Этот режим определяет, будет ли поле CONTEXT включаться в сообщения. В режиме NEVER поле CONTEXT не включается никогда, в то время как в режиме ALWAYS оно включается всегда, если имеется. В режиме ERRORS (по умолчанию) поля CONTEXT включаются только в сообщения об ошибках, но не в замечания и предупреждения. (Однако если установлен режим детализации TERSE или SQLSTATE, поля CONTEXT опускаются независимо от режима отображения контекста.)

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

PQtrace

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

void PQtrace(PGconn *conn, FILE *stream);

Каждая строка состоит из: необязательной метки времени, индикатора направления (F для сообщений от клиента серверу или B для сообщений от сервера клиенту), длины, типа и содержимого сообщения. Поля содержимого, не относящиеся к сообщению (метка времени, направление, длина и тип сообщения) разделяются табуляцией. Содержимое сообщения разделяется пробелами. Строки протокола заключаются в кавычки, тогда как строки, используемые в качестве значений данных, заключаются в апострофы. Непечатаемые символы выводятся в виде шестнадцатеричных управляющих последовательностей. Дополнительную информацию о типах сообщений см. в разделе Форматы сообщений.

PQsetTraceFlags

Управляет поведением трассировки клиент-серверного взаимодействия.

void PQsetTraceFlags(PGconn *conn, int flags);

Аргумент flags содержит биты флагов, описывающие режим работы трассировки. Если flags содержит PQTRACE_SUPPRESS_TIMESTAMPS, то метка времени не включается при выводе каждого сообщения. Если flags содержит PQTRACE_REGRESS_MODE, то при выводе каждого сообщения редактируются некоторые поля, например OID объектов, чтобы этот вывод было удобнее использовать в средах тестирования. Эту функцию следует вызывать после вызова функции PQtrace.

PQuntrace

Выключает трассировку, запущенную функцией PQtrace.

void PQuntrace(PGconn *conn);

Прочие функции

Как всегда, существуют функции, не попадающие ни в одну из категорий.

PQfreemem

Освобождает память, выделенную libpq.

void PQfreemem(void *ptr);

Освобождает память, выделенную libpq, в частности, функциями PQescapeByteaConn, PQescapeBytea, PQunescapeBytea и PQnotifies. Эта функция на всех платформах (кроме Microsoft Windows) действует так же, как и стандартная библиотечная функция free().

PQconninfoFree

Освобождает структуры данных, выделенные функцией PQconndefaults или PQconninfoParse.

void PQconninfoFree(PQconninfoOption *connOptions);

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

PQencryptPasswordConn

Подготавливает зашифрованную форму пароля QHB.

char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm);

Эта функция предназначена для использования клиентскими приложениями, желающими передавать команды наподобие ALTER USER joe PASSWORD 'pwd'. Рекомендуется не передавать в такой команде исходный пароль открытым текстом, поскольку он может появиться в журналах команд, выводах активности и т. д. Вместо этого примените данную функцию, чтобы перед отправкой перевести пароль в зашифрованную форму.

В аргументах passwd и user передается пароль открытым текстом и SQL-имя пользователя, для которого он предназначен. В аргументе algorithm задается алгоритм для шифрования пароля. В настоящее время поддерживаются алгоритмы md5 и scram-sha-256 (для совместимости со старыми версиями сервера в качестве псевдонимов md5 также принимаются значения on и off). Если algorithm равен NULL, эта функция запросит у сервера текущее значение параметра password_encryption. При этом возможна блокировка функции, а если текущая транзакция прервется или если соединение занято выполнением другого запроса, произойдет сбой. Если вы желаете использовать алгоритм сервера по умолчанию, но при этом избежать блокировки, сами запросите значение password_encryption перед вызовом функции PQencryptPasswordConn и передайте его в аргументе algorithm.

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

PQencryptPassword

Подготавливает зашифрованную md5 форму пароля QHB.

char *PQencryptPassword(const char *passwd, const char *user);

Функция PQencryptPassword является более старой, выводимой из употребления версией функции PQencryptPasswordConn. Разница между ними заключается в том, что PQencryptPassword не требуется объект подключения, а в качестве алгоритма шифрования всегда используется md5.

PQmakeEmptyPGresult

Конструирует пустой объект PGresult с заданным статусом.

PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);

Это внутренняя функция libpq для выделения памяти и инициализации пустого объекта PGresult. Эта функция возвращает NULL, если память выделить невозможно. Она является экспортируемой, поскольку некоторые приложения находят полезным создавать объекты результатов (в частности, объекты со статусом ошибки) самостоятельно. Если conn отличен от NULL и status указывает на ошибку, в PGresult копируется текущее сообщение об ошибке для заданного подключения. Кроме того, если conn отличен от NULL, в PGresult копируются все процедуры событий, зарегистрированные в этом подключении. (Они не производят вызовы PGEVT_RESULTCREATE; см. описание функции PQfireResultCreateEvents.) Обратите внимание, что в итоге для этого объекта нужно вызвать функцию PQclear, как и для объекта PGresult, возвращенного самой libpq.

PQfireResultCreateEvents

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

int PQfireResultCreateEvents(PGconn *conn, PGresult *res);

Аргумент conn передается по процедурам событий, но не используется напрямую. Он может быть равен NULL, если процедуры событий в нем не нуждаются.

Процедуры событий, уже получившие событие PGEVT_RESULTCREATE или PGEVT_RESULTCOPY для этого объекта, повторно не запускаются.

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

PQcopyResult

Создает копию объекта PGresult. Эта копия никак не связана с исходным результатом, и когда в ней больше нет необходимости, следует вызвать PQclear. Если функция завершается ошибкой, возвращается NULL.

PGresult *PQcopyResult(const PGresult *src, int flags);

Эта функция не предназначена для создания точной копии. Возвращаемый результат всегда имеет статус PGRES_TUPLES_OK, и в него не копируются никакие сообщения об ошибках из исходного результата. (Однако в него копируется строка статуса команды.) Аргумент flags определяет, что еще будет в него копироваться. Этот аргумент представляет собой побитовое логическое сложение нескольких флагов. Флаг PG_COPYRES_ATTRS задает копирование атрибутов исходного результата (определений столбцов). Флаг PG_COPYRES_TUPLES задает копирование кортежей исходного результата. (Это подразумевает копирование также и атрибутов.) Флаг PG_COPYRES_NOTICEHOOKS задает копирование обработчиков уведомлений исходного результата. Флаг PG_COPYRES_EVENTS задает копирование событий исходного результата. (Но любые данные, связанные с экземпляром исходного результата, не копируются.) Процедуры событий получают события PGEVT_RESULTCOPY.

PQsetResultAttrs

Устанавливает атрибуты объекта PGresult.

int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs);

Предоставленная структура attDescs копируется в результат. Если указатель attDescs равен NULL или numAttributes меньше единицы, запрос игнорируется, и функция выполняется успешно. Если res уже содержит атрибуты, функция завершается ошибкой. В случае ошибки возвращается ноль, а в случае успеха — ненулевое значение.

PQsetvalue

Устанавливает значение поля кортежа в объекте PGresult.

int PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len);

При необходимости эта функция автоматически увеличит внутренний массив кортежей. Однако значение аргумента tup_num должно быть меньше или равно PQntuples, подразумевая, что эта функция может увеличивать массив кортежей только на один кортеж за раз. Но любое поле в любом существующем кортеже можно модифицировать в любом порядке. Если значение поля с номером field_num уже существует, оно будет перезаписано. Если len равен -1 или value равен NULL, в поле будет установлено значение SQL NULL. Аргумент value (устанавливаемое значение) копируется в закрытую область памяти результата, поэтому после завершения функции в нем не будет необходимости. В случае ошибки возвращается ноль, в случае успеха — ненулевое значение.

PQresultAlloc

Выделяет подчиненную область памяти для объекта PGresult.

void *PQresultAlloc(PGresult *res, size_t nBytes);

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

PQresultMemorySize

Выводит количество байтов, выделенное для объекта PGresult.

size_t PQresultMemorySize(const PGresult *res);

Это значение равно сумме всех запросов malloc, связанных с этим объектом PGresult, то есть это общий объем памяти, который будет освобожден функцией PQclear. Эта информация может быть полезна для управления потреблением памяти.

PQlibVersion

Возвращает версию используемой библиотеки libpq.

int PQlibVersion(void);

Результат этой функции можно использовать для определения (во время выполнения), доступна ли определенная функциональность в загруженной в настоящий момент версии libpq. Эту функцию можно применять, к примеру, для определения того, какие параметры подключения доступны в функции PQconnectdb.

Результат формируется путем умножения номера основной версии библиотеки на 10000 и добавления дополнительного номера версии. Например, версия 1.5 будет возвращена как 10005.

Если номер версии состоит из трех чисел, то основную версию представляют первые два числа. Для таких версий функция PQlibVersion использует для каждой части по две цифры; например, версия 1.4.1 будет возвращена как 10401, а версия 1.5.0 — как 10500.

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

Обработка замечаний

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

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

Функция PQsetNoticeReceiver устанавливает или просматривает текущий приемник замечаний для объекта подключения. Аналогично функция PQsetNoticeProcessor устанавливает или просматривает текущий обработчик замечаний.

typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res);

PQnoticeReceiver
PQsetNoticeReceiver(PGconn *conn,
                    PQnoticeReceiver proc,
                    void *arg);

typedef void (*PQnoticeProcessor) (void *arg, const char *message);

PQnoticeProcessor
PQsetNoticeProcessor(PGconn *conn,
                     PQnoticeProcessor proc,
                     void *arg);

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

Когда сообщение с замечанием или предупреждением поступает от сервера или внутренне генерируется libpq, вызывается функция приемника замечаний. Сообщение передается ей в форме объекта PGresult со статусом PGRES_NONFATAL_ERROR. (Это позволяет приемнику извлечь из него отдельные поля с помощью функции PQresultErrorField или получить полное сообщение в заданном формате с помощью функции PQresultErrorMessage или PQresultVerboseErrorMessage.) Также ей передается тот же неопределенный указатель, что был передан функции PQsetNoticeReceiver. (При необходимости этот указатель можно использовать для обращения к внутреннему состоянию приложения.)

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

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

Стандартный обработчик замечаний прост:

static void
defaultNoticeProcessor(void *arg, const char *message)
{
    fprintf(stderr, "%s", message);
}

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

Система событий

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

Каждый зарегистрированный обработчик событий связывается с двумя единицами данных, известными libpq только как скрытые указатели void *. Первый — сквозной указатель, который предоставляет приложение, когда обработчик событий регистрируется в PGconn. Сквозной указатель никогда не меняется на протяжении существования PGconn, и все PGresult генерируются из него, поэтому, если он используется, он должен указывать на долгоживущие данные. В дополнение к нему имеется указатель данных экземпляра, который изначально равен NULL во всех объектах PGconn и PGresult. Этим указателем можно манипулировать с помощью функций PQinstanceData, PQsetInstanceData, PQresultInstanceData и PQresultSetInstanceData. Обратите внимание, что, в отличие от сквозного указателя, данные экземпляра PGconn не наследуются автоматически объектами PGresult, создаваемыми из него. libpq не знает, на что указывают сквозной указатель и указатель данных экземпляра (если таковые имеются) и никогда не будет пытаться освободить их — за это отвечает обработчик событий.

Типы событий

Перечисление PGEventId именует типы событий, обрабатываемых системой событий. Имена всех их значений начинаются с PGEVT. Для каждого типа событий имеется соответствующая структура информации о событии, которая содержит параметры, передаваемые обработчикам событий. Существуют следующие типы событий:

PGEVT_REGISTER

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

typedef struct
{
    PGconn *conn;
} PGEventRegister;

При получении события PGEVT_REGISTER указатель evtInfo следует привести к PGEventRegister *. Эта структура содержит объект PGconn, который должен иметь статус CONNECTION_OK; это гарантируется, если вызвать функцию PQregisterEventProc сразу после получения действующего PGconn. В случае возвращения кода ошибки потребуется провести всю очистку самостоятельно, поскольку событие PGEVT_CONNDESTROY передано не будет.

PGEVT_CONNRESET

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

typedef struct
{
    PGconn *conn;
} PGEventConnReset;

При получении события PGEVT_CONNRESET указатель evtInfo следует привести к PGEventConnReset *. Хотя содержащийся в нем объект PGconn только что был переустановлен, все данные события остаются неизменными. Это событие нужно использовать для переустановки/перезагрузки/повторного запроса всех связанных данных instanceData. Обратите внимание, что даже если процедуре событий не удается обработать PGEVT_CONNRESET, при закрытии соединения она все равно получит событие PGEVT_CONNDESTROY.

PGEVT_CONNDESTROY

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

typedef struct
{
    PGconn *conn;
} PGEventConnDestroy;

При получении события PGEVT_CONNDESTROY указатель evtInfo следует привести к PGEventConnDestroy *. Это событие запускается до того, как функция PQfinish производит всю остальную очистку. Значение, возвращаемое процедурой событий, игнорируется, так как из PQfinish никак нельзя сообщить об ошибке. Кроме того, ошибка в процедуре событий не должна прерывать процесс очистки ненужной памяти.

PGEVT_RESULTCREATE

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

typedef struct
{
    PGconn *conn;
    PGresult *result;
} PGEventResultCreate;

При получении события PGEVT_RESULTCREATE указатель evtInfo следует привести к PGEventResultCreate *. В conn передается подключение, используемое для генерирования результата. Это наиболее подходящее место для инициализации любых данных instanceData, которые нужно связать с результатом. Если процедура событий завершается ошибкой (возвращает ноль), она будет игнорироваться до конца срока существования результата; то есть она не получит события PGEVT_RESULTCOPY или PGEVT_RESULTDESTROY для этого результата или результатов, скопированных из него.

PGEVT_RESULTCOPY

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

typedef struct
{
    const PGresult *src;
    PGresult *dest;
} PGEventResultCopy;

При получении события PGEVT_RESULTCOPY указатель evtInfo следует привести к PGEventResultCopy *. Результат src — это то, что копируется, а dest — целевой результат копируемых данных. Это событие можно использовать для реализации детального копирования данных instanceData, поскольку функция PQcopyResult не может этого сделать. Если процедура событий завершается ошибкой(возвращает ноль), она будет игнорироваться до конца срока существования результата; то есть она не получит события PGEVT_RESULTCOPY или PGEVT_RESULTDESTROY для этого результата или результатов, скопированных из него.

PGEVT_RESULTDESTROY

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

typedef struct
{
    PGresult *result;
} PGEventResultDestroy;

При получении события PGEVT_RESULTDESTROY указатель evtInfo следует привести к PGEventResultDestroy *. Это событие запускается до того, как функция PQclear производит всю остальную очистку. Значение, возвращаемое процедурой событий, игнорируется, так как из PQclear никак нельзя сообщить об ошибке. Кроме того, ошибка в процедуре событий не должна прерывать процесс очистки ненужной памяти.

Процедура обратного вызова для событий

PGEventProc

PGEventProc — это определение типа для указателя на процедуру событий, то есть пользовательскую функцию обратного вызова, получающую события от libpq. Сигнатура процедуры событий должна быть следующей:

int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)

Параметр evtId указывает, какое событие PGEVT произошло. Для получения дополнительной информации о событии указатель evtInfo следует привести к подходящему типу структуры. Параметр passThrough является указателем, переданным в функцию PQregisterEventProc при регистрации процедуры событий. Эта функция должна вернуть ненулевое значение в случае успехе, и ноль в случае ошибки.

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

Вспомогательные функции событий

PQregisterEventProc

Регистрирует процедуру обратного вызова для событий в libpq.

int PQregisterEventProc(PGconn *conn, PGEventProc proc,
                        const char *name, void *passThrough);

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

Процедура, указанная в аргументе proc, будет вызываться при запуске события libpq. Ее адрес в памяти также используется для поиска данных instanceData. Аргумент name используется для ссылки на процедуру событий в сообщениях об ошибках. Это значение не может быть равно NULL или быть строкой нулевой длины. Эта строка имени копируется в PGconn, поэтому переданной строке необязательно быть долгоживущей. Указатель passThrough передается процедуре proc при каждом возникновении события. Этот аргумент может быть равен NULL.

PQsetInstanceData

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

int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);

PQinstanceData

Возвращает для подключения conn указатель на instanceData, связанный с процедурой proc, или NULL, если таковая отсутствует.

void *PQinstanceData(const PGconn *conn, PGEventProc proc);

PQresultSetInstanceData

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

int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);

Примите к сведению, что память, представленная параметром data, не будет учитываться функцией PQresultMemorySize, если только она не была выделена с помощью функции PQresultAlloc. (Рекомендуется использовать эту функцию, поскольку это избавляет от необходимости явно освобождать подобную память после уничтожения результата.)

PQresultInstanceData

Возвращает для результата указатель на instanceData, связанный с процедурой proc, или NULL, если таковая отсутствует.

void *PQresultInstanceData(const PGresult *res, PGEventProc proc);

Пример события

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

/* требуется заголовок для событий libpq (примечание: включает libpq-fe.h) */
#include <libpq-events.h>

/* instanceData */
typedef struct
{
    int n;
    char *str;
} mydata;

/* PGEventProc */
static int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough);

int
main(void)
{
    mydata *data;
    PGresult *res;
    PGconn *conn =
        PQconnectdb("dbname=postgres options=-csearch_path=");

    if (PQstatus(conn) != CONNECTION_OK)
    {
        /* результат PQerrorMessage включает завершающий символ перевода строки */
        fprintf(stderr, "%s", PQerrorMessage(conn));
        PQfinish(conn);
        return 1;
    }

    /* вызывается один раз для любого подключения, которое должно получать события.
     * Передает событие PGEVT_REGISTER процедуре myEventProc.
     */
    if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL))
    {
        fprintf(stderr, "Cannot register PGEventProc\n");
        PQfinish(conn);
        return 1;
    }

    /* доступен указатель на instanceData для подключения conn */
    data = PQinstanceData(conn, myEventProc);

    /* Передает событие PGEVT_RESULTCREATE процедуре myEventProc */
    res = PQexec(conn, "SELECT 1 + 1");

    /* доступен указатель на instanceData для результата */
    data = PQresultInstanceData(res, myEventProc);

    /* Если используется PG_COPYRES_EVENTS, передает событие PGEVT_RESULTCOPY процедуре myEventProc */
    res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS);

    /* указатель на instanceData для результата доступен, если при вызове
     * функции PQcopyResult использовался PG_COPYRES_EVENTS.
     */
    data = PQresultInstanceData(res_copy, myEventProc);

    /* Обе функции очистки передают событие PGEVT_RESULTDESTROY процедуре myEventProc */
    PQclear(res);
    PQclear(res_copy);

    /* Передает событие PGEVT_CONNDESTROY процедуре myEventProc */
    PQfinish(conn);

    return 0;
}

static int
myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
{
    switch (evtId)
    {
        case PGEVT_REGISTER:
        {
            PGEventRegister *e = (PGEventRegister *)evtInfo;
            mydata *data = get_mydata(e->conn);

            /* связать внутренние данные приложения с подключением */
            PQsetInstanceData(e->conn, myEventProc, data);
            break;
        }

        case PGEVT_CONNRESET:
        {
            PGEventConnReset *e = (PGEventConnReset *)evtInfo;
            mydata *data = PQinstanceData(e->conn, myEventProc);

            if (data)
              memset(data, 0, sizeof(mydata));
            break;
        }

        case PGEVT_CONNDESTROY:
        {
            PGEventConnDestroy *e = (PGEventConnDestroy *)evtInfo;
            mydata *data = PQinstanceData(e->conn, myEventProc);

            /* освободить данные экземпляра, так как подключение conn было уничтожено */
            if (data)
              free_mydata(data);
            break;
        }

        case PGEVT_RESULTCREATE:
        {
            PGEventResultCreate *e = (PGEventResultCreate *)evtInfo;
            mydata *conn_data = PQinstanceData(e->conn, myEventProc);
            mydata *res_data = dup_mydata(conn_data);

            /* связать внутренние данные приложения с результатом (скопировать его из подключения conn) */
            PQresultSetInstanceData(e->result, myEventProc, res_data);
            break;
        }

        case PGEVT_RESULTCOPY:
        {
            PGEventResultCopy *e = (PGEventResultCopy *)evtInfo;
            mydata *src_data = PQresultInstanceData(e->src, myEventProc);
            mydata *dest_data = dup_mydata(src_data);

            /* связать внутренние данные приложения с результатом (скопировать его из результата) */
            PQresultSetInstanceData(e->dest, myEventProc, dest_data);
            break;
        }

        case PGEVT_RESULTDESTROY:
        {
            PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo;
            mydata *data = PQresultInstanceData(e->result, myEventProc);

            /* освободить данные экземпляра, так как результат был уничтожен */
            if (data)
              free_mydata(data);
            break;
        }

        /* неизвестный идентификатор события, просто вернуть true. */
        default:
            break;
    }

    return true; /* обработка события успешно завершена */
}

Переменные среды

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

PGHOST ведет себя так же, как параметр подключения host.

PGHOSTADDR ведет себя так же, как параметр подключения hostaddr. Эту переменную можно установить вместо или в дополнение к PGHOST для предотвращения затрат на просмотр DNS.

PGPORT ведет себя так же, как параметр подключения port.

PGDATABASE ведет себя так же, как параметр подключения dbname.

PGUSER ведет себя так же, как параметр подключения user.

PGPASSWORD ведет себя так же, как параметр подключения password. Использовать эту переменную среды не рекомендуется по соображениям безопасности, поскольку некоторые операционные системы позволяют пользователям, отличным от root, видеть переменные среды процессов через ps; вместо этого попробуйте воспользоваться файлом паролей (см. раздел Файл паролей).

PGPASSFILE ведет себя так же, как параметр подключения passfile.

PGCHANNELBINDING ведет себя так же, как параметр подключения channel_binding.

PGSERVICE ведет себя так же, как параметр подключения service.

PGSERVICEFILE задает имя файла подключений служб для отдельного пользователя (см. раздел Файл подключений служб). По умолчанию используется имя ~/.qhb_service.conf.

PGOPTIONS ведет себя так же, как параметр подключения options.

PGAPPNAME ведет себя так же, как параметр подключения application_name.

PGSSLMODE ведет себя так же, как параметр подключения sslmode.

PGREQUIRESSL ведет себя так же, как параметр подключения requiressl. Эта переменная среды выводится из употребления, и ее заменяет переменная PGSSLMODE; установка обеих переменных подавляет эффект значения данной.

PGSSLCOMPRESSION ведет себя так же, как параметр подключения sslcompression.

PGSSLCERT ведет себя так же, как параметр подключения sslcert.

PGSSLKEY ведет себя так же, как параметр подключения sslkey.

PGSSLROOTCERT ведет себя так же, как параметр подключения sslrootcert.

PGSSLCRL ведет себя так же, как параметр подключения sslcrl.

PGSSLCRLDIR ведет себя так же, как параметр подключения sslcrldir.

PGSSLSNI ведет себя так же, как параметр подключения sslsni.

PGREQUIREPEER ведет себя так же, как параметр подключения requirepeer.

PGSSLMINPROTOCOLVERSION ведет себя так же, как параметр подключения ssl_min_protocol_version.

PGSSLMAXPROTOCOLVERSION ведет себя так же, как параметр подключения ssl_max_protocol_version.

PGGSSENCMODE ведет себя так же, как параметр подключения gssencmode.

PGKRBSRVNAME ведет себя так же, как параметр подключения krbsrvname.

PGCONNECT_TIMEOUT ведет себя так же, как параметр подключения connect_timeout.

PGCLIENTENCODING ведет себя так же, как параметр подключения client_encoding.

PGTARGETSESSIONATTRS ведет себя так же, как параметр подключения target_session_attrs.

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

PGDATESTYLE устанавливает стиль представления даты/времени по умолчанию. (Равнозначно SET datestyle TO ....)

PGTZ устанавливает часовой пояс по умолчанию. (Равнозначно SET timezone TO ....)

PGGEQO устанавливает режим по умолчанию для генетического оптимизатора запросов. (Равнозначно SET geqo TO ....)

Информацию о корректных значениях для этих переменных среды см. на справочной странице команды SET.

Следующие переменные среды определяют внутреннее поведение libpq; они переопределяют встроенные значения по умолчанию.

PGSYSCONFDIR устанавливает каталог, содержащий файл qhb_service.conf, а в будущих версиях, возможно, и другие общесистемные файлы конфигурации.

PGLOCALEDIR устанавливает каталог, содержащий файлы locale, предназначенные для локализации сообщений.

Файл паролей

Файл .pgpass в домашнем каталоге пользователя может содержать пароли, которые будут использоваться, если для подключения требуется пароль (и пароль не был указан иным способом). Как вариант, используемый файл паролей можно указать в параметре подключения passfile или в переменной среды PGPASSFILE.

Этот файл должен содержать строки следующего формата:

имя_хоста:порт:база_данных:имя_пользователя:пароль

(Можно добавить в этот файл комментарий-памятку, скопировав в него вышеуказанную строку и поставив перед ней символ #.) Первые четыре поля могут содержать литеральное значение или символ *, соответствующий всему. Использоваться будет поле пароля из первой строки, соответствующей текущим параметрам подключения. (Поэтому при использовании звездочек поместите более конкретные записи первыми.) Если запись должна содержать символ : или \, экранируйте его символом \. Поле имени хоста соответствует параметру подключения host (если он задан) или параметру hostaddr (если задан он); если ни один из них не задан, то ищется имя хоста localhost. Также имя хоста localhost ищется, когда подключение устанавливается через сокет домена Unix и параметр host соответствует пути к каталогу сокетов, установленному в libpq по умолчанию. На резервном сервере поле базы данных replication соответствует подключениям к основному серверу, созданным для потоковой репликации. В остальных случаях поле базы данных имеет ограниченную ценность, поскольку пользователи используют один пароль для всех баз данных в кластере.

В системах Unix разрешения для файла паролей должны запрещать любой доступ к нему всем или группе; этого можно добиться, например, с помощью команды chmod 0600 ~/.pgpass. Если разрешения будут менее строгими, этот файл будет игнорироваться.

Файл подключений служб

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

Имена служб можно определить либо в файле служб каждого пользователя, либо в общесистемном файле. Если одно и то же имя службы существует как в пользовательском, так и в системном файле, пользовательский файл имеет приоритет. По умолчанию файл служб пользователя называется ~/.qhb_service.conf. Установив переменную среды PGSERVICEFILE, можно задать другое имя файла. Общесистемный файл называется qhb_service.conf. По умолчанию он ищется в каталоге etc установки QHB (используйте pg_config --sysconfdir чтобы точно определить этот каталог). Установив переменную среды PGSYSCONFDIR, можно задать другой каталог, но не другое имя файла.

Во всех файлах служб используется формат «INI-файлов», где имя раздела является именем службы, а параметры — параметрами подключения; их список см. в подразделе Ключевые слова-параметры. Например:

# comment
[mydb]
host=somehost
port=5433
user=admin

Параметры подключения, полученные из файла служб, объединяются с параметрами, полученными из других источников. Значение в файле служб переопределяет соответствующую переменную среды и, в свою очередь, может быть переопределено значением, заданным непосредственно в строке подключения. Например, используя вышеуказанный файл служб, строка подключения service=mydb port=5434 будет использовать хост somehost, порт 5434, пользователя admin и другие параметры, установленные переменными среды или встроенными значениями по умолчанию.

Поиск параметров подключения через LDAP

Если libpq была скомпилирована с поддержкой LDAP (параметр --with-ldap для configure), такие параметры подключения, как host или dbname, можно получить через LDAP из центрального сервера. Преимущество этого состоит в том, что при изменении параметров подключения к базе данных нет необходимости обновлять информацию о подключении на всех клиентских компьютерах.

При поиске параметров подключения через LDAP используется файл подключений служб qhb_service.conf (см. раздел Файл подключений служб). Строка в файле qhb_service.conf, начинающаяся с ldap://, будет распознаваться как URL в LDAP, и выполнится запрос LDAP. Результатом должен быть список пар keyword = value, которые будут использоваться для установки параметров подключения. Эта URL должна соответствовать RFC 1959 и иметь форму

ldap://[имя_хоста[:порт]]/база_поиска?атрибут?область_поиска?фильтр

где по умолчанию имя_хоста — localhost, а порт — 389.

Обработка qhb_service.conf прекращается после успешного поиска в LDAP, но продолжается, если не удается связаться с сервером LDAP. Это сделано для того, чтобы предоставить запасные варианты с дополнительными строками с URL LDAP, указывающими на другие серверы LDAP, классические пары keyword = value или параметры подключения по умолчанию. Если вы предпочитаете в этом случае получить сообщение об ошибке, добавьте после строки с URL LDAP синтаксически некорректную строку.

Простую запись LDAP, которая была создана из этого файла LDIF

version:1
dn:cn=mydatabase,dc=mycompany,dc=com
changetype:add
objectclass:top
objectclass:device
cn:mydatabase
description:host=dbserver.mycompany.com
description:port=5439
description:dbname=mydb
description:user=mydb_user
description:sslmode=require

можно запросить с помощью следующей URL LDAP:

ldap://ldap.mycompany.com/dc=mycompany,dc=com?description?one?(cn=mydatabase)

Также можно сочетать обычные записи в файле служб с поиском в LDAP. Полный пример строки в файле qhb_service.conf может быть следующим:

# в LDAP хранится только имя и порт хоста; имя базы данных и пользователя задайте явно
[customerdb]
dbname=customer
user=appuser
ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)

Поддержка SSL

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

libpq читает общесистемный файл конфигурации OpenSSL. По умолчанию этот файл называется openssl.cnf и располагается в каталоге, выводимом командой openssl version -d. Эти стандартные значения можно переопределить, установив в переменной среды OPENSSL_CONF имя желаемого файла конфигурации.

Проверка сертификатов сервера на стороне клиента

По умолчанию QHB не будет выполнять никакую проверку сертификата сервера. Это означает, что можно без ведома клиента имитировать подлинность сервера (например, модифицировав запись в DNS или заняв его IP-адрес). Чтобы предотвратить подмену, клиент должен иметь возможность проверить подлинность сервера посредством цепочки доверия. Цепочка доверия устанавливается путем размещения корневого (самоподписанного) сертификата центра сертификации (CA) на одном компьютере и конечного сертификата, подписанного корневым, на другом. Также можно использовать «промежуточный» сертификат, который подписывается корневым сертификатом и подписывает конечные сертификаты.

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

После установления цепочки доверия клиент может проверить переданный сервером конечный сертификат двумя способами. Если в параметре sslmode установлено значение verify-ca, libpq будет подтверждать достоверность сервера, проверяя цепочку сертификатов до корневого сертификата, хранящегося на клиенте. Если в sslmode установлено значение verify-full, libpq будет также проверять соответствие имени хоста сервера имени, сохраненному в сертификате сервера. SSL-подключение не будет установлено, если сертификат сервера нельзя проверить. Для большинства сред, требующих повышенной безопасности данных, рекомендуется устанавливать режим verify-full.

В режиме verify-full имя хоста сопоставляется с атрибутом(ами) Subject Alternative Name (Альтернативное имя субъекта, SAN) в сертификате или с атрибутом Common Name (Общее имя), если отсутствует SAN типа dNSName. Если атрибут имени сертификата начинается со звездочки (*), она будет восприниматься как подстановочный знак, которому будут соответствовать все символы, кроме точки (.). Это означает, что сертификат не будет соответствовать поддоменам. Если подключение устанавливается по IP-адресу, а не по имени хоста, этот IP-адрес будет сопоставляться (без поиска в DNS) с SAN типа iPAddress или dNSName. Если SAN типа iPAddress и соответствующее SAN типа dNSName отсутствуют, IP-адрес хоста сопоставляется с атрибутом Common Name.

Примечание
Для обратной совместимости с предыдущими версиями QHB IP-адрес хоста проверяется не совсем так, как того требует RFC 6125. IP-адрес хоста всегда сопоставляется с SAN типа dNSName, а также с SAN типа iPAddress и может сопоставляться с атрибутом Common Name, если подходящей записи SAN не существует.

Чтобы разрешить проверку сертификата сервера, нужно поместить один или несколько корневых сертификатов в файл ~/.qhb/root.crt в домашнем каталоге пользователя. Также следует добавить в этот файл промежуточные сертификаты, если они необходимы для связывания цепочки сертификатов, переданных сервером, с корневыми сертификатами, хранящимися на клиенте.

Если файл ~/.qhb/root.crl существует, также проверяются записи в списке отозванных сертификатов (Certificate Revocation List, CRL).

Расположение файла корневых сертификатов и CRL можно изменить, установив параметры подключения sslrootcert и sslcrl или переменные среды PGSSLROOTCERT и PGSSLCRL. Кроме того, чтобы задать каталог, содержащий файлы CRL, можно воспользоваться параметром sslcrldir или переменной среды PGSSLCRLDIR.

Примечание
Для обратной совместимости с предыдущими версиями QHB, если файл с сертификатами корневых CA существует, поведение режима sslmode=require не будет отличаться от режима verify-ca, то есть сертификат сервера проверяется по сертификату CA. Полагаться на это поведение не рекомендуется, и приложения, которым необходима проверка сертификата, должны всегда применять режим verify-ca или verify-full.

Сертификаты клиента

Если сервер пытается проверить подлинность клиента, запрашивая его конечный сертификат, libpq передаст сертификаты, сохраненные в файле ~/.qhb/qhb.crt в домашнем каталоге пользователя. Эти сертификаты должны связываться по цепочке с корневым сертификатом, которому доверяет сервер. Также должен присутствовать соответствующий файл закрытого ключа ~/.qhb/qhb.key. Расположение файлов сертификатов и ключа можно переопределить с помощью параметров подключения sslcert и sslkey или переменных среды PGSSLCERT и PGSSLKEY.

В системах Unix разрешения для файла закрытого ключа должны запрещать любой доступ к нему всех или группе; этого можно добиться, например, с помощью команды chmod 0600 ~/.qhb/qhb.key. Как вариант, этим файлом может владеть root, а группа имеет доступ на чтение (то есть маска разрешений 0640). Такая схема предназначена для установок, где файлами сертификатов и ключа управляет сама операционная система. При этом пользователь libpq должен быть членом группы, имеющей доступ к этим файлам сертификатов и ключа.

Первым сертификатом в qhb.crt должен быть сертификат клиента, поскольку он должен соответствовать закрытому ключу клиента. При необходимости в этот файл могут быть добавлены «промежуточные» сертификаты — это позволяет избежать необходимости в хранении промежуточных сертификатов на сервере (ssl_ca_file).

Сертификат и ключ могут задаваться в формате PEM или ASN.1 DER.

Ключ можно хранить в открытом виде или зашифровать паролем, используя любой алгоритм, поддерживаемый OpenSSL, например AES-128. Если ключ хранится зашифрованным, то пароль можно задать в параметре подключения sslpassword. Если предоставляется зашифрованный ключ, а параметр sslpassword отсутствует или имеет пустое значение, пароль будет запрашиваться OpenSSL интерактивно в строке приглашения Enter PEM pass phrase: (введите пароль для PEM:), если имеется TTY. Приложения могут переопределить приглашение на ввод пароля для сертификата клиента и обработку параметра sslpassword, предоставив собственную функцию обратного вызова для пароля ключа; см. PQsetSSLKeyPassHook_OpenSSL.

Инструкции по созданию сертификатов см. в подразделе Создание сертификатов.

Защита, обеспечиваемая в различных режимах

Разные значения параметра sslmode предоставляют разные уровни защиты. SSL может обеспечить защиту от трех типов атак:

Подслушивание

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

Посредник (MITM)

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

Подмена

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

Чтобы подключение точно было защищено SSL, применение SSL должно быть сконфигурировано и на клиенте, и на сервере до установки подключения. Если оно сконфигурировано только на сервере, клиент может начать передавать конфиденциальную информацию (например, пароли) раньше, чем поймет, что сервер требует высокого уровня безопасности. В libpq безопасные подключения можно обеспечить, установив в параметре sslmode значение verify-full или verify-ca и предоставив системе корневой сертификат для сопоставления при проверке. Это аналогично использованию URL с https для шифрования при просмотре сайтов.

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

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

Таблица 1. Описания режимов SSL

sslmode | Защита от подслушивания | Защита от MITM | Утверждение
disable | Нет | Нет | Мне не важна безопасность, и я не желаю иметь издержки, связанные с шифрованием.
allow | Возможно | Нет | Мне не важна безопасность, но я готов иметь издержки, связанные с шифрованием, если на этом настаивает сервер.
prefer | Возможно | Нет | Мне не важно шифрование, но я согласен иметь издержки, связанные с шифрованием, если это поддерживает сервер.
require | Да | Нет | Я хочу, чтобы мои данные шифровались, и я приемлю эти издержки. Я доверяю сети в том, что она всегда обеспечит подключение к нужному мне серверу.
verify-ca | Да | Зависит от политики CA | Я хочу, чтобы мои данные шифровались, и я приемлю эти издержки. Я хочу быть уверенным, что подключаюсь к доверенному серверу.
verify-full | Да | Да | Я хочу, чтобы мои данные шифровались, и я приемлю эти издержки. Я хочу быть уверенным, что подключаюсь к доверенному серверу и что это тот сервер, который я указал.

Различие между verify-ca и verify-full зависит от политики корневого CA. Если используется публичный CA, verify-ca допускает подключения к серверу, который в этом CA может зарегистрировать кто угодно. В таком случае нужно всегда использовать режим verify-full. Если используется локальный CA или даже самоподписанный сертификат, режим verify-ca зачастую обеспечивает достаточную защиту.

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

Файлы, используемые клиентом SSL

В Таблице 2 приведены файлы, имеющие отношение к настройке SSL на стороне клиента.

Таблица 2. Файлы, используемые клиентом SSL/libpq

Файл | Содержимое | Действие
~/.qhb/qhb.crt | сертификат клиента | передается серверу
~/.qhb/qhb.key | закрытый ключ клиента | подтверждает сертификат клиента, передаваемый серверу; не говорит от том, что владельцу сертификата можно доверять
~/.qhb/root.crt | сертификаты доверенных CA | проверяет, что сертификат сервера подписан доверенным CA
~/.qhb/root.crl | сертификаты, отозванные CA | сертификат сервера не должен присутствовать в этом списке

Инициализация библиотеки SSL

Если ваше приложение инициализирует библиотеки libssl и/или libcrypto и libpq собрана с поддержкой SSL, вам следует вызвать функцию PQinitOpenSSL, чтобы сообщить libpq, что библиотеки libssl и/или libcrypto были инициализированы вашим приложением, чтобы libpq не пыталась повторно инициализировать их. Однако в этом нет необходимости при использовании OpenSSL версии 1.1.0 или выше, поскольку в ней проблема повторных инициализаций уже решена.

PQinitOpenSSL

Позволяет приложениям выбрать, какие библиотеки безопасности инициализировать.

void PQinitOpenSSL(int do_ssl, int do_crypto);

Когда параметр do_ssl имеет ненулевое значение, libpq будет инициализировать библиотеку OpenSSL перед первым подключением к базе данных. Когда параметр do_crypto имеет ненулевое значение, будет инициализирована библиотека libcrypto. По умолчанию (если функция PQinitOpenSSL не вызывается), инициализируются обе библиотеки. Если поддержка SSL не была скомпилирована, эта функция присутствует, но ничего не делает.

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

PQinitSSL

Позволяет приложениям выбрать, какие библиотеки безопасности инициализировать.

void PQinitSSL(int do_ssl);

Эта функция равнозначна вызову PQinitOpenSSL(do_ssl, do_ssl). Этого достаточно для приложений, которые инициализируют сразу обе библиотеки OpenSSL и libcrypto или ни одну из них.

Поведение в многопоточных программах

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

PQisthreadsafe

Возвращает статус потокобезопасности библиотеки libpq.

int PQisthreadsafe();

Возвращает 1, если libpq потокобезопасна, и 0 в противном случае.

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

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

Устаревшие функции PQrequestCancel и PQoidStatus не ориентированы на многопоточное выполнение и не должны использоваться в многопоточных программах. PQrequestCancel можно заменить функцией PQcancel, а PQoidStatus — функцией PQoidValue.

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

Сборка программ с libpq

Чтобы собрать (т. е. скомпилировать и скомпоновать) программу, использующую libpq, нужно сделать следующее:

Включите заголовочный файл libpq-fe.h:

#include <libpq-fe.h>

Если вам это не удастся, то, скорее всего, вы получите от компилятора примерно такие сообщения об ошибках:

foo.c: In function `main':
foo.c:34: `PGconn' undeclared (first use in this function)
foo.c:35: `PGresult' undeclared (first use in this function)
foo.c:54: `CONNECTION_BAD' undeclared (first use in this function)
foo.c:68: `PGRES_COMMAND_OK' undeclared (first use in this function)
foo.c:95: `PGRES_TUPLES_OK' undeclared (first use in this function)

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

cc -c -I/var/lib/qhb/data/include testprog.c

Если вы используете сборочные файлы, добавьте этот параметр в переменную CPPFLAGS:

CPPFLAGS += -I/var/lib/qhb/data/include

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

$ pg_config --includedir
/var/lib/qhb/data/include

Если у вас установлена программа pkg-config, вместо этого вы можете выполнить:

$ pkg-config --cflags libpq
-I/var/lib/qhb/data/include

Обратите внимание, что при этом перед путем уже будет добавлен параметр -I.

Если компилятору не будет задан корректный параметр, то вы получите примерно такое сообщение:

testlibpq.c:8:22: libpq-fe.h: No such file or directory
/* Такой файл или каталог не существует */

При компоновке окончательной программы задайте параметр -lpq, чтобы подключить библиотеку libpq, а также параметр -Lкаталог, чтобы указать компилятору каталог, в котором находится библиотека libpq. (Опять же, компилятор будет просматривать некоторые каталоги по умолчанию.) Для максимальной переносимости укажите параметр -L перед параметром -lpq. Например:

cc -o testprog testprog1.o testprog2.o -L/var/lib/qhb/data/lib -lpq

Также каталог библиотеки можно найти с помощью pg_config:

$ pg_config --libdir
/var/lib/qhb/data/lib

Или с помощью pkg-config:

$ pkg-config --libs libpq
-L/var/lib/qhb/data/lib -lpq

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

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

testlibpq.o: In function `main':
testlibpq.o(.text+0x60): undefined reference to `PQsetdbLogin'
testlibpq.o(.text+0x71): undefined reference to `PQstatus'
testlibpq.o(.text+0xa4): undefined reference to `PQerrorMessage'

Это означает, что вы забыли указать параметр -lpq.

/usr/bin/ld: cannot find -lpq

Это означает, что вы забыли о параметре -L или не указали правильный каталог.

Примеры программ

Пример 1. Первый образец программы, использующей libpq

/* Проверка C-версии libpq, клиентской библиотеки QHB. */

#include <stdio.h>
#include <stdlib.h>
#include "libpq-fe.h"

static void
exit_nicely(PGconn *conn)
{
    PQfinish(conn);
    exit(1);
}

int
main(int argc, char **argv)
{
    const char *conninfo;
    PGconn     *conn;
    PGresult   *res;
    int         nFields;
    int         i,
                j;

    /*
     * Если пользователь передает параметр в командной строке, использовать ее как
     * строку информации о подключении (conninfo); в противном случае по умолчанию
     * установить dbname=qhb и использовать для всех остальных параметров подключения
     * переменные среды или значения по умолчанию.
     */
    if (argc > 1)
        conninfo = argv[1];
    else
        conninfo = "dbname = qhb";

    /* Установить соединение с базой данных */
    conn = PQconnectdb(conninfo);

    /* Проверить, что соединение с сервером было успешно установлено */
    if (PQstatus(conn) != CONNECTION_OK)
    {
        fprintf(stderr, "%s", PQerrorMessage(conn));
        exit_nicely(conn);
    }

    /*
     * Установить постоянно защищенный путь поиска, чтобы неблагонамеренные
     * пользователи не могли перехватить управление.
     */
    res = PQexec(conn,
                 "SELECT pg_catalog.set_config('search_path', '', false)");
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }

    /*
     * Когда объект PGresult больше не нужен, функция PQclear обязательно должна
     * его очистить во избежание утечек памяти
     */
    PQclear(res);

    /*
     * В нашем тестовом случае используется курсор, и для этого нам нужно находиться
     * внутри блока транзакции. Мы могли бы обработать его целиком с помощью одной
     * функции PQexec() в команде «select * from pg_database», но это было бы
     * слишком просто для хорошего примера.
     */

    /* Начать блок транзакции */
    res = PQexec(conn, "BEGIN");
    if (PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        fprintf(stderr, "BEGIN command failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }
    PQclear(res);

    /*
     * Извлечь строки из pg_database, системного каталога баз данных
     */
    res = PQexec(conn, "DECLARE myportal CURSOR FOR select * from pg_database");
    if (PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        fprintf(stderr, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }
    PQclear(res);

    res = PQexec(conn, "FETCH ALL in myportal");
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        fprintf(stderr, "FETCH ALL failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }

    /* сначала вывести имена атрибутов */
    nFields = PQnfields(res);
    for (i = 0; i < nFields; i++)
        printf("%-15s", PQfname(res, i));
    printf("\n\n");

    /* затем вывести строки */
    for (i = 0; i < PQntuples(res); i++)
    {
        for (j = 0; j < nFields; j++)
            printf("%-15s", PQgetvalue(res, i, j));
        printf("\n");
    }

    PQclear(res);

    /* закрыть портал ... мы не утруждаем себя проверкой на ошибки ... */
    res = PQexec(conn, "CLOSE myportal");
    PQclear(res);

    /* завершить транзакцию */
    res = PQexec(conn, "END");
    PQclear(res);

    /* закрыть соединение с базой данных и провести очистку */
    PQfinish(conn);

    return 0;
}

Пример 2. Второй образец программы, использующей libpq

/*
 *      Проверка интерфейса асинхронного уведомления
 *
 * Запустить эту программу, затем из psql в отдельном окне выполнить
 *   NOTIFY TBL2;
 * Повторить четыре раза, чтобы эта программа завершилась.
 *
 * Или, если вы хотите проявить изобретательность, попробуйте это:
 * заполнить базу данных с помощью следующих команд:
 *
 *   CREATE SCHEMA TESTLIBPQ2;
 *   SET search_path = TESTLIBPQ2;
 *   CREATE TABLE TBL1 (i int4);
 *   CREATE TABLE TBL2 (i int4);
 *   CREATE RULE r1 AS ON INSERT TO TBL1 DO
 *     (INSERT INTO TBL2 VALUES (new.i); NOTIFY TBL2);
 *
 * Запустить эту программу, затем из psql четыре раза выполнить следующее:
 *
 *   INSERT INTO TESTLIBPQ2.TBL1 VALUES (10);
 */

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include "libpq-fe.h"

static void
exit_nicely(PGconn *conn)
{
    PQfinish(conn);
    exit(1);
}

int
main(int argc, char **argv)
{
    const char *conninfo;
    PGconn     *conn;
    PGresult   *res;
    PGnotify   *notify;
    int         nnotifies;

    /*
     * Если пользователь передает параметр в командной строке, использовать ее как
     * строку информации о подключении (conninfo); в противном случае по умолчанию
     * установить dbname=qhb и использовать для всех остальных параметров подключения
     * переменные среды или значения по умолчанию.
     */
    if (argc > 1)
        conninfo = argv[1];
    else
        conninfo = "dbname = qhb";

    /* Установить соединение с базой данных */
    conn = PQconnectdb(conninfo);

    /* Проверить, что соединение с сервером было успешно установлено */
    if (PQstatus(conn) != CONNECTION_OK)
    {
        fprintf(stderr, "%s", PQerrorMessage(conn));
        exit_nicely(conn);
    }

    /*
     * Установить постоянно защищенный путь поиска, чтобы неблагонамеренные
     * пользователи не могли перехватить управление.
     */
    res = PQexec(conn,
                 "SELECT pg_catalog.set_config('search_path', '', false)");
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }

    /*
     * Когда объект PGresult больше не нужен, функция PQclear обязательно должна
     * его очистить во избежание утечек памяти
     */
    PQclear(res);

    /*
     * Выполнить команду LISTEN, чтобы включить уведомления из NOTIFY правила.
     */
    res = PQexec(conn, "LISTEN TBL2");
    if (PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        fprintf(stderr, "LISTEN command failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }
    PQclear(res);

    /* Выйти после получения четырех уведомлений. */
    nnotifies = 0;
    while (nnotifies < 4)
    {
        /*
         * Находиться в режиме ожидания, пока в соединении что-нибудь не произойдет.
         * Мы используем для ожидания ввода select(2), но также можно использовать
         * poll() или подобные ей средства.
         */
        int         sock;
        fd_set      input_mask;

        sock = PQsocket(conn);

        if (sock < 0)
            break;              /* не должно происходить */

        FD_ZERO(&input_mask);
        FD_SET(sock, &input_mask);

        if (select(sock + 1, &input_mask, NULL, NULL, NULL) < 0)
        {
            fprintf(stderr, "select() failed: %s\n", strerror(errno));
            exit_nicely(conn);
        }

        /* Теперь проверить, есть ли входные данные */
        PQconsumeInput(conn);
        while ((notify = PQnotifies(conn)) != NULL)
        {
            fprintf(stderr,
                    "ASYNC NOTIFY of '%s' received from backend PID %d\n",
                    notify->relname, notify->be_pid);
            PQfreemem(notify);
            nnotifies++;
            PQconsumeInput(conn);
        }
    }

    fprintf(stderr, "Done.\n");

    /* закрыть соединение с базой данных и провести очистку */
    PQfinish(conn);

    return 0;
}

Пример 3. Третий образец программы, использующей libpq

/*
 *      Проверка вынесенных параметров и двоичного ввода/вывода.
 *
 * Прежде чем выполнить это, заполните базу данных с помощью следующих команд:
 *
 * CREATE SCHEMA testlibpq3;
 * SET search_path = testlibpq3;
 * SET standard_conforming_strings = ON;
 * CREATE TABLE test1 (i int4, t text, b bytea);
 * INSERT INTO test1 values (1, 'joe''s place', '\000\001\002\003\004');
 * INSERT INTO test1 values (2, 'ho there', '\004\003\002\001\000');
 *
 * Ожидаемый вывод:
 *
 * tuple 0: got
 *  i = (4 bytes) 1
 *  t = (11 bytes) 'joe's place'
 *  b = (5 bytes) \000\001\002\003\004
 *
 * tuple 0: got
 *  i = (4 bytes) 2
 *  t = (8 bytes) 'ho there'
 *  b = (5 bytes) \004\003\002\001\000
 */

#ifdef WIN32
#include <windows.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "libpq-fe.h"

/* для ntohl/htonl */
#include <netinet/in.h>
#include <arpa/inet.h>

static void
exit_nicely(PGconn *conn)
{
    PQfinish(conn);
    exit(1);
}

/*
 * Эта функция выводит результат запроса в двоичном формате, извлеченный из
 * таблицы, определенной как описано в комментарии выше. Мы разделяем его, потому
 * что функция main() использует ее дважды.
 */
static void
show_binary_results(PGresult *res)
{
    int         i,
                j;
    int         i_fnum,
                t_fnum,
                b_fnum;

    /* Использовать PQfnumber во избежание предположений о порядке полей в результате */
    i_fnum = PQfnumber(res, "i");
    t_fnum = PQfnumber(res, "t");
    b_fnum = PQfnumber(res, "b");

    for (i = 0; i < PQntuples(res); i++)
    {
        char       *iptr;
        char       *tptr;
        char       *bptr;
        int         blen;
        int         ival;

        /* Получить поле значений (мы игнорируем возможность, что они равны NULL!) */
        iptr = PQgetvalue(res, i, i_fnum);
        tptr = PQgetvalue(res, i, t_fnum);
        bptr = PQgetvalue(res, i, b_fnum);

        /*
         * Двоичное представление INT4 находится в сетевом порядке байтов, который
         * лучше привести к локальному порядку байтов.
         */
        ival = ntohl(*((uint32_t *) iptr));

        /*
         * Двоичное представление TEXT является, собственно, текстом, и поскольку
         * libpq любезно добавила к нему нулевой байт, он прекрасно сработает
         * как строка на C.
         *
         * Двоичное представление BYTEA является кучкой байтов, которые могут включать
         * в себя значения NULL, поэтому нам придется обращать внимание на длину поля.
         */
        blen = PQgetlength(res, i, b_fnum);

        printf("tuple %d: got\n", i);
        printf(" i = (%d bytes) %d\n",
               PQgetlength(res, i, i_fnum), ival);
        printf(" t = (%d bytes) '%s'\n",
               PQgetlength(res, i, t_fnum), tptr);
        printf(" b = (%d bytes) ", blen);
        for (j = 0; j < blen; j++)
            printf("\\%03o", bptr[j]);
        printf("\n\n");
    }
}

int
main(int argc, char **argv)
{
    const char *conninfo;
    PGconn     *conn;
    PGresult   *res;
    const char *paramValues[1];
    int         paramLengths[1];
    int         paramFormats[1];
    uint32_t    binaryIntVal;

    /*
     * Если пользователь передает параметр в командной строке, использовать ее как
     * строку информации о подключении (conninfo); в противном случае по умолчанию
     * установить dbname=qhb и использовать для всех остальных параметров подключения
     * переменные среды или значения по умолчанию
     */
    if (argc > 1)
        conninfo = argv[1];
    else
        conninfo = "dbname = qhb";

    /* Установить соединение с базой данных */
    conn = PQconnectdb(conninfo);

    /* Проверить, что соединение с сервером было успешно установлено */
    if (PQstatus(conn) != CONNECTION_OK)
    {
        fprintf(stderr, "%s", PQerrorMessage(conn));
        exit_nicely(conn);
    }

    /*
     * Установить постоянно защищенный путь поиска, чтобы неблагонамеренные
     * пользователи не могли перехватить управление.
     */
    res = PQexec(conn, "SET search_path = testlibpq3");
    if (PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }
    PQclear(res);

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

    /* Вот наше значение внешнего параметра */
    paramValues[0] = "joe's place";

    res = PQexecParams(conn,
                       "SELECT * FROM test1 WHERE t = $1",
                       1,       /* один параметр */
                       NULL,    /* позволить серверу определить тип параметра */
                       paramValues,
                       NULL,    /* длина параметра не нужна, поскольку это текст */
                       NULL,    /* применить по умолчанию ко все текстовым параметрам */
                       1);      /* попросить двоичные результаты */

    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }

    show_binary_results(res);

    PQclear(res);

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

    /* Преобразовать целочисленное значение «2» в сетевой порядок байтов */
    binaryIntVal = htonl((uint32_t) 2);

    /* Установить значения массивов параметров для PQexecParams */
    paramValues[0] = (char *) &binaryIntVal;
    paramLengths[0] = sizeof(binaryIntVal);
    paramFormats[0] = 1;        /* двоичный */

    res = PQexecParams(conn,
                       "SELECT * FROM test1 WHERE i = $1::int4",
                       1,       /* один параметр */
                       NULL,    /* позволить серверу определить тип параметра */
                       paramValues,
                       paramLengths,
                       paramFormats,
                       1);      /* попросить двоичные результаты */

    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }

    show_binary_results(res);

    PQclear(res);

    /* закрыть соединение с базой данных и провести очистку */
    PQfinish(conn);

    return 0;
}

Большие объекты

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

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

Введение

Все большие объекты хранятся в одной системной таблице с именем pg_largeobject. Также для каждого большого объекта имеется запись в системной таблице pg_largeobject_metadata. Большие объекты можно создавать, изменять и удалять с помощью API чтения/записи, аналогичного стандартным операциям с файлами.

Кроме того, QHB поддерживает систему хранения под названием «TOAST», которая автоматически сохраняет значения в таблице, занимающие более одной страницы базы данных, во вторичную область хранения. Из-за этого механизм для работы с большими объектами оказывается отчасти устаревшим. Одно из оставшихся преимуществ больших объектов заключается в том, что они позволяют использовать значения размером до 4 ТБ, тогда как поля в TOAST могут иметь объем не более 1 ГБ. Кроме того, чтение и изменение большого объекта можно успешно выполнять по частям, тогда как при большинстве операций с полем в TOAST значение будет считываться или записываться как единое целое.

Особенности реализации

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

Фрагменты сохраненного большого объекта необязательно должны быть смежными. Например, если приложение открывает новый большой объект, перемещается к смещению 1000000 байт и записывает там несколько байтов, это не приводит к выделению дополнительных 1000000 байт в хранилище; там будут размещены только фрагменты, охватывающие диапазон фактически записанных байтов данных. Однако операция чтения будет считывать нули для любых нераспределенных участков, предшествующих последнему существующему фрагменту. Это соответствует обычному поведению «разреженных» файлов в файловых системах Unix.

У больших объектов имеется владелец и набор прав доступа, которыми можно управлять с помощью команд GRANT и REVOKE. Для чтения большого объекта требуются права SELECT, а для записи или опустошения — права UPDATE. Удалять большой объект, добавлять для него комментарий или менять его владельца может только его владелец (или суперпользователь базы данных). Это поведение можно скорректировать, изменив параметр времени выполнения lo_compat_privileges.

Клиентские интерфейсы

В этом разделе описываются средства, которые библиотека клиентского интерфейса QHB libpq предоставляет для доступа к большим объектам. Интерфейс больших объектов QHB похож на интерфейс файловой системы Unix, с аналогами функций open, read, write, lseek и т. д.

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

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

Клиентские приложения, использующие эти функции, должны включать файл заголовка libpq/libpq-fs.h и иметь возможность соединяться с библиотекой libpq.

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

Создание большого объекта

Функция

Oid lo_create(PGconn *conn, Oid Id_БО);

создает новый большой объект. Назначаемый OID можно задать с помощью аргумента Id_БО; если этот OID уже используется для некоторого большого объекта, возникает ошибка. Если Id_БО имеет значение InvalidOid (ноль), то lo_create назначает неиспользуемый OID. Возвращаемое значение — это OID, присвоенный новому большому объекту, или InvalidOid (ноль) при ошибке.

Пример:

inv_oid = lo_create(conn, желаемый_oid);

Импорт большого объекта

Чтобы импортировать в качестве большого объекта файл операционной системы, вызовите:

Oid lo_import(PGconn *conn, const char *имя_файла);

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

Функция

Oid lo_import_with_oid(PGconn *conn, const char *имя_файла, Oid Id_БО);

также импортирует новый большой объект. Назначаемый OID можно задать с помощью аргумента Id_БО; если этот OID уже используется для некоторого большого объекта, то возникает ошибка. Если Id_БО имеет значение InvalidOid (ноль), то lo_import_with_oid назначает неиспользуемый OID (это поведение аналогично lo_import). Возвращаемое значение — это OID, присвоенный новому большому объекту, или InvalidOid (ноль) при ошибке.

Экспорт большого объекта

Чтобы экспортировать большой объект в файл операционной системы, вызовите

int lo_export(PGconn *conn, Oid Id_БО, const char *имя_файла);

В аргументе Id_БО указывается OID большого объекта для экспорта, а в аргументе имя_файла — имя файла в операционной системе. Обратите внимание, что файл записывается библиотекой клиентского интерфейса, а не сервером. Возвращает 1 при успешном выполнении, -1 при ошибке.

Открытие существующего большого объекта

Чтобы открыть существующий большой объект для чтения или записи, вызовите

int lo_open(PGconn *conn, Oid Id_БО, int режим);

В аргументе Id_БО задается OID открываемого большого объекта. Биты в аргументе режим определяют, будет ли объект открыт для чтения (INV_READ), записи (INV_WRITE) или того и другого. (Эти символьные константы определены в заголовочном файле libpq/libpq-fs.х.) lo_open возвращает дескриптор большого объекта (неотрицательный) для последующего использования в lo_read, lo_write, lo_lseek, lo_lseek64, lo_tell, lo_tell64, lo_truncate, lo_truncate64 и lo_close. Дескриптор действителен только во время выполнения текущей транзакции. При ошибке возвращается значение -1.

В настоящее время сервер не различает режимы INV_WRITE и INV_READ | INV_WRITE: читать данные из дескриптора разрешено в любом случае. Однако между этими режимами и режимом INV_READ есть существенная разница: в режиме INV_READ записывать данные в дескриптор нельзя, а считанные из него данные будут отображать содержимое большого объекта во время снимка состояния транзакции, который был активен при выполнении lo_open, независимо от последующих записей, сделанных этой или другими транзакциями. Чтение из дескриптора, открытого с помощью INV_WRITE, возвращает данные, отражающие все операции записи других зафиксированных транзакций, а также операции записи текущей транзакции. Это похоже на отличия режимов REPEATABLE READ и READ COMMITTED для обычных команд SQL SELECT.

Функция lo_open завершится ошибкой, если у пользователя нет права SELECT для этого большого объекта или если указан режим INV_WRITE и отсутствует право UPDATE. Эти проверки наличия прав можно выключить с помощью параметра времени выполнения lo_compat_privileges.

Пример:

inv_fd = lo_open(conn, inv_oid, INV_READ|INV_WRITE);

Запись данных в большой объект

Функция

int lo_write(PGconn *conn, int fd, const char *буфер, size_t длина);

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

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

Чтение данных из большого объекта

Функция

int lo_read(PGconn *conn, int fd, char *буфер, size_t длина);

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

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

Перемещение в большом объекте

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

int lo_lseek(PGconn *conn, int fd, int смещение, int whence);

Эта функция перемещает указатель текущего местоположения для дескриптора большого объекта, определенного с помощью fd, к новому местоположению, заданному аргументом смещение. Допустимыми значениями для аргумента whence являются SEEK_SET (перемещение от начала объекта), SEEK_CUR (перемещение с текущей позиции) и SEEK_END (перемещение от конца объекта). Возвращаемое значение — это указатель нового местоположения или -1 при ошибке.

При работе с большими объектами, размер которых может превышать 2 ГБ, вместо этого используйте

pg_int64 lo_lseek64(PGconn *conn, int fd, pg_int64 смещение, int whence);

Эта функция ведет себя так же, как и lo_lseek, но может принять значение смещение, превышающее 2 ГБ и/или вернуть результат, превышающий 2 ГБ. Обратите внимание, что если указатель нового местоположения больше 2 ГБ, то lo_lseek выдаст ошибку.

Получение текущего положения в большом объекте

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

int lo_tell(PGconn *conn, int fd);

Если возникает ошибка, возвращаемое значение равно -1.

При работе с большими объектами, размер которых может превышать 2 ГБ, вместо этого используйте

pg_int64 lo_tell64(PGconn *conn, int fd);

Эта функция ведет себя так же, как и lo_tell, но может вернуть результат, превышающий 2 ГБ. Обратите внимание, что если текущее местоположение чтения/записи больше 2 ГБ, то lo_tell выдаст ошибку.

Усечение большого объекта

Чтобы усечь большой объект до заданной длины, вызовите

int lo_truncate(PGcon *conn, int fd, size_t длина);

Эта функция усекает дескриптор большого объекта fd до заданной длины. В аргументе fd должно стоять значение, возвращенное предыдущим вызовом lo_open. Если длина превышает текущую длину большого объекта, тот расширяется до заданной длины нулевыми байтами ('\0'). В случае успеха lo_truncate возвращает ноль. При ошибке возвращается значение -1.

Местоположение чтения/записи, связанное с дескриптором fd, не изменяется.

Хотя параметр длина объявлен как size_t, lo_truncate будет отклонять значения длины, превышающие INT_MAX.

При работе с большими объектами, размер которых может превышать 2 ГБ, вместо этого используйте

int lo_truncate64(PGcon *conn, int fd, pg_int64 длина);

Эта функция ведет себя так же, как и lo_truncate, но может принимать длину, превышающую 2 ГБ.

Закрытие дескриптора большого объекта

Дескриптор большого объекта можно закрыть, вызвав

int lo_close(PGconn *conn, int fd);

где fd — это дескриптор большого объекта, возвращенный функцией lo_open. В случае успеха lo_close возвращает ноль. При ошибке возвращается значение -1.

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

Удаление большого объекта

Чтобы удалить большой объект из базы данных, вызовите

int lo_unlink(PGconn *conn, Oid Id_БО);

В аргументе Id_БО указывается OID большого объекта, подлежащего удалению. Возвращает 1 в случае успеха и -1 при ошибке.

Серверные функции

Серверные функции, предназначенные для работы с большими объектами из SQL, перечислены в Таблице 1.

Таблица 1. SQL-ориентированные функции для больших объектов

Функция

Описание
Пример(ы)

lo_from_bytea ( id_бо oid, данные bytea ) → oid

Создает большой объект и сохраняет в нем переданные данные. Если id_бо равен нулю, то система выберет свободный OID; в противном случае используется заданный OID (если этот OID уже присвоен какому-то большому объекту, возникает ошибка). В случае успеха возвращается OID созданного большого объекта.
lo_from_bytea(0, '\xffffff00') → 24528

lo_put ( id_бо oid, смещение bigint, данные bytea ) → void

Записывает данные в большой объект, начиная с заданного смещения; при необходимости большой объект расширяется.
lo_put(24528, 1, '\xaa') →

lo_get ( id_бо oid [, смещение bigint, длина integer ] ) → bytea

Извлекает содержимое большого объекта или его подстроку.
lo_get(24528, 0, 3) → \xffaaff

Каждой из описанных ранее клиентских функций соответствуют дополнительные серверные функции; на самом деле по большей части клиентские функции представляют собой просто интерфейсы к аналогичным серверным функциям. К функциям, которые так же удобно вызывать с помощью команд SQL, относятся lo_create, lo_unlink, lo_import и lo_export. Вот примеры их использования:

CREATE TABLE image (
    name            text,
    raster          oid
);

SELECT lo_create(43213);   -- пытается создать большой объект с OID 43213

SELECT lo_unlink(173454);  -- удаляет большой объект с OID 173454

INSERT INTO image (name, raster)
    VALUES ('beautiful image', lo_import('/etc/motd'));

INSERT INTO image (name, raster)  -- то же, что выше, но с заданным OID
    VALUES ('beautiful image', lo_import('/etc/motd', 68583));

SELECT lo_export(image.raster, '/tmp/motd') FROM image
    WHERE name = 'beautiful image';

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

ВНИМАНИЕ!
С помощью команды GRANT можно разрешить использование серверных функций lo_import и lo_export не только суперпользователям, но при этом необходимо внимательно рассмотреть последствия для безопасности. Злонамеренный пользователь, имеющий такие права, может с легкостью воспользоваться ими, чтобы стать суперпользователем (например, путем перезаписи файлов конфигурации сервера), или атаковать остальную файловую систему сервера, не утруждаясь получением собственно прав суперпользователя базы данных. Поэтому доступ к ролям, имеющим подобные права, следует контролировать так же тщательно, как и доступ к ролям суперпользователя. Тем не менее если для выполнения некоторых рутинных задач требуется применение серверных функций lo_import или lo_export, безопаснее использовать роль с такими правами, чем с полными правами суперпользователя, поскольку это помогает снизить риск сбоев от случайных ошибок.

Функциональные возможности lo_read и lo_write также предоставляются через вызовы на стороне сервера, но имена серверных функций, в отличие от имен интерфейсов на стороне клиента, не содержат подчеркиваний. Эти функции следует вызывать как loread и lowrite.

Пример программы

Данный пример — простая программа, которая показывает, как можно использовать интерфейс больших объектов в libpq. Части этой программы закомментированы, но оставлены в тексте для удобства читателя.

Большие объекты с примером программы libpq

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "libpq-fe.h"
#include "libpq/libpq-fs.h"

#define BUFSIZE         1024

/*
 * importFile -
 *    импортировать файл "in_filename" в базу данных как большой объект "lobjOid"
 *
 */
static Oid
importFile(PGconn *conn, char *filename)
{
    Oid         lobjId;
    int         lobj_fd;
    char        buf[BUFSIZE];
    int         nbytes,
                tmp;
    int         fd;

    /*
     * открыть файл для чтения
     */
    fd = open(filename, O_RDONLY, 0666);
    if (fd < 0)
    {                           /* ошибка */
        fprintf(stderr, "cannot open unix file\"%s\"\n", filename);
    }

    /*
     * создать большой объект
     */
    lobjId = lo_creat(conn, INV_READ | INV_WRITE);
    if (lobjId == 0)
        fprintf(stderr, "cannot create large object");

    lobj_fd = lo_open(conn, lobjId, INV_WRITE);

    /*
     * прочитать данные из файла Unix и записать их в инверсионный файл
     */
    while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
    {
        tmp = lo_write(conn, lobj_fd, buf, nbytes);
        if (tmp < nbytes)
            fprintf(stderr, "error while reading \"%s\"", filename);
    }

    close(fd);
    lo_close(conn, lobj_fd);

    return lobjId;
}

static void
pickout(PGconn *conn, Oid lobjId, int start, int len)
{
    int         lobj_fd;
    char       *buf;
    int         nbytes;
    int         nread;

    lobj_fd = lo_open(conn, lobjId, INV_READ);
    if (lobj_fd < 0)
        fprintf(stderr, "cannot open large object %u", lobjId);

    lo_lseek(conn, lobj_fd, start, SEEK_SET);
    buf = malloc(len + 1);

    nread = 0;
    while (len - nread > 0)
    {
        nbytes = lo_read(conn, lobj_fd, buf, len - nread);
        buf[nbytes] = '\0';
        fprintf(stderr, ">>> %s", buf);
        nread += nbytes;
        if (nbytes <= 0)
            break;              /* больше данных не нужно? */
    }
    free(buf);
    fprintf(stderr, "\n");
    lo_close(conn, lobj_fd);
}

static void
overwrite(PGconn *conn, Oid lobjId, int start, int len)
{
    int         lobj_fd;
    char       *buf;
    int         nbytes;
    int         nwritten;
    int         i;

    lobj_fd = lo_open(conn, lobjId, INV_WRITE);
    if (lobj_fd < 0)
        fprintf(stderr, "cannot open large object %u", lobjId);

    lo_lseek(conn, lobj_fd, start, SEEK_SET);
    buf = malloc(len + 1);

    for (i = 0; i < len; i++)
        buf[i] = 'X';
    buf[i] = '\0';

    nwritten = 0;
    while (len - nwritten > 0)
    {
        nbytes = lo_write(conn, lobj_fd, buf + nwritten, len - nwritten);
        nwritten += nbytes;
        if (nbytes <= 0)
        {
            fprintf(stderr, "\nWRITE FAILED!\n");
            break;
        }
    }
    free(buf);
    fprintf(stderr, "\n");
    lo_close(conn, lobj_fd);
}

/*
 * exportFile -
 *    экспортировать большой объект "lobjOid" в файл "out_filename"
 *
 */
static void
exportFile(PGconn *conn, Oid lobjId, char *filename)
{
    int         lobj_fd;
    char        buf[BUFSIZE];
    int         nbytes,
                tmp;
    int         fd;

    /*
     * открыть большой объект
     */
    lobj_fd = lo_open(conn, lobjId, INV_READ);
    if (lobj_fd < 0)
        fprintf(stderr, "cannot open large object %u", lobjId);

    /*
     * открыть файл для записи
     */
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if (fd < 0)
    {                           /* ошибка */
        fprintf(stderr, "cannot open unix file\"%s\"",
                filename);
    }

    /*
     * прочитать данные из инверсионного файла и записать их в файл Unix
     */
    while ((nbytes = lo_read(conn, lobj_fd, buf, BUFSIZE)) > 0)
    {
        tmp = write(fd, buf, nbytes);
        if (tmp < nbytes)
        {
            fprintf(stderr, "error while writing \"%s\"",
                    filename);
        }
    }

    lo_close(conn, lobj_fd);
    close(fd);
}

static void
exit_nicely(PGconn *conn)
{
    PQfinish(conn);
    exit(1);
}

int
main(int argc, char **argv)
{
    char       *in_filename,
               *out_filename;
    char       *database;
    Oid         lobjOid;
    PGconn     *conn;
    PGresult   *res;

    if (argc != 4)
    {
        fprintf(stderr, "Usage: %s database_name in_filename out_filename\n",
                argv[0]);
        exit(1);
    }

    database = argv[1];
    in_filename = argv[2];
    out_filename = argv[3];

    /*
     * установить соединение
     */
    conn = PQsetdb(NULL, NULL, NULL, NULL, database);

    /* проверить, что соединение с сервером было успешно установлено */
    if (PQstatus(conn) != CONNECTION_OK)
    {
        fprintf(stderr, "%s", PQerrorMessage(conn));
        exit_nicely(conn);
    }

    /* Задать надежно защищенный путь поиска, чтобы злонамеренные пользователи не
     * могли перехватить управление.
     */
    res = PQexec(conn,
                 "SELECT pg_catalog.set_config('search_path', '', false)");
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }
    PQclear(res);

    res = PQexec(conn, "begin");
    PQclear(res);
    printf("importing file \"%s\" ...\n", in_filename);
/*  lobjOid = importFile(conn, in_filename); */
    lobjOid = lo_import(conn, in_filename);
    if (lobjOid == 0)
        fprintf(stderr, "%s\n", PQerrorMessage(conn));
    else
    {
        printf("\tas large object %u.\n", lobjOid);

        printf("picking out bytes 1000-2000 of the large object\n");
        pickout(conn, lobjOid, 1000, 1000);

        printf("overwriting bytes 1000-2000 of the large object with X's\n");
        overwrite(conn, lobjOid, 1000, 1000);

        printf("exporting large object to file \"%s\" ...\n", out_filename);
/*      exportFile(conn, lobjOid, out_filename); */
        if (lo_export(conn, lobjOid, out_filename) < 0)
            fprintf(stderr, "%s\n", PQerrorMessage(conn));
    }

    res = PQexec(conn, "end");
    PQclear(res);
    PQfinish(conn);
    return 0;
}

Информационная схема

Информационная схема состоит из набора представлений, содержащих информацию об объектах, определенных в текущей базе данных. Информационная схема определена в стандарте SQL, и поэтому можно ожидать, что она будет переносима и при этом останется стабильной — в отличие от системных каталогов, которые специфичны для QHB и моделируются, исходя из потребностей реализации. Однако представления информационной схемы не содержат информацию о функциональности, характерной только для QHB; чтобы узнать о нем, нужно запросить системные каталоги или другие специализированные представления QHB.

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

Эта проблема может возникнуть при запросе информации из таких представлений информационной схемы, как check_constraint_routine_usage, check_constraints, domain_constraints и referential_constraints. В некоторых других представлениях тоже имеются схожие затруднения, но эти представления содержат имя таблицы, что помогает различить повторяющиеся строки, например: constraint_column_usage, constraint_table_usage, table_constraints.

Схема

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

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

Типы данных

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

Это следующие типы:

cardinal_number
Неотрицательное целое число.

character_data
Строка символов (без конкретной максимальной длины).

sql_identifier
Строка символов. Этот тип используется для идентификаторов SQL, тогда как тип character_data используется для всех остальных видов текстовых данных.

time_stamp
Домен поверх типа timestamp with time zone.

yes_or_no
Домен строки символов, содержащий YES или NO. Этот домен используется для представления логических данных (true/false, истина/ложь) в информационной схеме. (Информационная схема была изобретена до того, как в стандарт SQL добавили тип boolean, поэтому такое преобразование необходимо для сохранения обратной совместимости информационной схемы.)

Каждый столбец в информационной схеме имеет один из этих пяти типов.

information_schema_catalog_name

Таблица information_schema_catalog_name всегда содержит одну строку и один столбец с именем текущей базы данных (текущий каталог в терминологии SQL).

Таблица 1. Столбцы information_schema_catalog_name

Столбец Тип

Описание

catalog_name sql_identifier

Имя базы данных, содержащей эту информационную схему

administrable_role_​authorizations

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

Таблица 2. Столбцы administrable_role_authorizations

Столбец Тип

Описание

grantee sql_identifier

Имя роли, которой было выдано членство в этой роли (может быть текущий пользователь или другая роль, в случае вложенного членства)

role_name sql_identifier

Имя роли

is_grantable yes_or_no

Всегда YES

applicable_roles

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

Таблица 3. Столбцы applicable_roles

Столбец Тип

Описание

grantee sql_identifier

Имя роли, которой было выдано членство в этой роли (может быть текущий пользователь или другая роль, в случае вложенного членства)

role_name sql_identifier

Имя роли

is_grantable yes_or_no

YES, если правополучатель имеет права администратора для этой роли, NO, если не имеет

attributes

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

Таблица 4. Столбцы attributes

Столбец Тип

Описание

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, содержащей тип данных

udt_name sql_identifier

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

attribute_name sql_identifier

Имя атрибута

ordinal_position cardinal_number

Порядковый номер атрибута внутри типа данных (нумерация начинается с 1)

attribute_default character_data

Выражение по умолчанию для атрибута

is_nullable yes_or_no

YES, если атрибут может содержать NULL, NO, если не может.

data_type character_data

Тип данных атрибута, если это встроенный тип, или ARRAY, если это некоторый массив (в этом случае см. представление element_types), иначе USER-DEFINED (в этом случае тип описывается в attribute_udt_name и связанных столбцах).

character_maximum_length cardinal_number

Если в data_type указан тип символьной или битовой строки, здесь содержится объявленная максимальная длина; NULL для всех остальных типов данных или если максимальная длина не объявлена.

character_octet_length cardinal_number

Если в data_type указан символьный тип, здесь содержится максимально возможная длина данных в октетах (байтах); NULL для всех остальных типов данных. Максимальная длина в октетах зависит от объявленной максимальной длины в символах (см. выше) и от кодировки сервера.

character_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_catalog sql_identifier

Имя базы данных, содержащей правило сортировки атрибута (всегда текущая база данных); NULL, если это правило по умолчанию или тип данных атрибута несортируемый

collation_schema sql_identifier

Имя схемы, содержащей правило сортировки атрибута; NULL, если это правило по умолчанию или тип данных атрибута несортируемый

collation_name sql_identifier

Имя правила сортировки атрибута; NULL, если это правило по умолчанию или тип данных атрибута несортируемый

numeric_precision cardinal_number

Если в data_type указан числовой тип, этот столбец содержит (объявленную или неявную) точность типа для этого атрибута. Точность определяет количество значащих цифр. Она может выражаться в десятичных (по основанию 10) или двоичных (по основанию 2) единицах, согласно столбцу numeric_precision_radix. Для всех остальных типов данных этот столбец содержит NULL.

numeric_precision_radix cardinal_number

Если в data_type указан числовой тип, этот столбец определяет, по какому основанию выражаются значения в столбцах numeric_precision и numeric_scale. Значение может быть 2 или 10. Для всех остальных типов данных этот столбец содержит NULL.

numeric_scale cardinal_number

Если в data_type указан точный числовой тип, этот столбец содержит (объявленный или неявный) масштаб типа для этого атрибута. Масштаб определяет количество значащих цифр справа от десятичной точки. Он может выражаться в десятичных (по основанию 10) или двоичных (по основанию 2) единицах, согласно столбцу numeric_precision_radix. Для всех остальных типов данных этот столбец содержит NULL.

datetime_precision cardinal_number

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

interval_type character_data

Если в data_type указан тип интервала, этот столбец содержит уточнение, какие именно поля интервалов включены в этот атрибут, например: YEAR TO MONTH, DAY TO SECOND и т. д. Если не заданы ограничения полей (то есть интервал принимает все поля), а также для всех остальных типов данных этот столбец содержит NULL.

interval_precision cardinal_number

Относится к функциональности, отсутствующей в QHB (точность в долях секунды типов интервалов для атрибута см. в столбце datetime_precision)

attribute_udt_catalog sql_identifier

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

attribute_udt_schema sql_identifier

Имя схемы, в которой определен тип данных атрибута

attribute_udt_name sql_identifier

Имя типа данных атрибута

scope_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_name sql_identifier

Относится к функциональности, отсутствующей в QHB

maximum_cardinality cardinal_number

Всегда NULL, поскольку в QHB массивы всегда имеют неограниченную максимальную емкость

dtd_identifier sql_identifier

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

is_derived_reference_attribute yes_or_no

Относится к функциональности, отсутствующей в QHB

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

character_sets

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

Обратите внимание на то, как следующие термины трактуются в стандарте SQL:

перечень символов
Абстрактная коллекция символов, например UNICODE, UCS или LATIN1. Не выступает в качестве объекта SQL, но видима в этом представлении.

форма кодировки символов
Кодировка некоторого перечня символов. Для большинства старых перечней используется только одна форма кодировки, и поэтому у них нет отдельных имен (например, LATIN1 — это форма кодировки, применимая к перечню LATIN1). Но, к примеру, Unicode имеет формы кодировки UTF8, UTF16 и т. д. (не все они поддерживаются в QHB). Формы кодировки не выступают в качестве объектов SQL, но видимы в этом представлении.

набор символов
Именованный объект SQL, определяющий перечень и кодировку символов, а также правило сортировки по умолчанию. Предопределенный набор символов обычно называется так же, как форма кодировки, но пользователи могут определить и другие имена. Например, набор символов UTF8 обычно определяет перечень символов UCS, форму кодировки UTF8 и некоторое правило сортировки по умолчанию.

Вы можете считать, что «кодировка» в QHB является либо набором, либо формой кодировки символов. У них будет одинаковое имя, и в одной базе данных может быть только одна кодировка.

Таблица 5. Столбцы character_sets

Столбец Тип

Описание

character_set_catalog sql_identifier

В настоящее время наборы символов не реализованы в виде объектов схемы, поэтому данный столбец содержит NULL

character_set_schema sql_identifier

В настоящее время наборы символов не реализованы в виде объектов схемы, поэтому данный столбец содержит NULL

character_set_name sql_identifier

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

character_repertoire sql_identifier

Перечень символов, отображает UCS для кодировки UTF8, в иных случаях просто имя кодировки

form_of_use sql_identifier

Форма кодировки символов; то же, что и кодировка базы данных

default_collate_catalog sql_identifier

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

default_collate_schema sql_identifier

Имя схемы, содержащей правило сортировки по умолчанию

default_collate_name sql_identifier

Имя правила сортировки по умолчанию. Правило сортировки по умолчанию определяется как правило сортировки, соответствующее параметрам COLLATE и CTYPE текущей базы данных. Если такого правила нет, этот столбец и связанные столбцы схемы и каталога содержат NULL.

check_constraint_routine_usage

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

Таблица 6. Столбцы check_constraint_routine_usage

Столбец Тип

Описание

constraint_catalog sql_identifier

Имя базы данных, содержащей ограничение (всегда текущая база данных)

constraint_schema sql_identifier

Имя схемы, содержащей ограничение

constraint_name sql_identifier

Имя ограничения

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

check_constraints

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

Таблица 7. Столбцы check_constraints

Столбец Тип

Описание

constraint_catalog sql_identifier

Имя базы данных, содержащей ограничение (всегда текущая база данных)

constraint_schema sql_identifier

Имя схемы, содержащей ограничение

constraint_name sql_identifier

Имя ограничения

check_clause character_data

Выражение проверки для этого проверочного ограничения

collations

В представлении collations содержатся все правила сортировки, доступные в текущей базе данных.

Таблица 8. Столбцы collations

Столбец Тип

Описание

collation_catalog sql_identifier

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

collation_schema sql_identifier

Имя схемы, содержащей правило сортировки

collation_name sql_identifier

Имя правила сортировки по умолчанию

pad_attribute character_data

Всегда NO PAD (Альтернативный вариант PAD SPACE не поддерживается QHB)

collation_character_set_​applicability

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

Таблица 9. Столбцы collation_character_set_applicability

Столбец Тип

Описание

collation_catalog sql_identifier

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

collation_schema sql_identifier

Имя схемы, содержащей правило сортировки

collation_name sql_identifier

Имя правила сортировки по умолчанию

character_set_catalog sql_identifier

В настоящее время наборы символов не реализованы в виде объектов схемы, поэтому данный столбец содержит NULL

character_set_schema sql_identifier

В настоящее время наборы символов не реализованы в виде объектов схемы, поэтому данный столбец содержит NULL

character_set_name sql_identifier

Имя набора символов

column_column_usage

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

Таблица 10. Столбцы column_column_usage

Столбец Тип

Описание

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу

table_name sql_identifier

Имя таблицы

column_name sql_identifier

Имя базового столбца, от которого зависит генерируемый столбец

dependent_column sql_identifier

Имя генерируемого столбца

column_domain_usage

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

Таблица 11. Столбцы column_domain_usage

Столбец Тип

Описание

domain_catalog sql_identifier

Имя базы данных, содержащей домен (всегда текущая база данных)

domain_schema sql_identifier

Имя схемы, содержащей домен

domain_name sql_identifier

Имя домена

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу

table_name sql_identifier

Имя таблицы

column_name sql_identifier

Имя столбца

column_options

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

Таблица 12. Столбцы column_options

Столбец Тип

Описание

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей стороннюю таблицу

table_name sql_identifier

Имя сторонней таблицы

column_name sql_identifier

Имя столбца

option_name sql_identifier

Имя параметра

option_value character_data

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

column_privileges

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

Если право было предоставлено для всей таблицы, оно будет отображаться как право для каждого столбца, но только для типов прав, у которых возможна детализация до уровня столбцов: SELECT, INSERT, UPDATE, REFERENCES.

Таблица 13. Столбцы column_privileges

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу с этим столбцом

table_name sql_identifier

Имя таблицы, содержащей этот столбец

column_name sql_identifier

Имя столбца

privilege_type character_data

Тип права: SELECT, INSERT, UPDATE или REFERENCES

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

column_udt_usage

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

Таблица 14. Столбцы column_udt_usage

Столбец Тип

Описание

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, в которой определен тип данных столбца (если применимо, нижележащий тип домена)

udt_name sql_identifier

Имя типа данных столбца (если применимо, нижележащий тип домена)

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу

table_name sql_identifier

Имя таблицы

column_name sql_identifier

Имя столбца

columns

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

Таблица 15. Столбцы columns

Столбец Тип

Описание

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу

table_name sql_identifier

Имя таблицы

column_name sql_identifier

Имя столбца

ordinal_position cardinal_number

Порядковый номер столбца в таблице (нумерация начинается с 1)

column_default character_data

Выражение по умолчанию для столбца

is_nullable yes_or_no

YES, если столбец может содержать NULL, NO, если не может. Столбец не будет принимать NULL, если у него имеется ограничение NOT NULL, но возможны и другие варианты.

data_type character_data

Тип данных столбца, если это встроенный тип, или ARRAY, если это некоторый массив (в этом случае см. представление element_types), иначе USER-DEFINED (в этом случае тип описывается в udt_name и связанных столбцах). Если столбец основан на домене, то здесь отображается нижележащий тип домена (а сам домен описывается в domain_name и связанных столбцах).

character_maximum_length cardinal_number

Если в data_type указан тип символьной или битовой строки, здесь содержится объявленная максимальная длина; NULL для всех остальных типов данных или если максимальная длина не объявлена.

character_octet_length cardinal_number

Если в data_type указан символьный тип, здесь содержится максимально возможная длина данных в октетах (байтах); NULL для всех остальных типов данных. Максимальная длина в октетах зависит от объявленной максимальной длины в символах (см. выше) и от кодировки сервера.

numeric_precision cardinal_number

Если в data_type указан числовой тип, этот столбец содержит (объявленную или неявную) точность типа для этого столбца. Точность определяет количество значащих цифр. Она может выражаться в десятичных (по основанию 10) или двоичных (по основанию 2) единицах, согласно столбцу numeric_precision_radix. Для всех остальных типов данных этот столбец содержит NULL.

numeric_precision_radix cardinal_number

Если в data_type указан числовой тип, этот столбец определяет, по какому основанию выражаются значения в столбцах numeric_precision и numeric_scale. Значение может быть 2 или 10. Для всех остальных типов данных этот столбец содержит NULL.

numeric_scale cardinal_number

Если в data_type указан точный числовой тип, этот столбец содержит (объявленный или неявный) масштаб типа для этого столбца. Масштаб определяет количество значащих цифр справа от десятичной точки. Он может выражаться в десятичных (по основанию 10) или двоичных (по основанию 2) единицах, согласно столбцу numeric_precision_radix. Для всех остальных типов данных этот столбец содержит NULL.

datetime_precision cardinal_number

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

interval_type character_data

Если в data_type указан тип интервала, этот столбец содержит уточнение, какие именно поля интервалов включены в этот столбец, например: YEAR TO MONTH, DAY TO SECOND и т. д. Если не заданы ограничения полей (то есть интервал принимает все поля), а также для всех остальных типов данных этот столбец содержит NULL.

interval_precision cardinal_number

Относится к функциональности, отсутствующей в QHB (точность в долях секунды типов интервалов для столбца см. в поле datetime_precision)

character_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_catalog sql_identifier

Имя базы данных, содержащей правило сортировки для столбца (всегда текущая база данных); NULL, если это правило сортировки по умолчанию или тип данных столбца несортируемый

collation_schema sql_identifier

Имя схемы, содержащей правило сортировки для столбца; NULL, если это правило сортировки по умолчанию или тип данных столбца несортируемый

collation_name sql_identifier

Имя правила сортировки для столбца; NULL, если это правило сортировки по умолчанию или тип данных столбца несортируемый

domain_catalog sql_identifier

Если столбец имеет тип домена, то имя базы данных, в которой определен домен (всегда текущая база данных); иначе NULL

domain_schema sql_identifier

Если столбец имеет тип домена, то имя схемы, в которой определен домен; иначе NULL

domain_name sql_identifier

Если столбец имеет тип домена, то имя домена; иначе NULL

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, в которой определен тип данных столбца (если применимо, нижележащий тип домена)

udt_name sql_identifier

Имя типа данных столбца (если применимо, нижележащий тип домена)

scope_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_name sql_identifier

Относится к функциональности, отсутствующей в QHB

maximum_cardinality cardinal_number

Всегда NULL, поскольку в QHB массивы всегда имеют неограниченную максимальную емкость

dtd_identifier sql_identifier

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

is_self_referencing yes_or_no

Относится к функциональности, отсутствующей в QHB

is_identity yes_or_no

Если столбец является столбцом идентификации, то YES, иначе NO.

identity_generation character_data

Если столбец является столбцом идентификации, то значение ALWAYS или BY DEFAULT, отражающее определение столбца.

identity_start character_data

Если столбец является столбцом идентификации, то начальное значение внутренней последовательности, иначе NULL.

identity_increment character_data

Если столбец является столбцом идентификации, то шаг приращения внутренней последовательности, иначе NULL.

identity_maximum character_data

Если столбец является столбцом идентификации, то максимальное значение внутренней последовательности, иначе NULL.

identity_minimum character_data

Если столбец является столбцом идентификации, то минимальное значение внутренней последовательности, иначе NULL.

identity_cycle yes_or_no

Если столбец является столбцом идентификации, то YES, если внутренняя последовательность зацикливается, NO, если не зацикливается; иначе NULL.

is_generated character_data

Если столбец является генерируемым, то ALWAYS, иначе NEVER.

generation_expression character_data

Если столбец является генерируемым, то генерирующее выражение, иначе NULL.

is_updatable yes_or_no

YES, если столбец можно изменять, NO, если нельзя (Столбцы в базовых таблицах всегда изменяемые, столбцы в представлениях — необязательно)

Поскольку типы данных могут определяться в SQL множеством способов и QHB содержит дополнительные способы для этого, их представление в информационной схеме может быть довольно сложным. Столбец data_type предназначен для идентификации нижележащего встроенного типа столбца. В QHB это означает, что данный тип определен в схеме системного каталога pg_catalog. Этот столбец может быть полезен, если приложение способно особым образом обрабатывать общеизвестные встроенные типы (например, форматировать числовые типы по-другому или использовать данные в столбцах точности). Столбцы udt_name, udt_schema и udt_catalog всегда указывают на нижележащий тип данных столбца, даже если столбец основан на домене. (Поскольку QHB воспринимает встроенные типы так же, как пользовательские, встроенные типы тоже выводятся в этом представлении. Это расширение стандарта SQL.) Эти столбцы должны применяться, если приложение хочет обрабатывать данные по-разному в зависимости от типа, потому что в этом случае неважно, действительно ли столбец основан на домене. Если столбец основан на домене, характеристики этого домена хранятся в столбцах domain_name, domain_schema и domain_catalog. Если вы желаете попарно соединить столбцы с их связанными типами данных и обработать домены как отдельные типы, вы можете написать coalesce(domain_name, udt_name) и т. п.

constraint_column_usage

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

Таблица 16. Столбцы constraint_column_usage

Столбец Тип

Описание

table_catalog sql_identifier

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

table_schema sql_identifier

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

table_name sql_identifier

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

column_name sql_identifier

Имя столбца, используемого некоторым ограничением

constraint_catalog sql_identifier

Имя базы данных, содержащей ограничение (всегда текущая база данных)

constraint_schema sql_identifier

Имя схемы, содержащей ограничение

constraint_name sql_identifier

Имя ограничения

constraint_table_usage

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

Таблица 17. Столбцы constraint_table_usage

Столбец Тип

Описание

table_catalog sql_identifier

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

table_schema sql_identifier

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

table_name sql_identifier

Имя таблицы, которая используется некоторым ограничением

constraint_catalog sql_identifier

Имя базы данных, содержащей ограничение (всегда текущая база данных)

constraint_schema sql_identifier

Имя схемы, содержащей ограничение

constraint_name sql_identifier

Имя ограничения

data_type_privileges

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

Таблица 18. Столбцы data_type_privileges

Столбец Тип

Описание

object_catalog sql_identifier

Имя базы данных, содержащей описываемый объект (всегда текущая база данных)

object_schema sql_identifier

Имя схемы, содержащей описываемый объект

object_name sql_identifier

Имя описываемого объекта

object_type character_data

Тип описываемого объекта: TABLE (дескриптор типа данных относится к столбцу этой таблицы), DOMAIN (дескриптор типа данных относится к этому домену), ROUTINE (дескриптор типа данных относится к типу данных параметра или результата этой функции).

dtd_identifier sql_identifier

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

domain_constraints

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

Таблица 19. Столбцы domain_constraints

Столбец Тип

Описание

constraint_catalog sql_identifier

Имя базы данных, содержащей ограничение (всегда текущая база данных)

constraint_schema sql_identifier

Имя схемы, содержащей ограничение

constraint_name sql_identifier

Имя ограничения

domain_catalog sql_identifier

Имя базы данных, содержащей домен (всегда текущая база данных)

domain_schema sql_identifier

Имя схемы, содержащей домен

domain_name sql_identifier

Имя домена

is_deferrable yes_or_no

YES, если ограничение откладываемое, NO, если это не так

initially_deferred yes_or_no

YES, если ограничение откладываемое и отложенное изначально, NO, если это не так

domain_udt_usage

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

Таблица 20. Столбцы domain_udt_usage

Столбец Тип

Описание

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, в которой определен тип данных домена

udt_name sql_identifier

Имя типа данных домена

domain_catalog sql_identifier

Имя базы данных, содержащей домен (всегда текущая база данных)

domain_schema sql_identifier

Имя схемы, содержащей домен

domain_name sql_identifier

Имя домена

domains

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

Таблица 21. Столбцы domains

Столбец Тип

Описание

domain_catalog sql_identifier

Имя базы данных, содержащей домен (всегда текущая база данных)

domain_schema sql_identifier

Имя схемы, содержащей домен

domain_name sql_identifier

Имя домена

data_type character_data

Тип данных домена, если это встроенный тип, или ARRAY, если это некоторый массив (в этом случае см. представление element_types), иначе USER-DEFINED (в этом случае тип описывается в udt_name и связанных столбцах).

character_maximum_length cardinal_number

Если домен имеет тип символьной или битовой строки, здесь содержится объявленная максимальная длина; NULL для всех остальных типов данных или если максимальная длина не объявлена.

character_octet_length cardinal_number

Если домен имеет символьный тип, здесь содержится максимально возможная длина данных в октетах (байтах); NULL для всех остальных типов данных. Максимальная длина в октетах зависит от объявленной максимальной длины в символах (см. выше) и от кодировки сервера.

character_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_catalog sql_identifier

Имя базы данных, содержащей правило сортировки для домена (всегда текущая база данных); NULL, если это правило сортировки по умолчанию или тип данных домена несортируемый

collation_schema sql_identifier

Имя схемы, содержащей правило сортировки для домена; NULL, если это правило сортировки по умолчанию или тип данных домена несортируемый

collation_name sql_identifier

Имя правила сортировки для домена; NULL, если это правило сортировки по умолчанию или тип данных домена несортируемый

numeric_precision cardinal_number

Если домен имеет числовой тип, этот столбец содержит (объявленную или неявную) точность типа для этого домена. Точность определяет количество значащих цифр. Она может выражаться в десятичных (по основанию 10) или двоичных (по основанию 2) единицах, согласно столбцу numeric_precision_radix. Для всех остальных типов данных этот столбец содержит NULL.

numeric_precision_radix cardinal_number

Если домен имеет числовой тип, этот столбец определяет, по какому основанию выражаются значения в столбцах numeric_precision и numeric_scale. Значение может быть 2 или 10. Для всех остальных типов данных этот столбец содержит NULL.

numeric_scale cardinal_number

Если домен имеет точный числовой тип, этот столбец содержит (объявленный или неявный) масштаб типа для этого домена. Масштаб определяет количество значащих цифр справа от десятичной точки. Он может выражаться в десятичных (по основанию 10) или двоичных (по основанию 2) единицах, согласно столбцу numeric_precision_radix. Для всех остальных типов данных этот столбец содержит NULL.

datetime_precision cardinal_number

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

interval_type character_data

Если в data_type указан тип интервала, этот столбец содержит уточнение, какие именно поля интервалов включены в этот домен, например: YEAR TO MONTH, DAY TO SECOND и т. д. Если не заданы ограничения полей (то есть интервал принимает все поля), а также для всех остальных типов данных этот столбец содержит NULL.

interval_precision cardinal_number

Относится к функциональности, отсутствующей в QHB (точность в долях секунды типов интервалов для доменов см. в поле datetime_precision)

domain_default character_data

Выражение по умолчанию для домена

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, в которой определен тип данных домена

udt_name sql_identifier

Имя типа данных домена

scope_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_name sql_identifier

Относится к функциональности, отсутствующей в QHB

maximum_cardinality cardinal_number

Всегда NULL, поскольку в QHB массивы всегда имеют неограниченную максимальную емкость

dtd_identifier sql_identifier

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

element_types

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

SELECT c.column_name, c.data_type, e.data_type AS element_type
FROM information_schema.columns c LEFT JOIN information_schema.element_types e
     ON ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier)
       = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier))
WHERE c.table_schema = '...' AND c.table_name = '...'
ORDER BY c.ordinal_position;

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

Таблица 22. Столбцы element_types

Столбец Тип

Описание

object_catalog sql_identifier

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

object_schema sql_identifier

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

object_name sql_identifier

Имя объекта, в котором используется описываемый массив

object_type character_data

Тип объекта, в котором используется описываемый массив: TABLE (массив используется столбцом этой таблицы), USER-DEFINED (массив используется атрибутом этого составного типа), DOMAIN (массив используется этим доменом), ROUTINE (массив используется параметром или типом данных результата этой функции).

collection_type_identifier sql_identifier

Идентификатор дескриптора типа данных описываемого массива. Его можно использовать для соединения со столбцами dtd_identifier других представлений информационной схемы.

data_type character_data

Тип данных элементов массива, если это встроенный тип, иначе USER-DEFINED (в этом случае тип описывается в udt_name и связанных столбцах).

character_maximum_length cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных элементов массива в QHB

character_octet_length cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных элементов массива в QHB

character_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_catalog sql_identifier

Имя базы данных, содержащей правило сортировки для типа элемента (всегда текущая база данных); NULL, если это правило сортировки по умолчанию или тип данных элемента несортируемый

collation_schema sql_identifier

Имя схемы, содержащей правило сортировки для типа элемента; NULL, если это правило сортировки по умолчанию или тип данных элемента несортируемый

collation_name sql_identifier

Имя правила сортировки для типа элемента; NULL, если это правило сортировки по умолчанию или тип данных элемента несортируемый

numeric_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных элементов массива в QHB

numeric_precision_radix cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных элементов массива в QHB

numeric_scale cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных элементов массива в QHB

datetime_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных элементов массива в QHB

interval_type character_data

Всегда NULL, поскольку эта информация неприменима к типам данных элементов массива в QHB

interval_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных элементов массива в QHB

domain_default character_data

Пока не реализовано

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, в которой определен тип данных элементов

udt_name sql_identifier

Имя типа данных элементов

scope_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_name sql_identifier

Относится к функциональности, отсутствующей в QHB

maximum_cardinality cardinal_number

Всегда NULL, поскольку в QHB массивы всегда имеют неограниченную максимальную емкость

dtd_identifier sql_identifier

Идентификатор дескриптора типа данных элемента. В настоящее время бесполезен

enabled_roles

В представлении enabled_roles указаны текущие «разрешенные роли». Разрешенные роли рекурсивно определяются как текущий пользователь плюс все роли, выданные разрешенным ролям, с автоматическим наследованием. Другими словами, это все роли, в которых текущий пользователь прямо или косвенно (путем автоматического наследования) получил членство.

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

Таблица 23. Столбцы enabled_roles

Столбец Тип

Описание

role_name sql_identifier

Имя роли

foreign_data_wrapper_options

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

Таблица 24. Столбцы foreign_data_wrapper_options

Столбец Тип

Описание

foreign_data_wrapper_catalog sql_identifier

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

foreign_data_wrapper_name sql_identifier

Имя обертки сторонних данных

option_name sql_identifier

Имя параметра

option_value character_data

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

foreign_data_wrappers

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

Таблица 25. Столбцы foreign_data_wrappers

Столбец Тип

Описание

foreign_data_wrapper_catalog sql_identifier

Имя базы данных, содержащей обертку сторонних данных (всегда текущая база данных)

foreign_data_wrapper_name sql_identifier

Имя обертки сторонних данных

authorization_identifier sql_identifier

Имя владельца стороннего сервера

library_name character_data

Имя файла библиотеки, реализующей эту обертку сторонних данных

foreign_data_wrapper_language character_data

Язык, применяемый для реализации этой обертки сторонних данных

foreign_server_options

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

Таблица 26. Столбцы foreign_server_options

Столбец Тип

Описание

foreign_server_catalog sql_identifier

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

foreign_server_name sql_identifier

Имя стороннего сервера

option_name sql_identifier

Имя параметра

option_value character_data

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

foreign_servers

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

Таблица 27. Столбцы foreign_servers

Столбец Тип

Описание

foreign_server_catalog sql_identifier

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

foreign_server_name sql_identifier

Имя стороннего сервера

foreign_data_wrapper_catalog sql_identifier

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

foreign_data_wrapper_name sql_identifier

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

foreign_server_type character_data

Информация о типе стороннего сервера, если она была указана при создании

foreign_server_version character_data

Информация о версии стороннего сервера, если она была указана при создании

authorization_identifier sql_identifier

Имя владельца стороннего сервера

foreign_table_options

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

Таблица 28. Столбцы foreign_table_options

Столбец Тип

Описание

foreign_table_catalog sql_identifier

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

foreign_table_schema sql_identifier

Имя схемы, содержащей стороннюю таблицу

foreign_table_name sql_identifier

Имя сторонней таблицы

option_name sql_identifier

Имя параметра

option_value character_data

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

foreign_tables

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

Таблица 29. Столбцы foreign_tables

Столбец Тип

Описание

foreign_table_catalog sql_identifier

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

foreign_table_schema sql_identifier

Имя схемы, содержащей стороннюю таблицу

foreign_table_name sql_identifier

Имя сторонней таблицы

foreign_server_catalog sql_identifier

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

foreign_server_name sql_identifier

Имя стороннего сервера

key_column_usage

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

Таблица 30. Столбцы key_column_usage

Столбец Тип

Описание

constraint_catalog sql_identifier

Имя базы данных, содержащей ограничение (всегда текущая база данных)

constraint_schema sql_identifier

Имя схемы, содержащей ограничение

constraint_name sql_identifier

Имя ограничения

table_catalog sql_identifier

Имя базы данных, содержащей таблицу со столбцом, на который наложено это ограничение (всегда текущая база данных)

table_schema sql_identifier

Имя схемы, содержащей таблицу со столбцом, на который наложено это ограничение

table_name sql_identifier

Имя таблицы, содержащей столбец, на который наложено это ограничение

column_name sql_identifier

Имя столбца, на который наложено это ограничение

ordinal_position cardinal_number

Порядковый номер столбца в ключе ограничения (нумерация начинается с 1)

position_in_unique_constraint cardinal_number

Для ограничения внешнего ключа это порядковый номер ссылочного столбца в его ограничении уникальности (нумерация начинается с 1); иначе NULL

parameters

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

Таблица 31. Столбцы parameters

Столбец Тип

Описание

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

ordinal_position cardinal_number

Порядковый номер параметра в списке аргументов функции (нумерация начинается с 1)

parameter_mode character_data

IN для входного параметра, OUT для выходного параметра и INOUT для входного/выходного параметра.

is_result yes_or_no

Относится к функциональности, отсутствующей в QHB

as_locator yes_or_no

Относится к функциональности, отсутствующей в QHB

parameter_name sql_identifier

Имя параметра или NULL, если параметр безымянный

data_type character_data

Тип данных параметра, если это встроенный тип, или ARRAY, если это некоторый массив (в этом случае см. представление element_types), иначе USER-DEFINED (в этом случае тип описывается в udt_name и связанных столбцах).

character_maximum_length cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

character_octet_length cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

character_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_catalog sql_identifier

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

collation_schema sql_identifier

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

collation_name sql_identifier

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

numeric_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

numeric_precision_radix cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

numeric_scale cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

datetime_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

interval_type character_data

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

interval_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных параметров в QHB

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, в которой определен тип данных параметра

udt_name sql_identifier

Имя типа данных параметра

scope_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_name sql_identifier

Относится к функциональности, отсутствующей в QHB

maximum_cardinality cardinal_number

Всегда NULL, поскольку в QHB массивы всегда имеют неограниченную максимальную емкость

dtd_identifier sql_identifier

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

parameter_default character_data

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

referential_constraints

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

Таблица 32. Столбцы referential_constraints

Столбец Тип

Описание

constraint_catalog sql_identifier

Имя базы данных, содержащей ограничение (всегда текущая база данных)

constraint_schema sql_identifier

Имя схемы, содержащей ограничение

constraint_name sql_identifier

Имя ограничения

unique_constraint_catalog sql_identifier

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

unique_constraint_schema sql_identifier

Имя схемы, содержащей ограничение уникальности или первичного ключа, на которое ссылается ограничение внешнего ключа

unique_constraint_name sql_identifier

Имя ограничения уникальности или первичного ключа, на которое ссылается ограничение внешнего ключа

match_option character_data

Вариант совпадения для ограничения внешнего ключа: FULL, PARTIAL или NONE.

update_rule character_data

Правило изменения для ограничения внешнего ключа: CASCADE, SET NULL, SET DEFAULT, RESTRICT или NO ACTION.

delete_rule character_data

Правило удаления для ограничения внешнего ключа: CASCADE, SET NULL, SET DEFAULT, RESTRICT или NO ACTION.

role_column_grants

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

Таблица 33. Столбцы role_column_grants

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу с этим столбцом

table_name sql_identifier

Имя таблицы, содержащей этот столбец

column_name sql_identifier

Имя столбца

privilege_type character_data

Тип права: SELECT, INSERT, UPDATE или REFERENCES

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

role_routine_grants

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

Таблица 34. Столбцы role_routine_grants

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

routine_catalog sql_identifier

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

routine_schema sql_identifier

Имя схемы, содержащей функцию

routine_name sql_identifier

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

privilege_type character_data

Всегда EXECUTE (единственный тип прав для функций)

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

role_table_grants

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

Таблица 35. Столбцы role_table_grants

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу

table_name sql_identifier

Имя таблицы

privilege_type character_data

Тип права: SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES или TRIGGER

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

with_hierarchy yes_or_no

В стандарте SQL WITH HIERARCHY OPTION является отдельным (подчиненным) правом, позволяющим выполнять определенные операции в иерархиях наследования таблиц. В QHB эта возможность входит в право SELECT, так что в этом столбце отображается YES для права SELECT и NO для остальных прав.

role_udt_grants

Представление role_udt_grants предназначено для указания прав USAGE, предоставленных для пользовательских типов, где праводателем или правополучателем является текущая разрешенная роль. Дополнительную информацию можно найти в описании udt_privileges. Единственное существенное отличие этого представления от udt_privileges заключается в том, что в этом представлении опускаются объекты, к которым текущий пользователь получил доступ через роль PUBLIC. Поскольку в QHB для типов данных нет фактических прав, кроме неявно предоставляемых роли PUBLIC, это представление пустое.

Таблица 36. Столбцы role_udt_grants

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, содержащей тип

udt_name sql_identifier

Имя типа

privilege_type character_data

Всегда TYPE USAGE

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

role_usage_grants

В представлении role_usage_grants указаны права USAGE, предоставленные для разных видов объектов, где праводателем или правополучателем является текущая разрешенная роль. Дополнительную информацию можно найти в описании usage_privileges. Единственное существенное отличие этого представления от usage_privileges заключается в том, что в этом представлении опускаются объекты, к которым текущий пользователь получил доступ через роль PUBLIC.

Таблица 37. Столбцы role_usage_grants

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

object_catalog sql_identifier

Имя базы данных, содержащей объект (всегда текущая база данных)

object_schema sql_identifier

Имя схемы, содержащей объект

object_name sql_identifier

Имя объекта

object_type character_data

COLLATION, DOMAIN, FOREIGN DATA WRAPPER, FOREIGN SERVER или SEQUENCE

privilege_type character_data

Всегда USAGE

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

routine_column_usage

Представление routine_column_usage предназначено для указания всех столбцов, используемых в функции или процедуре. В настоящее время эта информация в QHB не отслеживается.

Таблица 38. Столбцы routine_column_usage

Столбец Тип

Описание

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

routine_catalog sql_identifier

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

routine_schema sql_identifier

Имя схемы, содержащей функцию

routine_name sql_identifier

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

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу, используемую функцией

table_name sql_identifier

Имя таблицы, используемой функцией

column_name sql_identifier

Имя столбца, используемого функцией

routine_privileges

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

Таблица 39. Столбцы routine_privileges

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

routine_catalog sql_identifier

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

routine_schema sql_identifier

Имя схемы, содержащей функцию

routine_name sql_identifier

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

privilege_type character_data

Всегда EXECUTE (единственный тип прав для функций)

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

routine_routine_usage

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

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

Таблица 40. Столбцы routine_routine_usage

Столбец Тип

Описание

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей использующую функцию

specific_name sql_identifier

«Собственное имя» использующей функции

routine_catalog sql_identifier

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

routine_schema sql_identifier

Имя схемы, содержащей функцию, используемую первой функцией

routine_name sql_identifier

«Собственное имя» функции, используемой первой функцией

routine_sequence_usage

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

Таблица 41. Столбцы routine_sequence_usage

Столбец Тип

Описание

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

routine_catalog sql_identifier

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

routine_schema sql_identifier

Имя схемы, содержащей функцию

routine_name sql_identifier

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

sequence_catalog sql_identifier

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

sequence_schema sql_identifier

Имя схемы, содержащей последовательность, используемую функцией

sequence_name sql_identifier

Имя последовательности, используемой функцией

routine_table_usage

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

Таблица 42. Столбцы routine_table_usage

Столбец Тип

Описание

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

routine_catalog sql_identifier

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

routine_schema sql_identifier

Имя схемы, содержащей функцию

routine_name sql_identifier

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

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу, используемую функцией

table_name sql_identifier

Имя таблицы, используемой функцией

routines

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

Таблица 43. Столбцы routines

Столбец Тип

Описание

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

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

routine_catalog sql_identifier

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

routine_schema sql_identifier

Имя схемы, содержащей функцию

routine_name sql_identifier

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

routine_type character_data

FUNCTION для функции, PROCEDURE для процедуры

module_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

module_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

module_name sql_identifier

Относится к функциональности, отсутствующей в QHB

udt_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

udt_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

udt_name sql_identifier

Относится к функциональности, отсутствующей в QHB

data_type character_data

Тип данных результата функции, если это встроенный тип, или ARRAY, если это некоторый массив (в этом случае см. представление element_types), иначе USER-DEFINED (в этом случае тип описывается в udt_name и связанных столбцах). NULL для процедуры.

character_maximum_length cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

character_octet_length cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

character_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_catalog sql_identifier

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

collation_schema sql_identifier

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

collation_name sql_identifier

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

numeric_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

numeric_precision_radix cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

numeric_scale cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

datetime_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

interval_type character_data

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

interval_precision cardinal_number

Всегда NULL, поскольку эта информация неприменима к типам данных результатов в QHB

type_udt_catalog sql_identifier

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

type_udt_schema sql_identifier

Имя схемы, в которой определен тип данных результата функции. NULL для процедуры.

type_udt_name sql_identifier

Имя типа данных результата функции. NULL для процедуры.

scope_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

scope_name sql_identifier

Относится к функциональности, отсутствующей в QHB

maximum_cardinality cardinal_number

Всегда NULL, поскольку в QHB массивы всегда имеют неограниченную максимальную емкость

dtd_identifier sql_identifier

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

routine_body character_data

Если это функция SQL, то SQL, иначе EXTERNAL.

routine_definition character_data

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

external_name character_data

Если это функция на C, то внешнее имя (объектный символ) функции; иначе NULL. (Это будет то же значение, которое выводится в routine_definition.)

external_language character_data

Язык, на котором написана функция

parameter_style character_data

Всегда GENERAL (В стандарте SQL определены и другие стили параметров, которые отсутствуют в QHB.)

is_deterministic yes_or_no

Если функция объявлена как постоянная (IMMUTABLE) (в стандарте SQL она называется детерминированной), то YES, иначе NO. (запросить другие категории изменчивости, доступные в QHB, через информационную схему нельзя.)

sql_data_access character_data

Всегда MODIFIES, что означает, что функция может модифицировать данные SQL. Для QHB эта информация бесполезна.

is_null_call yes_or_no

Если функция автоматически возвращает NULL, когда один из аргументов равен NULL, то YES, иначе NO. NULL для процедуры.

sql_path character_data

Относится к функциональности, отсутствующей в QHB

schema_level_routine yes_or_no

Всегда YES (Противоположное значение было бы у метода пользовательского типа, но в QHB эта функциональность отсутствует.)

max_dynamic_result_sets cardinal_number

Относится к функциональности, отсутствующей в QHB

is_user_defined_cast yes_or_no

Относится к функциональности, отсутствующей в QHB

is_implicitly_invocable yes_or_no

Относится к функциональности, отсутствующей в QHB

security_type character_data

Если функция выполняется с правами текущего пользователя, то INVOKER; если функция выполняется с правами пользователя, определившего ее, то DEFINER.

to_sql_specific_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

to_sql_specific_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

to_sql_specific_name sql_identifier

Относится к функциональности, отсутствующей в QHB

as_locator yes_or_no

Относится к функциональности, отсутствующей в QHB

created time_stamp

Относится к функциональности, отсутствующей в QHB

last_altered time_stamp

Относится к функциональности, отсутствующей в QHB

new_savepoint_level yes_or_no

Относится к функциональности, отсутствующей в QHB

is_udt_dependent yes_or_no

В настоящее время всегда NO. Вариант YES относится к функциональности, отсутствующей в QHB

result_cast_from_data_type character_data

Относится к функциональности, отсутствующей в QHB

result_cast_as_locator yes_or_no

Относится к функциональности, отсутствующей в QHB

result_cast_char_max_length cardinal_number

Относится к функциональности, отсутствующей в QHB

result_cast_char_octet_length cardinal_number

Относится к функциональности, отсутствующей в QHB

result_cast_char_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_char_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_char_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_collation_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_collation_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_collation_name sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_numeric_precision cardinal_number

Относится к функциональности, отсутствующей в QHB

result_cast_numeric_precision_radix cardinal_number

Относится к функциональности, отсутствующей в QHB

result_cast_numeric_scale cardinal_number

Относится к функциональности, отсутствующей в QHB

result_cast_datetime_precision cardinal_number

Относится к функциональности, отсутствующей в QHB

result_cast_interval_type character_data

Относится к функциональности, отсутствующей в QHB

result_cast_interval_precision cardinal_number

Относится к функциональности, отсутствующей в QHB

result_cast_type_udt_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_type_udt_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_type_udt_name sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_scope_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_scope_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_scope_name sql_identifier

Относится к функциональности, отсутствующей в QHB

result_cast_maximum_cardinality cardinal_number

Относится к функциональности, отсутствующей в QHB

result_cast_dtd_identifier sql_identifier

Относится к функциональности, отсутствующей в QHB

schemata

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

Таблица 44. Столбцы schemata

Столбец Тип

Описание

catalog_name sql_identifier

Имя базы данных, содержащей схему (всегда текущая база данных)

schema_name sql_identifier

Имя схемы

schema_owner sql_identifier

Имя владельца схемы

default_character_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

default_character_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

default_character_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

sql_path character_data

Относится к функциональности, отсутствующей в QHB

sequences

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

Таблица 45. Столбцы sequences

Столбец Тип

Описание

sequence_catalog sql_identifier

Имя базы данных, содержащей последовательность (всегда текущая база данных)

sequence_schema sql_identifier

Имя схемы, содержащей последовательность

sequence_name sql_identifier

Имя последовательности

data_type character_data

Тип данных последовательности

numeric_precision cardinal_number

Этот столбец содержит (объявленную или неявную) точность типа данных последовательности. Точность определяет количество значащих цифр. Она может выражаться в десятичных (по основанию 10) или двоичных (по основанию 2) единицах, согласно столбцу numeric_precision_radix.

numeric_precision_radix cardinal_number

Этот столбец определяет, по какому основанию выражаются значения в столбцах numeric_precision и numeric_scale. Значение может быть 2 или 10.

numeric_scale cardinal_number

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

start_value character_data

Начальное значение последовательности

minimum_value character_data

Минимальное значение последовательности

maximum_value character_data

Максимальное значение последовательности

increment character_data

Шаг приращения последовательности

cycle_option yes_or_no

YES, если последовательность зацикливается, иначе NO

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

sql_features

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

Таблица 46. Столбцы sql_features

Столбец Тип

Описание

feature_id character_data

Строка идентификатора функциональности

feature_name character_data

Описательное имя функциональности

sub_feature_id character_data

Строка идентификатора подчиненной функциональности или строка нулевой длины, если это не подчиненная функциональность

sub_feature_name character_data

Описательное имя подчиненной функциональности или строка нулевой длины, если это не подчиненная функциональность

is_supported yes_or_no

YES, если функциональность полностью поддерживается текущей версией QHB, NO, если это не так

is_verified_by character_data

Всегда NULL, поскольку группа разработки QHB не проводит формального тестирования функционального соответствия

comments character_data

Необязательный комментарий о поддерживаемом статусе функциональности

sql_implementation_info

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

Таблица 47. Столбцы sql_implementation_info

Столбец Тип

Описание

implementation_info_id character_data

Строка идентификатора единицы информации реализации

implementation_info_name character_data

Описательное имя единицы информации реализации

integer_value cardinal_number

Значение единицы информации реализации или NULL, если это значение содержится в столбце character_value

character_value character_data

Значение единицы информации реализации или NULL, если это значение содержится в столбце integer_value

comments character_data

Необязательный комментарий, относящийся к единице информации реализации

sql_parts

В таблице sql_parts содержится информация о различных частях стандарта SQL, поддерживаемых QHB.

Таблица 48. Столбцы sql_parts

Столбец Тип

Описание

feature_id character_data

Строка идентификатора, содержащая номер части

feature_name character_data

Описательное имя части

is_supported yes_or_no

YES, если часть полностью поддерживается текущей версией QHB, NO, если это не так

is_verified_by character_data

Всегда NULL, поскольку группа разработки QHB не проводит формального тестирования функционального соответствия

comments character_data

Необязательный комментарий о поддерживаемом статусе части

sql_sizing

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

Таблица 49. Столбцы sql_sizing

Столбец Тип

Описание

sizing_id cardinal_number

Идентификатор элемента размера

sizing_name character_data

Описательное имя элемента размера

supported_value cardinal_number

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

comments character_data

Необязательный комментарий, относящийся к элементу размера

table_constraints

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

Таблица 50. Столбцы table_constraints

Столбец Тип

Описание

constraint_catalog sql_identifier

Имя базы данных, содержащей ограничение (всегда текущая база данных)

constraint_schema sql_identifier

Имя схемы, содержащей ограничение

constraint_name sql_identifier

Имя ограничения

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу

table_name sql_identifier

Имя таблицы

constraint_type character_data

Тип ограничения: CHECK, FOREIGN KEY, PRIMARY KEY или UNIQUE

is_deferrable yes_or_no

YES, если ограничение откладываемое, NO, если это не так

initially_deferred yes_or_no

YES, если ограничение откладываемое и отложенное изначально, NO, если это не так

enforced yes_or_no

Относится к функциональности, отсутствующей в QHB (в настоящее время всегда YES)

nulls_distinct yes_or_no

Если это ограничение уникальности, то YES, если ограничение воспринимает NULL как различные значение, или NO, если оно воспринимает NULL как одинаковые; для других типов ограничений — NULL.

table_privileges

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

Таблица 51. Столбцы table_privileges

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу

table_name sql_identifier

Имя таблицы

privilege_type character_data

Тип права: SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES или TRIGGER

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

with_hierarchy yes_or_no

В стандарте SQL WITH HIERARCHY OPTION является отдельным (подчиненным) правом, позволяющим выполнять определенные операции в иерархиях наследования таблиц. В QHB эта возможность входит в право SELECT, так что в этом столбце отображается YES для права SELECT и NO для остальных прав.

tables

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

Таблица 52. Столбцы tables

Столбец Тип

Описание

table_catalog sql_identifier

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

table_schema sql_identifier

Имя схемы, содержащей таблицу

table_name sql_identifier

Имя таблицы

table_type character_data

Тип таблицы: BASE TABLE для постоянной базовой таблицы (обычный тип таблиц), VIEW для представления, FOREIGN для сторонней таблицы или LOCAL TEMPORARY для временной таблицы

self_referencing_column_name sql_identifier

Относится к функциональности, отсутствующей в QHB

reference_generation character_data

Относится к функциональности, отсутствующей в QHB

user_defined_type_catalog sql_identifier

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

user_defined_type_schema sql_identifier

Если таблица является типизированной, это имя схемы, содержащей нижележащий тип данных, иначе NULL.

user_defined_type_name sql_identifier

Если таблица является типизированной, это имя нижележащего типа данных, иначе NULL.

is_insertable_into yes_or_no

YES, если в таблицу можно добавлять данные, и NO, если нельзя (В базовые таблицы всегда можно добавлять данные, но в представления — не обязательно.)

is_typed yes_or_no

YES, если таблица является типизированной, NO, если это не так

commit_action character_data

Пока не реализовано

transforms

В представлении transforms содержится информация о трансформациях, определенных в текущей базе данных. Если точнее, оно содержит строку для каждой функции, участвующей в трансформации (функции «из SQL» или «в SQL»).

Таблица 53. Столбцы transforms

Столбец Тип

Описание

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, содержащей тип, для которого предназначена трансформация

udt_name sql_identifier

Имя типа, для которого предназначена трансформация

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

group_name sql_identifier

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

transform_type character_data

FROM SQL или TO SQL

triggered_update_columns

Для триггеров в текущей базе данных, для которых задан список столбцов (например, UPDATE OF column1, column2), в представлении triggered_update_columns указаны эти столбцы. Триггеры, для которых не задан список столбцов, в это представление не включаются. Отображаются только те столбцы, к которым имеет доступ текущий пользователь (будучи их владельцем или обладая некоторыми правами помимо SELECT).

Таблица 54. Столбцы triggered_update_columns

Столбец Тип

Описание

trigger_catalog sql_identifier

Имя базы данных, содержащей триггер (всегда текущая база данных)

trigger_schema sql_identifier

Имя схемы, содержащей триггер

trigger_name sql_identifier

Имя триггера

event_object_catalog sql_identifier

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

event_object_schema sql_identifier

Имя схемы, содержащей таблицу, для которой определен триггер

event_object_table sql_identifier

Имя таблицы, для которой определен триггер

event_object_column sql_identifier

Имя столбца, для которого определен триггер

triggers

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

Таблица 55. Столбцы triggers

Столбец Тип

Описание

trigger_catalog sql_identifier

Имя базы данных, содержащей триггер (всегда текущая база данных)

trigger_schema sql_identifier

Имя схемы, содержащей триггер

trigger_name sql_identifier

Имя триггера

event_manipulation character_data

Событие, запускающее триггер (INSERT, UPDATE или DELETE)

event_object_catalog sql_identifier

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

event_object_schema sql_identifier

Имя схемы, содержащей таблицу, для которой определен триггер

event_object_table sql_identifier

Имя таблицы, для которой определен триггер

action_order cardinal_number

Порядок срабатывания триггеров для одной таблицы, имеющих одинаковые значения event_manipulation, action_timing и action_orientation. В QHB триггеры срабатывают по порядку их имен, что отражается в этом столбце.

action_condition character_data

Условие WHEN триггера или NULL, если его нет (также NULL, если таблица не принадлежит текущей разрешенной роли)

action_statement character_data

Оператор, выполняемый триггером (в настоящее время всегда EXECUTE FUNCTION функция(...))

action_orientation character_data

Определяет, срабатывает ли триггер для каждой обрабатываемой строки или для каждого оператора (ROW или STATEMENT)

action_timing character_data

Время срабатывания триггера (BEFORE, AFTER или INSTEAD OF)

action_reference_old_table sql_identifier

Имя «старой» таблицы переходов или NULL, если таковой нет

action_reference_new_table sql_identifier

Имя «новой» таблицы переходов или NULL, если таковой нет

action_reference_old_row sql_identifier

Относится к функциональности, отсутствующей в QHB

action_reference_new_row sql_identifier

Относится к функциональности, отсутствующей в QHB

created time_stamp

Относится к функциональности, отсутствующей в QHB

Триггеры в QHB не совпадают со стандартом SQL в двух аспектах, влияющих на их представление в информационной схеме. Во-первых, имена триггеров являются локальными для каждой таблицы в QHB, а не независимыми объектами схемы. Поэтому в одной схеме могут быть определены повторяющиеся имена триггеров, если они принадлежат разным таблицам. (Значения trigger_catalog и trigger_schema в действительности относятся к таблице, для которой определен триггер.) Во-вторых, в QHB триггеры можно определить так, чтобы они срабатывали при нескольких событиях (например, ON INSERT OR UPDATE), тогда как стандарт SQL допускает только одно событие. Если триггер настроен на срабатывание при нескольких событиях, он представляется в информационной схеме в виде нескольких строк, по одной для каждого типа события. Вследствие этих двух особенностей первичный ключ в представлении triggers на самом деле (trigger_catalog, trigger_schema, event_object_table, trigger_name, event_manipulation), а не (trigger_catalog, trigger_schema, trigger_name), как указано в стандарте SQL. Тем не менее, если вы определяете триггеры в соответствии со стандартом SQL (имена триггеров уникальны в схеме и каждый триггер привязан только к одному событию), это расхождение ни на что не повлияет.

udt_privileges

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

Таблица 56. Столбцы udt_privileges

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

udt_catalog sql_identifier

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

udt_schema sql_identifier

Имя схемы, содержащей тип

udt_name sql_identifier

Имя типа

privilege_type character_data

Всегда TYPE USAGE

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

usage_privileges

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

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

В QHB последовательности в дополнение к праву USAGE поддерживают также права SELECT и UPDATE. Они являются нестандартными и поэтому не видны в информационной схеме.

Таблица 57. Столбцы usage_privileges

Столбец Тип

Описание

grantor sql_identifier

Имя роли, предоставившей право (праводатель)

grantee sql_identifier

Имя роли, которой было предоставлено право (правополучатель)

object_catalog sql_identifier

Имя базы данных, содержащей объект (всегда текущая база данных)

object_schema sql_identifier

Имя схемы, содержащей объект, если применимо, иначе пустая строка

object_name sql_identifier

Имя объекта

object_type character_data

COLLATION, DOMAIN, FOREIGN DATA WRAPPER, FOREIGN SERVER или SEQUENCE

privilege_type character_data

Всегда USAGE

is_grantable yes_or_no

YES, если право может передаваться, NO, если не может

user_defined_types

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

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

Таблица 58. Столбцы user_defined_types

Столбец Тип

Описание

user_defined_type_catalog sql_identifier

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

user_defined_type_schema sql_identifier

Имя схемы, содержащей тип

user_defined_type_name sql_identifier

Имя типа

user_defined_type_category character_data

В настоящее время всегда STRUCTURED

is_instantiable yes_or_no

Относится к функциональности, отсутствующей в QHB

is_final yes_or_no

Относится к функциональности, отсутствующей в QHB

ordering_form character_data

Относится к функциональности, отсутствующей в QHB

ordering_category character_data

Относится к функциональности, отсутствующей в QHB

ordering_routine_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

ordering_routine_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

ordering_routine_name sql_identifier

Относится к функциональности, отсутствующей в QHB

reference_type character_data

Относится к функциональности, отсутствующей в QHB

data_type character_data

Относится к функциональности, отсутствующей в QHB

character_maximum_length cardinal_number

Относится к функциональности, отсутствующей в QHB

character_octet_length cardinal_number

Относится к функциональности, отсутствующей в QHB

character_set_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

character_set_name sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_catalog sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_schema sql_identifier

Относится к функциональности, отсутствующей в QHB

collation_name sql_identifier

Относится к функциональности, отсутствующей в QHB

numeric_precision cardinal_number

Относится к функциональности, отсутствующей в QHB

numeric_precision_radix cardinal_number

Относится к функциональности, отсутствующей в QHB

numeric_scale cardinal_number

Относится к функциональности, отсутствующей в QHB

datetime_precision cardinal_number

Относится к функциональности, отсутствующей в QHB

interval_type character_data

Относится к функциональности, отсутствующей в QHB

interval_precision cardinal_number

Относится к функциональности, отсутствующей в QHB

source_dtd_identifier sql_identifier

Относится к функциональности, отсутствующей в QHB

ref_dtd_identifier sql_identifier

Относится к функциональности, отсутствующей в QHB

user_mapping_options

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

Таблица 59. Столбцы user_mapping_options

Столбец Тип

Описание

authorization_identifier sql_identifier

Имя сопоставляемого пользователя или PUBLIC, если это сопоставление для всех пользователей

foreign_server_catalog sql_identifier

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

foreign_server_name sql_identifier

Имя стороннего сервера, используемого для этого сопоставления

option_name sql_identifier

Имя параметра

option_value character_data

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

user_mappings

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

Таблица 60. Столбцы user_mappings

Столбец Тип

Описание

authorization_identifier sql_identifier

Имя сопоставляемого пользователя или PUBLIC, если это сопоставление для всех пользователей

foreign_server_catalog sql_identifier

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

foreign_server_name sql_identifier

Имя стороннего сервера, используемого для этого сопоставления

view_column_usage

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

Примечание
Столбцы системных таблиц в это представление не включаются. В будущем это будет исправлено.

Таблица 61. Столбцы view_column_usage

Столбец Тип

Описание

view_catalog sql_identifier

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

view_schema sql_identifier

Имя схемы, содержащей представление

view_name sql_identifier

Имя представления

table_catalog sql_identifier

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

table_schema sql_identifier

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

table_name sql_identifier

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

column_name sql_identifier

Имя столбца, используемого представлением

view_routine_usage

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

Таблица 62. Столбцы view_routine_usage

Столбец Тип

Описание

view_catalog sql_identifier

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

view_schema sql_identifier

Имя схемы, содержащей представление

view_name sql_identifier

Имя представления

specific_catalog sql_identifier

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

specific_schema sql_identifier

Имя схемы, содержащей функцию

specific_name sql_identifier

«Собственное имя» функции. Подробную информацию см. в описании представления routines.

view_table_usage

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

Примечание
Системные таблицы в это представление не включаются. В будущем это будет исправлено.

Таблица 63. Столбцы view_table_usage

Столбец Тип

Описание

view_catalog sql_identifier

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

view_schema sql_identifier

Имя схемы, содержащей представление

view_name sql_identifier

Имя представления

table_catalog sql_identifier

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

table_schema sql_identifier

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

table_name sql_identifier

Имя таблицы, используемой представлением

views

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

Таблица 64. Столбцы views

Столбец Тип

Описание

view_catalog sql_identifier

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

view_schema sql_identifier

Имя схемы, содержащей представление

view_name sql_identifier

Имя представления

view_definition character_data

Выражение запроса, определяющее представление (NULL, если представление не принадлежит текущей разрешенной роли)

check_option character_data

CASCADED или LOCAL, если для представление определено свойство CHECK OPTION, и NONE, если не определено

is_updatable yes_or_no

YES, если представление допускает изменение (команды UPDATE и DELETE), и NO, если не допускает

is_insertable_into yes_or_no

YES, если представление допускает добавление данных (команду INSERT), и NO, если не допускает

is_trigger_updatable yes_or_no

YES, если для представления определен триггер INSTEAD OF UPDATE, и NO, если не определен

is_trigger_deletable yes_or_no

YES, если для представления определен триггер INSTEAD OF DELETE, и NO, если не определен

is_trigger_insertable_into yes_or_no

YES, если для представления определен триггер INSTEAD OF INSERT, и NO, если не определен

Серверное программирование

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

Примечание
В других частях, в главах Процедурные языки и PL/pgSQL — процедурный язык SQL описываются языки программирования на стороне сервера, доступные в дистрибутиве QHB, а также общие вопросы, касающиеся языков программирования на стороне сервера. Но прежде чем погружаться в этот материал, важно изучить разделы главы Расширение SQL, где рассматриваются функции.

Содержание

Расширение SQL

Триггеры

Триггеры событий

Интерфейс программирования сервера

Фоновые рабочие процессы

Логическое декодирование

Отслеживание прогресса репликации

Модули архивирования

Расширение SQL

В следующих разделах мы рассмотрим, как можно расширить язык запросов SQL в QHB посредством добавления:

функций (начиная с раздела Пользовательские функции)

агрегатов (начиная с раздела Пользовательские агрегаты)

типов данных (начиная с раздела Пользовательские типы)

операторов (начиная с раздела Пользовательские операторы)

классов операторов для индексов (начиная с раздела Интерфейсные расширения для индексов)

пакетов связанных объектов (начиная с раздела Упаковка связанных объектов в расширение)

Как работает расширяемость

QHB является расширяемой, потому что ее работа управляется каталогами. Если вы знакомы со стандартными реляционными системами баз данных, то знаете, что они хранят информацию о базах данных, таблицах, столбцах и т. д. в так называемых системных каталогах (в некоторых системах называемых словарями данных). Каталоги доступны пользователю в виде таблиц, похожих на любые другие таблицы, но СУБД хранит в них свою внутреннюю отчетность. Одно из ключевых отличий QHB от стандартных реляционных систем баз данных состоит в том, что QHB хранит в своих каталогах гораздо больше информации: не только о таблицах и столбцах, но и обо всех существующих типах данных, функциях, методах доступа и т. п. Эти таблицы могут быть изменены пользователем, а поскольку работа QHB основана на этих таблицах, это означает, что пользователи могут расширять QHB. Для сравнения: обычные системы баз данных можно расширить, только изменив жестко закодированные процедуры в их исходном коде или загрузив модули, специально написанные поставщиком СУБД.

Более того, сервер QHB может динамически загружать в себя код, написанный пользователем. То есть пользователь может выбрать файл с объектным кодом (например разделяемую библиотеку), который реализует новый тип или функцию, и QHB будет загружать его по мере необходимости. Код, написанный на SQL, добавить на сервер еще проще. Эта способность изменять свою работу на ходу делает QHB исключительно подходящей для быстрого создания прототипов новых приложений и структур хранения.

Система типов QHB

Типы данных QHB можно разделить на базовые типы, контейнерные типы, домены и псевдотипы.

Базовые типы

Базовые типы — это типы вроде integer, которые реализуются ниже уровня языка SQL (обычно на низкоуровневом языке, например C или RUST). В целом, они соответствуют тому, что часто называют абстрактными типами данных. QHB может работать с такими типами только через функции, предоставляемые пользователем, и понимает поведение таких типов только в той степени, в которой пользователь их описывает. Встроенные базовые типы описаны в главе Типы данных.

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

Контейнерные типы

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

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

Составные типы, или типы строк, создаются всякий раз, когда пользователь создает таблицу. Кроме того, с помощью команды CREATE TYPE можно создать «независимый» составной тип без привязки к таблице. Составной тип — это просто список типов со связанными именами полей. Значением составного типа является строка или запись из значений полей. Подробную информацию см. в разделе Составные типы.

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

Домены

Домен основывается на конкретном нижележащем типе и во многих смыслах взаимозаменяем с ним. Однако у домена могут быть ограничения, сужающие его допустимые значения до подмножества, допускаемого нижележащим типом. Домены создаются с помощью команды SQL CREATE DOMAIN. Подробную информацию см. в разделе Доменные типы.

Псевдотипы

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

Полиморфные типы

Особый интерес представляют некоторые псевдотипы, называемые полиморфными типами, которые используются для объявления полиморфных функций. Особенности этих типов позволяют определить одну функцию, которая будет работать со множеством различных типов данных, причем конкретный тип (или типы) определяется из типов данных, фактически переданных ей в конкретном вызове. Полиморфные типы приведены в Таблице 1. Некоторые примеры их использования можно найти в подразделе Полиморфные функции SQL.

Таблица 1. Полиморфные типы

Имя | Семейство | Описание
anyelement | Простое | Указывает, что функция принимает любой тип данных
anyarray | Простое | Указывает, что функция принимает любой тип массива
anynonarray | Простое | Указывает, что функция принимает любой тип, отличный от массива
anyenum | Простое | Указывает, что функция принимает любой перечислимый тип (см. раздел Перечислимые типы)
anyrange | Простое | Указывает, что функция принимает любой диапазонный тип (см. раздел Диапазонные типы)
anymultirange | Простое | Указывает, что функция принимает любой мультидиапазонный тип (см. раздел Диапазонные типы)
anycompatible | Общее | Указывает, что функция принимает любой тип данных, с автоматическим приведением нескольких аргументов к общему типу
anycompatiblearray | Общее | Указывает, что функция принимает любой тип массива, с автоматическим приведением нескольких аргументов к общему типу
anycompatiblenonarray | Общее | Указывает, что функция принимает любой тип, отличный от массива, с автоматическим приведением нескольких аргументов к общему типу
anycompatiblerange | Общее | Указывает, что функция принимает любой диапазонный тип, с автоматическим приведением нескольких аргументов к общему типу
anycompatiblemultirange | Общее | Указывает, что функция принимает любой мультидиапазонный тип, с автоматическим приведением нескольких аргументов к общему типу

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

Для «простого» семейства полиморфных типов правила сочетания и выведения типов действуют следующим образом:

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

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

Аналогично если одни позиции объявлены как anyrange, а другие как anyelement или anyarray, фактическим диапазонным типом в позициях anyrange должен быть диапазон, подтип которого совпадает с типом, передаваемым в позициях anyelement, и типом элементов в позициях anyarray. Если имеются позиции, объявляемые как anymultirange, их фактический мультидиапазонный тип должен содержать диапазоны, соответствующие параметрам, объявленным как anyrange, и базовые элементы, соответствующие параметрам, объявленным как anyelement и anyarray.

Таким образом, когда несколько аргументов объявлены с полиморфным типом, в конечном счете допускается только одна определенная комбинация фактических типов аргументов. Например, функция, объявленная как equal(anyelement, anyelement), примет в аргументах любые два значения, но только если они принадлежат к одному типу данных.

Если возвращаемое значение функции объявляется как полиморфный тип, должна быть хотя бы одна позиция аргумента, тоже относящаяся к полиморфному типу, а предоставленный в качестве аргумента фактический тип данных определяет фактический тип результата для этого вызова. Например, если бы уже не существовало механизма индексирования массива, можно было бы создать функцию, реализующую индексирование как subscript(anyarray, integer) returns anyelement. Это объявление ограничивает первый фактический аргумент типом массива и позволяет синтаксическому анализатору вывести из него правильный тип результата. Другой пример: функция, объявленная как f(anyarray) returns anyenum будет принимать только массивы перечислимого типа.

В большинстве случаев синтаксический анализатор может вывести фактический тип данных для полиморфного типа результата из аргументов другого полиморфного типа; например, anyarray можно вывести из anyelement и наоборот. Исключение составляет полиморфный результат типа anyrange, требующий аргумент типа anyrange; его нельзя вывести из аргументов anyarray или anyelement. Это связано с тем, что у нескольких диапазонных типов может быть один подтип.

Обратите внимание, что псевдотипы anynonarray и anyenum представляют не отдельные типовые переменные; они принадлежат к тому же типу, что и anyelement, всего лишь с одним дополнительным ограничением. Например, объявление функции как f(anyelement, anyenum) равнозначно объявлению ее как f(anyenum, anyenum): оба фактических аргумента должны относиться к одному перечислимому типу.

Для «общего» семейства полиморфных типов правила сочетания и выведения типов работают примерно так же, как для «простого» семейства, с единственным главным отличием: фактическим типам аргументов необязательно быть идентичными, если их можно неявно привести к одному общему типу. Этот общий тип выбирается по тем же правилам, что действуют в UNION и связанных конструкциях (см. раздел UNION, CASE и связанные конструкции). При выборе общего типа учитываются фактические типы аргументов anycompatible и anycompatiblenonarray, типы элементов массива аргументов anycompatiblearray, диапазонные подтипы аргументов anycompatiblerange и мультидиапазонные подтипы аргументов anycompatiblemultirange. Если присутствует anycompatiblenonarray, то общий тип должен быть отличным от массива. После определения общего типа аргументы в позициях anycompatible и anycompatiblenonarray автоматически приводятся к этому типу, а аргументы в позициях anycompatiblearray автоматически приводятся к типу массива для этого типа.

Поскольку невозможно выбрать диапазонный тип, зная только его подтип, использование anycompatiblerange и/или anycompatiblemultirange требует, чтобы все аргументы, объявленные с этим типом, имели один фактический диапазонный и/или мультидиапазонный тип, и чтобы этот тип соответствовал выбранному общему типу, поэтому в приведении типов для диапазонных значений нет необходимости. Как и в случае с anyrange и anymultirange, использование anycompatiblerange и anymultirange в качестве типа результата функции требует наличия у нее аргумента типа anycompatiblerange или anycompatiblemultirange.

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

«Простое» и «общее» полиморфные семейства представляют два независимых набора переменных типов. Рассмотрим, например, объявление функции:

CREATE FUNCTION myfunc(a anyelement, b anyelement,
                       c anycompatible, d anycompatible)
RETURNS anycompatible AS ...

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

Функция с переменным числом аргументов (которая принимает переменное число аргументов, как описано в подразделе Функции SQL с переменным числом аргументов) тоже может быть полиморфной: для этого надо объявить ее последний параметр как VARIADIC anyarray или VARIADIC anycompatiblearray. В целях сопоставления аргументов и определения фактического типа результата такая функция ведет себя так же, как если бы в ней записали соответствующее число параметров anynonarray или anycompatiblenonarray.

Пользовательские функции

В QHB предусмотрено четыре вида функций:

функции на языке запросов (функции, написанные на SQL)

функции на процедурном языке (например, на PL/pgSQL или PL/Tcl)

внутренние функции

функции на нативном языке

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

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

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

Для лучшего понимания примеров при чтении этой главы может быть полезно параллельно смотреть справочную страницу команды CREATE FUNCTION.

Пользовательские процедуры

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

Процедуры определяются командой CREATE PROCEDURE, а не CREATE FUNCTION.

Процедуры, в отличие от функций, не возвращают значения, поэтому у команды CREATE PROCEDURE нет предложения RETURNS. Однако процедуры могут возвращать данные в вызывающую функцию через выходные параметры.

В том время как функция вызывается как часть запроса или команды DML, процедура вызывается отдельно с помощью команды CALL.

Процедура во время своего выполнения может фиксировать или откатывать транзакции (а затем автоматически начинать новую транзакцию), если команда CALL не является частью явного блока транзакции. Для функции это невозможно.

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

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

Функции и процедуры в совокупности также называются подпрограммами. Существуют команды ALTER ROUTINE и DROP ROUTINE, которые могут работать с функциями и процедурами без необходимости указания точного вида объекта. Однако обратите внимание, что команды CREATE ROUTINE нет.

Функции на языке запросов (SQL)

Функции SQL выполняют произвольный список операторов SQL, возвращая результат последнего запроса в списке. В простом случае (не с множеством) будет возвращена первая строка результата последнего запроса. (Следует учесть, что понятие «первая строка» в результате из нескольких строк определено точно, только если используется ORDER BY). Если последний запрос вообще не вернет никаких строк, будет возвращен NULL.

Как вариант, можно объявить функцию SQL как возвращающую множество (то есть несколько строк), указав в качестве возвращаемого типа функции SETOF некий_тип или объявив ее с указанием RETURNS TABLE(столбцы). В этом случае будут возвращены все строки результата последнего запроса. Более подробная информация приведена ниже.

Тело функции SQL должно представлять собой список операторов SQL, разделенных точкой с запятой. После последнего оператора точку с запятой ставить необязательно. Если не объявлено, что функция возвращает void, последним оператором должен быть SELECT либо INSERT, UPDATE или DELETE с предложением RETURNING.

Любой набор команд на языке SQL можно собрать вместе и определить как функцию. Помимо запросов SELECT эти команды могут включать запросы, изменяющие данные (INSERT, UPDATE, DELETE и MERGE), а также другие команды SQL. (В функциях SQL нельзя использовать команды управления транзакциями, например COMMIT, SAVEPOINT, и некоторые служебные команды, например VACUUM). Однако последней командой должна быть SELECT или команда с предложением RETURNING, возвращающая то, что указано в качестве типа результата функции. Или же, если нужно создать функцию SQL, которая выполняет действие, но не возвращает полезное значение, можно определить ее как возвращающую void. Например, эта функция удаляет строки с отрицательными зарплатами из таблицы emp:

CREATE FUNCTION clean_emp() RETURNS void AS '
    DELETE FROM emp
        WHERE salary < 0;
' LANGUAGE SQL;

SELECT clean_emp();

 clean_emp
-----------
(1 row)

Также можно записать это в виде процедуры, тем самым избежав необходимости указывать возвращаемый тип. Например:

CREATE PROCEDURE clean_emp() AS '
    DELETE FROM emp
        WHERE salary < 0;
' LANGUAGE SQL;

CALL clean_emp();

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

Примечание
До выполнения какой-либо из ее команд тело функции SQL анализируется целиком. Хотя функция SQL может содержать команды, которые изменяют системные каталоги (например, CREATE TABLE), эффекты таких команд не будут видны во время синтаксического анализа последующих команд в функции. Так, например, команды CREATE TABLE foo (...); INSERT INTO foo VALUES(...); не будут работать, как ожидается, если они упакованы в одну SQL-функцию, поскольку на момент синтаксического анализа команды INSERT таблица foo еще не будет существовать. В такой ситуации рекомендуется вместо функции SQL использовать PL/pgSQL.

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

Аргументы для функций SQL

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

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

В более старом подходе с номерами на аргументы ссылаются, используя синтаксис $n: $1 обозначает первый входной аргумент, $2 — второй и т. д. Это будет работать независимо от того, был ли конкретный аргумент объявлен с именем или нет.

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

Аргументы функции SQL могут использоваться только как значения данных, но не как идентификаторы. Так, например, это приемлемо:

INSERT INTO mytable VALUES ($1);

а это не будет работать:

INSERT INTO $1 VALUES (42);

Функции SQL c базовыми типами

Простейшая возможная функция SQL не имеет аргументов и просто возвращает базовый тип, например integer:

CREATE FUNCTION one() RETURNS integer AS $$
    SELECT 1 AS result;
$$ LANGUAGE SQL;

-- Альтернативный синтаксис строковой константы:
CREATE FUNCTION one() RETURNS integer AS '
    SELECT 1 AS result;
' LANGUAGE SQL;

SELECT one();

 one
-----
   1

Обратите внимание, что мы определили псевдоним столбца в теле функции для ее результата (с именем result), но этот псевдоним не виден вне функции. Соответственно, и результат помечен как one, а не result.

Почти так же легко определять функции SQL, которые принимают в качестве аргументов базовые типы:

CREATE FUNCTION add_em(x integer, y integer) RETURNS integer AS $$
    SELECT x + y;
$$ LANGUAGE SQL;

SELECT add_em(1, 2) AS answer;

 answer
--------
      3

Как вариант, можно обойтись без имен аргументов и использовать номера:

CREATE FUNCTION add_em(integer, integer) RETURNS integer AS $$
    SELECT $1 + $2;
$$ LANGUAGE SQL;

SELECT add_em(1, 2) AS answer;

 answer
--------
      3

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

CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$
    UPDATE bank
        SET balance = balance - debit
        WHERE accountno = tf1.accountno;
    SELECT 1;
$$ LANGUAGE SQL;

Пользователь может выполнить эту функцию для снятия 100 рублей со счета 17 следующим образом:

SELECT tf1(17, 100.0);

В этом примере мы выбрали для первого аргумента имя accountno, но оно совпадает с именем столбца в таблице bank. В команде UPDATE accountno ссылается на столбец bank.accountno, поэтому для обращения к аргументу нужно использовать tf1.accountno. Конечно, этого можно было бы избежать, использовав для аргумента другое имя.

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

CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$
    UPDATE bank
        SET balance = balance - debit
        WHERE accountno = tf1.accountno;
    SELECT balance FROM bank WHERE accountno = tf1.accountno;
$$ LANGUAGE SQL;

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

CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$
    UPDATE bank
        SET balance = balance - debit
        WHERE accountno = tf1.accountno
    RETURNING balance;
$$ LANGUAGE SQL;

Если итоговая команда SELECT или предложение RETURNING в функции SQL не возвращает в точности объявленный этой функцией тип результата, QHB автоматически приведет это значение к требуемому типу, если это возможно с неявным приведением или приведением присваиванием. В противном случае следует написать явное приведение. Например, предположим, что мы захотели, чтобы функция add_em из предыдущего примера вернула тип float8. Для этого достаточно написать

CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
    SELECT $1 + $2;
$$ LANGUAGE SQL;

поскольку сумма типа integer может быть неявно приведена к типу float8. (Подробную информацию о приведениях см. в главе Преобразование типов или на справочной странице команды CREATE CAST.)

Функции SQL с составными типами

При написании функций, работающих с аргументами составных типов, следует указывать не только, какой аргумент, но и какой атрибут (поле) этого аргумента нам нужен. Например, предположим, что emp — это таблица, содержащая данные о сотрудниках, и, следовательно, это же имя составного типа каждой строки данной таблицы. А это функция double_salary, вычисляющая, какой была бы чья-либо зарплата, если ее удвоить:

CREATE TABLE emp (
    name        text,
    salary      numeric,
    age         integer,
    cubicle     point
);

INSERT INTO emp VALUES ('Bill', 4200, 45, '(2,1)');

CREATE FUNCTION double_salary(emp) RETURNS numeric AS $$
    SELECT $1.salary * 2 AS salary;
$$ LANGUAGE SQL;

SELECT name, double_salary(emp.*) AS dream
    FROM emp
    WHERE emp.cubicle ~= point '(2,1)';

 name | dream
------+-------
 Bill |  8400

Обратите внимание на использование синтаксиса $1.salary для выбора одного поля из значения строки аргумента. Также обратите внимание, как вызывающая команда SELECT использует указание имя_таблицы.*, чтобы выбрать текущую строку таблицы как составное значение. На строку таблицы также можно сослаться, используя только имя таблицы, например, так:

SELECT name, double_salary(emp) AS dream
    FROM emp
    WHERE emp.cubicle ~= point '(2,1)';

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

Иногда удобно создавать значение составного аргумента на ходу. Это можно сделать с помощью конструкции ROW. Например, мы можем подправить данные перед передачей в функцию:

SELECT name, double_salary(ROW(name, salary * 1.1, age, cubicle)) AS dream
    FROM emp;

Также можно создать функцию, которая возвращает составной тип. Вот пример функции, которая возвращает одну строку emp:

CREATE FUNCTION new_emp() RETURNS emp AS $$
    SELECT text 'None' AS name,
        1000.0 AS salary,
        25 AS age,
        point '(2,2)' AS cubicle;
$$ LANGUAGE SQL;

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

Обратите внимание на два важных условия в определении функции:

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

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

ERROR:  return type mismatch in function declared to return emp
DETAIL:  Final statement returns text instead of point at column 4.
-- ОШИБКА: возвращаемый тип не совпадает в функции, объявленной как возвращающая тип emp
-- ДЕТАЛИЗАЦИЯ: Последний оператор возвращает text вместо point в столбце 4.

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

Другой способ определить ту же функцию:

CREATE FUNCTION new_emp() RETURNS emp AS $$
    SELECT ROW('None', 1000.0, 25, '(2,2)')::emp;
$$ LANGUAGE SQL;

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

Эту функцию можно вызвать напрямую, указав ее в выражении значения:

SELECT new_emp();

         new_emp
--------------------------
 (None,1000.0,25,"(2,2)")

или вызвав ее как табличную функцию:

SELECT * FROM new_emp();

 name | salary | age | cubicle
------+--------+-----+---------
 None | 1000.0 |  25 | (2,2)

Второй способ более подробно описан в подразделе Функции SQL как источники таблиц.

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

SELECT (new_emp()).name;

 name
------
 None

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

SELECT new_emp().name;
ERROR:  syntax error at or near "."
--ОШИБКА: синтаксическая ошибка (примерное положение: ".")
LINE 1: SELECT new_emp().name;
                        ^

Другой вариант — использовать функциональную запись для извлечения атрибута:

SELECT name(new_emp());

 name
------
 None

Как объяснено в подразделе Использование составных типов в запросах, запись поля и функциональная запись равнозначны.

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

CREATE FUNCTION getname(emp) RETURNS text AS $$
    SELECT $1.name;
$$ LANGUAGE SQL;

SELECT getname(new_emp());
 getname
---------
 None
(1 row)

Функции SQL с выходными параметрами

Альтернативный способ описания результатов функции — определить ее с выходными параметрами, как в этом примере:

CREATE FUNCTION add_em (IN x int, IN y int, OUT sum int)
AS 'SELECT x + y'
LANGUAGE SQL;

SELECT add_em(3,7);
 add_em
--------
     10
(1 row)

В сущности, это не отличается от версии с add_em, показанной в подразделе Функции SQL c базовыми типами. Реальная ценность выходных параметров в том, что они предоставляют удобный способ определения функций, возвращающих несколько столбцов. Например,

CREATE FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int)
AS 'SELECT x + y, x * y'
LANGUAGE SQL;

 SELECT * FROM sum_n_product(11,42);
 sum | product
-----+---------
  53 |     462
(1 row)

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

CREATE TYPE sum_prod AS (sum int, product int);

CREATE FUNCTION sum_n_product (int, int) RETURNS sum_prod
AS 'SELECT $1 + $2, $1 * $2'
LANGUAGE SQL;

но в первом варианте нет необходимости тратить время на отдельное определение составного типа, что зачастую бывает удобно. Обратите внимание, что имена, привязанные к выходным параметрам, присвоены им не просто для красоты — они определяют имена столбцов анонимного составного типа. (Если опустить имя выходного параметра, система выберет его самостоятельно.)

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

DROP FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int);
DROP FUNCTION sum_n_product (int, int);

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

Процедуры SQL с выходными параметрами

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

CREATE PROCEDURE tp1 (accountno integer, debit numeric, OUT new_balance numeric) AS $$
    UPDATE bank
        SET balance = balance - debit
        WHERE accountno = tp1.accountno
    RETURNING balance;
$$ LANGUAGE SQL;

Для вызова этой процедуры необходимо добавить аргумент, соответствующий параметру OUT. Обычно принято писать NULL:

CALL tp1(17, 100.0, NULL);

Если вы пишете что-то еще, это должно быть выражение, неявно приводимое к объявленному типу параметра, как и для входных параметров. Однако обратите внимание, что такое выражение не будет вычисляться.

При вызове процедуры из PL/pgSQL вместо NULL следует написать переменную, которая будет получать вывод процедуры. Подробную информацию см. в подразделе Вызов процедуры.

Функции SQL с переменным числом аргументов

Функции SQL могут быть объявлены как принимающие переменное число аргументов, при условии, что все «необязательные» аргументы имеют один тип данных. Необязательные аргументы будут переданы функции в виде массива. Для этого при объявлении функции последний параметр помечается как VARIADIC и должен быть объявлен как имеющий тип массива. Например:

CREATE FUNCTION mleast(VARIADIC arr numeric[]) RETURNS numeric AS $$
    SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;

SELECT mleast(10, -1, 5, 4.4);
 mleast
--------
     -1
(1 row)

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

SELECT mleast(ARRAY[10, -1, 5, 4.4]);    -- это не сработает

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

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

SELECT mleast(VARIADIC ARRAY[10, -1, 5, 4.4]);

Это предотвращает развертывание переменного параметра функции в тип его элемента, тем самым позволяя нормально сопоставить его со значением аргумента-массива. VARIADIC можно добавить только к последнему фактическому аргументу вызова функции.

Указание VARIADIC в вызове также является единственным способом передачи пустого массива в функцию с переменным числом аргументов, например:

SELECT mleast(VARIADIC ARRAY[]::numeric[]);

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

Параметры элемента массива, создаваемые из переменных параметров, обрабатываются как не имеющие собственных имен. Это значит, что вызвать функцию с переменными параметрами, используя именованные аргументы, нельзя (см. раздел Вызов функции), если только при вызове не указано VARIADIC. Например, этот вариант сработает:

SELECT mleast(VARIADIC arr => ARRAY[10, -1, 5, 4.4]);

а эти варианты — нет:

SELECT mleast(arr => 10);
SELECT mleast(arr => ARRAY[10, -1, 5, 4.4]);

Функции SQL со значениями аргументов по умолчанию

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

Например:

CREATE FUNCTION foo(a int, b int DEFAULT 2, c int DEFAULT 3)
RETURNS int
LANGUAGE SQL
AS $$
    SELECT $1 + $2 + $3;
$$;

SELECT foo(10, 20, 30);
 foo
-----
  60
(1 row)

SELECT foo(10, 20);
 foo
-----
  33
(1 row)

SELECT foo(10);
 foo
-----
  15
(1 row)

SELECT foo();  -- не работает, потому что для первого аргумента нет значения по умолчанию
ERROR:  function foo() does not exist
-- ОШИБКА: функция foo() не существует

Вместо ключевого слова DEFAULT можно также использовать знак =.

Функции SQL как источники таблиц

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

Например:

CREATE TABLE foo (fooid int, foosubid int, fooname text);
INSERT INTO foo VALUES (1, 1, 'Joe');
INSERT INTO foo VALUES (1, 2, 'Ed');
INSERT INTO foo VALUES (2, 1, 'Mary');

CREATE FUNCTION getfoo(int) RETURNS foo AS $$
    SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;

SELECT *, upper(fooname) FROM getfoo(1) AS t1;

 fooid | foosubid | fooname | upper
-------+----------+---------+-------
     1 |        1 | Joe     | JOE
(1 row)

Как показывает пример, мы можем работать со столбцами результата функции точно так же, как если бы они были столбцами обычной таблицы.

Обратите внимание, что мы получили из функции только одну строку. Это связано с тем, что мы не использовали указание SETOF. Оно описано в следующем подразделе.

Функции SQL, возвращающие множества

Когда функция SQL объявляется как возвращающая SETOF некий_тип, итоговый запрос функции выполняется полностью, и каждая выводимая ей строка возвращается как элемент результирующего множества.

Это свойство обычно используется при вызове функции в предложении FROM. В этом случае каждая строка, возвращаемая функцией, становится строкой таблицы, видимой запросом. Например, предположим, что таблица foo имеет то же содержимое, что и выше, и выполняем:

CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$
    SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;

SELECT * FROM getfoo(1) AS t1;

После чего получаем:

fooid | foosubid | fooname
-------+----------+---------
    1 |        1 | Joe
    1 |        2 | Ed
(2 rows)

Также можно вернуть несколько строк со столбцами, определяемыми выходными параметрами, следующим образом:

CREATE TABLE tab (y int, z int);
INSERT INTO tab VALUES (1, 2), (3, 4), (5, 6), (7, 8);

CREATE FUNCTION sum_n_product_with_tab (x int, OUT sum int, OUT product int)
RETURNS SETOF record
AS $$
    SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;

SELECT * FROM sum_n_product_with_tab(10);
 sum | product
-----+---------
  11 |      10
  13 |      30
  15 |      50
  17 |      70
(4 rows)

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

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

SELECT * FROM nodes;
   name    | parent
-----------+--------
 Top       |
 Child1    | Top
 Child2    | Top
 Child3    | Top
 SubChild1 | Child1
 SubChild2 | Child1
(6 rows)

CREATE FUNCTION listchildren(text) RETURNS SETOF text AS $$
    SELECT name FROM nodes WHERE parent = $1
$$ LANGUAGE SQL STABLE;

SELECT * FROM listchildren('Top');
 listchildren
--------------
 Child1
 Child2
 Child3
(3 rows)

SELECT name, child FROM nodes, LATERAL listchildren(name) AS child;
  name  |   child
--------+-----------
 Top    | Child1
 Top    | Child2
 Top    | Child3
 Child1 | SubChild1
 Child1 | SubChild2
(5 rows)

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

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

SELECT listchildren('Top');
 listchildren
--------------
 Child1
 Child2
 Child3
(3 rows)

SELECT name, listchildren(name) FROM nodes;
  name  | listchildren
--------+--------------
 Top    | Child1
 Top    | Child2
 Top    | Child3
 Child1 | SubChild1
 Child1 | SubChild2
(5 rows)

В последнем SELECT обратите внимание, что для Child2, Child3 и т. д. нет выходных строк. Это происходит потому, что listchildren возвращает пустое множество для этих аргументов, поэтому строки результата не генерируются. Такое же поведение мы получили при внутреннем соединении с результатом функции с использованием синтаксиса LATERAL.

Поведение QHB с функцией, возвращающей множество, в списке выборки запроса почти не отличается от поведения с такой функцией, записанной в предложении LATERAL FROM. Например,

SELECT x, generate_series(1,5) AS g FROM tab;

почти равнозначно

SELECT x, g FROM tab, LATERAL generate_series(1,5) AS g;

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

Если в списке выборки запроса находится несколько функций, возвращающих множества, поведение сходно с тем, которое мы получаем, помещая функции в один элемент LATERAL ROWS FROM( ... ) предложения FROM. Для каждой строки из нижележащего запроса выдается выходная строка, использующая первый результат из каждой функции, затем выходная строка, использующая второй результат, и так далее. Если какие-либо из функций, возвращающих множества, выдают меньше результатов, чем другие, недостающие данные заменяются значениями NULL, так что общее количество строк, генерируемых для одной нижележащей строки, равно количеству строк, выдаваемых функцией с наибольшим числом строк в возвращаемом множестве. Таким образом, функции, возвращающие множества, выполняются «в унисон», пока не будут исчерпаны, после чего выполнение продолжается со следующей нижележащей строкой.

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

SELECT srf1(srf2(x), srf3(y)), srf4(srf5(z)) FROM tab;

возвращающие множества функции srf2, srf3 и srf5 будут выполняться в унисон для каждой строки tab, а затем к каждой строке, произведенной нижними функциями, будут применяться srf1 и srf4.

Функции, возвращающие множества, нельзя использовать в конструкциях с условным вычислением, таких как CASE или COALESCE. Например, рассмотрим запрос

SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;

Может показаться, что он должен выдать пять копий входных строк, в которых x > 0, и по одной копии всех остальных строк, но на самом деле, поскольку generate_series(1, 5) будет выполняться в неявном элементе LATERAL FROM до того, как выражение CASE вообще будет рассмотрено, запрос должен был бы создать по пять копий каждой входной строки. Во избежание путаницы в таких случаях выдается ошибка при синтаксическом анализе запроса.

Примечание
Если последней командой функции является INSERT, UPDATE или DELETE с RETURNING, эта команда всегда будет выполняться полностью, даже если функция не объявлена с помощью SETOF или вызывающий запрос не извлекает все строки результата. Любые дополнительные строки, созданные предложением RETURNING, просто отбрасываются, но изменения в обрабатываемой таблице все равно произойдут (и все завершатся до возврата из функции).

Функции SQL, возвращающие таблицы

Есть еще один способ объявить функцию как возвращающую множество — использовать синтаксис RETURNS TABLE(столбцы). Это равнозначно использованию одного или нескольких параметров OUT с обозначением функции как возвращающей SETOF record (или SETOF тип единственного выходного параметра, если применимо). Эта запись указана в последних версиях стандарта SQL, так что этот вариант может быть более переносимым, чем использование SETOF.

Например, предыдущий пример с суммой и произведением можно также переписать так:

CREATE FUNCTION sum_n_product_with_tab (x int)
RETURNS TABLE(sum int, product int) AS $$
    SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;

С записью RETURNS TABLE не допускается использование явных параметров OUT или INOUT — все выходные столбцы следует записать в списке TABLE.

Полиморфные функции SQL

Функции SQL могут принимать и возвращать полиморфные типы, описанные в подразделе Полиморфные типы. Вот пример полиморфной функции make_array, которая создает массив из двух элементов произвольного типа данных:

CREATE FUNCTION make_array(anyelement, anyelement) RETURNS anyarray AS $$
    SELECT ARRAY[$1, $2];
$$ LANGUAGE SQL;

SELECT make_array(1, 2) AS intarray, make_array('a'::text, 'b') AS textarray;
 intarray | textarray
----------+-----------
 {1,2}    | {a,b}
(1 row)

Обратите внимание на использование приведения типа 'a'::text, указывающего, что аргумент имеет тип text. Это необходимо, если аргумент является простой строковой константой, поскольку иначе он будет восприниматься как имеющий тип unknown, а массив типов unknown является недопустимым. Без приведения типа вы будете получать ошибки вроде этой:

ERROR:  could not determine polymorphic type because input has type "unknown"
-- ОШИБКА: не удалось определить полиморфный тип, так как входные аргументы имеют тип «unknown»

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

SELECT make_array(1, 2.5) AS numericarray;
ERROR:  function make_array(integer, numeric) does not exist
-- ОШИБКА: функции make_array(integer, numeric) не существует

Альтернативный подход заключается в использовании «общего» семейства полиморфных типов, позволяющего системе попытаться определить подходящий общий тип:

CREATE FUNCTION make_array2(anycompatible, anycompatible)
RETURNS anycompatiblearray AS $$
    SELECT ARRAY[$1, $2];
$$ LANGUAGE SQL;

SELECT make_array2(1, 2.5) AS numericarray;
 numericarray
--------------
 {1,2.5}
(1 row)

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

SELECT make_array2('a', 'b') AS textarray;
 textarray
-----------
 {a,b}
(1 row)

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

CREATE FUNCTION is_greater(anyelement, anyelement) RETURNS boolean AS $$
    SELECT $1 > $2;
$$ LANGUAGE SQL;

SELECT is_greater(1, 2);
 is_greater
------------
 f
(1 row)

CREATE FUNCTION invalid_func() RETURNS anyelement AS $$
    SELECT 1;
$$ LANGUAGE SQL;
ERROR:  cannot determine result data type
DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
-- ОШИБКА: не удалось определить тип данных результата
-- ДЕТАЛИЗАЦИЯ: Для результата типа anyelement требуется хотя бы один аргумент типа anyelement, anyarray, anynonarray, anyenum или anyrange.

Полиморфизм можно использовать с функциями, имеющими выходные аргументы. Например:

CREATE FUNCTION dup (f1 anyelement, OUT f2 anyelement, OUT f3 anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE SQL;

SELECT * FROM dup(22);
 f2 |   f3
----+---------
 22 | {22,22}
(1 row)

Кроме того, полиморфизм можно использовать с функциями с переменным числом аргументов. Например:

CREATE FUNCTION anyleast (VARIADIC anyarray) RETURNS anyelement AS $$
    SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;

SELECT anyleast(10, -1, 5, 4);
 anyleast
----------
       -1
(1 row)

SELECT anyleast('abc'::text, 'def');
 anyleast
----------
 abc
(1 row)

CREATE FUNCTION concat_values(text, VARIADIC anyarray) RETURNS text AS $$
    SELECT array_to_string($2, $1);
$$ LANGUAGE SQL;

SELECT concat_values('|', 1, 4, 2);
 concat_values
---------------
 1|4|2
(1 row)

Функции SQL с правилами сортировки

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

SELECT anyleast('abc'::text, 'ABC');

будет зависеть от правила сортировки базы данных по умолчанию. В локали C результатом будет ABC, но во многих других локалях это будет abc. Правило сортировки можно установить принудительно, добавив предложение COLLATE к любому из аргументов, например:

SELECT anyleast('abc'::text, 'ABC' COLLATE "C");

Либо, если вы хотите, чтобы функция работала с конкретным правилом сортировки, независимо от того, с каким она была вызвана, вставьте в определение функции нужное количество предложений COLLATE. Эта версия anyleast всегда будет использовать для сравнения строк локаль en_US:

CREATE FUNCTION anyleast (VARIADIC anyarray) RETURNS anyelement AS $$
    SELECT min($1[i] COLLATE "en_US") FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;

Но обратите внимание, что применение правила к несортируемому типу данных вызовет ошибку.

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

Поведение сортируемых параметров можно рассматривать как ограниченную форму полиморфизма, применимую только к текстовым типам данных.

Перегрузка функций

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

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

CREATE FUNCTION test(int, real) RETURNS ...
CREATE FUNCTION test(smallint, double precision) RETURNS ...

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

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

Другой конфликт может возникнуть между обычной функцией и функцией с переменными параметрами. Например, можно создать функции foo(numeric) и foo(VARIADIC numeric[]). В этом случае неясно, какая из них должна подходить для вызова при передаче одного числового аргумента, например foo(10.1). Правило состоит в том, что используется функция, появляющаяся в пути поиска раньше, или, если обе функции находятся в одной схеме, выбирается обычная.

При перегрузке функций на нативном языке существует дополнительное ограничение: имя уровня С каждой функции в семействе перегруженный функций должно отличаться от имен уровня С всех остальных функций, как внутренних, так и загружаемых динамически. При нарушении этого правила поведение будет зависеть от платформы. Можно получить ошибку компоновщика во время выполнения, либо будет вызвана одна из функций (обычно внутренняя). Альтернативная форма предложения AS команды SQL CREATE FUNCTION отделяет имя функции SQL от имени в исходном коде на C/RUST. Например:

CREATE FUNCTION test(int) RETURNS int
    AS 'имя_файла', 'test_1arg'
    LANGUAGE C;
CREATE FUNCTION test(int, int) RETURNS int
    AS 'имя_файла', 'test_2arg'
    LANGUAGE C;

Имена функций на нативном языке здесь отражают одно из многих возможных соглашений.

Категории изменчивости функций

Все функции делятся по степени изменчивости с возможными вариантами: VOLATILE, STABLE или IMMUTABLE. Если категория не указана в команде CREATE FUNCTION, по умолчанию выбирается VOLATILE. Категория изменчивости — это обещание оптимизатору касательно поведения функции:

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

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

Постоянная функция (IMMUTABLE) не может изменять данные и гарантированно возвращает одинаковые результаты, всегда получая одинаковые аргументы. Эта категория позволяет оптимизатору предварительно вычислить функцию, когда запрос вызывает ее с постоянными аргументами. Например, запрос типа SELECT ... WHERE x = 2 + 2 можно упростить до SELECT ... WHERE x = 4, потому что лежащая в его основе функция оператора сложения целых чисел помечена как IMMUTABLE.

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

Любая функция с побочными эффектами должна быть помечена как VOLATILE, чтобы оптимизатор не мог исключить ее вызовы. Даже функция без побочных эффектов должна быть помечена как VOLATILE, если ее значение может измениться в течение выполнения одного запроса, например: random(), currval(), timeofday().

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

Разница между категориями STABLE и IMMUTABLE почти незаметна, если рассматривать простые интерактивные запросы, которые планируются и выполняются немедленно: не имеет большого значения, выполняется ли функция однократно во время планирования или однократно в начале выполнения запроса. Но если план сохраняется для последующего повторного использования, возникает существенная разница. Если пометить функцию как IMMUTABLE, когда она не является постоянной, эта функция может оказаться преждевременно сжатой до константы во время планирования, и в итоге при последующих выполнениях плана будет использоваться неактуальное значение. Это опасно при использовании подготовленных операторов или языков функций, кэширующих планы (например, PL/pgSQL).

У функций, написанных на SQL или на одном из стандартных процедурных языков, имеется еще одно важное свойство, определяемое категорией изменчивости, а именно видимость любых изменений данных, которые были сделаны командой SQL, вызывающей эту функцию. Функция VOLATILE увидит такие изменения, а функция STABLE или IMMUTABLE — нет. Это поведение реализуется с помощью снимков в MVCC (см. главу Управление параллельным доступом): функции STABLE и IMMUTABLE используют снимок, созданный в начале вызывающего запроса, тогда как функции VOLATILE получают новый снимок в начале каждого запроса, который они выполняют.

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

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

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

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

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

Функции на процедурном языке

QHB позволяет писать пользовательские функции на других языках, помимо SQL и C/RUST. Эти языки обычно называются процедурными языками (procedural language, PL). Процедурные языки не встроены в сервер QHB; они реализованы в подгружаемых модулях. Дополнительную информацию см. в главе Процедурные языки и следующих за ней главах.

Внутренние функции

Внутренние функции — это функции, написанные на нативном языке, которые статически скомпонованы в сервер QHB. В «теле» определения функции указано ее имя на нативном языке, которое не обязательно должно совпадать с именем, объявленным для использования в SQL. (Из соображений обратной совместимости принимается и пустое «тело» функции, означая, что имя функции на нативном языке совпадает с SQL-именем).

Обычно все присутствующие на сервере внутренние функции объявляются во время инициализации кластера базы данных (см. раздел Создание кластера баз данных), но пользователь может использовать CREATE FUNCTION для создания дополнительного псевдонима внутренней функции. Внутренние функции объявляются в CREATE FUNCTION с именем языка internal. Например, таким образом можно создать псевдоним для функции sqrt:

CREATE FUNCTION square_root(double precision) RETURNS double precision
    AS 'dsqrt'
    LANGUAGE internal
    STRICT;

(Большинство внутренних функций следует объявлять как «строгие» (STRICT).)

Примечание
Не все «предопределенные» функции являются «внутренними» в вышеописанном смысле. Некоторые предопределенные функции написаны на SQL.

Функции на нативном языке

Пользовательские нативные функции могут быть написаны на C, RUST, С++ или других компилируемых с С языках. Такие функции компилируются в динамически загружаемые объекты (также называемые разделяемыми библиотеками) и загружаются сервером по требованию. Именно возможность динамической загрузки отличает «нативные» функции от «внутренних» — фактические стандарты оформления кода у них по сути одинаковы. (Соответственно, стандартная библиотека внутренних функций является обширным источником примеров кода для пользовательских нативных функций.)

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

Динамическая загрузка

При первом вызове в сеансе пользовательской функции в определенном загружаемом объектном файле динамический загрузчик загружает этот объектный файл в память, чтобы можно было вызвать эту функцию. Поэтому в команде CREATE FUNCTION для пользовательской функции на C/RUST нужно указывать две детали: имя загружаемого объектного файла и имя уровня С (символ ссылки) конкретной функции, вызываемой в этом объектном файле. Если имя уровня С не указано явно, предполагается, что оно совпадает с именем функции в SQL.

Для поиска разделяемого объектного файла по имени, указанному в команде CREATE FUNCTION, используется следующий алгоритм:

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

Если имя начинается со строки $libdir, эта часть заменяется именем каталога библиотек пакетов QHB, которое определяется во время сборки.

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

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

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

Рекомендуется размещать разделяемые библиотеки относительно $libdir или по пути динамической библиотеки. Это упрощает обновление версий, если новая установка находится в другом месте. Какой именно каталог обозначается как $libdir, можно узнать с помощью команды pg_config --pkglibdir.

Идентификатор пользователя, от имени которого работает сервер QHB, должен иметь возможность пройти путь к файлу, который вы собираетесь загрузить. Создание файла или каталога более высокого уровня, который недоступен для чтения или исполнения пользователем qhb, — распространенная ошибка.

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

Примечание
QHB не будет компилировать функцию на C/RUST автоматически. Прежде чем ссылаться на объектный файл в команде CREATE FUNCTION, его необходимо скомпилировать. Дополнительную информацию см в подразделе Компиляция и связывание динамически загружаемых функций.

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

PG_MODULE_MAGIC;

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

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

Базовые типы в функциях на нативном языке

Чтобы знать, как писать функции на нативном языке, нужно знать, как QHB внутренне представляет базовые типы данных и как их можно передавать в функции и из функций. Внутри QHB рассматривает базовый тип как «блок памяти». Пользовательские функции, которые вы определяете для типа, в свою очередь, определяют, каким образом QHB может с ним работать. То есть QHB будет только сохранять и извлекать данные с диска, а ваши пользовательские функции использовать для ввода, обработки и вывода данных.

Базовые типы могут иметь один из трех внутренних форматов:

передается по значению, фиксированной длины

передается по ссылке, фиксированной длины

передается по ссылке, переменной длины

Типы, передаваемые по значению, могут иметь длину только 1, 2 или 4 байта (а также 8 байт, если sizeof(Datum) равен 8 на вашей машине). Собственные типы следует определять таким образом, чтобы они были одинакового размера (в байтах) на всех архитектурах. Например, тип long опасен, потому что на одних машинах его размер составляет 4 байта, а на других — 8 байт, тогда как размер типа int на большинстве машин Unix составляет 4 байта. Разумной реализацией типа int4 на машинах Unix может быть:

/* 4-байтовое целое, передаваемое по значению */
typedef int int4;

(В настоящем коде С/RUST QHB этот тип называется int32, потому что в C существует соглашение, согласно которому int XX означает XX бит. Поэтому обратите внимание также на то, что тип int8 в С имеет размер 1 байт. Тип int8, принятый в SQL, в С называется int64. См. также Таблицу 2.)

С другой стороны, типы фиксированной длины любого размера могут передаваться по ссылке. Например, вот пример реализации типа QHB:

/* 16-байтовая структура, передаваемая по ссылке */
typedef struct
{
    double  x, y;
} Point;

В функции QHB и из них могут передаваться только указатели на такие типы. Чтобы вернуть значение такого типа, выделите требуемый объем памяти с помощью palloc, заполните выделенную память и верните указатель на нее. (При этом, если вы просто хотите вернуть то же значение, что было получено в одном из ваших входных аргументов того же типа данных, вы можете пропустить лишнюю palloc и просто вернуть указатель на входное значение.)

Наконец, все типы переменной длины также должны передаваться по ссылке. Все типы переменной длины должны начинаться с непрозрачного поля длины размером ровно 4 байта, которое будет установлено с помощью SET_VARSIZE; никогда не устанавливайте это поле напрямую! Все данные, которые будут храниться в этом типе, должны быть расположены в памяти сразу после этого поля длины. Поле длины содержит общую длину структуры, то есть включает в себя размер самого поля длины.

Еще один важный момент — старайтесь не оставлять неинициализированных фрагментов в значениях типа данных; например, обязательно обнуляйте любые байты заполнителя выравнивания, которые могут присутствовать в структурах. Без этого планировщик может рассматривать логически равнозначные константы вашего типа данных как неравные, что приведет к построению неэффективных (хотя и корректных) планов.

ПРЕДУПРЕЖДЕНИЕ
Никогда не изменяйте содержимое входного значения, передаваемого по ссылке. Если вы это сделаете, то, скорее всего, повредите данные на диске, поскольку полученный вами указатель может указывать напрямую на буфер диска. Единственное исключение из этого правила описано в разделе Пользовательские агрегаты.

В качестве примера мы можем определить тип text следующим образом:

typedef struct {
    int32 length;
    char data[FLEXIBLE_ARRAY_MEMBER];
} text;

Запись [FLEXIBLE_ARRAY_MEMBER] означает, что фактическая длина части данных в этом объявлении не указана.

При манипулировании типами переменной длины следует быть внимательными, чтобы правильно распределить объем памяти и правильно задать поле длины. Например, если мы хотим сохранить 40 байт в структуре типа text, можно использовать такой фрагмент кода:

#include "qhb.h"
...
char buffer[40]; /* наши исходные данные */
...
text *destination = (text *) palloc(VARHDRSZ + 40);
SET_VARSIZE(destination, VARHDRSZ + 40);
memcpy(destination->data, buffer, 40);
...

VARHDRSZ — это то же самое, что и sizeof(int32), но для обозначения размера заголовка типа переменной длины хорошим стилем считается использование макроса VARHDRSZ. Кроме того, поле длины должно устанавливаться макросом SET_VARSIZE, а не путем простого присваивания.

В Таблице 2 представлены типы языка C/RUST, соответствующие многим встроенным типам данных SQL в QHB. В столбце «Определен в» указан заголовочный файл, который необходимо включить, чтобы получить определение типа. (Фактическое определение может быть в другом файле, который включается из указанного. Рекомендуется, чтобы пользователи придерживались определенного интерфейса.) Обратите внимание, что в любой исходный файл всегда следует сначала включать qhb.h, поскольку он объявляет ряд вещей, которые вам в любом случае понадобятся, и потому что включение первыми других заголовков может вызвать проблемы с переносимостью.

Таблица 2. Эквивалентные типы C для встроенных типов SQL

Тип SQL | Тип C | Определен в
boolean | bool | qhb.h (возможно, встроенный компилятор)
box | BOX* | utils/geo_decls.h
bytea | bytea* | qhb.h
"char" | char | (встроенный компилятор)
character | BpChar* | qhb.h
cid | CommandId | qhb.h
date | DateADT | utils/date.h
float4 (real) | float4* | qhb.h
float8 (double precision) | float8* | qhb.h
int2 (smallint) | int16 | qhb.h
int4 (integer) | int32 | qhb.h
int8 (bigint) | int64 | qhb.h
interval | Interval* | datatype/timestamp.h
lseg | LSEG* | utils/geo_decls.h
name | Name | qhb.h
numeric | Numeric | utils/numeric.h
oid | Oid | qhb.h
oidvector | oidvector* | qhb.h
path | PATH* | utils/geo_decls.h
point | POINT* | utils/geo_decls.h
regproc | regproc | qhb.h
text | text* | qhb.h
tid | ItemPointer | storage/itemptr.h
time | TimeADT | utils/date.h
time with time zone | TimeTzADT | ***utils/date.h ***
timestamp | Timestamp* | datatype/timestamp.h
timestamp with time zone | TimestampTz | datatype/timestamp.h
varchar | VarChar* | qhb.h
xid | TransactionId | qhb.h

Теперь, когда мы рассмотрели все возможные структуры для базовых типов, мы можем показать некоторые примеры реальных функций.

Соглашение о вызовах версии 1

Соглашение о вызовах версии 1 опирается на макросы, которые устраняют большую часть сложности передачи аргументов и результатов. Функция на C/RUST версии 1 всегда объявляется так:

Datum funcname(PG_FUNCTION_ARGS)

И дополнительно в том же исходном файле должен присутствовать вызов макроса:

PG_FUNCTION_INFO_V1(funcname);

(Обычно его записывают непосредственно перед самой функцией.) Этот вызов макроса не требуется для функций internal (внутренних), поскольку QHB предполагает, что все внутренние функции используют соглашение версии 1. Однако для динамически загружаемых функций этот макрос необходим.

В функции версии 1 каждый фактический аргумент выбирается с помощью макроса PG_GETARG_xxx(), который соответствует типу данных аргумента. (В нестрогих функциях необходимо предварительно проверить аргумент на значения NULL, используя PG_ARGISNULL(); см. ниже.) Результат возвращается макросом PG_RETURN_xxx() для возвращаемого типа. PG_GETARG_xxx() принимает в качестве аргумента номер выбираемого аргумента функции, причем нумерация начинается с 0. PG_RETURN_xxx() принимает в качестве аргумента фактическое значение, которое нужно вернуть.

Вот несколько примеров использования соглашения о вызовах версии 1:

#include "qhb.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"

PG_MODULE_MAGIC;

/* по значению */

PG_FUNCTION_INFO_V1(add_one);

Datum
add_one(PG_FUNCTION_ARGS)
{
    int32   arg = PG_GETARG_INT32(0);

    PG_RETURN_INT32(arg + 1);
}

/* по ссылке, фиксированной длины */

PG_FUNCTION_INFO_V1(add_one_float8);

Datum
add_one_float8(PG_FUNCTION_ARGS)
{
    /* Макрос для FLOAT8 скрывает свою способность передачи по ссылке. */
    float8   arg = PG_GETARG_FLOAT8(0);

    PG_RETURN_FLOAT8(arg + 1.0);
}

PG_FUNCTION_INFO_V1(makepoint);

Datum
makepoint(PG_FUNCTION_ARGS)
{
    /* Здесь способность Point к передаче по ссылке не скрыта. */
    Point     *pointx = PG_GETARG_POINT_P(0);
    Point     *pointy = PG_GETARG_POINT_P(1);
    Point     *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;
    new_point->y = pointy->y;

    PG_RETURN_POINT_P(new_point);
}

/* по ссылке, переменной длины */

PG_FUNCTION_INFO_V1(copytext);

Datum
copytext(PG_FUNCTION_ARGS)
{
    text     *t = PG_GETARG_TEXT_PP(0);

    /*
     * VARSIZE_ANY_EXHDR — это размер структуры в байтах минус VARHDRSZ или VARHDRSZ_SHORT
     * ее заголовочного файла. Постройте копию с заголовочным файлом полной длины.
     */
    text     *new_t = (text *) palloc(VARSIZE_ANY_EXHDR(t) + VARHDRSZ);
    SET_VARSIZE(new_t, VARSIZE_ANY_EXHDR(t) + VARHDRSZ);

    /*
     * VARDATA — это указатель на область данных новой структуры. Источником может быть
     * короткий элемент данных, поэтому извлекайте его данные посредством VARDATA_ANY.
     */
    memcpy((void *) VARDATA(new_t), /* цель */
           (void *) VARDATA_ANY(t), /* источник */
           VARSIZE_ANY_EXHDR(t));   /* сколько байтов */
    PG_RETURN_TEXT_P(new_t);
}

PG_FUNCTION_INFO_V1(concat_text);

Datum
concat_text(PG_FUNCTION_ARGS)
{
    text  *arg1 = PG_GETARG_TEXT_PP(0);
    text  *arg2 = PG_GETARG_TEXT_PP(1);
    int32 arg1_size = VARSIZE_ANY_EXHDR(arg1);
    int32 arg2_size = VARSIZE_ANY_EXHDR(arg2);
    int32 new_text_size = arg1_size + arg2_size + VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);
    memcpy(VARDATA(new_text), VARDATA_ANY(arg1), arg1_size);
    memcpy(VARDATA(new_text) + arg1_size, VARDATA_ANY(arg2), arg2_size);
    PG_RETURN_TEXT_P(new_text);
}

Предполагая, что приведенный выше код был подготовлен в файле funcs.c и скомпилирован в общий объект, мы могли бы определить функции для QHB с помощью таких команд:

CREATE FUNCTION add_one(integer) RETURNS integer
     AS 'КАТАЛОГ/funcs', 'add_one'
     LANGUAGE C STRICT;

-- обратите внимание — это перегрузка имени функции SQL "add_one"
CREATE FUNCTION add_one(double precision) RETURNS double precision
     AS 'КАТАЛОГ/funcs', 'add_one_float8'
     LANGUAGE C STRICT;

CREATE FUNCTION makepoint(point, point) RETURNS point
     AS 'КАТАЛОГ/funcs', 'makepoint'
     LANGUAGE C STRICT;

CREATE FUNCTION copytext(text) RETURNS text
     AS 'КАТАЛОГ/funcs', 'copytext'
     LANGUAGE C STRICT;

CREATE FUNCTION concat_text(text, text) RETURNS text
     AS 'КАТАЛОГ/funcs', 'concat_text'
     LANGUAGE C STRICT;

Здесь КАТАЛОГ обозначает каталог файла разделяемой библиотеки (например, каталог учебного руководства по QHB, который содержит код для примеров, используемых в этом разделе). (Лучше было бы просто написать 'funcs' в предложении AS после добавления КАТАЛОГА в путь поиска. В любом случае мы можем опустить принятое в системе расширение для разделяемых библиотек, обычно .so.)

Обратите внимание, что мы указали функции как «строгие» (strict), имея в виду, что система должна автоматически подразумевать результат NULL, если в каком-то из входных значений передается NULL. Благодаря этому, мы избегаем необходимости проверять входные значения на NULL в коде функции. Без этого нам пришлось бы явно проверять значения на NULL, используя PG_ARGISNULL().

Макрос PG_ARGISNULL(n) позволяет функции проверять каждое входное значение на NULL. (Конечно, это необходимо делать только в функциях, не объявленных как «строгие»). Как и в случае с макросом PG_GETARG_xxx(), нумерация входных аргументов начинается с нуля. Обратите внимание, что не следует выполнять PG_GETARG_xxx(), не убедившись, что соответствующий аргумент не равен NULL. Чтобы вернуть результат NULL, выполните макрос PG_RETURN_NULL(); это работает как в строгих, так и в нестрогих функциях.

На первый взгляд соглашения о кодировании в версии 1 могут показаться просто бессмысленным мракобесием по сравнению с использованием простых соглашений о вызовах языка C/RUST. Тем не менее они позволяют работать с аргументами и возвращаемыми значениями, в которых может передаваться NULL, а также со значениями в формате TOAST (сжатыми или находящимися вне основной программы).

Другие возможности, предоставляемые интерфейсом версии 1, — это два варианта макроса PG_GETARG_xxx(). Первый из них, PG_GETARG_xxx_COPY(), гарантирует возврат копии указанного аргумента, который безопасен для внесения записей. (Обычные макросы иногда будут возвращать указатель на значение, физически хранящееся в таблице, в которую нельзя записывать. Использование макроса PG_GETARG_xxx_COPY() гарантирует доступный для записи результат). Второй вариант состоит из макроса PG_GETARG_xxx_SLICE(), который принимает три аргумента. Первый — это номер аргумента функции (как указано выше). Второй и третий — это смещение и длина возвращаемого сегмента. Смещения отсчитываются от нуля, а отрицательная длина требует возврата оставшейся части значения. Эти макросы обеспечивают более эффективный доступ к частям больших значений в том случае, если они имеют «внешний» (external) тип хранения. (Тип хранения столбца можно указать командой ALTER TABLE имя_таблицы ALTER COLUMN имя_столбца SET STORAGE тип_хранения. тип_хранения может быть plain, external, extended или main.)

Наконец, соглашения о вызовах функций версии 1 позволяют возвращать множества результатов (подраздел Возврат множеств) и реализовывать триггерные функции (глава Триггеры) и обработчики вызовов процедурного языка (глава Написание обработчика процедурного языка).

Написание кода

Прежде чем перейти к более сложным темам, мы должны рассмотреть некоторые правила кодирования для функций на нативном языке QHB. Хотя и возможно загрузить в QHB функции, написанные на других языках помимо C/RUST, обычно это сложно (если вообще возможно), потому что другие языки, такие как C++, FORTRAN или Pascal, зачастую не следуют соглашению о вызовах, принятому в C. То есть другие языки передают аргументы и возвращают значения между функциями разными способами. По этой причине мы будем предполагать, что ваши функции на нативном языке на самом деле написаны на C/RUST.

Основные правила написания и построения функций C/RUST следующие:

Чтобы узнать, где в вашей системе (или в системе, на которой будут работать ваши пользователи) установлены заголовочные файлы сервера QHB, используйте pg_config --includedir-server.

Для компиляции и компоновки вашего кода, который можно динамически загрузить в QHB, всегда требуются специальные флаги. Подробное объяснение того, как это сделать для вашей конкретной операционной системы, см. в подразделе Компиляция и связывание динамически загружаемых функций.

Не забудьте определить «магический блок» для вашей разделяемой библиотеки, как описано в подразделе Динамическая загрузка.

При выделении памяти используйте функции QHB palloc и pfree вместо соответствующих функций библиотеки C malloc и free. Память, выделенная palloc, будет автоматически освобождаться в конце каждой транзакции, предотвращая утечки памяти.

Всегда обнуляйте байты ваших структур, используя функцию memset (или сразу выделяйте их функцией palloc0). Даже если вы присвоите значение каждому полю вашей структуры, в ней могут остаться заполняющие байты выравнивания («прорехи» в структуре), содержащие «мусорные »значения. Без этого будет трудно поддерживать индексы или соединения по хешу, так как для вычисления хеша придется выбирать из вашей структуры данных только значимые биты. Планировщик тоже иногда полагается на сравнение констант с помощью побитового равенства, так что если логически равные значения окажутся неравными побитово, можно получить нежелательные результаты планирования.

Большинство внутренних типов QHB объявлены в qhb.h, в то время как интерфейсы менеджера функций ( PG_FUNCTION_ARGS и т. д.) находятся в fmgr.h, поэтому нужно будет включить как минимум эти два файла. По причинам переносимости лучше сначала включить qhb.h, а не заголовочные файлы других систем или пользователей. При включении qhb.h также автоматически будут включены elog.h и palloc.h.

Имена символов, определенные в объектных файлах, не должны конфликтовать друг с другом или с символами, определенными в исполняемых файлах сервера QHB. При получении сообщения об ошибках в связи с подобными конфликтами вам придется переименовать ваши функции или переменные.

Компиляция и связывание динамически загружаемых функций

Прежде чем вы сможете использовать свои функции C/RUST, расширяющие возможности QHB, их нужно скомпилировать и скомпоновать особым образом для создания файла, который сервер сможет динамически загрузить. Точнее говоря, необходимо создать разделяемую библиотеку.

Для получения информации, выходящей за рамки этого раздела, вам следует прочитать документацию по вашей операционной системе, в частности, страницы руководства по компилятору C, cc, и компоновщику, ld. Кроме того, исходный код QHB содержит несколько рабочих примеров в каталоге share/extension. Однако, полагаясь на эти примеры, вы сделаете свои модули зависимыми от доступности исходного кода QHB.

Создание разделяемых библиотек обычно аналогично сборке исполняемых файлов: сначала исходные файлы компилируются в объектные файлы, затем объектные файлы связываются вместе. Объектные файлы должны быть созданы как позиционно-независимый код (position-independent code, PIC), что концептуально означает, что при загрузке исполняемым файлом они могут быть размещены в произвольном месте в памяти и загружаются исполняемым файлом. (Объектные файлы, предназначенные для компоновки самих исполняемых файлов, обычно компилируются по-другому). Команда для сборки разделяемой библиотеки содержит специальные флаги, чтобы отличать ее от сборки исполняемого файла (по крайней мере, теоретически — в некоторых системах эта практика более неприглядна).

В следующих примерах предполагается, что ваш исходный код находится в файле foo.c, и мы создадим разделяемую библиотеку foo.so Промежуточный объектный файл будет называться foo.o, если не указано иное. Разделяемая библиотека может содержать более одного объектного файла, но здесь мы используем только один.

FreeBSD

Флаг компилятора для создания PIC — -fPIC. Флаг компилятора для создания разделяемой библиотеки — -shared.

gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o

Это применимо, начиная с FreeBSD версии 3.0.

HP-UX

Флаг системного компилятора для создания PIC — +z, а при использовании GCC — -fPIC. Флаг компоновщика для разделяемых библиотек — -b. Таким образом, следует написать:

cc +z -c foo.c

или:

gcc -fPIC -c foo.c

а затем:

ld -b -o foo.sl foo.o

В отличие от большинства других систем, HP-UX использует для разделяемых библиотек расширение .sl.

Linux

Флаг компилятора для создания PIC — -fPIC. Флаг компилятора для создания разделяемой библиотеки — -shared. Полный пример выглядит так:

cc -fPIC -c foo.c
cc -shared -o foo.so foo.o

macOS

Рассмотрим следующий пример с необходимыми командами. Здесь предполагается, что установлены инструменты разработчика.

cc -c foo.c
cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o

NetBSD

Флаг компилятора для создания PIC — -fPIC. В системах ELF для компоновки разделяемых библиотек используется компилятор с флагом -shared. В более старых системах без ELF применяется команда ld -Bshareable.

gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o

OpenBSD

Флаг компилятора для создания PIC — -fPIC. Для компоновки разделяемых библиотек используется команда ld -Bshareable.

gcc -fPIC -c foo.c
ld -Bshareable -o foo.so foo.o

Solaris

Флаг для создания PIC с компилятором Sun — -KPIC, с компилятором GCC — -fPIC. Для компоновки разделяемых библиотек с обоими компиляторами можно использовать флаг -G, а с GCC — флаг -shared.

cc -KPIC -c foo.c
cc -G -o foo.so foo.o

или:

gcc -fPIC -c foo.c
gcc -G -o foo.so foo.o

Совет
Если для вас это слишком сложно, следует рассмотреть возможность использования средства GNU Libtool, которое скрывает различия платформ за единым интерфейсом.

Полученный файл разделяемой библиотеки затем можно загрузить в QHB. Задавая имя файла команде CREATE FUNCTION, необходимо указать имя файла разделяемой библиотеки, а не промежуточного объектного файла. Обратите внимание, что стандартное расширение разделяемой библиотеки, принятое в системе (обычно .so или .sl ), в команде CREATE FUNCTION можно опустить, и обычно так и следует делать для лучшей переносимости.

Где именно сервер ожидает найти файлы разделяемой библиотеки, см. в подразделе Динамическая загрузка.

Аргументы составного типа

У составных типов нет фиксированного макета, как у структур C/RUST. Вхождения составного типа могут содержать поля NULL. Кроме того, составные типы, являющиеся частью иерархии наследования, могут иметь иные поля, нежели остальные члены той же иерархии. Поэтому QHB предоставляет функциям интерфейс для доступа к полям составных типов из C/RUST.

Предположим, мы хотим написать функцию для ответа на запрос:

SELECT name, c_overpaid(emp, 1500) AS overpaid
    FROM emp
    WHERE name = 'Bill' OR name = 'Sam';

Используя соглашения о вызовах версии 1, мы можем определить c_overpaid как:

#include "qhb.h"
#include "executor/executor.h"  /* для GetAttributeByName() */

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(c_overpaid);

Datum
c_overpaid(PG_FUNCTION_ARGS)
{
    HeapTupleHeader  t = PG_GETARG_HEAPTUPLEHEADER(0);
    int32            limit = PG_GETARG_INT32(1);
    bool isnull;
    Datum salary;

    salary = GetAttributeByName(t, "salary", &isnull);
    if (isnull)
        PG_RETURN_BOOL(false);
    /* Как вариант, можно записать PG_RETURN_NULL() для значения поля «зарплата», равного NULL. */

    PG_RETURN_BOOL(DatumGetInt32(salary) > limit);
}

GetAttributeByName — системная функция QHB, которая возвращает атрибуты из указанной строки. Она принимает три аргумента: аргумент типа HeapTupleHeader, передаваемый в функцию, имя требуемого атрибута и возвращаемый параметр, который сигнализирует о том, что атрибут имеет значение NULL. GetAttributeByName возвращает значение Datum, которое можно преобразовать в правильный тип данных с помощью соответствующего макроса DatumGetXXX(). Обратите внимание, что возвращаемое значение не имеет смысла, если установлен флаг NULL; всегда проверяйте этот флаг, прежде чем пытаться что-либо сделать с результатом.

Существует также функция GetAttributeByNum, которая выбирает целевой атрибут по номеру столбца, а не по имени.

Следующая команда объявляет функцию c_overpaid в SQL:

CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
    AS 'КАТАЛОГ/funcs', 'c_overpaid'
    LANGUAGE C STRICT;

Обратите внимание, что мы использовали STRICT, чтобы не пришлось проверять, имеют ли входные аргументы значение NULL.

Возврат строк (составных типов)

Чтобы вернуть значение строки или составного типа из функции на нативном языке, можно использовать специальный API, который предоставляет макросы и функции, скрывающие большую часть сложности построения составных типов данных. Чтобы использовать этот API, исходный файл должен включать:

#include "funcapi.h"

Существует два способа создания составного значения данных (далее «кортеж»): его можно построить из массива значений Datum или из массива строк C/RUST, которые можно передать во функции преобразования ввода для типов данных столбца кортежа. В любом случае сначала необходимо получить или создать дескриптор TupleDesc для структуры кортежа. При работе со значениями Datum вы передаете TupleDesc функции BlessTupleDesc, а затем вызываете для каждой строки heap_form_tuple. При работе со строками C/RUST вы передаете TupleDesc функции TupleDescGetAttInMetadata, а затем вызываете для каждой строки BuildTupleFromCStrings. В случае функции, возвращающей множество кортежей, все этапы настройки можно выполнить один раз при первом вызове функции.

Для настройки необходимого TupleDesc имеется несколько вспомогательных функций. Рекомендуемый способ сделать это в большинстве функций, возвращающих составные значения, это вызвать:

TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
                                   Oid *resultTypeId,
                                   TupleDesc *resultTupleDesc)

передавая ту же самую структуру fcinfo, которая была передана самой вызывающей функции. (Для этого, конечно, нужно использовать соглашения о вызовах версии 1). В resultTypeId можно передать NULL или адрес локальной переменной для получения OID типа результата функции. resultTupleDesc должен быть адресом локальной переменной TupleDesc. Убедитесь, что результатом является TYPEFUNC_COMPOSITE; если это так, значит, resultTupleDesc был заполнен требуемой структурой TupleDesc. (Если это не так, вы можете сообщить об ошибке по образцу: «функция, возвращающая запись, вызвана в контексте, не допускающем запись типа»).

Совет
Из функции get_call_result_type можно получить фактический тип результата полиморфной функции, так что она полезна тех в функциях, которые возвращают скалярные полиморфные результаты, а не только в тех, которые возвращают составные типы. Выходной параметр resultTypeId полезен в первую очередь для функций, возвращающих полиморфные скаляры.

Примечание
У get_call_result_type есть родственная функция get_expr_result_type, которую можно использовать для получения ожидаемого типа результата для вызова функции, представленного деревом выражения. Ее можно использовать, если требуется определить тип результата вне самой функции. Существует также функция get_func_result_type, которую можно использовать, когда известен только OID функции. Однако обе эти функции не могут работать с функциями, объявленными как возвращающие record, а функция get_func_result_type не может привести к полиморфным типам, поэтому вместо них рекомендуется все-таки пользоваться функцией get_call_result_type.

Ранее для получения TupleDesc использовались также следующие, сейчас признанные устаревшими функции:

TupleDesc RelationNameGetTupleDesc(const char *relname)

(возвращает TupleDesc** для типа строки указанного отношения) и:

TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)

(возвращает TupleDesc на основе OID типа). Ее можно использовать для получения TupleDesc для базового или составного типа. Однако она не подходит для функции, которая возвращает record, и не может привести к полиморфным типам.

После получения TupleDesc вызовите:

TupleDesc BlessTupleDesc(TupleDesc tupdesc)

если планируете работать со значениями Datum, или:

AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)

если планируете работать со строками C/RUST. Если вы пишете функцию, возвращающую множество данных, можно сохранить результаты этих функций в структуре FuncCallContext — используйте поле tuple_desc или attinmeta соответственно.

При работе с Datum используйте функцию:

HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)

чтобы скомпоновать HeapTuple из переданных ей пользовательских данных в форме Datum.

При работе со строками C/RUST используйте функцию:

HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)

чтобы скомпоновать HeapTuple из переданных ей пользовательских данных в форме строки C/RUST. *values — это массив строк C/RUST, по одной на каждый атрибут возвращаемой строки. Каждая строка C/RUST должна иметь формат, ожидаемый функцией ввода типа данных атрибута. Чтобы вернуть значение NULL для одного из этих атрибутов, нужно задать NULL в соответствующем указателе в массиве values. Эту функцию нужно будет вызывать снова для каждой возвращаемой строки.

Как только вы соберете кортеж для возврата из своей функции, его следует преобразовать в тип Datum. Воспользуйтесь функцией:

HeapTupleGetDatum(HeapTuple tuple)

чтобы преобразовать HeapTuple в надлежащий Datum. Этот тип Datum можно вернуть напрямую, если вы намереваетесь вернуть только одну строку, или использовать как текущее возвращаемое значение в функции, возвращающей множество.

Пример приведен в следующем разделе.

Возврат множеств

Для функций на нативном языке существует две возможности возврата множеств (нескольких строк). Первый метод, называемый режимом ValuePerCall (значение за вызов), состоит в многократном вызове функции, возвращающей множество (при этом ей каждый раз передаются одни и те же аргументы), и при каждом вызове она возвращает по одной новой строке, пока те не закончатся, о чем функция просигнализирует, возвращая NULL. Таким образом, функция, возвращающая множество (Set-Returning Function, SRF), должна от вызова к вызову сохранять свое состояние в достаточной степени, чтобы помнить, что она делает, и при каждом вызове возвращать надлежащие данные. Другой метод, называемый режимом Materialize (материализация), состоит в том, что SRF заполняет и возвращает объект tuplestore, содержащий сразу все результирующее множество; таким образом, для получения всего результата производится всего один вызов, и никакое состояние между вызовами сохранять не нужно.

Пользуясь режимом ValuePerCall, важно помнить, что выполнение запроса до завершения не гарантировано; иными словами, при указании такого параметра, как LIMIT, исполнитель запроса может перестать вызывать функцию, возвращающую множества, раньше, чем будут выбраны все строки. Это означает, что выполнять в последнем вызове действия, связанные с очисткой, небезопасно, так как он может вообще не произойти. Для функций, которым необходим доступ к внешним ресурсам, например файловым дескрипторам, рекомендуется использовать режим Materialize.

В оставшейся части данного раздела описывается ряд наиболее часто применяющихся (хотя и необязательных) вспомогательных макросов для SRF, реализующих режим ValuePerCall. Кроме того, в модулях share/extension установки QHB содержится много примеров SRF, реализующих как режим ValuePerCall, так и режим Materialize.

Для использования описанных здесь макросов поддержки ValuePerCall включите funcapi.h. Эти макросы работают со структурой FuncCallContext, содержащей состояние, которое нужно сохранять между вызовами. Внутри вызываемой SRF указатель на FuncCallContext удерживается между вызовами в поле fcinfo->flinfo->fn_extra. Макросы автоматически заполняют данное поле при первом использовании и в при последующих вызовах ожидают обнаружить в нем этот же указатель.

typedef struct FuncCallContext
{
    /*
     * Счетчик числа произведенных ранее вызовов
     *
     * посредством макроса SRF_FIRSTCALL_INIT() call_cntr присваивается начальное
     * значение 0 , которое увеличивается на 1 при каждом вызове SRF_RETURN_NEXT().
     */
    uint64 call_cntr;

    /*
     * (НЕОБЯЗАТЕЛЬНО) максимальное число вызовов
     *
     * max_calls не является обязательным и присутствует здесь только для удобства.
     * Если это значение не задано, вы должны предоставить альтернативный способ
     * определить, когда функция закончила работу.
     */
    uint64 max_calls;

    /*
     * (НЕОБЯЗАТЕЛЬНО) указатель на разнообразную дополнительную информацию,
     * предоставленную пользователем
     *
     * user_fctx используется как указатель на ваши собственные данные, позволяя
     * сохранять произвольную контекстную информацию между вызовами вашей функции.
     */
    void *user_fctx;

    /*
     * (НЕОБЯЗАТЕЛЬНО) указатель на структуру, содержащую метаданные ввода типа
     * атрибута
     *
     * attinmeta применяется при возврате кортежей (т. е. составных типов данных)
     * и не применяется при возврате базовых типов данных. Он нужен, только
     * если вы намерены использовать BuildTupleFromCStrings() для создания
     * возвращаемого кортежа.
     */
    AttInMetadata *attinmeta;

    /*
     * Контекст памяти, используемый для структур, которые должны пережить
     * несколько вызовов
     *
     * multi_call_memory_ctx настраивается при помощи SRF_FIRSTCALL_INIT() и
     * используется SRF_RETURN_DONE() для очистки. Это наиболее подходящий
     * контекст для любых блоков памяти, которые предназначены для многократного
     * использования при повторных вызовах SRF.
     */
    MemoryContext multi_call_memory_ctx;

    /*
     * (НЕОБЯЗАТЕЛЬНО) указатель на структуру, содержащую описание кортежа
     *
     * tuple_desc применяется при возврате кортежей (т. е. составных типов данных)
     * и требуется, только если вы собираетесь формировать кортежи с помощью функции
     * heap_form_tuple(), а не BuildTupleFromCStrings().  Обратите внимание, что
     * сохраняемый здесь указатель TupleDesc обычно должен сначала пройти через
     * вызов BlessTupleDesc().
     */
    TupleDesc tuple_desc;

} FuncCallContext;

Для SRF предоставляется несколько макросов, использующих эту инфраструктуру:

SRF_IS_FIRSTCALL()

Используйте этот макрос, чтобы определить, вызывается ли ваша функция в первый раз или нет. При первом вызове (но не при последующих) используйте:

SRF_FIRSTCALL_INIT()

чтобы инициализировать FuncCallContext. При каждом вызове функции, включая первый, используйте:

SRF_PERCALL_SETUP()

чтобы подготовиться к применению FuncCallContext.

Если у вашей функции есть данные для возврата, используйте:

SRF_RETURN_NEXT(funcctx, result)

чтобы передать их вызывающей функции. (Переменная result должна иметь тип Datum и содержать либо одно значение, либо кортеж, подготовленный, как описано выше). Наконец, когда ваша функция закончит возвращать данные, используйте:

SRF_RETURN_DONE(funcctx)

чтобы провести очистку и завершить работу SRF.

Текущий контекст памяти, в котором вызывается SRF, является временным и будет очищаться между вызовами. Это означает, что вам не нужно вызывать pfree для всех блоков памяти, которые вы выделили с помощью palloc; они все равно будут освобождены. Однако если вы хотите распределить какие-либо структуры данных между вызовами, вам следует поместить их в другое место. Подходящим местом для любых данных, которые должны сохраняться до завершения работы SRF, является контекст памяти, на который ссылается multi_call_memory_ctx. В большинстве случаев это означает, что вы должны переключиться на multi_call_memory_ctx при выполнении настройки первого вызова. Для сохранения указателя на такие долгоживущие структуры данных используйте поле funcctx->user_fctx. (Данные, выделенные в контексте multi_call_memory_ctx, автоматически освободятся в конце запроса, поэтому их также нет необходимости освобождать вручную.)

ПРЕДУПРЕЖДЕНИЕ
В то время как фактические аргументы функции остаются неизменными между вызовами, если вы распаковываете значения аргументов (что обычно делается прозрачно с помощью макроса PG_GETARG_xxx) во временном контексте, то распакованные копии будут освобождаться в каждом цикле. Соответственно, если вы сохраняете ссылки на такие значения в вашем user_fctx, следует либо скопировать их после распаковки в multi_call_memory_ctx, либо обязательно распаковывать значения только в этом контексте.

Полный пример псевдокода выглядит следующим образом:

Datum
my_set_returning_function(PG_FUNCTION_ARGS)
{
    FuncCallContext  *funcctx;
    Datum             result;
    другие необходимые объявления

    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext oldcontext;

        funcctx = SRF_FIRSTCALL_INIT();
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
        /* Здесь содержится код подготовки при первом вызове: */
        пользовательский код
        если возвращается составной тип
            сформировать TupleDesc и, возможно, AttInMetadata
        конец условия возвращаемого составного типа
        пользовательский код
        MemoryContextSwitchTo(oldcontext);
    }

    /* Здесь содержится код подготовки при каждом вызове: */
    пользовательский код
    funcctx = SRF_PERCALL_SETUP();
    пользовательский код

    /* это единственный способ, которым мы можем проверить, последний ли это вызов: */
    if (funcctx->call_cntr < funcctx->max_calls)
    {
        /* Здесь мы возвращаем еще один результат: */
        пользовательский код
        получение результирующих значений Datum
        SRF_RETURN_NEXT(funcctx, result);
    }
    else
    {
        /* Здесь мы прекращаем возвращать результаты, так что просто отражаем этот факт. */
        /* (Воздержаться от искушения вставить сюда код очистки.) */
        SRF_RETURN_DONE(funcctx);
    }
}

Полный пример простой SRF, возвращающей составной тип, выглядит следующим образом:

PG_FUNCTION_INFO_V1(retcomposite);

Datum
retcomposite(PG_FUNCTION_ARGS)
{
    FuncCallContext     *funcctx;
    int                  call_cntr;
    int                  max_calls;
    TupleDesc            tupdesc;
    AttInMetadata       *attinmeta;

    /* действия, производимые только при первом вызове функции */
    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext   oldcontext;

        /* создать контекст функции для сохранения данных между вызовами */
        funcctx = SRF_FIRSTCALL_INIT();

        /* переключить на контекст памяти, подходящий для нескольких вызовов функции */
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

        /* общее число кортежей, подлежащих возврату */
        funcctx->max_calls = PG_GETARG_UINT32(0);

        /* Сформировать дескриптор кортежей для нашего результирующего типа */
        if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                     errmsg("function returning record called in context "
                            "that cannot accept type record")));
                            /* "функция, возвращающая запись, вызвана в контексте, который не может принять запись типа" /*

        /*
         * создать метаданные атрибута, которые позже понадобятся для формирования
         * кортежей из исходных строк C/RUST
         */
        attinmeta = TupleDescGetAttInMetadata(tupdesc);
        funcctx->attinmeta = attinmeta;

        MemoryContextSwitchTo(oldcontext);
    }

    /* действия, производимые при каждом вызове функции */
    funcctx = SRF_PERCALL_SETUP();

    call_cntr = funcctx->call_cntr;
    max_calls = funcctx->max_calls;
    attinmeta = funcctx->attinmeta;

    if (call_cntr < max_calls)    /* выполняется, когда предстоит еще несколько вызовов */
    {
        char       **values;
        HeapTuple    tuple;
        Datum        result;

        /*
         * подготовить массив значений для формирования возвращаемого кортежа.
         * Это должен быть массив строк C/RUST, который
         * позже будет обработан функциями ввода для типов данных.
         */
        values = (char **) palloc(3 * sizeof(char *));
        values[0] = (char *) palloc(16 * sizeof(char));
        values[1] = (char *) palloc(16 * sizeof(char));
        values[2] = (char *) palloc(16 * sizeof(char));

        snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1));
        snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1));
        snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1));

        /* сформировать кортеж */
        tuple = BuildTupleFromCStrings(attinmeta, values);

        /* преобразовать кортеж в datum */
        result = HeapTupleGetDatum(tuple);

        /* провести очистку (на самом деле в ней нет необходимости) */
        pfree(values[0]);
        pfree(values[1]);
        pfree(values[2]);
        pfree(values);

        SRF_RETURN_NEXT(funcctx, result);
    }
    else    /* выполняется, когда вызовов больше не осталось */
    {
        SRF_RETURN_DONE(funcctx);
    }
}

Один из способов объявить эту функцию в SQL:

CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer);

CREATE OR REPLACE FUNCTION retcomposite(integer, integer)
    RETURNS SETOF __retcomposite
    AS 'имя_файла', 'retcomposite'
    LANGUAGE C IMMUTABLE STRICT;

Другой способ — использовать параметры OUT:

CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
    OUT f1 integer, OUT f2 integer, OUT f3 integer)
    RETURNS SETOF record
    AS 'имя_файла', 'retcomposite'
    LANGUAGE C IMMUTABLE STRICT;

Обратите внимание, что в этом методе выходной тип функции формально является анонимным типом record.

Полиморфные аргументы и возвращаемые типы

Функции на нативном языке можно объявить как принимающие и возвращающие полиморфные типы, описанные в подразделе Полиморфные типы. Когда аргументы или возвращаемые типы функции определены как полиморфные типы, автор функции не может заранее знать, с каким типом данных она будет вызываться или какой должна возвращать. В fmgr.h предусмотрены две подпрограммы, позволяющие функции C/RUST версии 1 определить фактические типы данных своих аргументов и тип, который ей нужно вернуть. Эти подпрограммы называются get_fn_expr_rettype(FmgrInfo *flinfo) и get_fn_expr_argtype(FmgrInfo *flinfo, int argnum). Они возвращают OID типа результата или аргумента, либо InvalidOid, если информация недоступна. Структура flinfo обычно доступна по ссылке fcinfo->flinfo. Параметр argnum (номер аргумента) задается, начиная с нуля. В качестве альтернативы get_fn_expr_rettype также можно использовать get_call_result_type. Помимо этого, существует функция get_fn_expr_variadic, которая позволяет определить, были ли переменные аргументы объединены в массив. Это полезно прежде всего для функций с VARIADIC "any", поскольку такое объединение всегда будет происходить для функций с переменными аргументами, принимающих обычные типы массивов.

Например, предположим, что мы хотим написать функцию, которая принимает один элемент любого типа и возвращает одномерный массив этого типа:

PG_FUNCTION_INFO_V1(make_array);
Datum
make_array(PG_FUNCTION_ARGS)
{
    ArrayType  *result;
    Oid         element_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
    Datum       element;
    bool        isnull;
    int16       typlen;
    bool        typbyval;
    char        typalign;
    int         ndims;
    int         dims[MAXDIM];
    int         lbs[MAXDIM];

    if (!OidIsValid(element_type))
        elog(ERROR, "could not determine data type of input");

    /* получить предоставляемый элемент; будьте осторожны, если это NULL */
    isnull = PG_ARGISNULL(0);
    if (isnull)
        element = (Datum) 0;
    else
        element = PG_GETARG_DATUM(0);

    /* мы имеем дело с одной размерностью */
    ndims = 1;
    /* и одним элементом */
    dims[0] = 1;
    /* и нижняя граница равна 1 */
    lbs[0] = 1;

    /* получить требуемую информацию о типе элемента */
    get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);

    /* теперь сформировать массив */
    result = construct_md_array(&element, &isnull, ndims, dims, lbs,
                                element_type, typlen, typbyval, typalign);

    PG_RETURN_ARRAYTYPE_P(result);
}

Следующая команда объявляет функцию make_array в SQL:

CREATE FUNCTION make_array(anyelement) RETURNS anyarray
    AS 'КАТАЛОГ/funcs', 'make_array'
    LANGUAGE C IMMUTABLE;

Существует вариант полиморфизма, который доступен только для функций на нативном языке: они могут быть объявлены как принимающие параметры типа "any". (Обратите внимание, что это имя типа должно быть заключено в кавычки, поскольку оно также является зарезервированным словом SQL). Он работает так же, как anyelement, за исключением того, что он не требует, чтобы различные аргументы "any" имели одинаковый тип, и не помогает определить тип результата функции. Функцию на нативном языке также можно объявить с последним параметром VARIADIC "any". Он будет соответствовать одному или нескольким фактическим аргументам любого типа (необязательно одинакового). Эти аргументы не будут собраны в массив, как это происходит с обычными функциями с переменными аргументами; они просто будут переданы функции по отдельности. Если применяется это свойство, то определять количество фактических аргументов и их типов нужно с помощью макроса PG_NARGS() и методов, описанных выше. Кроме того, пользователи такой функции могут захотеть использовать ключевое слово VARIADIC в вызове своей функции, ожидая, что та будет рассматривать элементы массива как отдельные аргументы. При необходимости это поведение должна реализовать сама функция, предварительно определив с помощью get_fn_expr_variadic, был ли фактический аргумент помечен как VARIADIC.

Разделяемая память и легкие блокировки

Встраиваемые расширения могут резервировать легкие блокировки (LWLock) и область в разделяемой памяти при запуске сервера. Разделяемую библиотеку расширения нужно загрузить заранее, указав ее в параметре shared_preload_libraries. Разделяемая библиотека должна зарегистрировать обработчик shmem_request_hook в своей функции _PG_init. Этот shmem_request_hook может резервировать легкие блокировки или разделяемую память. Разделяемая память резервируется путем вызова:

void RequestAddinShmemSpace(int size)

из вашего shmem_request_hook.

Легкие блокировки резервируются путем вызова:

void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)

из shmem_request_hook. Это обеспечит доступность массива легких блокировок num_lwlocks под именем tranche_name. Для получения указателя на этот массив воспользуйтесь функцией GetNamedLWLockTranche.

Во избежание возможных условий гонки каждый сервер должен использовать легкую блокировку AddinShmemInitLock при подключении и инициализации распределения разделяемой памяти, как показано здесь:

static mystruct *ptr = NULL;

if (!ptr)
{
        bool    found;

        LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
        ptr = ShmemInitStruct("my struct name", size, &found);
        if (!found)
        {
             /* инициализировать содержимое области shmem (разделяемой памяти); */
             /* получить все требуемые легкие блокировки с помощью: */
                ptr->locks = GetNamedLWLockTranche("my tranche name");
        }
        LWLockRelease(AddinShmemInitLock);
}

Использование C++ для расширяемости

Хотя серверная часть QHB написана на C/RUST, расширения для него можно написать на C++, если следовать этим рекомендациям:

Все функции, к которым обращается сервер, должны представлять для него интерфейс C/RUST; затем эти функции C/RUST могут вызывать функции C++. Например, для функций, к которым обращается сервер, нужно указать extern C. Это также необходимо для любых функций, которые передаются в виде указателей между серверной частью и кодом C++.

Освободите память, используя подходящий метод освобождения. Например, большая часть памяти сервера выделяется с помощью palloc(), поэтому для ее освобождения воспользуйтесь pfree(). В таких случаях использование принятой в C++ операции delete приведет к ошибке.

Не допускайте распространения исключений в коде C/RUST (используйте универсальный блок на верхнем уровне всех функций extern C). Это необходимо, даже если код C++ явно не генерирует какие-либо исключения, потому исключения все равно могут генерироваться такими событиями, как нехватка памяти. Любые исключения должны быть перехвачены, и соответствующие ошибки переданы обратно в интерфейс C/RUST. Если возможно, скомпилируйте код C++ с указанием -fno-exception, чтобы полностью убрать исключения; в таких случаях вы должны выявлять ошибки в вашем коде C++, например, проверять на NULL адрес, возвращенный new().

При вызове серверных функций из кода C++ убедитесь, что стек вызовов C++ содержит только простые старые структуры данных (plain old data, POD). Это необходимо, потому что ошибки сервера генерируют удаленную функцию longjmp (), которая не разворачивает стек вызовов C++ надлежащим образом для объектов, отличных от POD.

Резюмируя вышесказанное, лучше всего поместить код C++ за стену из функций extern C, которые взаимодействуют с сервером и предотвращают возникновение исключений, утечки памяти и потери стека вызовов.

Информация по оптимизации функций

По умолчанию функция — это просто «черный ящик», о поведении которого СУБД знает очень мало. Однако это означает, что запросы, вызывающие функцию, могут выполняться менее эффективно, чем могли бы. Существует возможность предоставить планировщику дополнительную информацию, которая поможет ему оптимизировать вызовы функций.

Некоторые основные факты передаются декларативными аннотациями в команде CREATE FUNCTION. Наиболее важным из них является категория изменчивости функции (IMMUTABLE, STABLE или VOLATILE); при определении функции следует уделять особое внимание тому, чтобы правильно указать эту категорию. Также необходимо задать характеристику безопасности параллельного исполнения (PARALLEL UNSAFE, PARALLEL RESTRICTED или PARALLEL SAFE), если вы рассчитываете использовать эту функцию в параллельных запросах. Кроме того, может быть полезно указать примерную стоимость выполнения функции и/или количество строк, которые должна вернуть функция, возвращающая множества. Однако декларативный способ указания этих двух фактов позволяет указывать только константное значение, а этого зачастую недостаточно.

Также есть возможность прикрепить вспомогательную функцию для планировщика к функции, вызываемой из SQL (для вспомогательной она будет целевой функцией), и таким образом передать информацию о целевой функции, которая слишком сложна, чтобы ее можно было представить декларативно. Вспомогательную функцию для планировщика следует писать на языке C/RUST (хотя язык целевых функций может быть другим), поэтому ее можно отнести к расширенным возможностям, которыми будет пользоваться сравнительно небольшое число людей.

Вспомогательная функция для планировщика должна иметь следующую сигнатуру SQL:

supportfn(internal) returns internal

Чтобы соединить ее с целевой функций, нужно при создании последней добавить предложение SUPPORT.

Далее мы в общих чертах рассмотрим, что могут делать вспомогательные функции для планировщика. Множество возможных запросов к вспомогательной функции является расширяемым, поэтому в будущем могут появиться и другие возможности.

Некоторые вызовы функций можно упростить при планировании, исходя из характеристик самой функции. Например, функцию int4mul(n, 1) можно упростить до просто n. Преобразование такого рода может выполнять вспомогательная функция для планировщика, обрабатывая запросы типа SupportRequestSimplify. Вспомогательная функция будет вызываться всякий раз, когда в дереве проанализированного запроса будет найдена ее целевая функция. Если вспомогательная функция обнаружит, что этот конкретный вызов можно упростить до какого-либо другого вида, она может сформировать и возвратить дерево запроса, представляющее уже это измененное выражение. Это автоматически работает и для операторов, основанных на этой функции, — в данном примере n * 1 также будет упрощено до n. (Но имейте в виду, что это просто пример; конкретно эту оптимизацию стандартный QHB на самом деле не производит.) Мы не гарантируем, что QHB никогда не вызовет целевую функцию в случаях, которые может упростить вспомогательная функция. Следует обеспечить строгую идентичность между упрощенным выражением и фактическим выполнением целевой функции.

Для целевых функций, возвращающих boolean, часто бывает полезно оценить процент строк, которые будут выбраны предложением WHERE при помощи этой функции. Эту оценку может выполнить вспомогательная функция, обрабатывающая запросы типа SupportRequestSelectivity.

Если время выполнения целевой функции сильно зависит от ее входных аргументов, имеет смысл предоставить ей переменную оценку затрат. Это можно сделать при помощи вспомогательной функции, обрабатывающей запросы типа SupportRequestCost.

Для целевых функций, возвращающих множества, часто бывает полезно предоставить переменную оценку числа строк, которые будут возвращены. Это можно сделать с помощью вспомогательной функции, обрабатывающей запросы типа SupportRequestRows.

Для целевых функций, возвращающих boolean, существует возможность преобразовать вызов функции, находящейся в WHERE, в предложение (или предложения) с индексируемыми операторами. Преобразованные предложения должны быть строго идентичны условию функции либо могут быть в некоторой степени менее строгими (то есть они могут принимать некоторые значения, которые не принимает функция с этим условием). В последнем случае условие индекса считается неполным; его можно использовать для поиска по индексу, но для каждой строки, возвращаемой этим индексом, должен выполняться вызов функции, чтобы проверить, удовлетворяет ли строка условию WHERE. Для создания таких условий вспомогательная функция должна обрабатывать запросы типа SupportRequestIndexCondition.

Пользовательские агрегаты

Агрегатные функции в QHB определяются в терминах значений состояния и функций перехода состояния. То есть агрегат работает с некоторым значением состояния, которое обновляется при обработке каждой последующей входной строки. Чтобы определить новую агрегатную функцию, надо выбрать тип данных для значения состояния, его начальное значение и функцию перехода состояния. Функция перехода состояния принимает предыдущее значение состояния и входное значение (или значения) агрегата для текущей строки и возвращает новое значение состояния. В случае, если желаемый результат агрегата отличается от данных, которые нужно сохранить в изменяющемся значении состояния, можно также указать функцию завершения. Функция завершения принимает конечное значение состояния и возвращает то, что требуется в качестве результата агрегата. В принципе, функции перехода и завершения — это просто обычные функции, которые можно использовать и вне контекста агрегата. (На практике для повышения производительности часто бывает полезно создать специализированные функции перехода, которые могут работать только при вызове в составе агрегата).

Таким образом, помимо типов данных аргумента и результата, видимых пользователю агрегата, существует тип данных внутреннего значения состояния, который может отличаться как от типа аргумента, так и от типа результата.

Если мы определяем агрегат, не использующий функцию завершения, то он будет вычислять выполняемую функцию значений столбца из каждой строки. Примером такого агрегата является sum. sum начинается с нуля и всегда добавляет к накапливаемой сумме значение текущей строки. Например, если мы хотим заставить агрегат sum работать с типом данных для комплексных чисел, нам понадобится всего лишь функция сложения для этого типа данных. Определение агрегата может выглядеть так:

CREATE AGGREGATE sum (complex)
(
    sfunc = complex_add,
    stype = complex,
    initcond = '(0,0)'
);

И использовать эту функцию можно будет так:

SELECT sum(a) FROM test_complex;

   sum
-----------
 (34,53.9)

(Обратите внимание, что мы полагаемся на перегрузку функций: существует более одного агрегата с именем sum, но QHB может определить, какой из них применять к столбцу типа complex.)

Приведенное выше определение sum вернет ноль (значение начального состояния), если среди входных данных нет значений, отличных от NULL. Возможно, в таком случае у нас возникнет желание вернуть вместо этого NULL — по стандарту SQL ожидается, что sum будет вести себя именно так. Мы можем добиться этого, просто опустив фразу initcond, так что начальным значением состояния будет NULL. Обычно это будет означать, что функции sfunc понадобится проверить входное значение состояния на NULL. Но для sum и некоторых других простых агрегатов вроде max и min достаточно вставить в переменную состояния первое входное значение не NULL, а затем начать применять функцию перехода со второго значения не NULL. QHB сделает это автоматически, если значение начального состояния равно NULL и функция перехода помечена как «strict» (т. е. не должна вызываться для входных значений NULL).

Еще одна особенность поведения по умолчанию функции перехода «strict» заключается в том, что каждый раз, когда попадается входное значение NULL, предыдущее значение состояние остается неизменным. Таким образом, значения NULL игнорируются. Если вам нужно другое поведение для входных значений NULL, не объявляйте вашу функцию перехода строгой; вместо этого запрограммируйте ее проверять входные значения на NULL и обрабатывать их как требуется.

Функция avg (среднее арифметическое) является примером более сложного агрегата. Ей требуется два компонента текущего состояния: сумма входных значений и их количество. Итоговый результат получается делением этих величин. Обычно эта функция реализуется, используя в качестве значения состояния массив. Например, встроенная реализация avg(float8) выглядит так:

CREATE AGGREGATE avg (float8)
(
    sfunc = float8_accum,
    stype = float8[],
    finalfunc = float8_avg,
    initcond = '{0,0,0}'
);

Примечание
Для float8_accum требуется массив не из двух, а из трех элементов, потому что помимо суммы и количества входных значений она также вычисляет сумму квадратов. Это сделано для того, чтобы данную функцию можно было использовать и для других агрегатов помимо avg.

Вызовы агрегатных функций в SQL позволяют использовать указания DISTINCT и ORDER BY, которые определяют, какие строки и в каком порядке передаются в функцию перехода агрегата. Эта возможность реализована за кадром и не затрагивает вспомогательные функции агрегатов.

Дополнительную информацию см. на справочной странице команды CREATE AGGREGATE.

Режим движущегося агрегата

Агрегатные функции могут дополнительно поддерживать режим движущегося агрегата, который позволяет гораздо быстрее выполнять агрегатные функции в окнах со сдвигающимися начальными точками рамки. (Информацию об использовании агрегатных функций в качестве оконных функций см. в разделе Оконные функции и подразделе Вызовы оконных функций.) Основная идея в том, что в дополнение к обычной «прямой» функции перехода агрегат предоставляет обратную функцию перехода, которая позволяет удалять строки из значения состояния выполняющегося агрегата, когда те покидают рамку окна. Например, агрегат sum, который использует сложение в качестве функции прямого перехода, будет использовать в качестве функции обратного перехода вычитание. Без функции обратного перехода механизм оконной функции должен пересчитывать агрегат заново при каждом перемещении рамки окна, в результате чего время выполнения будет пропорционально числу входных строк, умноженному на среднюю длину рамки. При использовании функции обратного перехода время выполнения пропорционально только количеству входных строк.

В функцию обратного перехода передается текущего значение состояния и входное значение(я) агрегата для самой первой строки, включенной в текущее состояние. Она должна восстановить то значение состояния, которое получилось бы, если бы эта входная строка никогда не была агрегирована, но агрегировались все последующие. Иногда для этого нужно, чтобы функция прямого перехода сохраняла больше сведений о состоянии, чем требуется для простого режима агрегирования. По этой причине режим движущегося агрегата реализуется совершенно иначе, чем обычный режим: для него определяется свой тип данных состояния, собственная функция прямого перехода и, при необходимости, своя функция завершения. Они могут совпадать с типом данных и аналогичными функциями обычного режима, если нет необходимости в дополнительном состоянии.

В качестве примера мы можем дополнить рассмотренный выше агрегат sum, чтобы он поддерживал режим движущегося агрегата так:

CREATE AGGREGATE sum (complex)
(
    sfunc = complex_add,
    stype = complex,
    initcond = '(0,0)',
    msfunc = complex_add,
    minvfunc = complex_sub,
    mstype = complex,
    minitcond = '(0,0)'
);

Параметры, имена которых начинаются с m, относятся к реализации движущегося агрегата. За исключением функции обратного перехода minvfunc, они соответствуют параметрам обычного агрегата без m.

Функция прямого перехода для режима движущегося агрегата не разрешено возвращать NULL в качестве нового значения состояния. Если функция обратного перехода возвращает NULL, это воспринимается как признак того, что она не может инвертировать расчет состояния для этого конкретного входного значения, а значит, агрегатное вычисление нужно произвести заново с отметки текущей позиции начала рамки. Это соглашение позволяет использовать режим движущегося агрегата в нечастых ситуациях, когда отматывать обратно текущее значение состояния непрактично. В таких случаях функция обратного перехода может «застопориться», но в большинстве случаев она все равно продолжит пробиваться вперед, насколько это возможно. Например, агрегат, работающий с числами с плавающей запятой, может застопориться, когда ему понадобится убрать из текущего значения состояния значение NaN (не число).

При написании вспомогательных функций движущегося агрегата важно убедиться, что функция обратного перехода может точно восстановить верное значение состояния. Иначе в результатах могут возникнуть заметные пользователю различия в зависимости от того, используется ли режим движущегося агрегата. Примером агрегата, для которого добавление функции обратного перехода на первый взгляд кажется простым, а на самом деле вышеуказанное требование не будет выполняться, является sum с входным типом float4 или float8. Наивное объявление sum(float8) может быть таким:

CREATE AGGREGATE unsafe_sum (float8)
(
    stype = float8,
    sfunc = float8pl,
    mstype = float8,
    msfunc = float8pl,
    minvfunc = float8mi
);

Этот агрегат, однако, может выдать результаты, значительно отличающиеся от тех, которые получились бы без функции обратного перехода. Например, рассмотрим

SELECT
  unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
FROM (VALUES (1, 1.0e20::float8),
             (2, 1.0::float8)) AS v (n,x);

Вторым результатом этого запроса будет 0, а не ожидаемое значение 1. Причиной является ограниченная точность значений с плавающей запятой: при добавлении 1 к 1e20 снова получится 1e20, а при вычитании 1e20 из этого получится 0, а не 1. Обратите внимание, что это принципиальное ограничение всей арифметики чисел с плавающей запятой, а не только QHB.

Агрегаты с полиморфными и переменными аргументами

Агрегатные функции могут использовать полиморфные функции перехода состояния или функции завершения, так что эти же функции можно использовать для реализации нескольких агрегатов. Объяснение полиморфных функций см. в подразделе Полиморфные типы. Если пойти еще дальше, сама агрегатная функция может быть указана с полиморфными типами входных данных и состояния, что позволяет одному определению агрегата служить для использования с разными типами входных данных. Вот пример полиморфного агрегата:

CREATE AGGREGATE array_accum (anyelement)
(
    sfunc = array_append,
    stype = anyarray,
    initcond = '{}'
);

Здесь фактическим типом состояния для любого конкретного вызова агрегата является массив, элементы которого имеют фактический тип входных данных. Поведение данного агрегата заключается в объединении всех входных данных в массиве этого типа. (Примечание: встроенный агрегат array_agg обеспечивает аналогичную функциональность с лучшей производительностью, чем была бы у функции с приведенным выше определением.)

Вот результат использования в качестве аргументов двух различных фактических типов данных:

SELECT attrelid::regclass, array_accum(attname)
    FROM pg_attribute
    WHERE attnum > 0 AND attrelid = 'pg_tablespace'::regclass
    GROUP BY attrelid;

   attrelid    |              array_accum
---------------+---------------------------------------
 pg_tablespace | {spcname,spcowner,spcacl,spcoptions}
(1 row)

SELECT attrelid::regclass, array_accum(atttypid::regtype)
    FROM pg_attribute
    WHERE attnum > 0 AND attrelid = 'pg_tablespace'::regclass
    GROUP BY attrelid;

   attrelid    |        array_accum
---------------+---------------------------
 pg_tablespace | {name,oid,aclitem[],text[]}
(1 row)

Обычно у агрегатной функции с полиморфным типом результата тип состояния тоже будет полиморфным, как в приведенном выше примере. Это необходимо, поскольку иначе нельзя адекватно объявить функцию завершения: у нее должен быть полиморфный тип результата, но тип аргумента не будет полиморфным, что команда CREATE FUNCTION отвергнет на основании того, что при вызове невозможно определить тип результата. Но иногда полиморфный тип состояния неудобен в использовании. Чаще всего это происходит, когда вспомогательные функции агрегата должны быть написаны на нативном языке и тип состояния должен быть объявлен как internal, поскольку для него нет эквивалента на уровне SQL. Для решения этой проблемы можно объявить функцию завершения принимающей дополнительные «фиктивные» аргументы, которые соответствуют входным аргументам агрегата. В этих фиктивных аргументах всегда передаются значения NULL, так как при вызове функции завершения какое-либо определенное значение недоступно. Единственное их предназначение — позволить типу результата полиморфной функции завершения связаться с типом(ами) входных данных агрегата. Например, определение встроенного агрегата array_agg выглядит так:

CREATE FUNCTION array_agg_transfn(internal, anynonarray)
  RETURNS internal ...;
CREATE FUNCTION array_agg_finalfn(internal, anynonarray)
  RETURNS anyarray ...;

CREATE AGGREGATE array_agg (anynonarray)
(
    sfunc = array_agg_transfn,
    stype = internal,
    finalfunc = array_agg_finalfn,
    finalfunc_extra
);

Здесь параметр finalfunc_extra указывает, что функция завершения помимо значения состояния получает дополнительные фиктивные аргументы, соответствующие входным аргументам агрегата. Дополнительный аргумент anynonarray позволяет сделать объявление array_agg_finalfn допустимым.

Агрегатную функцию можно заставить принимать различное количество аргументов, объявив ее последний аргумент как массив VARIADIC по аналогии с обычными функциями; см. подраздел Функции SQL с переменным числом аргументов. Функции перехода агрегата должны иметь тот же тип массива, что и их последний аргумент. Обычно функции перехода тоже объявляются как VARIADIC, но это не строгое требование.

Примечание
Агрегаты с переменным числом аргументов легко использовать неправильно в сочетании с указанием ORDER BY (см. подраздел Агрегатные выражения), поскольку синтаксический анализатор не может определить, было ли передано нужное количество фактических параметров в такой комбинации. Помните, что все, расположенное справа от ORDER BY, является ключом сортировки, а не аргументом агрегата. Например, в

SELECT myaggregate(a ORDER BY a, b, c) FROM ...

анализатор увидит один аргумент агрегатной функции и три ключа сортировки. Однако пользователь мог иметь в виду следующее:

SELECT myaggregate(a, b, c ORDER BY a) FROM ...

Если функция myaggregate является VARIADIC, то оба этих вызова совершенно корректны.

По той же причине стоит дважды подумать, прежде чем создавать агрегатные функции с одинаковыми именами, но разным количеством обычных аргументов.

Сортирующие агрегаты

Все вышеописанные агрегаты относятся к «нормальным» агрегатам. QHB также поддерживает сортирующие агрегаты, которые имеют два ключевых отличия от нормальных. Во-первых, помимо обычных агрегируемых аргументов, которые вычисляются для каждой входной строки, сортирующий агрегат может принимать «непосредственные» аргументы, которые вычисляются только один раз за всю операцию агрегирования. Во-вторых, синтаксис обычных агрегируемых аргументов задает порядок их сортировки явно. Сортирующий агрегат обычно используется для выполнения вычисления, которое зависит от конкретного порядка строк, например ранг или процентиль, поэтому порядок сортировки является обязательным аспектом каждого вызова. К примеру, встроенное определение функции percentile_disc равнозначно следующему:

CREATE FUNCTION ordered_set_transition(internal, anyelement)
  RETURNS internal ...;
CREATE FUNCTION percentile_disc_final(internal, float8, anyelement)
  RETURNS anyelement ...;

CREATE AGGREGATE percentile_disc (float8 ORDER BY anyelement)
(
    sfunc = ordered_set_transition,
    stype = internal,
    finalfunc = percentile_disc_final,
    finalfunc_extra
);

Этот агрегат принимает непосредственный аргумент float8 (дробь процентиля) и агрегируемые входные данные, которые могут иметь любой сортируемый тип. Например, его можно использовать для расчета среднего семейного дохода:

SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY income) FROM households;
 percentile_disc
-----------------
           50489

Здесь 0.5 — это непосредственный аргумент; если бы дробь процентиля менялась от строки к строке, это не имело бы никакого смысла.

В отличие от нормальных агрегатов, сортировка входных строк для сортирующего агрегата проходит не за кадром, а является задачей вспомогательных функций агрегата. Типичный подход к реализации такой сортировки заключается в сохранении ссылки на объект «tuplesort» в значении состояния агрегата, загрузке входящих строк в этот объект и последующем окончании сортировки и выдаче данных в функции завершения. Такая модель позволяет функции завершения выполнять специальные операции, например добавлять «гипотетические» строки в сортируемые данные. Хотя нормальные агрегаты часто можно реализовать с помощью вспомогательных функций, написанных на PL/pgSQL или другом процедурном языке, сортирующие агрегаты, как правило, должны записываться на нативном языке, так как их значение состояния нельзя выразить каким-либо типом данных SQL. (Обратите внимание, что в приведенном выше примере значение состояния объявлено как имеющее тип internal — это типичный случай.) Кроме того, поскольку сортировку выполняет функция завершения, невозможно продолжать добавление строк, повторно вызывая функцию перехода. Это означает, что функция завершения не может иметь характеристику READ_ONLY; она должна объявляться в CREATE AGGREGATE с характеристикой READ_WRITE или SHAREABLE (если для дополнительных вызовов функции завершения можно использовать уже отсортированное состояние).

Функция перехода состояния для сортирующего агрегата получает текущее значение состояния и агрегируемые входные значения каждой строки и возвращает измененное значение состояния. Это определение аналогично таковому обычных агрегатов, но обратите внимание, что непосредственные аргументы (если таковые имеются) не передаются. Функция завершения получает последнее значение состояния, значения непосредственных аргументов (если таковые имеются) и (если указано finalfunc_extra) значения NULL, соответствующие агрегируемым входным данным. Для нормальных агрегатов указание finalfunc_extra по-настоящему полезно, только если агрегат полиморфный; тогда дополнительные фиктивные аргументы нужны, чтобы связать тип результата функции завершения с типом(ами) входных данных агрегата.

В настоящее время сортирующие агрегаты нельзя использовать в качестве оконных функций, поэтому им нет необходимости поддерживать режим движущегося агрегата.

Частичная агрегация

Дополнительно агрегатная функция может поддерживать частичную агрегацию. Идея частичной агрегации состоит в том, чтобы независимо выполнять функцию перехода состояния агрегата для различных подмножеств входных данных, а затем комбинировать значения состояний, вычисленные по этим подмножествам, для получения того же значения состояния, которое получилось бы при сканировании всех входных данных за один раз. Этот режим можно использовать для параллельной агрегации, когда разные рабочие процессы сканируют разные части таблицы. Каждый процесс создает частичное значение состояния, и в конце эти значения объединяются для получения окончательного значения состояния. (Возможно, в будущем этот режим также будет использоваться для таких целей, как комбинированная агрегация локальных и удаленных таблиц, но пока это не реализовано).

Для поддержки частичной агрегации определение агрегата должно предоставлять комбинирующую функцию, которая принимает два значения типа состояния агрегата (представляющих результаты агрегации по двум подмножествам входных строк) и создает новое значение типа состояния, представляющее состояние, каким оно было бы после агрегации комбинации этих подмножеств строк. При этом относительный порядок входных строк в этих двух подмножествах не указывается. Это означает, что для агрегатов, зависящих от порядка строк, обычно невозможно определить осмысленную комбинирующую функцию.

В качестве простых примеров, агрегаты MAX и MIN могут поддерживать частичную агрегацию, если в качестве комбинирующей функции указать функцию сравнения значений (большее-из-двух или меньшее-из-двух соответственно), которую они также используют в качестве функции перехода. Агрегаты SUM просто используют в качестве комбинирующей функцию сложения. (Опять же, эта функция выступает и в качестве их функции перехода, если только значение состояния не выходит за рамки типа входных данных.)

Комбинирующая функция используется почти так же, как функция перехода, но принимает в качестве второго аргумента значение типа состояния, а не лежащего в основе входного типа. В частности, у нее такие же правила относительно значений NULL и строгих функций. Кроме того, если в определении агрегата задается значение initcond, отличное от NULL, имейте в виду, оно будет использоваться не только как начальное состояние для каждого прохода частичной агрегации, но и как начальное состояние для комбинирующей функции, которая будет вызываться для комбинирования каждого частичного результата в этом состоянии.

Если тип состояния агрегата объявлен как internal, именно комбинирующая функция отвечает за то, чтобы ее результат был размещен в контексте памяти, подходящем для значений состояния агрегата. В частности, это означает, что получив в первом аргументе NULL, нельзя просто вернуть второй аргумент, поскольку это значение будет иметь неправильный контекст и недостаточно долгий срок существования.

Когда тип состояния агрегата объявлен как internal, обычно в определении агрегата также уместно задать функцию сериализации и функцию десериализации, которые позволяют копировать значение состояния из одного процесса в другой. Без этих функция нельзя осуществить параллельную агрегацию, а также, вероятно, не будут работать будущие приложения, например локальная/удаленная агрегация.

Функция сериализации должна принимать один аргумент типа internal и возвращать результат типа bytea, который представляет собой значение состояния, упакованное в плоский массив байтов. Функция десериализации, наоборот, обращает это преобразование. Она должна принимать два аргумента с типами bytea и internal и возвращать результат типа internal. (Второй аргумент не используется и всегда равен нулю, но он необходим из соображений безопасности типов.) Результат функции десериализации нужно просто разместить в текущем контексте памяти, поскольку, в отличие от результата комбинирующей функции, он недолговечен.

Стоит также отметить, что для выполнения агрегата в параллельном режиме его следует пометить как PARALLEL SAFE (безопасен для распараллеливания). Для его вспомогательных функциях такая пометка значения не имеет.

Вспомогательные функции для агрегатов

Функция, написанная на нативном языке, может определить, была ли она вызвана как вспомогательная функция агрегата, вызвав AggCheckCallContext, например:

if (AggCheckCallContext(fcinfo, NULL))

Единственная причина такой проверки состоит в том, что в случае положительного результата первый входной аргумент должен быть временным значением состояния, которое можно безопасно изменить на месте, вместо того чтобы размещать новую копию. Пример можно увидеть в функции int8inc(). (Хотя агрегатным функциям перехода всегда разрешено изменять значение перехода на месте, агрегатным функциям завершения в целом следует этого избегать; если они это делают, такое поведение должно быть объявлено при создании агрегата. Более подробную информацию см. на справочной странице команды CREATE AGGREGATE.)

Второй аргумент AggCheckCallContext можно использовать для получения контекста памяти, в котором хранятся значения состояния агрегата. Это полезно для функций перехода, которые хотят использовать в качестве значений состояния «развернутые» объекты (см. подраздел Особенности TOAST). При первом вызове эта функция перехода должна вернуть развернутый объект, чей контекст памяти является дочерним по отношению к контексту состояния агрегата, и затем продолжать возвращать тот же развернутый объект при последующих вызовах. Для примера рассмотрим функцию array_append(). (array_append() не является функцией перехода какого-либо встроенного агрегата, но написана так, чтобы эффективно работать в таком качестве в пользовательском агрегате).

Другой вспомогательной подпрограммой, доступной для агрегатных функций, написанных на нативном языке, является AggGetAggref, которая возвращает узел разбора Aggref, определяющий вызов агрегата. Это в основном полезно для сортирующих агрегатов, которые могут проверить подструктуру узла Aggref, чтобы выяснить, какой порядок сортировки они должны реализовать.

Пользовательские типы

Как описано в разделе Система типов QHB, QHB может быть расширена для поддержки новых типов данных. В этом разделе описывается, как определить новые базовые типы, то есть типы данных, определенные ниже уровня языка SQL. Создание нового базового типа требует реализации функций для работы с этим типом на языке низкого уровня, как правило, на нативном языке.

Пользовательский тип должен всегда иметь функции ввода и вывода. Эти функции определяют, как тип будет отображаться в строках (при вводе и выводе для пользователя) и как этот тип расположен в памяти. Функция ввода принимает в качестве аргумента строку символов, заканчивающуюся нулем, и возвращает внутреннее (в памяти) представление типа. Функция вывода принимает в качестве аргумента внутреннее представление типа и возвращает строку символов, заканчивающуюся нулем. Если мы хотим сделать с типом что-то большее, нежели просто сохранить его, то для реализации любых операций, которые мы хотели бы иметь для этого типа, нужно предоставить дополнительные функции.

Предположим, мы хотим определить тип complex, который представляет комплексные числа. Естественным способом представления комплексного числа в памяти будет следующая структура C:

typedef struct Complex {
    double      x;
    double      y;
} Complex;

Нам нужно будет сделать этот тип передаваемым по ссылке, поскольку он слишком велик, чтобы поместиться в одно значение Datum.

В качестве внешнего строкового представления типа мы выберем строку вида (x,y).

Функции ввода и вывода обычно несложно написать, особенно функцию вывода. Но при определении внешнего строкового представления типа помните, что в конечном итоге вам придется написать законченный и надежный синтаксический анализатор для этого представления в входной функции. Например:

PG_FUNCTION_INFO_V1(complex_in);

Datum
complex_in(PG_FUNCTION_ARGS)
{
    char       *str = PG_GETARG_CSTRING(0);
    double      x,
                y;
    Complex    *result;

    if (sscanf(str, " ( %lf, %lf )", &x, &y) != 2)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "complex", str)));

    result = (Complex *) palloc(sizeof(Complex));
    result->x = x;
    result->y = y;
    PG_RETURN_POINTER(result);
}

Функция вывода может быть простой:

PG_FUNCTION_INFO_V1(complex_out);

Datum
complex_out(PG_FUNCTION_ARGS)
{
    Complex    *complex = (Complex *) PG_GETARG_POINTER(0);
    char       *result;

    result = psprintf("(%g,%g)", complex->x, complex->y);
    PG_RETURN_CSTRING(result);
}

Нужно позаботиться о том, чтобы входные и выходные функции получились противоположными друг другу. Если вы этого не сделаете, то столкнетесь с серьезными проблемами, когда вам понадобится сохранить данные в файл и затем опять прочитать их. Это особенно распространенная проблема, когда дело касается чисел с плавающей запятой.

Дополнительно пользовательский тип может предоставлять функции ввода и вывода в двоичном формате. Двоичный ввод/вывод обычно быстрее, но сильнее зависит от платформы, чем текстовый. Как и в случае с текстовым вводом/выводом, точное определение того, каким будет внешнее двоичное представление, остается за вами. Большинство встроенных типов данных пытаются обеспечить машинно-независимое двоичное представление. Для типа complex мы воспользуемся преобразователями двоичного ввода/вывода для типа float8:

PG_FUNCTION_INFO_V1(complex_recv);

Datum
complex_recv(PG_FUNCTION_ARGS)
{
    StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
    Complex    *result;

    result = (Complex *) palloc(sizeof(Complex));
    result->x = pq_getmsgfloat8(buf);
    result->y = pq_getmsgfloat8(buf);
    PG_RETURN_POINTER(result);
}

PG_FUNCTION_INFO_V1(complex_send);

Datum
complex_send(PG_FUNCTION_ARGS)
{
    Complex    *complex = (Complex *) PG_GETARG_POINTER(0);
    StringInfoData buf;

    pq_begintypsend(&buf);
    pq_sendfloat8(&buf, complex->x);
    pq_sendfloat8(&buf, complex->y);
    PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}

После того, как мы написали функции ввода/вывода и скомпилировали их в разделяемую библиотеку, мы можем определить тип complex в SQL. Сначала мы объявим тип-оболочку:

CREATE TYPE complex;

Он служит заполнителем, позволяющим нам ссылаться на этот тип при определении его функций ввода/вывода. Теперь мы можем определить функции ввода/вывода:

CREATE FUNCTION complex_in(cstring)
    RETURNS complex
    AS 'имя_файла'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_out(complex)
    RETURNS cstring
    AS 'имя_файла'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_recv(internal)
   RETURNS complex
   AS 'имя_файла'
   LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_send(complex)
   RETURNS bytea
   AS 'имя_файла'
   LANGUAGE C IMMUTABLE STRICT;

Наконец мы можем предоставить полное определение типа данных:

CREATE TYPE complex (
   internallength = 16,
   input = complex_in,
   output = complex_out,
   receive = complex_recv,
   send = complex_send,
   alignment = double
);

Когда вы определяете новый базовый тип, QHB автоматически обеспечивает поддержку массивов этого типа. Тип массива обычно имеет то же имя, что и базовый тип, с добавлением символа подчеркивания (_) в начале.

Теперь, когда тип данных существует, мы можем объявить дополнительные функции для выполнения полезных операций с этим типом. Поверх этих функций можно определить операторы и при необходимости создать классы операторов для поддержки индексации этого типа. Эти дополнительные уровни рассматриваются в следующих разделах.

Если внутреннее представление типа данных имеет переменную длину, оно должно соответствовать стандартной схеме данных переменной длины: первые четыре байта должны занимать поле char[4], к которому никогда не следует обращаться напрямую (традиционно называемое vl_len_). Чтобы сохранить в этом поле общий размер элемента (включая само поле длины), нужно использовать макрос SET_VARSIZE(), а чтобы получить его — VARSIZE(). (Эти макросы существуют, потому что поле длины может кодироваться по-разному в зависимости от платформы.)

Более подробную информацию см. на справочной странице команды CREATE TYPE.

Особенности TOAST

Если значения вашего типа данных сильно различаются по размеру (во внутреннем представлении), обычно желательно сделать это тип подходящим для TOAST (см. раздел TOAST). Стоит это сделать даже в том случае, когда значения всегда слишком малы для сжатия или внешнего хранения, поскольку TOAST может сэкономить пространство и с данными малого размера, уменьшая издержки в заголовке.

Чтобы поддерживать хранение TOAST, функции на C/RUST, работающие с этим типом данных, должны позаботиться о распаковке любых сжатых значений, которые передаются им с помощью макроса PG_DETOAST_DATUM. (Эта деталь обычно скрывается путем определения макроса GETARG_DATATYPE_P для конкретного типа). Затем при запуске команды CREATE TYPE укажите внутреннюю длину как variable и выберите какой-нибудь подходящий вариант хранения, отличный от plain.

Если выравнивание данных неважно (либо для конкретной функции, либо потому, что для этого типа данных в любом случае применяется выравнивание по байтам), то можно избежать некоторых издержек, связанных с макросом PG_DETOAST_DATUM. Вместо него можно использовать макрос PG_DETOAST_DATUM_PACKED (обычно скрывается путем определения макроса GETARG_DATATYPE_PP) и применить макросы VARSIZE_ANY_EXHDR и VARDATA_ANY для обращения к потенциально сжатым данным. Опять же, данные, возвращаемые этими макросами, не выравниваются, даже если определение типа данных требует выравнивания. Если выравнивание важно, следует задействовать обычный интерфейс PG_DETOAST_DATUM.

Примечание
В более старом коде поле vl_len_ часто объявлялось как int32, а не char[4]. Это нормально, пока в структуре есть другие поля с выравниванием как минимум int32. Но при работе с потенциально невыровненными данными такое строго определение использовать опасно; компилятор может воспринять его как право предполагать, что данные действительно выровнены, что приведет к аварийным выключениям ядра в архитектурах, строгих к выравниванию.

Еще одна функциональность, предоставляемая поддержкой TOAST, — это возможность иметь развернутое представление данных в памяти, работать с которым удобнее, чем с форматом, хранящимся на диске. Обычный, или «плоский», формат хранения varlena, в конечном счете, является простым набором байтов; например, он не может содержать указатели, так как может быть скопирован в другие области памяти. Для сложных типов данных может быть довольно дорого работать с плоским форматом, поэтому QHB предоставляет способ «развернуть» плоский формат в представление, более подходящее для вычислений, а затем передавать этот формат в памяти между функциями, работающими с этим типом данных.

Чтобы использовать развернутое хранилище, тип данных должен определять развернутый формат, который следует правилам, и предоставить функции для «разворачивания» плоского значения varlena в этот формат и «сворачивания» его обратно в обычное представление varlena. Затем добейтесь того, чтобы все функции на C/RUST для этого типа данных могли принимать любое представление, возможно, путем преобразования одной в другую сразу при получении. Это не требует одновременного исправления всех существующих функций для этого типа данных, поскольку стандартный макрос PG_DETOAST_DATUM способен преобразовать развернутые входные данные в обычный плоский формат. Таким образом, существующие функции, работающие с плоским форматом varlena, продолжат работать, хотя и не очень эффективно, с развернутыми входными данными; их не нужно преобразовывать до тех пор, пока не понадобится повысить производительность.

Функции на C/RUST, которые знают, как работать с развернутым представлением, обычно делятся на две категории: те, которые могут обрабатывать только развернутый формат, и те, которые могут обрабатывать как развернутые, так и плоские входные данные varlena. Первые легче написать, но они могут быть менее эффективными в целом, потому что преобразование плоских входных данных в развернутую форму для использования одной-единственной функцией может стоить больше, чем экономится при работе с развернутым форматом. Когда требуется обрабатывать только развернутый формат, преобразование плоских входных данных в развернутую форму можно скрыть внутри макроса, извлекающего аргументы, чтобы функция была не более сложной, чем та, которая работает с традиционными входными данными varlena. Чтобы обработать оба типа входных данных, напишите функцию извлечения аргументов, которая будет распаковывать входные данные varlena с коротким заголовком, а также внешние и сжатые, но не развернутые. Такую функцию можно определить как возвращающую указатель на объединение плоского формата varlena и развернутого формата. Чтобы определить, какой именно формат был получен, можно воспользоваться макросом VARATT_IS_EXPANDED_HEADER().

Инфраструктура TOAST не только позволяет отличать обычные значения varlena от развернутых значений, но также различает указатели «для чтения-записи» и «только для чтения» на развернутые значения. Функции на C/RUST, которым нужно проверять только развернутое значение или которые будут изменять его только безопасными и не видимыми семантически способами, нет необходимости обращать внимание на то, какой тип указателя они получают. Функциям на C/RUST, которые выдают измененную версию входных данных, разрешено изменять развернутые входные данные на месте при получении указателя для чтения-записи, но не когда получают указатель только для чтения; в этом случае они должны сначала скопировать это значение, создав новое значение, подлежащее изменению. Функция на C/RUST, которая создала новое развернутое значение, всегда должна возвращать указатель на него для чтения-записи. Кроме того, функция на C/RUST, которая изменяет развернутое значение для чтения-записи на месте, должна позаботиться о том, чтобы оставить его в нормальном состоянии, если во время ее работы произойдет сбой.

Пользовательские операторы

Каждый оператор является «синтаксическим сахаром» для вызова лежащей в его основе функции, которая выполняет реальную работу; поэтому прежде чем вы сможете создать оператор, сначала нужно создать базовую функцию. Тем не менее оператор не просто «синтаксический сахар», так как несет дополнительную информацию, которая помогает планировщику запросов оптимизировать запросы, использующие этот оператор. Следующий раздел будет посвящен рассмотрению этой дополнительной информации.

QHB поддерживает префиксный и инфиксный операторы. Операторы могут быть перегружены; то есть одно и то же имя оператора может использоваться для разных операторов с разным количеством и типами операндов. При выполнении запроса система определяет, какой оператор вызвать, по числу и типам предоставленных аргументов.

Вот пример создания оператора для сложения двух комплексных чисел. Предположим, что мы уже создали определение типа complex (см. раздел Пользовательские типы). Сначала нам нужна функция, выполняющая работу, а затем мы сможем определить оператор:

CREATE FUNCTION complex_add(complex, complex)
    RETURNS complex
    AS 'имя_файла', 'complex_add'
    LANGUAGE C IMMUTABLE STRICT;

CREATE OPERATOR + (
    leftarg = complex,
    rightarg = complex,
    function = complex_add,
    commutator = +
);

Теперь мы можем выполнить такой запрос:

SELECT (a + b) AS c FROM test_complex;

        c
-----------------
 (5.2,6.05)
 (133.42,144.95)

Здесь мы показали, как создать бинарный оператор. Чтобы создать префиксный оператор, просто опустите leftarg. Единственными обязательными элементами в команде CREATE OPERATOR являются предложение function и объявления аргументов. Предложение commutator, показанное в примере, служит дополнительной подсказкой оптимизатору запросов. Более подробно о предложении commutator и других подсказках для оптимизатора рассказывается в следующем разделе.

Информация по оптимизации операторов

Определение оператора QHB может включать несколько дополнительных предложений, которые сообщают системе полезные сведения о поведении оператора. Эти предложения следует, по возможности, указывать, поскольку они могут значительно ускорить выполнение запросов, использующих этот оператор. Но если вы их указываете, следует убедиться, что они корректны! Неправильное использование предложения оптимизации может привести к замедлению запросов, неочевидно ошибочному результату или другим бедам. Если вы не уверены в предложении оптимизации, его всегда можно опустить; единственным последствием будет то, что запросы могут выполняться медленнее, чем нужно.

В будущих версиях QHB могут появиться дополнительные предложения оптимизации. Здесь описываются те предложения, которые поддерживаются версией 1.5.2.

Также есть возможность добавить к функции, реализующей оператор, вспомогательную функцию для планировщика, тем самым открывая другой способ информирования системы о поведении оператора. Дополнительную информацию см. в разделе Информация по оптимизации функций.

COMMUTATOR

Предложение COMMUTATOR, если имеется, задает оператор, коммутирующий определяемый. Мы говорим, что оператор A является коммутатором оператора B, если (x A y) равно (y B x) для всех возможных входных значений x, y. Обратите внимание, что B также является коммутатором A. Например, операторы < и > для определенного типа данных обычно являются коммутаторами друг друга, а оператор + обычно коммутирует сам с собой. А вот оператор - обычно не коммутирует ни с одним оператором.

Тип левого операнда коммутируемого оператора такой же, как тип правого операнда его коммутатора, и наоборот. Таким образом, имя коммутирующего оператора — это все, что нужно дать QHB для поиска коммутатора, и все, что необходимо указать в предложении COMMUTATOR.

Очень важно предоставлять информацию о коммутаторах тем операторам, которые будут использоваться в индексах и предложениях соединения, поскольку это позволяет оптимизатору запросов «переворачивать» такое предложение, приводя его к форме, необходимой для различных типов планов. Например, рассмотрим запрос с предложением WHERE вроде tab1.x = tab2.y, где tab1.x и tab2.y имеют пользовательский тип, и предположим, что по столбцу tab2.y есть индекс. Оптимизатор сможет задействовать сканирование по индексу, только если сумеет определить, как перевернуть это предложение, превратив его в tab2.y = tab1.x, поскольку механизм сканирования по индексу ожидает увидеть индексированный столбец слева от переданного ему оператора. QHB не будет по умолчанию считать, что такое преобразование возможно — создатель оператора = должен это указать, добавив в оператор информацию о коммутаторе.

Когда вы определяете оператор, коммутирующий сам себя, вы просто делаете это. Когда вы определяете пару коммутирующих операторов, все становится немного сложнее: как оператор определяемый первым, может ссылаться на другой, который вы еще не определили? Есть два решения этой проблемы:

Один из способов — опустить предложение COMMUTATOR в первом определяемом вами операторе, а затем указать его в определении второго оператора. Поскольку QHB знает, что коммутирующие операторы идут парами, когда она увидит второе определение, то автоматически вернется и заполнит пропущенное предложение COMMUTATOR в первом определении.

Другой, более простой способ — просто включить предложения COMMUTATOR в оба определения. Когда QHB обрабатывает первое определение и понимает, что COMMUTATOR ссылается на несуществующий оператор, система сделает фиктивную запись для этого оператора в системном каталоге. Эта фиктивная запись будет содержать актуальные данные только для имени оператора, левого и правого типов операндов и типа результата, поскольку это все, что QHB может выяснить на данный момент. Запись первого оператора будет связана с этой фиктивной записью. Позже, когда вы определите второй оператор, система обновит фиктивную запись дополнительной информацией из второго определения. Если вы попытаетесь использовать фиктивный оператор до его заполнения, то просто получите сообщение об ошибке.

NEGATOR

Предложение NEGATOR, если имеется, задает оператор, обратный определяемому. Мы говорим, что оператор A является обратным к оператору B, если оба возвращают логические результаты и (x A y) равно NOT (x B y) для всех возможных входных значений x, y. Обратите внимание, что B также является обратным к A. Например, < и >= являются парой обратных друг к другу операторов для большинства типов данных. Никакой оператор никогда не может быть обратным себе самому.

В отличие от коммутаторов, пару унарных операторов вполне можно пометить как обратные друг к другу; это будет означать, что (A x) равно NOT (B x) для всех x (и аналогично для правых унарных операторов).

У обратного оператора должны быть те же типы левого и/или правого операнда, что и у определяемого оператор, поэтому, как и в случае с COMMUTATOR, в предложении NEGATOR следует указывать только имя оператора.

Предоставление обратного оператора очень полезно для оптимизатора запросов, поскольку тот позволяет упростить выражения типа NOT (x >= y) до x < y. Это происходит чаще, чем можно подумать, так как операции NOT могут добавляться вследствие других преобразований.

Пары обратных операторов можно определить, используя те же методы, которые описаны выше для пар коммутаторов.

RESTRICT

Предложение RESTRICT, если имеется, задает функцию оценки избирательности ограничения для оператора. (Обратите внимание, что это имя функции, а не оператора.) Предложения RESTRICT имеют смысл только для бинарных операторов, возвращающих boolean. Идея оценки избирательности ограничения состоит в том, чтобы примерно определить, какая доля строк в таблице будет удовлетворять условию предложения WHERE вида:

column OP constant

для текущего оператора и определенного значения константы. Это помогает оптимизатору, давая ему некоторое представление о том, сколько строк будет исключено предложениями WHERE такого вида. (А что произойдет, если константа будет слева, спросите вы? Ну, как раз для таких случаев и предназначен COMMUTATOR...)

Написание новых функций оценки избирательности ограничения выходит за рамки этой главы, но, к счастью, обычно для большинства ваших собственных операторов можно просто воспользоваться одним из стандартных системных оценщиков. Вот стандартные оценщики ограничений:

eqsel для =
neqsel для <>
scalarltsel для <
scalarlesel для <=
scalargtsel для >
scalargesel для >=

Зачастую вы можете обойтись функциями eqsel или neqsel для операторов, которые имеют очень высокую или очень низкую избирательность, даже если на самом деле они не имеют никакого отношения к равенству или неравенству. Например, геометрические операторы приблизительного равенства используют eqsel в предположении, что обычно им соответствует лишь небольшая часть записей в таблице.

Для сравнения типов данных, которые можно каким-либо разумным способом преобразовать в числовые скалярные значения для сравнения диапазонов, можно использовать функции scalarltsel, scalarlesel, scalargtsel иscalargesel. По возможности, добавьте свой тип данных в число типов, которые понимает функция convert_to_scalar() в src/backend/utils/adt/selfuncs.c. (В конечном итоге эту функцию следует заменить функциями для каждого типа данных, указанного в столбце системного каталога pg_type, но пока этого не произошло.) Если вы этого не сделаете, все будет работать, но оценки оптимизатора будут не так хороши, как могли бы быть.

Еще одна полезная встроенная функция оценки избирательности — matchingsel, которая будет работать практически со всеми бинарными операторами, если для их входных типов данных собирается стандартная статистика MCV и/или строится гистограмма. Ее оценка по умолчанию в два раза больше оценки по умолчанию, выдаваемой eqsel, благодаря чему эта функция лучше всего подходит для операторов сравнения, несколько менее строгих, чем оператор равенства. (Либо можно вызвать нижележащую функцию generic_restriction_selectivity, предоставив ей другую оценку по умолчанию.)

JOIN

Предложение JOIN, если имеется, задает функцию оценки избирательности соединения для оператора. (Обратите внимание, что это имя функции, а не оператора.) Предложения JOIN имеют смысл только для бинарных операторов, возвращающих boolean. Идея оценки избирательности соединения состоит в том, чтобы примерно определить, какая доля строк в паре таблиц будет удовлетворять условию предложения WHERE вида:

ON table1.column1 OP table2.column2

для данного оператора. Как и в случае с RESTRICT, это существенно помогает оптимизатору, позволяя ему выяснить, какая из нескольких возможных последовательностей соединения скорее всего окажется наименее трудоемкой.

Как и ранее, в этой главе мы не будем разъяснять, как писать свой функции оценки избирательности соединения, а просто предложим использовать один из подходящих стандартных оценщиков:

eqjoinsel для =
neqjoinsel для <>
scalarltjoinsel для <
scalarlejoinsel для <=
scalargtjoinsel для >
scalargejoinsel для >=
matchingjoinsel для типовых операторов сопоставления
areajoinsel для сравнений областей в плоскости
positionjoinsel для сравнений позиций в плоскости
contjoinsel для сравнений включения в плоскости

HASHES

Предложение HASHES, если присутствует, сообщает системе, что для соединения на основе этого оператора разрешается использовать метод соединения по хешу. HASHES актуально только для бинарного оператора, который возвращает boolean, и на практике оператор должен выражать равенство значений для некоторого типа данных или пары типов данных.

Соединение по хешу основано на том, что оператор соединения может возвращать true только для тех пар значений слева и справа, которые хешируют один и тот же хеш-код. Если два значения помещаются в разные хеш-блоки, объединение вообще никогда их не сравнит, неявно предполагая, что результат оператора соединения должен быть false. Поэтому нет смысла указывать HASHES для операторов, которые не выражают некоторую форму равенства. В большинстве случаев практично поддерживать хеширование только для тех операторов, которые принимают одинаковый тип данных с обеих сторон. Однако иногда возможно разработать хеш- функции, совместимые с двумя и более типами данных, то есть функции, которые будут генерировать одинаковые хеш-коды для «равных» значений, даже если эти значения имеют разные представления. Например, довольно просто реализовать это свойство при хешировании целых чисел различной ширины.

Чтобы получить характеристику HASHES, оператор соединения должен принадлежать семейству операторов индексирования по хешу. Это не проверяется при создании оператора, поскольку, разумеется, в этот момент соответствующее семейство операторов еще не может существовать. Но попытки использовать такой оператор для соединений по хешу приведут к ошибке во время выполнения, если такого семейства операторов не существует. Семейство операторов нужно системе, чтобы найти функции для хеширования входных типов данных оператора. Конечно, прежде чем получится создать семейство операторов, нужно создать подходящие функции хеширования.

При создании функции хеширования следует соблюдать осторожность, поскольку машинная архитектура может провоцировать ошибки. Например, если ваш тип данных представляет собой структуру, в которой есть незначащие биты-заполнители, то нельзя просто передать всю структуру функции hash_any. (Если только вы не напишете свои операторы и функции так, чтобы число неиспользуемых битов всегда равнялось нулю, что является рекомендуемой стратегией.) В качестве другого примера можно привести машины, использующих числа с плавающей запятой в соответствии со стандартом IEEE, в которых отрицательный ноль и положительный ноль — это разные значения (отличаются комбинациями битов), но при сравнении они считаются равными. Если значение с плавающей запятой может содержать отрицательный ноль, то перед хешированием нужен дополнительный шаг, чтобы для него выдавалось то же значение хеша, что и для положительного нуля.

Оператор соединения по хешу должен иметь коммутатор (это может быть тот же оператор, если у него два типа данных операнда одного вида, либо, если виды разные, связанный оператор равенства), принадлежащий тому же семейству операторов. В противном случае при использовании оператора могут возникнуть ошибки планировщика. Кроме того, желательно (но не обязательно), чтобы семейство операторов хеширования, поддерживающее несколько типов данных, предоставляло операторы равенства для всех возможных комбинаций этих типов; это способствует лучшей оптимизации.

Примечание
Функция, реализующая оператор соединения по хешу, должна быть помечена как постоянная (IMMUTABLE) или стабильная (STABLE). Если эта функция изменчивая (VOLATILE), система никогда не будет пытаться использовать этот оператор для соединения по хешу.

Примечание
Если оператор соединения по хешу реализован через строгую функцию (STRICT), эта функция также должна быть полной: то есть она должна возвращать true или false, но не NULL для любых входных значений не NULL. Если это правило не соблюдается, оптимизация операций IN с хешем может привести к неверным результатам. (В частности, IN может вернуть false, когда правильным ответом, в соответствии со стандартом, должен быть NULL, или выдать ошибку, сообщающую, что он не готов к результату NULL.)

MERGES

Предложение MERGES, если присутствует, сообщает системе, что для соединения на основе этого оператора разрешается использовать метод соединения слиянием. MERGES актуально только для бинарного оператора, который возвращает boolean, и на практике оператор должен выражать равенство значений для некоторого типа данных или пары типов данных.

Объединение слиянием основано на идее упорядочивания левой и правой таблиц и последующего их параллельного сканирования. Таким образом, оба типа данных должны поддерживать сортировку в полном объеме, а оператор соединения должен успешно работать только с парами значений, попадающих при данном порядке сортировки в «одно и то же место». На практике это означает, что оператор соединения должен вести себя как равенство. Но также возможно объединить слиянием и два разных типа данных, если они логически совместимы. Например, оператор равенства smallint и integer является оператором соединения слиянием. Нам понадобятся только операторы сортировки, которые приведут оба типа данных в логически совместимую последовательность.

Чтобы получить характеристику MERGES, оператор соединения должен являться членом семейства операторов индекса btree, реализующим равенство. Это не проверяется при создании оператора, поскольку, разумеется, соответствующее семейство операторов еще не может существовать. Но оператор не будет фактически использоваться для соединений слиянием, пока не будет найдено соответствующее семейство операторов. Таким образом, флаг MERGES служит подсказкой для планировщика, что стоит искать подходящее семейство.

Оператор соединением слиянием должен иметь коммутатор (это может быть тот же оператор, если у него два типа данных операнда одного вида, либо, если виды разные, связанный оператор равенства), относящийся к тому же семейству операторов. В противном случае при использовании оператора могут возникнуть ошибки планировщика. Кроме того, желательно (но не обязательно), чтобы семейство операторов btree, поддерживающее несколько типов данных, предоставляло операторы равенства для всех возможных комбинаций этих типов данных; это способствует лучшей оптимизации.

Примечание
Функция, реализующая оператор соединения слиянием, должна быть помечена как постоянная (IMMUTABLE) или стабильная (STABLE). Если эта функция изменчивая (VOLATILE), система никогда не будет пытаться использовать этот оператор для соединения слиянием.

Интерфейсные расширения для индексов

Описанные выше процедуры позволяют определять новые типы, функции и операторы. Однако мы еще не можем определить индекс по столбцу нового типа данных. Для этого нужно определить класс операторов для нового типа данных. Далее в этом разделе мы проиллюстрируем эту концепцию на примере: новый класс операторов для индексного метода B-дерева, который сохраняет и сортирует комплексные числа в порядке возрастания абсолютного значения.

Классы операторов можно сгруппировать в семейства операторов, показывающие зависимости между семантически совместимыми классами. Когда задействован только один тип данных, достаточно класса операторов, поэтому сначала мы сосредоточимся на этом случае, а затем вернемся к семействам операторов.

Индексные методы и классы операторов

Таблица pg_am содержит одну строку для каждого индексного метода (внутренне известного как метод доступа). Поддержка обычного доступа к таблицам встроена в QHB, но все индексные методы описаны в pg_am. Можно добавить новый индексный метод доступа, написав необходимый код и затем создав запись в pg_am — но это выходит за рамки этой главы (см. главу Определение интерфейса для индексных методов доступа).

Процедуры индексного метода непосредственно ничего не знают о типах данных, с которыми тот будет работать. Вместо этого набор операций, которые нужны индексному методу для работы с конкретным типом данных, определяет класс операторов. Классы операторов называются так, потому что они указывают только одну вещь — множество операторов в предложении WHERE, которые можно использовать с индексом (то есть можно преобразовать в префикс для сканирования индекса). Класс операторов также может указывать некую вспомогательную функцию, которая необходима внутренним операциям индексного метода, но не соответствует напрямую ни одному оператору предложения WHERE, который может обрабатываться с индексом.

Для одного и того же типа данных и индексного метода можно определить несколько классов операторов. Благодаря этому для одного типа данных можно определить несколько вариантов доступа по индексу с различной семантикой. Например, для B-дерева требуется определить порядок сортировки для каждого типа данных, с которым он работает. Для типа комплексных чисел может быть полезно иметь один класс операторов B-дерева, который сортирует данные по абсолютному значению комплексного числа, еще один, который сортирует по вещественной части, и т. д. Как правило, считается, что один из классов операторов будет применяться чаще всего, и его помечают как класс операторов по умолчанию для этого типа данных и индексного метода.

Одно и то же имя класса операторов можно использовать для нескольких различных индексных методов (например, для методов B-дерева и хеш-индекса применяются классы операторов с именем int4_ops), но каждый такой класс является независимым объектом и должен определяться отдельно.

Стратегии индексного метода

Операторам, связанным с классом операторов, присваиваются «номера стратегий», которые служат для идентификации семантики каждого оператора в контексте его класса операторов. Например, B-деревья предписывают строгий порядок ключей, от меньшего к большему, и поэтому в данном контексте представляют интерес операторы типа «меньше» и «больше или равно». Поскольку QHB позволяет пользователю определять операторы, QHB не может посмотреть на имя оператора (например < или >=) и сказать, какое это сравнение. Вместо этого индексный метод определяет набор «стратегий», которые можно рассматривать как обобщенные операторы. Каждый класс операторов определяет, какой фактический оператор соответствует каждой стратегии для конкретного типа данных и интерпретации семантики индекса.

Индексный метод B-дерева определяет пять стратегий, показанных в Таблице 3.

Таблица 3. Стратегии В-дерева

Операция | Номер стратегии
меньше | 1
меньше или равно | 2
равно | 3
больше или равно | 4
больше | 5

Хеш-индексы поддерживают только сравнения на равенство, поэтому они используют только одну стратегию, показанную в Таблице 4.

Таблица 4. Хеш-стратегии

Операция | Номер стратегии
равно | 1

Индексы GiST более гибкие: у них вообще нет фиксированного набора стратегий. Вместо этого вспомогательная процедура «согласованности» каждого конкретного класса операторов GiST интерпретирует номера стратегий как ей угодно. Например, некоторые из встроенных классов операторов индекса GiST индексируют двумерные геометрические объекты, реализуя стратегии «R-дерева», показанные в Таблице 5. Четыре из них являются настоящими двумерными тестами (пересекается с, одинаковы, содержит, содержится в); четыре из них учитывают только абсциссы, а еще четыре проводят те же тесты, только с ординатами.

Таблица 5. Стратегии двумерного R-дерева GiST

Операция | Номер стратегии
строго слева от | 1
не простирается правее | 2
пересекается с | 3
не простирается левее | 4
строго справа от | 5
одно и то же | 6
содержит | 7
содержится в | 8
не простирается выше | 9
строго ниже | 10
строго выше | 11
не простирается ниже | 12

По гибкости индексы SP-GiST аналогичны индексам GiST: у них нет фиксированного набора стратегий. Вместо этого вспомогательные процедуры каждого класса операторов интерпретируют номера стратегий в соответствии с определением класса операторов. В качестве примера в Таблице 6 приведены номера стратегий, используемые встроенными классами операторов для точек.

Таблица 6. Стратегии SP-GiST для точек

Операция | Номер стратегии
строго слева от | 1
строго справа от | 5
одно и то же | 6
содержится в | 8
строго ниже | 10
строго выше | 11

Индексы GIN аналогичны индексам GiST и SP-GiST: у них тоже нет фиксированного набора стратегий. Вместо этого вспомогательные процедуры каждого класса операторов интерпретируют номера стратегий в соответствии с определением класса операторов. В качестве примера в Таблице 7 приведены номера стратегий, используемые встроенным классом операторов для массивов.

Таблица 7. Стратегии GIN для массивов

Операция | Номер стратегии
пересекается с | 1
содержит | 2
содержится в | 3
равно | 4

Индексы BRIN аналогичны индексам GiST, SP-GiST и GIN: у них тоже нет фиксированного набора стратегий. Вместо этого вспомогательные процедуры каждого класса операторов интерпретируют номера стратегий в соответствии с определением класса операторов. В качестве примера в Таблице 8 приведены номера стратегий, используемые встроенными классами операторов Minmax.

Таблица 8. Стратегии BRIN Minmax

Операция | Номер стратегии
меньше | 1
меньше или равно | 2
равно | 3
больше или равно | 4
больше | 5

Обратите внимание, что все вышеперечисленные операторы возвращают логические значения. На практике все операторы, определенные как операторы поиска для индексного метода, должны возвращать тип boolean, так как они должны находиться на верхнем уровне предложения WHERE, чтобы обрабатываться с индексом. (Некоторые индексные методы доступа также поддерживают операторы упорядочивания, которые обычно не возвращают логические значения; это свойство обсуждается в подразделе Операторы упорядочивания.)

Вспомогательные процедуры индексного метода

Стратегии обычно не дают системе достаточно информации, чтобы понять, как использовать индекс. На практике для работы индексным методам требуются дополнительные вспомогательные процедуры. Например, индексный метод B-дерева должен уметь сравнивать два ключа и определять, больше, равен или меньше ли один другого. Точно так же метод хеш-индекса должен иметь возможность вычислять хеш-коды для значений ключа. Эти операции не соответствуют операторам, используемым в условиях в командах SQL; они являются внутренними административными процедурами, используемыми индексными методами.

Как и в случае стратегий, класс операторов определяет, какие конкретные функции должны играть каждую из этих ролей для данного типа данных и семантической интерпретации. Индексный метод определяет набор функций, которые ему нужны, а класс операторов определяет подходящие для использования функции, назначая им «номера функций поддержки», указанные в индексном методе.

Кроме того, некоторые классы операторов позволяют пользователям задавать параметры, управляющие их поведением. У каждого встроенного индексного метода доступа имеется необязательная вспомогательная функция options, определяющая набор параметров, специфичных для данного класса.

Для B-деревьев требуется вспомогательная функция сравнения и могут предоставляться четыре дополнительные вспомогательные функции по выбору разработчика класса операторов, как показано в Таблице 9. Требования к этим вспомогательным функциям рассматриваются в разделе Вспомогательные функции B-деревьев.

Таблица 9. Вспомогательные функции B-деревьев

Функция | Номер функции
Сравнивает два ключа и возвращает целое число меньше нуля, ноль или целое число больше нуля, показывающее, что первый ключ меньше, равен или больше второго | 1
Возвращает адреса вызываемых из C/RUST вспомогательных функций сортировки (необязательная) | 2
Сравнивает значение теста с базовым значением плюс/минус смещение и возвращает true или false в зависимости от результата сравнения (необязательная) | 3
Определяет, безопасно ли для индексов, использующих этот класс операторов, применять оптимизацию с дедупликацией, реализованную в btree (необязательная) | 4
Определяет параметры, специфичные для этого класса операторов (необязательная) | 5

Для хеш-индексов требуется одна вспомогательная функция, и могут предоставляться еще две дополнительные по выбору разработчика класса операторов, как показано в Таблице 10.

Таблица 10. Вспомогательные функции хеша

Функция | Номер функции
Вычисляет 32-битное значение хеша для ключа | 1
Вычисляет 64-битное значение хеша для ключа с заданной 64-битной солью; если соль равна 0, младшие 32 бита результата должны соответствовать значению, которое было бы вычислено функцией 1 (необязательная) | 2
Определяет параметры, специфичные для этого класса операторов (необязательная) | 3

Индексы GiST имеют одиннадцать вспомогательных функций, шесть из которых являются необязательными, как показано в Таблице 11. (Дополнительную информацию см. в главе Индексы GiST.)

Таблица 11. Вспомогательные функции GiST

Функция | Описание | Номер функции
consistent | определяет, удовлетворяет ли ключ условию запроса | 1
union | вычисляет объединение набора ключей | 2
compress | вычисляет сжатое представление ключа или индексируемого значения (необязательная) | 3
decompress | вычисляет развернутое представление сжатого ключа (необязательная) | 4
penalty | вычисляет издержки добавления нового ключа в поддерево с заданным ключом | 5
picksplit | определяет, какие записи страницы должны быть перемещены на новую страницу, и вычисляет ключи объединения для результирующих страниц | 6
equal | сравнивает два ключа и возвращает true, если они равны | 7
distance | определяет расстояние от ключа до значения запроса (необязательная) | 8
fetch | вычисляет исходное представление сжатого ключа для сканирования только по индексу (необязательная) | 9
options | определяет параметры, специфичные для этого класса операторов (необязательная) | 10
sortsupport | предоставляет компаратор для сортировки, который будет использоваться при быстром построении индекса (необязательная) | 11

Для индексов SP-GiST имеется шесть вспомогательных функций, одна из которых является необязательной, как показано в Таблице 12. (Дополнительную информацию см. в главе Индексы SP-GiST.)

Таблица 12. Вспомогательные функции SP-GiST

Функция | Описание | Номер функции
config | предоставляет основную информацию о классе операторов | 1
choose | определяет, как вставить новое значение во внутренний кортеж | 2
picksplit | определяет, как разделить множество значений | 3
inner_consistent | определяет, какие внутренние ветви нужно искать для запроса | 4
leaf_consistent | определяет, удовлетворяет ли ключ условию запроса | 5
options | определяет параметры, специфичные для этого класса операторов (необязательная) | 6

Индексы GIN имеют семь вспомогательных функций, четыре из которых являются необязательными, как показано в Таблице 13. (Дополнительную информацию см. в главе Индексы GIN.)

Таблица 13. Вспомогательные функции GIN

Функция | Описание | Номер функции
compare | сравнивает два ключа и возвращает целое число меньше нуля, ноль или целое число больше нуля, показывающее, что первый ключ меньше, равен или больше второго | 1
extractValue | извлекает ключи из индексируемого значения | 2
extractQuery | извлекает ключи из условия запроса | 3
consistent | определяет, соответствует ли значение условию запроса (логический вариант) (необязательна, если присутствует вспомогательная функция 6) | 4
comparePartial | сравнивает частичный ключ из запроса и ключ из индекса и возвращает целое число меньше нуля, ноль или целое число больше нуля, показывающее, что GIN должен игнорировать эту запись индекса, относиться к записи как к соответствующей или остановить сканирование индекса (необязательная) | 5
triConsistent | определяет, соответствует ли значение условию запроса (троичный вариант) (необязательна, если присутствует вспомогательная функция 4) | 6
options | определяет параметры, специфичные для этого класса операторов (необязательная) | 7

Индексы BRIN имеют пять основных вспомогательных функций, одна из которых является необязательной, как показано в Таблице 14; для некоторых версий этих основных функций может потребоваться предоставить дополнительные вспомогательные функции. (Дополнительную информацию см. в разделе Расширяемость.)

Таблица 14. Вспомогательные функции BRIN

Функция | Описание | Номер функции
opcInfo | возвращает внутреннюю информацию, описывающую сводные данные индексированных столбцов | 1
add_value | добавляет новое значение в существующий сводный кортеж индекса | 2
consistent | определяет, соответствует ли значение условию запроса | 3
union | вычисляет объединение двух сводных кортежей | 4
options | определяет параметры, специфичные для этого класса операторов (необязательная) | 5

В отличие от операторов поиска, вспомогательные функции возвращают тот тип данных, который ожидает конкретный индексный метод; например, в случае функции сравнения для B-деревьев, целое число со знаком. Количество и типы аргументов каждой вспомогательной функции также зависят от индексного метода. Для B-дерева и хеша вспомогательные функции сравнения и хеширования принимают те же типы входных данных, что и операторы, включенные в класс операторов, но к большинству вспомогательных функций GiST, SP-GiST, GIN и BRIN это не относится.

Пример

Теперь, ознакомившись с основными идеями, можно перейти к обещанному примеру создания нового класса операторов. Класс операторов включает операторы, которые сортируют комплексные числа в порядке их абсолютных значений, поэтому мы выбираем для него имя complex_abs_ops. Во-первых, нам нужен набор операторов. Процедура определения операторов была рассмотрена в разделе Пользовательские операторы. Для класса операторов B-деревьев нам понадобятся следующие операторы:

абсолютное значение меньше (стратегия 1)

абсолютное значение меньше или равно (стратегия 2)

абсолютное значение равно (стратегия 3)

абсолютное значение больше или равно (стратегия 4)

абсолютное значение больше (стратегия 5)

Наименее подверженный ошибкам способ определения связанного набора операторов сравнения состоит в том, чтобы сначала написать вспомогательную функцию сравнения для B-дерева, а затем написать другие функции в виде однострочных оболочек это вспомогательной функции. Это снижает вероятность получения несогласованных результатов в тупиковых ситуациях. Следуя этому подходу, сначала мы пишем:

#define Mag(c)  ((c)->x*(c)->x + (c)->y*(c)->y)

static int
complex_abs_cmp_internal(Complex *a, Complex *b)
{
    double      amag = Mag(a),
                bmag = Mag(b);

    if (amag < bmag)
        return -1;
    if (amag > bmag)
        return 1;
    return 0;
}

Теперь функция «меньше» выглядит так:

PG_FUNCTION_INFO_V1(complex_abs_lt);

Datum
complex_abs_lt(PG_FUNCTION_ARGS)
{
    Complex    *a = (Complex *) PG_GETARG_POINTER(0);
    Complex    *b = (Complex *) PG_GETARG_POINTER(1);

    PG_RETURN_BOOL(complex_abs_cmp_internal(a, b) < 0);
}

Остальные четыре функции отличаются только тем, как они сравнивают результат внутренней функции с нулем.

Далее мы объявляем в SQL функции и операторы на основе этих функций:

CREATE FUNCTION complex_abs_lt(complex, complex) RETURNS bool
    AS 'имя_файла', 'complex_abs_lt'
    LANGUAGE C IMMUTABLE STRICT;

CREATE OPERATOR < (
   leftarg = complex, rightarg = complex, procedure = complex_abs_lt,
   commutator = >, negator = >=,
   restrict = scalarltsel, join = scalarltjoinsel
);

Важно указать правильные коммутирующие и обратные операторы, а также подходящие функции избирательности ограничения и соединения, иначе оптимизатор не сможет эффективно использовать этот индекс.

Здесь также стоит обратить внимание на следующие моменты:

Может быть только один оператор с именем, скажем, =, принимающий тип complex для обоих операндов. В этом случае у нас нет другого оператора = для complex, но если бы мы создавали тип данных, применимый на практике, то, вероятно, предпочли бы, чтобы оператор = был обычной операцией, проверяющей равенство комплексных чисел (а не равенство абсолютных значений). В этом случае для complex_abs_eq нужно выбрать другое имя оператора.

Хотя QHB может работать с функциями, имеющими одинаковое имя SQL, если у них разные типы данных аргументов, C/RUST может работать только с одной глобальной функцией, имеющей данное имя. Поэтому не следует давать функции на C/RUST какое-то простое имя вроде abs_eq. Обычно во избежание конфликтов с функциями для других типов данных рекомендуется включать в имя функции на C/RUST имя типа данных.

Мы могли бы дать функции abs_eq имя SQL, полагаясь на то, что QHB отличит ее по типу данных аргумента от любой другой функции SQL с тем же именем. Чтобы упростить пример, мы дали функции одинаковые имена на уровне C и на уровне SQL.

Следующим шагом является регистрация вспомогательной процедуры, необходимой для B-деревьев. В нашем примере реализующий это код C находится в том же файле, который содержит функции оператора. Вот как мы объявляем функцию:

CREATE FUNCTION complex_abs_cmp(complex, complex)
    RETURNS integer
    AS 'имя_файла'
    LANGUAGE C IMMUTABLE STRICT;

Теперь, когда у нас есть необходимые операторы и вспомогательная процедура, мы можем наконец создать класс операторов:

CREATE OPERATOR CLASS complex_abs_ops
    DEFAULT FOR TYPE complex USING btree AS
        OPERATOR        1       <,
        OPERATOR        2       <=,
        OPERATOR        3       =,
        OPERATOR        4       >=,
        OPERATOR        5       >,
        FUNCTION        1       complex_abs_cmp(complex, complex);

И готово! Теперь должно быть возможно создавать и использовать индексы B-деревья по столбцам complex.

Мы могли бы сделать записи операторов более подробными, например:

OPERATOR        1       < (complex, complex),

но когда операторы принимают тот же тип данных, для которого мы определяем класс операторов, в этом нет необходимости.

В приведенном выше примере предполагается, что вы хотите сделать этот новый класс операторов классом операторов B-деревьев по умолчанию для типа данных complex. Если вы этого не хотите, просто пропустите слово DEFAULT.

Классы операторов и семейства операторов

До сих пор мы неявно предполагали, что класс операторов имеет дело только с одним типом данных. Хотя, безусловно, в конкретном индексируемом столбце может быть только один тип данных, часто имеет смысл индексировать операции, сравнивающие индексированный столбец со значением другого типа. Кроме того, если в сочетании с классом операторов возможно использование межтипового оператора, зачастую у другого типа данных тоже имеется собственный связанный класс операторов. В подобных случаях полезно установить явную связь между связанными классами, так как это может помочь планировщику с оптимизацией запросов SQL (особенно для классов операторов B-дерева, поскольку планировщик хорошо знает, как с ними работать).

Для удовлетворения этих потребностей QHB использует концепцию семейства операторов. Семейство операторов содержит один или несколько классов операторов, а также может содержать индексируемые операторы и соответствующие вспомогательные функции, принадлежащие семейству в целом, но не к какому-либо одному классу в нем. Мы называем такие операторы и функции «слабосвязанными» с семейством, в противоположность обычной связи с определенным классом. Как правило, каждый класс операторов содержит операторы, работающие с одним типом данных, тогда как межтиповые операторы слабо связаны с семейством.

Все операторы и функции в семействе операторов должны иметь совместимую семантику, где требования совместимости устанавливаются индексным методом. Исходя из этого, вы можете спросить, зачем выделять отдельные подмножества семейства как классы операторов; и действительно, для многих целей разделение на классы не имеет значения, и единственной полезной группировкой является семейство. Смысл определения классов операторов состоит в том, что они указывают, какая часть семейства необходима для поддержки какого-либо конкретного индекса. Если существует индекс, использующий класс операторов, то этот класс нельзя удалить, не удалив сам индекс, — но остальные части семейства операторов, а именно другие классы операторов и операторы со слабой связью, удалить можно. Таким образом, класс операторов следует определить так, чтобы он содержал минимальный набор операторов и функций, которые объективно нужны для работы с индексом по определенному типу данных, а затем в качестве слабосвязанных членов семейства операторов можно добавить относящиеся к нему, но не необходимые операторы.

В качестве примера, в QHB есть встроенное семейство операторов B-дерева integer_ops, которое включает классы операторов int8_ops, int4_ops и int2_ops для индексов bigint (int8), integer (int4) и smallint (int2) соответственно. Это семейство также содержит межтиповые операторы сравнения, позволяющие сравнивать любые два из этих типов, так чтобы индекс по одному из этих типов можно было искать, используя значение сравнения другого типа. Такое семейство можно представить следующими определениями:

CREATE OPERATOR FAMILY integer_ops USING btree;

CREATE OPERATOR CLASS int8_ops
DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS
  -- стандартные сравнения int8
  OPERATOR 1 <,
  OPERATOR 2 <=,
  OPERATOR 3 =,
  OPERATOR 4 >=,
  OPERATOR 5 >,
  FUNCTION 1 btint8cmp(int8, int8),
  FUNCTION 2 btint8sortsupport(internal),
  FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) ;

CREATE OPERATOR CLASS int4_ops
DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS
  -- стандартные сравнения int4
  OPERATOR 1 <,
  OPERATOR 2 <=,
  OPERATOR 3 =,
  OPERATOR 4 >=,
  OPERATOR 5 >,
  FUNCTION 1 btint4cmp(int4, int4),
  FUNCTION 2 btint4sortsupport(internal),
  FUNCTION 3 in_range(int4, int4, int4, boolean, boolean) ;

CREATE OPERATOR CLASS int2_ops
DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS
  -- стандартные сравнения int2
  OPERATOR 1 <,
  OPERATOR 2 <=,
  OPERATOR 3 =,
  OPERATOR 4 >=,
  OPERATOR 5 >,
  FUNCTION 1 btint2cmp(int2, int2),
  FUNCTION 2 btint2sortsupport(internal),
  FUNCTION 3 in_range(int2, int2, int2, boolean, boolean) ;

ALTER OPERATOR FAMILY integer_ops USING btree ADD
  -- межтиповые сравнения int8 и int2
  OPERATOR 1 < (int8, int2),
  OPERATOR 2 <= (int8, int2),
  OPERATOR 3 = (int8, int2),
  OPERATOR 4 >= (int8, int2),
  OPERATOR 5 > (int8, int2),
  FUNCTION 1 btint82cmp(int8, int2),

  -- межтиповые сравнения int8 и int4
  OPERATOR 1 < (int8, int4),
  OPERATOR 2 <= (int8, int4),
  OPERATOR 3 = (int8, int4),
  OPERATOR 4 >= (int8, int4),
  OPERATOR 5 > (int8, int4),
  FUNCTION 1 btint84cmp(int8, int4),

  -- межтиповые сравнения int4 и int2
  OPERATOR 1 < (int4, int2),
  OPERATOR 2 <= (int4, int2),
  OPERATOR 3 = (int4, int2),
  OPERATOR 4 >= (int4, int2),
  OPERATOR 5 > (int4, int2),
  FUNCTION 1 btint42cmp(int4, int2),

  -- межтиповые сравнения int4 и int8
  OPERATOR 1 < (int4, int8),
  OPERATOR 2 <= (int4, int8),
  OPERATOR 3 = (int4, int8),
  OPERATOR 4 >= (int4, int8),
  OPERATOR 5 > (int4, int8),
  FUNCTION 1 btint48cmp(int4, int8),

  -- межтиповые сравнения int2 и int8
  OPERATOR 1 < (int2, int8),
  OPERATOR 2 <= (int2, int8),
  OPERATOR 3 = (int2, int8),
  OPERATOR 4 >= (int2, int8),
  OPERATOR 5 > (int2, int8),
  FUNCTION 1 btint28cmp(int2, int8),

  -- межтиповые сравнения int2 и int4
  OPERATOR 1 < (int2, int4),
  OPERATOR 2 <= (int2, int4),
  OPERATOR 3 = (int2, int4),
  OPERATOR 4 >= (int2, int4),
  OPERATOR 5 > (int2, int4),
  FUNCTION 1 btint24cmp(int2, int4),

  -- межтиповые функции in_range
  FUNCTION 3 in_range(int4, int4, int8, boolean, boolean),
  FUNCTION 3 in_range(int4, int4, int2, boolean, boolean),
  FUNCTION 3 in_range(int2, int2, int8, boolean, boolean),
  FUNCTION 3 in_range(int2, int2, int4, boolean, boolean) ;

Обратите внимание, что это определение «перегружает» стратегию оператора и номера вспомогательных функций: каждый номер встречается в семействе несколько раз. Это разрешено, если каждый экземпляр определенного номера имеет разные типы входных данных. Экземпляры, у которых оба типа входных данных совпадают с входным типом класса оператора, являются первичными операторами и вспомогательными функциями для этого класса операторов, и в большинстве случаев их следует объявлять как часть класса операторов, а не слабосвязанные члены семейства.

В семействе операторов B-дерева все операторы должны быть совместимы в контексте сортировки, как подробно описано в разделе Поведение классов операторов B-деревьев. Для каждого оператора в семействе должна быть предусмотрена вспомогательная функция с теми же двумя типами входных данных, что и у оператора. Рекомендуется, чтобы семейство было полным, т. е. содержало все операторы для каждой комбинации типов данных. Каждый класс операторов должен включать в себя только однотиповые операторы и вспомогательную функцию для своего типа данных.

Чтобы создать семейство операторов хеширования для нескольких типов данных, нужно создать совместимые вспомогательные функции хеша для каждого типа данных, поддерживаемого этим семейством. Здесь совместимость означает, что функции гарантированно возвращают один и тот же хеш-код для любых двух значений, которые операторы равенства семейства считают равными, даже когда те имеют разные типы. Обычно этого трудно добиться, когда типы имеют разные физические представления, но в некоторых случаях это можно сделать. Более того, преобразование значения одного типа данных, представленного в семействе операторов, в другой тип, также представленный в этом семействе, путем неявного или двоичного приведения, не должно изменять вычисленное значение хеша. Обратите внимание, что единственная вспомогательная функция задается для каждого типа данных, а не для каждого оператора равенства. Рекомендуется, чтобы семейство было полным, т. е. предоставляло оператор равенства для каждой комбинации типов данных. Каждый класс операторов должен включать только однотиповой оператор равенства и вспомогательную функцию для своего типа данных.

Индексы GiST, SP-GiST и GIN не имеют явного представления о межтиповых операциях. Набор поддерживаемых операторов определяется только теми операциями, которые могут обрабатывать основные вспомогательные функции данного класса операторов.

В BRIN требования зависят от структуры, предоставляющей классы операторов. Для классов операторов, основанных на minmax, требуется такое же поведение, как и для семейств операторов B-дерева: все операторы в семействе должны поддерживать совместимую сортировку, а приведение не должно изменять соответствующий порядок сортировки.

Системные зависимости от классов операторов

QHB использует классы операторов, чтобы оценивать свойства операторов разными способами, а не только определять, можно ли их использовать с индексами. Поэтому советуем создать классы операторов, даже если у вас нет намерения индексировать какие-либо столбцы вашего типа данных.

В частности, существуют средства SQL, такие как ORDER BY и DISTINCT, которым требуются сравнение и сортировка значений. Чтобы реализовать эту функциональность для определенного пользователем типа данных, QHB ищет класс оператора B-дерева по умолчанию для этого типа данных. Член «равно» этого класса операторов определяет представление системы о равенстве значений для GROUP BY и DISTINCT, а порядок сортировки, налагаемый классом операторов, определяет порядок ORDER BY по умолчанию.

Если для типа данных не существует класса операторов B-дерева по умолчанию, система будет искать класс операторов хеширования по умолчанию. Но поскольку этот класс операторов обеспечивает только равенство, он способен поддерживать только группирование, а не сортировку.

Если для типа данных не существует класса операторов по умолчанию, то при попытке использовать вышеперечисленную функциональность SQL с этим типом данных вы получите ошибки вида «не удалось определить оператор упорядочивания».

Возможна сортировка по классу операторов B-дерева, отличному от заданного по умолчанию, если указать, например, в предложении USING оператор «меньше» этого класса:

SELECT * FROM mytable ORDER BY somecol USING ~<~;

Как вариант, указав в USING оператор «больше» этого класса, можно выбрать сортировку по убыванию.

Сравнение массивов пользовательского типа также основывается на семантике, определенной классом операторов B-дерева по умолчанию для этого типа. Если таковой отсутствует, но есть класс операторов хеширования по умолчанию, то поддерживается равенство массивов, но не упорядочивание.

Еще одна особенность SQL, которая требует даже больших знаний о типе данных, — это режим определения рамки RANGE смещение PRECEDING/FOLLOWING для оконных функций (см. подраздел Вызовы оконных функций). Для запроса вида

SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
  FROM mytable;

недостаточно знать, как упорядочить по x; база данных также должна понимать, как «вычесть 5» или «прибавить 10» к значению x текущей строки, чтобы определить границы текущей рамки окна. Сравнить результирующие границы со значениями x других строк можно с помощью операторов сравнения, предоставляемых классом операторов B-дерева, который определяет порядок ORDER BY, — но операторы сложения и вычитания не входят в этот класс операторов. Тогда какие операторы следует использовать? Жестко фиксировать выбранные операторы в коде было бы нежелательно, поскольку для разных порядков сортировки (разные классы операторов B-дерева) может потребоваться разное поведение. Поэтому класс операторов B-дерева позволяет указывать вспомогательную функцию in_range, в которой объединены сложение и вычитание, подходящие для порядка сортировки. Он даже способен предоставить более одной такой функции, в случае если в качестве смещения в предложениях RANGE имеет смысл использовать несколько разных типов данных. Если в классе операторов B-деревьев, связанном с указанным для окна предложением ORDER BY, нет соответствующей вспомогательной функции in_range, режим RANGE смещение PRECEDING FOLLOWING не поддерживается.

Еще одной важной особенностью является то, что оператор равенства, находящийся в семействе операторов хеширования, является кандидатом для применения при объединении и агрегировании по хешу и связанными с ними оптимизациями. Семейство операторов хеширования играет здесь ключевую роль, так как именно в нем задаются функции хеширования.

Операторы упорядочивания

Некоторые методы доступа к индексу (в настоящее время только GiST и SP-GiST) поддерживают концепцию операторов упорядочивания. До сих пор мы рассматривали операторы поиска. Оператор поиска — это оператор, для которого можно выполнить поиск по индексу, чтобы найти все строки, удовлетворяющие условию WHERE индексированный_столбец оператор константа. Обратите внимание, что при этом ничего не говорится о порядке, в котором будут возвращены подходящие строки. Оператор упорядочивания, напротив, не ограничивает набор возвращаемых строк, а вместо этого определяет их порядок. С оператором упорядочивания можно, просканировав индекс, получить строки в порядке, представленном условием ORDER BY индексированный_столбец оператор константа. Причина определения операторов упорядочивания таким образом состоит в том, что оно поддерживает поиск ближайшего соседа, если этот оператор измеряет расстояние. Например, запрос типа

SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;

находит десять мест, ближайших к заданной целевой точке. Индекс GiST по столбцу location может сделать это эффективно, потому что <-> является оператором упорядочивания.

В то время как операторы поиска должны возвращать логические результаты, операторы упорядочивания обычно возвращают какой-то другой тип, например float или numeric для расстояний. Этот тип обычно не совпадает с типом индексируемых данных. Чтобы избежать жестко зафиксированных в коде предположений о поведении различных типов данных, при определении оператора упорядочивания необходимо указать семейство операторов B-дерева, которое определяет порядок сортировки типа данных результата. Как было сказано в предыдущем подразделе, семейства операторов B-дерева определяют понятие упорядочивания в QHB, так что это естественное представление. Поскольку оператор <-> для точек возвращает float8, его можно указать в команде создания класса операторов так:

OPERATOR 15    <-> (point, point) FOR ORDER BY float_ops

где float_ops — это встроенное семейство операторов, которое включает операции с float8. В этом объявлении говорится, что индекс может возвращать строки в порядке возрастания значений оператора <->.

Особенности классов операторов

Есть две особенности классов операторов, которые мы еще не обсуждали, в основном потому, что они не подходят для наиболее часто используемых индексных методов.

Как правило, объявление оператора в качестве члена класса (или семейства) операторов означает, что индексный метод может отобрать именно тот набор строк, который удовлетворяет условию WHERE с этим оператором. Например, запрос:

SELECT * FROM table WHERE integer_column < 4;

может быть точно удовлетворен индексом B-деревом по целочисленному столбцу. Но бывают случаи, когда индекс полезен как примерный указатель на соответствующие строки. Например, если индекс GiST хранит только ограничивающие прямоугольники для геометрических объектов, то он не сможет точно удовлетворить условию WHERE, которое проверяет пересечение между непрямоугольными объектами, такими как многоугольники. Тем не менее этот индекс можно применить для поиска объектов, у которых ограничивающий прямоугольник пересекает ограничивающий прямоугольник целевого объекта, а затем выполнить точную проверку пересечения только для объектов, найденных по индексу. Если этот сценарий применим, такой индекс считается «неполным» для оператора. Поиск по неполному индексу реализуется с помощью индексного метода, возвращающего флаг recheck (перепроверить), когда строка действительно может или не может удовлетворять условию запроса. Затем базовая система проверит извлеченную строку по исходному условию запроса, чтобы понять, следует ли возвращать ее как действительного соответствующую ему. Этот подход работает, если индекс гарантированно возвращает все необходимые строки плюс, возможно, некоторые дополнительные строки, которые можно отбросить, вызвав исходный оператор. Индексные методы, поддерживающие неполный поиск (в настоящее время это GiST, SP-GiST и GIN), позволяют вспомогательным функциям отдельных классов операторов устанавливать флаг перепроверки, так что по сути это свойство класса операторов.

Вернемся к ситуации, когда мы храним в индексе только ограничивающий прямоугольник сложного объекта, например многоугольника. В этом случае нет смысла хранить в элементе индекса весь многоугольник — с тем же успехом там можно хранить и более простой объект типа box. Эта ситуация выражается указанием STORAGE в команде CREATE OPERATOR CLASS, которое мы запишем примерно так:

CREATE OPERATOR CLASS polygon_ops
    DEFAULT FOR TYPE polygon USING gist AS
        ...
        STORAGE box;

В настоящее время только индексные методы GiST, GIN и BRIN поддерживают тип STORAGE, который отличается от типа данных столбца. В GiST преобразованием типов данных при помощи STORAGE должны заниматься вспомогательные процедуры compress и decompress. В GIN тип STORAGE определяет тип значений «ключа», который обычно отличается от типа индексируемого столбца — например, в классе операторов для столбцов с целочисленным массивом ключами могут быть просто целые числа. В GIN за извлечение ключей из индексированных значений отвечают вспомогательные функции extractValue и extractQuery. BRIN аналогичен GIN: тип STORAGE определяет тип хранимых обобщенных значений, а вспомогательные процедуры классов операторов отвечают за их правильную интерпретацию.

Упаковка связанных объектов в расширение

Полезное расширение QHB обычно включает несколько объектов SQL; например, для нового типа данных потребуются новые функции, новые операторы и, возможно, новые классы операторов индекса. Целесообразно собрать все эти объекты в один пакет, чтобы упростить управление базой данных. В QHB такой пакет называется расширением. Чтобы определить расширение, понадобится как минимум скрипт-файл, содержащий команды SQL для создания объектов расширения, и управляющий файл, задающий несколько основных свойств самого расширения. Если расширение содержит код на C/RUST, в него обычно также включается файл разделяемой библиотеки в котором был скомпилирован этот код. Как только вы предоставите все эти файлы, простая команда CREATE EXTENSION загрузит их в вашу базу данных.

При загрузке группы «слабосвязанных» объектов в вашу базу данных основное преимущество расширений перед обычным скриптом SQL заключается в том, что в первом случае QHB будет понимать, что объекты расширения связаны между собой. Все эти объекты можно удалить одной командой DROP EXTENSION (нет необходимости разрабатывать отдельный скрипт «uninstall» (деинсталляции)). Еще полезнее то, что утилита qhb_dump знает, что не должна выгружать отдельные компонентные объекты расширения — вместо этого она просто включит в дампы команду CREATE EXTENSION. Это значительно упрощает миграцию на новую версию расширения, которая может содержать дополнительные или другие объекты по сравнению со старой. Обратите внимание, однако, что при загрузке такого дампа в новую базу данных необходимо наличие управляющего файла, скрипта и других файлов расширения.

QHB не позволит вам удалить отдельный объект, содержащийся в расширении, кроме как при удалении всего расширения. Кроме того, хотя изменить определение компонентного объекта расширения и можно (например, с помощью команды CREATE OR REPLACE FUNCTION для функции), имейте в виду, что измененное определение не будет выгружено qhb_dump. Такое изменение обычно имеет смысл только в том случае, если вы одновременно вносите такое же изменение в файл скрипта расширения. (Но для таблиц, содержащих данные конфигурации, имеются специальные средства; см. подраздел Таблицы конфигурации расширений.) В производственных условиях для изменения компонентных объектов расширения обычно лучше создавать скрипт обновления расширения.

Скрипт расширения может устанавливать права доступа для объектов, являющихся частью расширения, с помощью команд GRANT и REVOKE. Окончательный набор прав для каждого объекта (если они установлены) будет сохранен в системном каталоге pg_init_privs. При использовании qhb_dump в дамп будет включена команда CREATE EXTENSION а затем набор операторов GRANT и REVOKE, необходимых, чтобы установить для объектов те же права, какие были на момент выгрузки.

В настоящее время QHB не поддерживает скрипты расширений, выполняющие операторы CREATE POLICY или SECURITY LABEL. Ожидается, что они будут установлены после создания расширения. В дампы, созданные qhb_dump, будут включены все политики RLS и метки безопасности на объектах расширения.

В механизме расширения также имеются средства поддержки для скриптов модификации упаковки, которые корректируют определения содержащихся в расширении объектов SQL. Например, если версия расширения 1.1 по сравнению с версией 1.0 добавляет одну функцию и изменяет тело другой, разработчик расширения может предоставить скрипт обновления, который вносит только эти два изменения. Затем, выполнив, команду ALTER EXTENSION UPDATE, можно применить эти изменения и отследить, какая версия расширения фактически установлена в заданной базе данных.

Виды объектов SQL, которые могут быть членами расширения, перечислены на справочной странице команды ALTER EXTENSION. В частности, объекты уровня кластера, такие как базы данных, роли и табличные пространства, не могут быть членами расширения, поскольку расширение известно только в пределах одной базе данных. (Хотя скрипту расширения и не запрещено создавать такие объекты, если он это сделает, они не будут отслеживаться как часть расширения.) Также обратите внимание, что хотя таблица может быть членом расширения, подконтрольные ей объекты, такие как индексы, не считаются непосредственными членами расширения. Другим важным моментом является то, что схемы могут принадлежать расширениям, но не наоборот: расширение как таковое имеет неполное имя и не существует «внутри» какой-либо схемы. Тем не менее компонентные объекты расширения будут принадлежать схемам, если это уместно для их типов. Самое расширение может иметь или не иметь оснований для того, чтобы владеть схемой (или схемами), в которой находятся его компонентные объекты.

Если скрипт расширения создает какие-либо временные объекты (например, временные таблицы), эти объекты обрабатываются как элементы расширения до конца текущего сеанса, но потом автоматически удаляются, как и любой временный объект. Это является исключением из правила, запрещающего удаление компонентных объектов без удаления всего расширения.

Файлы расширений

Команда CREATE EXTENSION задействует для каждого расширения управляющий файл, который должен называться так же, как расширение, с суффиксом .control, и должен быть помещен в каталог SHAREDIR/extension. Также должен быть хотя бы один скрипт-файл SQL с именем, соответствующим шаблону расширение-- версия.sql (например foo--1.0.sql для версии 1.0 расширения foo). По умолчанию скрипт (или скрипты) также размещается в каталоге SHAREDIR/extension, но в управляющем файле для них можно указать и другой каталог.

Формат управляющего файла расширения тот же, что и у файла qhb.conf, а именно список назначений имя_параметра = значение, по одному на строку. Также допускаются пустые строки и комментарии, начинающиеся с #. Не забудьте взять в кавычки все значения, отличные от единственного слова или числа.

В управляющем файле может задавать следующие параметры:

directory (string)
Каталог, содержащий скрипт-файл(ы) SQL расширения. Если не задан абсолютный путь, имя относится к серверному каталогу SHAREDIR. Поведение по умолчанию равнозначно указанию directory = 'extension'.

default_version (string)
Версия расширения по умолчанию (та, которая будет установлена, если в CREATE EXTENSION не указана никакая версия). Хотя этот параметр можно опустить, но в отсутствие указания VERSION это приведет к сбою CREATE EXTENSION, поэтому обычно так делать не рекомендуется.

comment (string)
Комментарий (любая строка) о расширении. Комментарий применяется при изначальном создании расширения, но не при его обновлении (поскольку при этом могут замениться комментарии, добавленные пользователем). Кроме того, комментарий расширения можно задать, написав команду COMMENT в скрипт-файле.

encoding (string)
Кодировка набора символов, используемая в скрипт-файлах. Этот параметр следует указывать, если скрипт-файлы содержат символы, не относящиеся к ASCII. В противном случае предполагается, что в этих файлах используется кодировка базы данных.

module_pathname (string)
Значение этого параметра будет подставляться вместо каждого вхождения MODULE_PATHNAME в скрипт-файлах. Если этот параметр не задан, замена не производится. Как правило, для этого параметра устанавливается значение $libdir/имя_разделяемой_библиотеки, а затем в командах CREATE FUNCTION для функций на нативном языке используется MODULE_PATHNAME, поэтому скрипт- файлам не нужно жестко задавать имя разделяемой библиотеки.

requires (string)
Список имен расширений, от которых зависит это расширение, например requires = 'foo, bar'. Эти расширения должны быть установлены до того, как можно будет установить это расширение.

superuser (boolean)
Если этот параметр имеет значение true (по умолчанию), только суперпользователи могут создать расширение или обновить его до новой версии (однако обратите внимание также на параметр trusted ниже). Если установлено значение false, требуются только те права, которые необходимы для выполнения команд в скрипте установки или обновления. Обычно значение true должно устанавливаться, если какой-либо команде скрипта требуются права суперпользователя. (Такие команды в любом случае завершатся ошибкой, но для удобства пользователя лучше сообщить об этом заранее.)

trusted (boolean)
Если этот параметр имеет значение true (это не значение по умолчанию), это позволяет некоторым обычными пользователям устанавливать расширение, для которого параметр superuser равен true. В частности, установка будет разрешена любому с правом CREATE в текущей базе данных. Когда пользователь, выполняющий CREATE EXTENSION, не является суперпользователем, но ему разрешена установка данного расширения посредством этого параметра, то скрипт установки или обновления запускается от имени инициализирующего суперпользователя, а не вызывающего пользователя. Этот параметр не имеет смысла, если superuser равен false. В целом, в этом параметре нельзя устанавливать true для расширений, которые могут открыть доступ к возможностям, иначе доступным только суперпользователям, например к файловой системе. Кроме того, если расширение помечается как доверенное, написание для него безопасных скриптов установки и обновления требует значительных дополнительных усилий; см. подраздел Примечания о безопасности расширений.

relocatable (boolean)
Расширение является перемещаемым, если после изначального его создания содержащиеся в нем объекты можно переместить в другую схему расширения. По умолчанию установлено значение false, то есть расширение не является перемещаемым. Дополнительную информацию см. в подразделе Перемещаемость расширений.

schema (string)
Этот параметр можно установить только для неперемещаемых расширений. Он обеспечивает загрузку расширения только в указанную схему и не в какую другую. Параметр schema используется только при изначальном создании расширения, но не при его обновлении. Дополнительную информацию см. в подразделе Перемещаемость расширений.

В дополнение к основному управляющему файлу расширение.control расширение может содержать добавочные управляющие файлы с именами вида расширение-- версия.control. Если они имеются, то должны находиться в каталоге скрипт-файлов. Добавочные управляющие файлы имеют тот же формат, что и основной управляющий файл. Любые параметры, установленные в добавочном управляющем файле, переопределяют параметры основного при установке этой версии расширения или обновлении до нее. Однако в добавочном управляющем файле нельзя установить параметры directory и default_version.

Скрипт-файлы SQL расширения могут содержать любые команды SQL, кроме команд управления транзакциями (BEGIN, COMMIT и т. д.) и команд, которые нельзя выполнить внутри блока транзакции (например VACUUM). Это связано с тем, что скрипт-файлы неявно выполняются внутри блока транзакции.

Скрипт-файлы SQL расширения также могут содержать строки, начинающиеся с \echo, которые будут игнорироваться (обрабатываться как комментарии) механизмом расширения. Это средство обычно используется для вывода ошибки, если скрипт-файл выполняется в psql, а не загружается командой CREATE EXTENSION (см. пример скрипта в подразделе Пример расширения). Без этого пользователи могут случайно загрузить содержимое расширения как «слабосвязанные» объекты, а не как собственно расширение — и получить состояние, которое несколько трудновато исправить.

Если скрипт расширения содержит строку @extowner@, эта строка заменяется именем (при необходимости заключенным в кавычки) пользователя, выполняющего команду CREATE EXTENSION or ALTER EXTENSION. Обычно эта функциональность используется расширениями, помеченными как доверенные, чтобы назначить владельцем выбранных объектов вызывающего пользователя, а не изначального суперпользователя. (Однако это следует делать с осторожностью. Например, назначение владельцем функции на нативном языке обычного пользователя, даст ему возможность повысить свои привилегии.)

Хотя скрипт-файлы могут содержать любые символы, разрешенные указанной кодировкой, управляющие файлы должны содержать только символы ASCII, поскольку QHB никак не может узнать кодировку управляющего файла. На практике вызывает сложности, только если вы хотите использовать символы не из ASCII в комментарии расширения. В этом случае рекомендуется не использовать параметр comment в управляющем файле, а вместо этого задать комментарий командой COMMENT ON EXTENSION в скрипт-файле.

Перемещаемость расширений

Пользователи часто испытывают желание загрузить объекты, содержащиеся в расширении, в схему, отличную от той, что планировал автор расширения. Существует три поддерживаемых уровня перемещаемости:

Полностью перемещаемое расширение можно переместить в другую схему в любое время, даже после его загрузки в базу данных. Это делается с помощью команды ALTER EXTENSION SET SCHEMA, которая автоматически переименовывает все компонентные объекты, перенося их в новую схему. Обычно это возможно только в том случае, если расширение не содержит внутренних предположений о том, в какой схеме находятся все его объекты. Кроме того, для начала все объекты расширения должны находиться в одной схеме (исключая объекты, которые не принадлежат никакой схеме, например процедурные языки). Отметьте расширение как полностью перемещаемое, задав relocatable = true в его управляющем файле.

Расширение может быть перемещаемым во время установки, но не после. Обычно это происходит, когда скрипт-файлу расширения нужно явно ссылаться на целевую схему, например, при настройке свойств search_path для функций SQL. Для такого расширения задайте relocatable = false в его управляющем файле и используйте псевдоимя @extschema@ для обращения к целевой схеме в скрипт-файле. Все вхождения этой строки будут заменены фактическим именем целевой схемы перед выполнением скрипта. Пользователь может задать целевую схему, используя параметр SCHEMA команды CREATE EXTENSION.

Если расширение вообще не поддерживает перемещение, установите в его управляющем файле relocatable = false, а также задайте в параметре schema имя предполагаемой целевой схемы. Это предотвратит использование параметра SCHEMA команды CREATE EXTENSION, если только в ней не указана та же схема, что и в управляющем файле. Этот выбор обычно необходим, если расширение содержит внутренние предположения об именах схем, которые нельзя заменить псевдоименем @extschema@. Механизм замещения @extschema@ доступен и в этом случае, хотя он будет иметь ограниченное применение, поскольку имя схемы определяется управляющим файлом.

Во всех случаях скрипт-файл будет выполняться с параметром search_path, изначально заданным для указания на целевую схему; то есть CREATE EXTENSION делает то же, что и:

SET LOCAL search_path TO @extschema@;

Это позволяет объектам, созданным скрипт-файлом, перейти в целевую схему. Скрипт файл может изменить search_path если пожелает, но обычно это нежелательно. search_path восстанавливает прежнее значение по завершении CREATE EXTENSION.

Целевая схема определяется параметром schema в управляющем файле, если он указан, либо параметром SCHEMA в команде CREATE EXTENSION, если он задан, либо текущей схемой создания объектов по умолчанию (первой в параметре search_path вызывающей функции). Когда используется параметр schema управляющего файла, целевая схема будет создана, если она еще не существует, но в двух других случаях она уже должна существовать.

Если в параметре requires в управляющем файле перечислены какие-либо обязательные расширения, их целевые схемы добавляются к начальному значению search_path. Это делает их объекты видимыми для скрипт-файла нового расширения.

В целях безопасности во всех случаях в конец search_path автоматически добавляется схема pg_temp.

Хотя неперемещаемое расширение может содержать объекты, распределенные по нескольким схемам, обычно желательно поместить все объекты, предназначенные для внешнего использования, в одну схему, которая считается целевой схемой расширения. Такое расположение удачно сочетается со значением search_path по умолчанию при создании зависимых расширений.

Таблицы конфигурации расширений

Некоторые расширения включают таблицы конфигурации, содержащие данные, которые могут быть добавлены или изменены пользователем после установки расширения. Обычно, если таблица является частью расширения, ни определение таблицы, ни ее содержимое не будут выгружены qhb_dump. Но такое поведение для таблицы конфигурации нежелательно; любые изменения данных, сделанные пользователем, должны быть включены в дампы, иначе после резервного копирования и перезагрузки расширение будет вести себя по-другому.

Чтобы решить эту проблему, скрипт-файл расширения может пометить созданную им таблицу или последовательность как отношение конфигурации, что заставит qhb_dump включить в дампы содержимое таблицы или последовательности (но не ее определение). Для этого после создания таблицы или последовательности нужно вызвать функцию pg_extension_config_dump(regclass, text), например

CREATE TABLE my_config (key text, value text);
CREATE SEQUENCE my_config_seq;

SELECT pg_catalog.pg_extension_config_dump('my_config', '');
SELECT pg_catalog.pg_extension_config_dump('my_config_seq', '');

Таким образом можно пометить любое количество таблиц или последовательностей. Также можно пометить последовательности, связанные со столбцами serial или bigserial.

Когда второй аргумент pg_extension_config_dump является пустой строкой, с помощью qhb_dump выгружается все содержимое таблицы. Обычно это правильно только в том случае, если при создании скриптом расширения данная таблица изначально пуста. Если же в таблице содержится смесь исходных данных и данных, добавленных пользователем, то второй аргумент pg_extension_config_dump предоставляет условие WHERE, которое выбирает данные, подлежащие выгрузке. Например, можно создать таблицу

CREATE TABLE my_config (key text, value text, standard_entry boolean);

SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entry');

а затем сделать так, чтобы поле standard_entry содержало true только в строках, созданных скриптом расширения.

Для последовательностей второй аргумент pg_extension_config_dump не имеет никакого значения.

С более сложными ситуациями, такими как изначально предоставленные строки, которые могут быть модифицированы пользователями, можно справиться путем создания триггеров в таблице конфигурации, которые гарантированно пометят модифицированные строки правильно.

Условие фильтра, связанное с таблицей конфигурации, можно изменить, снова вызвав pg_extension_config_dump. (Обычно это может пригодиться в скрипте обновления расширения). Единственный способ пометить таблицу как больше не являющуюся таблицей конфигурации — это отвязать ее от расширения с помощью команды ALTER EXTENSION ... DROP TABLE.

Обратите внимание, что отношения внешних ключей между этими таблицами будут определять порядок, в котором их выгружает qhb_dump. В частности, qhb_dump попытается выгрузить сначала основную таблицу, а затем таблицу, которая на нее ссылается. Поскольку отношения внешних ключей устанавливаются во время выполнения команды CREATE EXTENSION (до загрузки данных в таблицы), циклические зависимости не поддерживаются. При наличии циклических зависимостей данные все равно будут выгружены, но этот дамп нельзя будет восстановить напрямую, и потребуется вмешательство пользователя.

Чтобы выгрузить состояние последовательностей, связанных со столбцами serial или bigserial, их следует помечать напрямую. Одной только маркировки их родительских отношений для этой цели недостаточно.

Обновления расширений

Одним из преимуществ механизма расширения является то, что он предоставляет удобные способы управления обновлениями команд SQL, которые определяют объекты расширения. Это делается путем назначения имени или номера каждой выпущенной версии скрипта установки расширения. Кроме того, если вы хотите, чтобы пользователи могли динамически обновлять свои базы данных от одной версии до другой, нужно предоставить скрипты обновления, которые вносят необходимые изменения для перехода с одной версии на другую. Скриптам обновления назначаются имена вида расширение--старая_версия--целевая_версия.sql (например, foo--1.0--1.1.sql содержит команды для изменения версии расширения foo с 1.0 на 1.1).

При наличии подходящего скрипта обновления команда ALTER EXTENSION UPDATE обновит установленное расширение до указанной новой версии. Скрипт обновления выполняется в той же среде, которую команда CREATE EXTENSION предоставляет для скриптов установки: в частности, search_path устанавливается таким же образом, а любые новые объекты, созданные скриптом, автоматически добавляются в расширение. Кроме того, если скрипт решит удалить компонентные объекты расширения, они автоматически от него отсоединятся.

Если у расширения имеются добавочные управляющие файлы, для скрипта обновления используются те параметры управления, которые связаны с целевой (новой) версией скрипта.

Чтобы достигнуть запрошенной версии, команда ALTER EXTENSION может выполнять последовательности скрипт-файлов обновления. Например, если доступны только foo--1.0--1.1.sql и foo--1.1--2.0.sql, ALTER EXTENSION будет применять их последовательно, если будет запрошено обновление до версии 2.0, а в данный момент установлена версия 1.0.

QHB не делает никаких предположений о свойствах имен версий: например, она не знает, следует ли 1.1 за 1.0. Она просто сопоставляет доступные имена версий и следует по пути, который требует применения наименьшего количества скриптов обновления. (Имя версии может быть фактически любой строкой, которая не содержит -- или не начинается и не заканчивается -.)

Иногда бывает полезно предоставить скрипты «понижения версии», например foo- 1.1--1.0.sql, чтобы получить возможность отменить изменения, внесенные версией 1.1. Если вы их предоставляете, остерегайтесь вероятности неожиданного применения такого скрипта, если окажется, что он обеспечивает более короткий путь. Рискованная ситуация возникает тогда, когда имеется скрипт обновления «короткого пути», который перепрыгивает через несколько версий, и скрипт понижения версии до начальной точки первого скрипта. Возможно, для понижения версии с последующим обновлением по короткому пути потребуется меньше шагов, чем для последовательного повышения версии. Если скрипт понижения версии удалит какие-либо незаменимые объекты, это приведет к нежелательным результатам.

Чтобы проверить наличие неожиданных путей обновления, используйте эту команду:

SELECT * FROM pg_extension_update_paths('имя_расширения');

Она показывает каждую пару различных известных имен версий для указанного расширения, а также последовательность пути обновления, которая будет принята для перехода от исходной версии до целевой, или NULL, если доступного пути обновления нет. Путь выводится в текстовом виде с разделителями --. Если вы предпочитаете формат массива, можно использовать regexp_split_to_array(path,'--').

Установка расширений с помощью скриптов обновления

Расширение, существующее уже некоторое время, вероятно, будет иметь несколько версий, для которых автору потребуется написать скрипт обновления. Например, если вы выпустили расширение foo в версиях 1.0, 1.1 и 1.2, для них понадобятся скрипты обновления foo--1.0--1.1.sql и foo--1.1--.2.sql. Прежде нужно было также создавать новые скрипт-файлы foo--.1.sql и foo--1.2.sql, которые напрямую собирали более новые версии расширений, иначе те можно было установить только путем установки 1.0 с последующим обновлением. Это было утомительно и требовало многократных повторений, но теперь в этом нет необходимости, поскольку команда CREATE EXTENSION способна автоматически следовать цепочкам обновлений. Например, если доступны только скрипт-файлы foo--1.0.sql, foo--1.0--1.1.sql и foo--1.1--1.2.sql, то запрос на установку версии 1.2 выполняется с помощью последовательного запуска этих трех скриптов. Процесс будет проходить так же, как если бы вы сначала установили версию 1.0, а затем обновили ее до 1.2. (Как и в случае с ALTER EXTENSION UPDATE, если доступно несколько путей, предпочтение отдается кратчайший путь.) Размещение скрипт-файлов расширения по такому образцу может снизить трудозатраты на выпуск небольших обновлений.

Если вы используете добавочные (ориентированные на версию) управляющие файлы для расширения, поддерживаемого по этому образцу, имейте в виду, что управляющий файл нужен каждой версии, даже если у нее нет отдельного скрипта установки, поскольку этот управляющий файл будет определять, как именно выполняется неявное обновление до эту версии. Например, если в файле foo--1.0.control задается requires = 'bar', а в других управляющих файлах foo этого не происходит, зависимость расширения от bar будет сброшена при обновлении с версии 1.0 до другой.

Примечания о безопасности расширений

Широко распространяемые расширения должны иметь минимальные предположения о базе данных, в которой они находятся. Следовательно, функции, предоставляемые расширениями, необходимо писать в безопасном стиле, который нельзя будет скомпрометировать в атаках, использующих пути поиска.

Расширение, у которого свойство superuser имеет значение true, также должно быть защищено от угроз безопасности, связанных с действиями, которые происходят в скриптах установки и обновления этого расширения. Злонамеренному пользователю не составит особого труда создать объекты типа троянского коня, которые впоследствии скомпрометируют выполнение небрежно написанного скрипта, позволив этому пользователю получить права суперпользователя.

Если расширение помечено как trusted, то устанавливающий его пользователь может выбрать схему установки, причем он может намеренно использовать небезопасную схему в надежде получить права суперпользователя. Поэтому доверенное расширение крайне уязвимо с точки зрения безопасности, и все команды в его скриптах следует тщательно проверять, чтобы исключить возможность компрометации.

Советы по безопасному написанию функций представлены ниже в подразделе Примечания о безопасности функций в расширениях, а советы по написанию скриптов установки — в подразделе Примечания о безопасности скриптов в расширениях.

Примечания о безопасности функций в расширениях

Предоставляемые расширениями функции на языках SQL и PL, подвержены атакам, использующим пути поиска, во время выполнения, поскольку синтаксический анализ таких функций происходит при их выполнении, а не создании.

Справочная страница команды CREATE FUNCTION содержит совет относительно безопасного написания функций с характеристикой SECURITY DEFINER. Имеет смысл применять эти методики и для всех функций, предоставляемых расширением, так как эти функции может вызвать пользователь с расширенными правами.

Если вы не можете настроить search_path так, чтобы он содержал только безопасные схемы, считайте, что каждое неполное имя может быть преобразовано в объект, определенный злонамеренным пользователем. Избегайте конструкций, явно зависящих от search_path; например, IN и CASE выражение WHEN всегда выбирают оператор по пути поиска. Вместо них используйте OPERATOR(схема.=) ANY и CASE WHEN выражение.

Обычно расширение общего назначения не должно рассчитывать на то, что оно будет установлено в безопасной схеме, что означает, что даже уточненные схемой ссылки на его собственные объекты не вполне безопасны. Например, если в расширении есть функция myschema.myfunc(bigint), то вызовы вроде myschema.myfunc(42) могут быть перехвачены вредоносной функцией myschema.myfunc(integer). Позаботьтесь о том, чтобы типы данных функции и параметров оператора точно соответствовали объявленным типам их аргументов, и при необходимости используйте явные приведения.

Примечания о безопасности скриптов в расширениях

Скрипты установки и обновления расширения следует писать так, чтобы защитить их от атак с использованием пути поиска во время их выполнения. Если имя объекта в скрипте может оказаться разрешено в какой-либо другой объект, нежели планировал автор скрипта, то компрометация расширения произойдет сразу же или позже, при использовании неверно определенного объекта расширения.

Команды DDL, например CREATE FUNCTION и CREATE OPERATOR CLASS, в целом безопасны, но избегайте любых команд, содержащих расширения общего назначения. Например, требуют проверки команда CREATE VIEW, а также выражение DEFAULT в команде CREATE FUNCTION.

Иногда скрипту расширения может понадобиться выполнить SQL общего назначения, например, чтобы внести в каталог изменения, невозможные через DDL. Обязательно выполняйте такие команды с безопасным search_path; не полагайтесь на защищенность пути, установленного при выполнении CREATE/ALTER EXTENSION. Наилучшим выходом будет временно задать в search_path значение 'pg_catalog, pg_temp' и везде, где потребуется, добавить явные указания схемы, в которую устанавливается расширение. (Этот прием также может быть полезен при создании представлений.) Примеры можно найти в модулях share/extension в дистрибутиве исходного кода QHB.

Перекрестные ссылки между расширениями крайне сложно полностью обезопасить, отчасти из-за отсутствия уверенности, в какой схеме находится другое расширения. Риски уменьшаются, если оба расширения устанавливаются в одну схему, так как при этом вредоносный объект не сможет оказаться перед целевым расширением в используемом во время установки search_path. Однако в настоящее время нет механизма, который бы это требовал. На данным момент рекомендуется не помечать расширение как доверенное, если оно зависит от других расширений, за исключением тех, которые уже установлены в схему pg_catalog.

Пример расширения

Вот полный пример расширения, созданного исключительно средствами SQL, составного типа с двумя элементами, который может хранить в своих слотах, названных «k» и «v», значения любого типа. Для хранения все нетекстовые значения автоматически преобразуются в текст.

Скриптовый файл pair--1.0.sql выглядит так:

-- выразит недовольство, если скрипт получен из psql, а не посредством команды CREATE EXTENSION
\echo Use "CREATE EXTENSION pair" to load this file. \quit
-- (Используйте "CREATE EXTENSION pair", чтобы загрузить этот файл )

CREATE TYPE pair AS ( k text, v text );

CREATE OR REPLACE FUNCTION pair(text, text)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::@extschema@.pair;';

CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, FUNCTION = pair);

-- "SET search_path" легче корректировать, но полные имена работают лучше.
CREATE OR REPLACE FUNCTION lower(pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW(lower($1.k), lower($1.v))::@extschema@.pair;'
SET search_path = pg_temp;

CREATE OR REPLACE FUNCTION pair_concat(pair, pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k,
               $1.v OPERATOR(pg_catalog.||) $2.v)::@extschema@.pair;';

Управляющий файл pair.control выглядит так:

# pair extension
comment = 'A key/value pair data type' /* Тип данных для пары ключ/значение */
default_version = '1.0'
# cannot be relocatable because of use of @extschema@
/* не может быть перемещаемым, так как использует @extschema@ */
relocatable = false

Хотя вам вряд ли понадобится сборочный файл для установки этих двух файлов в правильный каталог, можно использовать Makefile следующего содержания:

EXTENSION = pair
DATA = pair--1.0.sql

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

Этот сборочный файл опирается на инфраструктуру PGXS, которая описывается в следующем подразделе. Команда make install установит управляющие файлы и скрипт-файлы в правильный каталог, который определит pg_config.

Как только файлы будут установлены, используйте команду CREATE EXTENSION, чтобы загрузить объекты в ту или иную базу данных.

Инфраструктура сборки расширений

Если вы обдумываете распространение ваших модулей расширения QHB, то учтите, что подготовить для них переносимую систему сборки может быть довольно сложно. Поэтому установка QHB включает в себя инфраструктуру сборки расширений, называемую PGXS, поэтому простые модули расширений можно собрать просто на уже установленном сервере. PGXS в основном предназначен для расширений, которые включают в себя код на C/RUST, хотя ее также можно использовать и для расширений на чистом SQL. Обратите внимание, что PGXS не претендует на то, чтобы быть универсальной инфраструктурой системы сборки, которую можно применять для создания любого программного средства, взаимодействующего с QHB; она всего лишь автоматизирует общие правила сборки для простых модулей расширения сервера. Для более сложных пакетов вам, по всей видимости, придется написать собственную систему сборки.

Чтобы использовать инфраструктуру PGXS для вашего расширения, следует написать простой сборочный файл. В нем нужно установить некоторые переменные и включить глобальный сборочный файл PGXS. Вот пример, который создает модуль расширения с именем isbn_issn, состоящий из разделяемой библиотеки, содержащей некоторый код на C, управляющего файла расширения, скрипта SQL, включаемого файла (он требуется только в том случае, если у других модулей возникнет необходимость получить доступ к функциям расширения напрямую, без использования SQL) и текстового файла документации:

MODULES = isbn_issn
EXTENSION = isbn_issn
DATA = isbn_issn--1.0.sql
DOCS = README.isbn_issn
HEADERS_isbn_issn = isbn_issn.h

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

Последние три строки всегда должны быть неизменными. Ранее в файле вы присваиваете значения переменным или добавляете собственные правила создания.

Установите одну из этих трех переменных, чтобы указать, что будет собрано:

MODULES
список объектов разделяемой библиотеки, которые должны быть собраны из исходных файлов с одинаковым основанием (не включайте в этот список суффиксы библиотеки)

MODULE_big
разделяемая библиотека, которая должна быть собрана из нескольких исходных файлов (перечислите объектные файлы в OBJS)

PROGRAM
исполняемая программа, которая должна быть собрана (перечислите объектные файлы в OBJS)

Также можно установить следующие переменные:

EXTENSION
имена расширений; для каждого имени нужно предоставить файл расширение.control, который будет установлен в префикс/share/extension

MODULEDIR
подкаталог в каталоге префикс/share в который должны быть установлены файлы DATA и DOCS (если не задан, по умолчанию используется extension, если установлена переменная EXTENSION, или contrib, если нет)

DATA
произвольные файлы для установки в префикс/share/$MODULEDIR

DATA_built
произвольные файлы для установки в префикс/share/$MODULEDIR, которые сначала нужно собрать

DATA_TSEARCH
произвольные файлы для установки в префикс/share/tsearch_data

DOCS
произвольные файлы для установки в префикс/doc/$MODULEDIR

HEADERS
HEADERS_built
Файлы для установки (и, возможно, сборки) в префикс/include/server/$MODULEDIR/$MODULE_big.
В отличие от файлов DATA_built, файлы в HEADERS_built не удаляются целью clean; если хотите их удалить, добавьте их в EXTRA_CLEAN или напишите собственные правила для этого.

HEADERS_$MODULE
HEADERS_built_$MODULE
Файлы для установки (после сборки, если указано) в префикс/include/server/$MODULEDIR/$MODULE, где $MODULE должно быть именем модуля, используемым в MODULES или MODULE_big.
В отличие от файлов DATA_built, файлы в HEADERS_built_$MODULE не удаляются целью clean; если вы хотите их удалить, добавьте их в EXTRA_CLEAN или напишите собственные правила для этого.
Допускается использовать для одного модуля обе переменные или любую их комбинацию, если только в списке MODULES нет двух имен модулей, которые отличаются только наличием префикса built_, что может привести к неоднозначности. В этом (будем надеяться, маловероятном) случае следует использовать только переменные HEADERS_built_$MODULE.

SCRIPTS
скрипт-файлы (не двоичные файлы) для установки в префикс/bin

SCRIPTS_built
скрипт-файлы (не двоичные файлы) для установки в префикс/bin, которые сначала нужно собрать

REGRESS
список регрессионных тестов (без суффикса), см. ниже

REGRESS_OPTS
дополнительные параметры для передачи pg_regress

ISOLATION
список изоляционных тестов, более подробную информацию см. ниже

ISOLATION_OPTS
дополнительные параметры для передачи pg_isolation_regress

TAP_TESTS
флаг, определяющий, нужно ли запускать тесты TAP; см. ниже

NO_INSTALL
не определять цель install; полезна для модулей тестирования, для которых не требуется установка результатов сборки

NO_INSTALLCHECK
не определять цель installcheck; полезна, например, если тестам требуется специальная конфигурация или не используется pg_regress

EXTRA_CLEAN
дополнительные файлы для удаления при make clean

PG_CPPFLAGS
флаги, которые будут добавлены перед другими в CPPFLAGS

PG_CFLAGS
флаги, которые будут добавлены после других в CFLAGS

PG_CXXFLAGS
флаги, которые будут добавлены после других в CXXFLAGS

PG_LDFLAGS
флаги, которые будут добавлены перед другими в LDFLAGS

PG_LIBS
будет добавлена в строку компоновки PROGRAM

SHLIB_LINK
будет добавлена в строку компоновки MODULE_big

PG_CONFIG
путь к программе pg_config в установке QHB, с которой будет проведена сборка (обычно указывается просто pg_config, и используется первая же такая программа в вашем пути PATH)

Поместите этот сборочный файл под именем Makefile в каталог, содержащий ваше расширение. Затем можно выполнить make для компиляции, а потом make install для установки вашего модуля. По умолчанию расширение компилируется и устанавливается для той установки QHB, которая соответствует первой программе pg_config, найденной при поиске по пути в вашем PATH. Можно воспользоваться и другой установкой, настроив PG_CONFIG так, чтобы тот указывал на ее программу pg_config, либо в сборочном файле, либо в командной строке make.

Кроме того, если вы хотите сохранить каталог сборки отдельно, можно запустить make в каталоге за пределами исходного дерева вашего расширения. Эта процедура также называется сборкой с VPATH. Выполняется она так:

mkdir build_dir
cd build_dir
make -f /path/to/extension/source/tree/Makefile
make -f /path/to/extension/source/tree/Makefile install

Как вариант, можно подготовить каталог для сборки с VPATH аналогично тому, как это делается для кода ядра. Один из способов сделать это — использовать скрипт ядра config/prep_buildtree. После этого можно выполнить сборку, установив переменную VPATH для make следующим образом:

make VPATH=/path/to/extension/source/tree
make VPATH=/path/to/extension/source/tree install

Эта процедура может работать с самыми разнообразными расположениями каталогов.

Скрипты, перечисленные в переменной REGRESS, используются для регрессионного тестирования вашего модуля, и вызвать их можно командой make installcheck после выполнения команды make install. Чтобы запустить тестирование, необходим работающий сервер QHB. Скрипт-файлы, перечисленные в REGRESS, должны находиться в подкаталоге с именем sql/ в каталоге вашего расширения. Эти файлы должны иметь расширение .sql, которое нельзя включать в список REGRESS в сборочном файле. Для каждого теста также должен создаваться файл с ожидаемым выводом в подкаталоге expected/, с тем же основанием и расширением .out. Команда make installcheck выполняет каждый тестовый скрипт с помощью psql и сравнивает полученный вывод с соответствующим ожидаемым файлом. Все обнаруженные различия будут записаны в файл regression.diffs в формате diff -c. Обратите внимание, что при попытке запустить тест, в котором отсутствует ожидаемый файл, этот тест будет отображаться как «проблемный», поэтому убедитесь, что у вас есть все ожидаемые файлы.

Скрипты, перечисленные в переменной ISOLATION, используются для нагрузочного тестирования работы модуля при проведении параллельного сеанса, и вызвать их можно командой make installcheck после выполнения команды make install. Чтобы запустить тестирование, необходим работающий сервер QHB. Скрипт-файлы, перечисленные в ISOLATION, должны находиться в подкаталоге specs/ в каталоге вашего расширения. Эти файлы должны иметь расширение .spec, которое нельзя включать в список ISOLATION в сборочном файле. Для каждого теста также должен быть файл с ожидаемым выводом в подкаталоге expected/, с тем же основанием и расширением .out. Команда make installcheck выполняет каждый тестовый скрипт и сравнивает полученный вывод с соответствующим ожидаемым файлом. Все обнаруженные различия будут записаны в файл output_iso/regression.diffs в формате diff -c. Обратите внимание, что при попытке запустить тест, в котором отсутствует ожидаемый файл, этот тест будет отображаться как «проблемный», поэтому убедитесь, что у вас есть все ожидаемые файлы.

Совет
Самый простой способ создать ожидаемые файлы — создать пустые файлы, а затем выполнить тестовый прогон (который, конечно, выявит различиях). Проверьте фактические файлы результатов, сохраненные в каталоге results/ (для тестов REGRESS) или каталоге output_iso/results/ (для тестов ISOLATION), а затем, если они соответствуют вашим ожиданиям от теста, скопируйте их в expected/.

Триггеры

В этой главе содержится общая информация о написании триггерных функций. Триггерные функции можно написать на большинстве доступных процедурных языков, включая PL/pgSQL. Прочитав текущую главу, вы можете обратиться к главе PL/pgSQL, чтобы узнать подробности о написании на триггера на этом языке.

Также можно написать триггерную функцию на C/RUST, хотя большинству людей проще использовать один из процедурных языков. В настоящее время написать триггерную функцию на простом языке функций SQL невозможно.

Обзор поведения триггеров

Триггер — это указание, согласно которому база данных должна автоматически выполнять определенную функцию всякий раз, когда осуществляется операция определенного типа. Триггеры могут быть прикреплены к таблицам (партиционированным или обычным), представлениям и сторонним таблицам.

В таблицах и сторонних таблицах можно определить триггеры, которые будут выполняться до или после любой операции INSERT, UPDATE или DELETE либо один раз для каждой модифицированной строки, либо один раз для каждого оператора SQL. Более того, триггеры UPDATE можно установить на срабатывание только в том случае, если в предложении SET команды UPDATE упоминаются определенные столбцы. Также триггеры могут срабатывать для операторов TRUNCATE. Если происходит событие триггера, функция триггера вызывается в соответствующее время для его обработки.

В представлениях можно определить триггеры, которые будут выполняться вместо операций INSERT, UPDATE или DELETE. Такие триггеры INSTEAD OF срабатывают один раз для каждой строки, которую необходимо модифицировать. Функция триггера отвечает за выполнение необходимых модификаций нижележащей базовой таблицы (или таблиц) представления и при необходимости возвращает модифицированную строку такой, какой она будет отображаться в представлении. Кроме того, для представлений можно определить триггеры, которые будут выполняться один раз для каждого оператора SQL, до или после операций INSERT, UPDATE или DELETE. Однако такие триггеры срабатывают, только если в представлении также имеется триггер INSTEAD OF. В противном случае любой оператор, направленный на представление, должен быть переписан в оператор, затрагивающий его нижележащую базовую таблицу(ы), и тогда будут срабатывать триггеры, прикрепленные к этой таблице(ам).

Триггерная функция должна быть определена до создания триггера и объявлена как функция, не имеющая аргументов и возвращающая тип trigger. (Триггерная функция получает входные данные через специально переданную структуру TriggerData, а не в форме аргументов обычной функции.)

После создания подходящей триггерной функции триггер устанавливается с помощью команды CREATE TRIGGER. Одна триггерная функция может использоваться для нескольких триггеров.

QHB предлагает как построчные, так и пооператорные триггеры. В случае построчного триггера триггерная функция вызывается один раз для каждой строки, которую затрагивает оператор, вызвавший триггер. Напротив, пооператорный триггер вызывается только один раз при выполнении соответствующего оператора, независимо от количества строк, затронутых этим оператором. В частности, оператор, который влияет на ноль строк, все равно будет провоцировать выполнение любых применимых пооператорных триггеров для каждого оператора. Эти два типа триггеров иногда называют триггерами уровня строки и уровня оператора соответственно. Триггеры для TRUNCATE можно определить только на уровне оператора, но не для каждой строки.

Кроме того, триггеры классифицируются в зависимости от того, срабатывают они до, после или вместо операции. Они называются триггерами BEFORE, AFTER и INSTEAD OF соответственно. Триггеры BEFORE уровня оператора естественным образом срабатывают до того, как оператор начинает что-либо делать, тогда как триггеры AFTER того же уровня срабатывают в самом конце оператора. Эти типы триггеров можно определить в таблицах, представлениях или сторонних таблицах. Триггеры BEFORE уровня строки срабатывают непосредственно перед тем, как будет обработана определенная строка, тогда как триггеры AFTER того же уровня срабатывают в конце оператора (но до триггеров AFTER уровня оператора). Эти типы триггеров можно определить только для партиционированных и сторонних таблиц, но не для представлений. Триггеры INSTEAD OF можно определить только для представлений и только на уровне строки; они срабатывают сразу после того, как каждая строка в представлении определяется как требующая обработки.

Если триггер AFTER определен как триггер ограничения, его выполнение может быть отложено до конца транзакции, а не оператора. В любом случае триггер выполняется как часть той же транзакции, которой принадлежит запустивший его оператор, поэтому если оператор или триггер вызывают ошибку, отменятся действия обоих.

Оператор, нацеленный на родительскую таблицу в иерархии наследования или партиционирования, не вызывает срабатывания триггеров уровня оператора для затрагиваемых дочерних таблиц; срабатывают только триггеры уровня оператора для родительской таблицы. Однако триггеры для этих дочерних таблиц на уровне строки срабатывать будут.

Если INSERT содержит предложение ON CONFLICT DO UPDATE, возможно, что эффекты триггеров BEFORE INSERT и BEFORE UPDATE уровня строки можно применить совместно таким образом, что это проявится в конечном состоянии изменяемой строки, если в запросе было обращение к столбцу EXCLUDED. Однако для выполнения обоих наборов триггеров BEFORE уровня строки необязательно указывать ссылку на столбец EXCLUDED. Когда имеются триггеры BEFORE INSERT и BEFORE UPDATE уровня строки, которые совместно изменяют добавляемую/ обновляемую строку, следует рассмотреть возможность получения неожиданного результата (это может стать проблемой, даже если изменения более или менее равнозначны, если только они не идемпотентны). Обратите внимание, что триггеры UPDATE уровня оператора выполняются, когда указано ON CONFLICT DO UPDATE, независимо от того, затронул ли UPDATE какие-либо строки или нет (и независимо от того, был ли выбран альтернативный путь UPDATE). INSERT с предложением ON CONFLICT DO UPDATE сначала выполнит триггеры BEFORE INSERT, затем триггеры BEFORE UPDATE, затем триггеры AFTER UPDATE и наконец триггеры AFTER INSERT (все триггеры уровня оператора).

Если команда UPDATE в партиционированной таблице приводит к перемещению строки в другую партицию, она будет проведена как выполнение сначала DELETE в исходной партиции, а затем INSERT в новой партиции. В этом случае все триггеры BEFORE UPDATE и BEFORE DELETE уровня строки срабатывают в исходной партиции. Затем в целевой партиции срабатывают все триггеры BEFORE INSERT уровня строки. Когда все эти триггеры затрагивают перемещение строки, следует рассмотреть возможность неожиданных результатов. Если речь идет о триггерах AFTER ROW, то применяются триггеры AFTER DELETE и AFTER INSERT, но не AFTER UPDATE, потому что команда UPDATE была преобразована в DELETE и INSERT. Если же говорить о триггерах уровня оператора, то ни один из триггеров DELETE или INSERT не срабатывает, даже если происходит перемещение строки; сработают только триггеры UPDATE, определенные в целевой таблице, используемой в операторе UPDATE.

Для команды MERGE отдельные триггеры не определяются. Вместо этого срабатывают триггеры уровня строки или уровня оператора команд UPDATE, DELETE и INSERT в зависимости от того, какие действия указаны в запросе MERGE (для триггеров уровня оператора) и какие действия выполняются (для триггеров уровня строки).

При выполнении команды MERGE триггеры BEFORE и AFTER уровня оператора срабатывают для событий, указанных в действиях команды MERGE, независимо от того, было ли действие в итоге выполнено. Это аналогично поведению оператора UPDATE, который не изменяет строки, но при этом триггеры уровня оператора для него все равно срабатывают. Триггеры уровня строки срабатывают, только когда строка действительно изменяется, добавляется или удаляется. Поэтому совершенно нормально, что триггеры уровня оператора для определенных типов действий срабатывают, тогда как триггеры уровня строки — нет.

Триггерные функции, вызываемые пооператорными триггерами, должны всегда возвращать NULL. Триггерные функции, вызываемые построчными триггерами, могут возвращать строку таблицы (значение типа HeapTuple) вызывающему исполнителю, если они того пожелают. Построчный триггер, срабатывающий до операции, имеет следующие варианты:

Он может вернуть NULL, чтобы пропустить операцию для текущей строки. Это указывает исполнителю не выполнять операцию на уровне строки, вызвавшей триггер (добавление, изменение или удаление определенной строки таблицы).

Только для триггеров INSERT и UPDATE уровня строки возвращаемая строка становится строкой, которая будет добавлена или заменит изменяемую строку. Это позволяет триггерной функции модифицировать добавляемую или изменяемую строку.

Триггер BEFORE уровня строки, который не планирует вызвать какое-либо из этих поведений, должен аккуратно вернуть в качестве своего результата ту же строку, что была передана на вход (то есть строку NEW для триггеров INSERT и UPDATE, строку OLD для триггеров DELETE).

Триггер INSTEAD OF уровня строки должен возвращать либо NULL, чтобы показать, что он не изменил никаких данных из нижележащих базовых таблиц представления, либо строку представления, которая была передана на вход (строку NEW для операций INSERT и UPDATE, строку OLD для операций DELETE). Возвращаемое значение, отличное от NULL, сигнализирует, что триггер выполнил необходимые модификации данных в представлении. Это приведет к увеличению счетчика количества строк, затрагиваемых командой. Только для операций INSERT и UPDATE триггер может модифицировать строку NEW перед тем, как вернуть ее. Это изменит данные, возвращаемые INSERT RETURNING или UPDATE RETURNING, и будет полезно, когда представление не будет отображать ровно те же данные, что были предоставлены.

Возвращаемое значение игнорируется для триггеров уровня строки, срабатывающих после операции, и поэтому они могут возвращать NULL.

Отдельно стоит рассмотреть генерируемые столбцы. Сохраняемые генерируемые столбцы вычисляются после триггеров BEFORE и до триггеров AFTER. Поэтому в триггерах AFTER можно проверить сгенерированное значение. В триггерах BEFORE строка OLD, как и следовало ожидать, содержит старое сгенерированное значение, но строка NEW еще не содержит нового сгенерированного значения и не должна быть доступна. В интерфейсе языка C содержимое столбца на этом этапе не определено; язык программирования более высокого уровня должен предотвращать доступ к сохраняемому генерируемому столбцу в строке NEW в триггере BEFORE. Изменения значения генерируемого столбца в триггере BEFORE игнорируются и будут перезаписаны.

Если для одного и того же события в одном и том же отношении определено несколько триггеров, они сработают в алфавитном порядке по имени триггера. В случае триггеров BEFORE и INSTEAD OF потенциально модифицированная строка, возвращаемая каждым триггером, становится входящей строкой для следующего триггера. Если какой-либо триггер BEFORE или INSTEAD OF возвращает NULL, для этой строки операция прекращается, и последующие триггеры не срабатывают (для этой строки).

В определении триггера также можно указать логическое условие WHEN, которое будет проверяться на предмет того, должен ли триггер срабатывать. В триггерах уровня строки условие WHEN может проверять старые и/или новые значения столбцов строки. (Триггеры уровня оператора также могут иметь условия WHEN, хотя для них эта возможность не так полезна.) В триггере BEFORE условие WHEN вычисляется непосредственно перед выполнением функции, поэтому использование WHEN не слишком отличается от проверки того же условия в начале триггерной функции. Однако в триггере AFTER условие WHEN вычисляется сразу после изменения строки и определяет, стоит ли в очереди событие для срабатывания триггера в конце оператора. Таким образом, когда условие WHEN триггера AFTER не возвращает true, нет необходимости ставить в очередь событие или повторно извлекать строку в конце оператора. Триггер, который нужно запустить только для нескольких строк, значительному ускорить операторы, изменяющие много строк. Триггеры INSTEAD OF не поддерживают условия WHEN.

Как правило, триггеры BEFORE уровня строки используются для проверки или модификации данных, которые будут добавлены или изменены. Например, триггер BEFORE можно использовать для добавления текущего времени в столбец timestamp или для проверки согласованности двух элементов строки. Триггеры AFTER наиболее разумно использовать для распространения изменений на другие таблицы или для проверки согласованности с другими таблицами. Причина такого разделения работы заключается в том, что триггер AFTER может быть уверен, что видит окончательное значение строки, а триггер BEFORE — нет, так как после него могут сработать другие триггеры BEFORE. Если у вас нет особой причины делать триггер именно BEFORE или AFTER, вариант с BEFORE более эффективен, поскольку информацию об операции не нужно сохранять до конца выполнения оператора.

Если триггерная функция выполняет команды SQL, эти команды могут снова запускать триггеры. Они называются каскадными триггерами. Прямого ограничения на количество каскадных уровней нет. Каскады могут приводить к рекурсивному вызову одного и того же триггера; например, триггер INSERT может выполнить команду, которая добавляет дополнительную строку в ту же таблицу, вызывая повторное срабатывание триггера INSERT. Программист триггера обязан избегать бесконечной рекурсии в таких сценариях.

При определении триггера для него можно указать аргументы. Цель этого заключается в том, чтобы разрешить различным триггерам с одинаковыми требованиями вызывать одну и ту же функцию. Например, это может быть обобщенная триггерная функция, принимающая в качестве аргументов два имени столбца и помещающая текущего пользователя в один из них, а текущую метку времени — в другой. При правильном написании эта триггерная функция не зависит от конкретной таблицы, для которой она срабатывает. Таким образом, эту же функцию можно использовать для событий INSERT в любой таблице с подходящими столбцами, например, для автоматического отслеживания создания записей в таблице транзакций. Кроме того, если она определена как триггер UPDATE, ее можно использовать для отслеживания событий последних изменений.

Каждый язык программирования, который поддерживает триггеры, имеет свой собственный метод, позволяющий сделать входные данные триггера доступными для триггерной функции. Эти входные данные включают тип события триггера (например INSERT или UPDATE), а также любые аргументы, которые были перечислены в команде CREATE TRIGGER. Для триггера уровня строки входные данные дополнительно выключают строку NEW для триггеров INSERT и UPDATE и/или строку OLD для триггеров UPDATE и DELETE.

По умолчанию триггеры уровня оператора не имеют никакой возможности проверить отдельные строки, измененные этим оператором. Но триггер AFTER STATEMENT может запросить создание переходных таблиц, чтобы наборы затронутых строк стали для него доступны. Триггеры AFTER ROW также могут запрашивать переходные таблицы, чтобы иметь возможность видеть общие изменения в таблице, а также изменения в отдельной строке, для которой они в данный момент срабатывают. Способ проверки переходных таблиц опять же зависит от используемого языка программирования, но типичный подход заключается в том, чтобы заставить переходные таблицы работать как временные таблицы только для чтения, к которым могут обращаться команды SQL, выполненные в триггерной функции.

Видимость изменений данных

Если в своей триггерной функции вы выполняете команды SQL, и эти команды обращаются к таблице, для которой создан этот триггер, вам необходимо знать правила видимости данных, поскольку они определяют, будут ли эти команды SQL видеть изменение данных, для которых срабатывает триггер. Кратко:

Триггеры уровня оператора следуют простым правилам видимости: ни одно из изменений, внесенных оператором, не видно триггерам BEFORE, тогда как триггерам AFTER видны все модификации.

Изменение данных (добавление, изменение или удаление), вызывающее срабатывание триггера, естественно, не видно командам SQL, выполняемым в триггере BEFORE уровня строки, поскольку это еще не произошло.

Однако команды SQL, выполняемые в триггере BEFORE уровня строки, увидят последствия изменений данных для строк, ранее обработанных в той же внешней команде. Это требует осторожности, поскольку порядок этих событий изменений в целом непредсказуем; команда SQL, затрагивающая несколько строк, может посещать их в любом порядке.

Аналогичным образом триггер INSTEAD OF уровня строки будет видеть последствия изменений данных, выполненных при предыдущих срабатываниях триггеров INSTEAD OF в той же внешней команде.

При срабатывании триггера AFTER уровня строки все изменения данных, внесенные внешней командой, уже завершены и видны для вызываемой триггерной функции.

Если ваша триггерная функция написана на любом из стандартных процедурных языков, то приведенные выше операторы применимы, только если функция объявлена как VOLATILE. Функции, которые объявлены как STABLE или IMMUTABLE, в любом случае не увидят изменений, внесенных вызывающей командой.

Дополнительную информацию о правилах видимости данных можно найти в разделе Видимость изменений данных. В примере в разделе Полный пример триггера содержится демонстрация этих правил.

Написание триггерных функций на C/RUST

В этом разделе описываются низкоуровневые детали интерфейса для триггерной функции. Эта информация необходима только при написании триггерных функций на языке C/RUST. Если вы используете язык более высокого уровня, то эти детали обрабатываются незаметно для вас. В большинстве случаев вам следует рассмотреть возможность использования процедурного языка, прежде чем писать свои триггеры на C/RUST. В документации каждого процедурного языка объясняется, как написать на нем триггер.

Триггерные функции должны использовать интерфейс менеджера функций «version 1».

Когда функция вызывается менеджером триггеров, ей не передаются никакие обычные аргументы, но передается указатель «context», указывающий на структуру TriggerData. Функции на C/RUST могут проверить, были ли они вызваны из менеджера триггеров или нет, выполнив макрос:

CALLED_AS_TRIGGER(fcinfo)

который разворачивается в:

((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))

Если эта запись возвращает true, то можно безопасно привести fcinfo->context к типу TriggerData * и использовать структуру TriggerData. Функция не должна изменять структуру TriggerData или любые данные, на которые она указывает.

struct TriggerData определена в commands/trigger.h:

typedef struct TriggerData
{
    NodeTag          type;
    TriggerEvent     tg_event;
    Relation         tg_relation;
    HeapTuple        tg_trigtuple;
    HeapTuple        tg_newtuple;
    Trigger         *tg_trigger;
    TupleTableSlot  *tg_trigslot;
    TupleTableSlot  *tg_newslot;
    Tuplestorestate *tg_oldtable;
    Tuplestorestate *tg_newtable;
    const Bitmapset *tg_updatedcols;
} TriggerData;

где члены определены следующим образом:

type
Всегда T_TriggerData.

tg_event
Описывает событие, для которого вызывается функция. Для проверки tg_event можно воспользоваться следующими макросами:

TRIGGER_FIRED_BEFORE(tg_event) — возвращает true, если триггер сработал до операции.

TRIGGER_FIRED_AFTER(tg_event) — возвращает true, если триггер сработал после операции.

TRIGGER_FIRED_INSTEAD(tg_event) — возвращает true, если триггер сработал вместо операции.

TRIGGER_FIRED_FOR_ROW(tg_event) — возвращает true, если триггер сработал для события уровня строки.

TRIGGER_FIRED_FOR_STATEMENT(tg_event) — возвращает true, если триггер сработал для события уровня оператора.

TRIGGER_FIRED_BY_INSERT(tg_event) — возвращает true, если триггер был запущен командой INSERT.

TRIGGER_FIRED_BY_UPDATE(tg_event) — возвращает true, если триггер был запущен командой UPDATE.

TRIGGER_FIRED_BY_DELETE(tg_event) — возвращает true, если триггер был запущен командой DELETE.

TRIGGER_FIRED_BY_TRUNCATE(tg_event) — возвращает true, если триггер был запущен командой TRUNCATE.

tg_relation
Указатель на структуру, описывающую отношение, для которого сработал триггер. Подробную информацию об этой структуре см. в utils/rel.h. Наиболее интересными элементами являются tg_relation->rd_att (дескриптор кортежей отношения) и tg_relation->rd_rel->relname (имя отношения; имеет тип не char*, а NameData; используйте SPI_getrelname(tg_relation) для получения char*, если вам нужна копия этого имени).

tg_trigtuple
Указатель на строку, для которой сработал триггер. Это строка, которая добавляется, изменяется или удаляется. Если этот триггер сработал для INSERT или DELETE, то это значение следует вернуть из функции, если вы не планируете заменять эту строку другой (в случае INSERT) или пропустить данную операцию. Для триггеров во внешних таблицах значения системных столбцов здесь не указываются.

tg_newtuple
Указатель на новую версию строки, если триггер сработал для UPDATE, и NULL, если он сработал для INSERT или DELETE. Это значение следует вернуть из функции, если событие является UPDATE и вы не планируете заменять эту строку другой или пропустить данную операцию. Для триггеров во внешних таблицах значения системных столбцов здесь не указываются.

tg_trigger
Указатель на структуру типа Trigger, определенную в utils/reltrigger.h:

typedef struct Trigger
{
    Oid         tgoid;
    char       *tgname;
    Oid         tgfoid;
    int16       tgtype;
    char        tgenabled;
    bool        tgisinternal;
    Oid         tgconstrrelid;
    Oid         tgconstrindid;
    Oid         tgconstraint;
    bool        tgdeferrable;
    bool        tginitdeferred;
    int16       tgnargs;
    int16       tgnattr;
    int16      *tgattr;
    char      **tgargs;
    char       *tgqual;
    char       *tgoldtable;
    char       *tgnewtable;
} Trigger;

где tgname — имя триггера, tgnargs — количество аргументов в tgargs, tgargs — массив указателей на аргументы, заданные в операторе CREATE TRIGGER. Остальные члены предназначены только для внутреннего использования.

tg_trigslot
Слот, содержащий tg_trigtuple, или указатель NULL, если такого кортежа нет.

tg_newslot
Слот, содержащий tg_newtuple, или указатель NULL, если такого кортежа нет.

tg_oldtable
Указатель на структуру типа Tuplestorestate, содержащую ноль или более строк в формате, заданном параметром tg_relation, или указатель NULL если переходного отношения OLD TABLE нет.

tg_newtable
Указатель на структуру типа Tuplestorestate, содержащую ноль или более строк в формате, заданном параметром tg_relation, или указатель NULL, если переходного отношения NEW TABLE нет.

tg_updatedcols
Для триггеров UPDATE это битовая карта, указывающая на столбец, измененный командой, которая вызвала срабатывание триггера. Универсальные триггерные функции могут использовать ее для оптимизации своих действий, чтобы не иметь дела со столбцами, которые не были изменены.
Например, чтобы определить, является ли столбец с атрибутом номер attnum (считая с 1) членом этой битовой карты, вызовите bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, trigdata->tg_updatedcols)).
Для триггеров, отличных от UPDATE, здесь будет значение NULL.

Чтобы позволить запросам, отправленным через SPI, обращаться к переходным таблицам, см. SPI_register_trigger_data.

Триггерная функция должна возвращать либо указатель HeapTuple, либо указатель NULL (но не SQL-значение NULL, т. е. не устанавливать isNull равным true). Обязательно возвращайте tg_trigtuple или tg_newtuple (в зависимости от ситуации), если не хотите модифицировать строку, с которой работаете.

Полный пример триггера

Вот очень простой пример триггерной функции, написанной на C. (Примеры триггеров, написанных на процедурных языках, можно найти в документации этих процедурных языков.)

Функция trigf выводит количество строк в таблице ttest и пропускает фактическую операцию, если команда пытается вставить значение NULL в столбец x. (Таким образом, триггер действует как ограничение NOT NULL, но не прерывает транзакцию.)

Для начала определяем таблицу:

CREATE TABLE ttest (
    x integer
);

Это исходный код триггерной функции:

#include "qhb.h"
#include "fmgr.h"
#include "executor/spi.h"       /* это то, что вам нужно для работы с SPI */
#include "commands/trigger.h"   /* ... триггеры ... */
#include "utils/rel.h"          /* ... и отношения */

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(trigf);

Datum
trigf(PG_FUNCTION_ARGS)
{
    TriggerData *trigdata = (TriggerData *) fcinfo->context;
    TupleDesc   tupdesc;
    HeapTuple   rettuple;
    char       *when;
    bool        checknull = false;
    bool        isnull;
    int         ret, i;

    /* убедимся, что она вообще вызывается как триггер */
    if (!CALLED_AS_TRIGGER(fcinfo))
        elog(ERROR, "trigf: not called by trigger manager");
        /* ОШИБКА: "trigf: не вызывается менеджером триггеров" */

    /* кортеж, возвращаемый исполнителю */
    if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
        rettuple = trigdata->tg_newtuple;
    else
        rettuple = trigdata->tg_trigtuple;

    /* проверяем на значения NULL */
    if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)
        && TRIGGER_FIRED_BEFORE(trigdata->tg_event))
        checknull = true;

    if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
        when = "before";
    else
        when = "after ";

    tupdesc = trigdata->tg_relation->rd_att;

    /* подключаемся к менеджеру SPI */
    if ((ret = SPI_connect()) < 0)
        elog(ERROR, "trigf (fired %s): SPI_connect returned %d", when, ret);

    /* получаем количество строк в таблице */
    ret = SPI_exec("SELECT count(*) FROM ttest", 0);

    if (ret < 0)
        elog(ERROR, "trigf (fired %s): SPI_exec returned %d", when, ret);

    /* count(*) возвращает int8, так что не забудьте преобразовать его */
    i = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
                                    SPI_tuptable->tupdesc,
                                    1,
                                    &isnull));

    elog (INFO, "trigf (fired %s): there are %d rows in ttest", when, i);

    SPI_finish();

    if (checknull)
    {
        SPI_getbinval(rettuple, tupdesc, 1, &isnull);
        if (isnull)
            rettuple = NULL;
    }

    return PointerGetDatum(rettuple);
}

После компиляции исходного кода (см. подраздел Компиляция и связывание динамически загружаемых функций), объявляем функцию и триггеры:

CREATE FUNCTION trigf() RETURNS trigger
    AS 'имя_файла'
    LANGUAGE C;

CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest
    FOR EACH ROW EXECUTE FUNCTION trigf();

CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest
    FOR EACH ROW EXECUTE FUNCTION trigf();

Теперь можно проверить работу триггера:

=> INSERT INTO ttest VALUES (NULL);
INFO:  trigf (fired before): there are 0 rows in ttest
INSERT 0 0

-- Добавление пропущено, и триггер AFTER не сработал

=> SELECT * FROM ttest;
 x
---
(0 rows)

=> INSERT INTO ttest VALUES (1);
INFO:  trigf (fired before): there are 0 rows in ttest
INFO:  trigf (fired after ): there are 1 rows in ttest
                                       ^^^^^^^^
                             вспомните, что мы говорили про видимость.
INSERT 167793 1
vac=> SELECT * FROM ttest;
 x
---
 1
(1 row)

=> INSERT INTO ttest SELECT x * 2 FROM ttest;
INFO:  trigf (fired before): there are 1 rows in ttest
INFO:  trigf (fired after ): there are 2 rows in ttest
                                       ^^^^^^
                             вспомните, что мы говорили про видимость.
INSERT 167794 1
=> SELECT * FROM ttest;
 x
---
 1
 2
(2 rows)

=> UPDATE ttest SET x = NULL WHERE x = 2;
INFO:  trigf (fired before): there are 2 rows in ttest
UPDATE 0
=> UPDATE ttest SET x = 4 WHERE x = 2;
INFO:  trigf (fired before): there are 2 rows in ttest
INFO:  trigf (fired after ): there are 2 rows in ttest
UPDATE 1
vac=> SELECT * FROM ttest;
 x
---
 1
 4
(2 rows)

=> DELETE FROM ttest;
INFO:  trigf (fired before): there are 2 rows in ttest
INFO:  trigf (fired before): there are 1 rows in ttest
INFO:  trigf (fired after ): there are 0 rows in ttest
INFO:  trigf (fired after ): there are 0 rows in ttest
                                       ^^^^^^
                             вспомните, что мы говорили про видимость.
DELETE 2
=> SELECT * FROM ttest;
 x
---
(0 rows)

Более сложные примеры можно найти на справочной странице модуля spi.

Триггеры событий

В дополнение к механизму триггеров, описанному в главе Триггеры, QHB также предоставляет триггеры событий. В отличие от обычных триггеров, которые прикрепляются к одной таблице и захватывают только события DML, триггеры событий являются глобальными для конкретной базы данных и способны захватывать события DDL.

Как и обычные триггеры, триггеры событий можно написать на любом процедурном языке, который включает поддержку триггеров событий на C/RUST, но не на простом SQL.

Обзор поведения триггеров событий

Триггер события срабатывает всякий раз, когда событие, с которым он связан, происходит в базе данных, в которой он определен. В настоящее время поддерживаются только события ddl_command_start, ddl_command_end, table_rewrite и sql_drop. Поддержка дополнительных событий может быть добавлена в будущих выпусках.

Событие ddl_command_start наступает непосредственно перед выполнением команды CREATE, ALTER, DROP, SECURITY LABEL, COMMENT, GRANT или REVOKE. Перед срабатыванием триггера события не проверяется, существует ли затронутый объект или нет. Однако, в виде исключения, это событие не наступает для команд DDL, направленных на разделяемые объекты — базы данных, роли и табличные пространства, — или для команд, направленных на сами триггеры событий. Механизм триггеров событий не поддерживает эти типы объектов. Кроме того, событие ddl_command_start наступает непосредственно перед выполнением команды SELECT INTO, поскольку это равнозначно CREATE TABLE AS.

Событие ddl_command_end наступает сразу после выполнения этого же набора команд. Для получения более подробной информации о выполненных операциях DDL воспользуйтесь функцией pg_event_trigger_ddl_commands(), возвращающей множества, из кода триггера события ddl_command_end (см. раздел Функции триггеров событий). Обратите внимание, что триггер срабатывает после выполнения действий (но до фиксации транзакции), поэтому системные каталоги могут быть прочитаны как уже измененные.

Событие sql_drop наступает непосредственно перед триггером ddl_command_end для любой операции, которая удаляет объекты базы данных. Для просмотра списка удаленных объектов воспользуйтесь функцией pg_event_trigger_dropped_objects(), возвращающей множества, из кода триггера события sql_drop (см. раздел Функции триггеров событий). Обратите внимание, что этот триггер выполняется после того, как объекты были удалены из системных каталогов, поэтому их больше невозможно увидеть.

Событие table_rewrite наступает непосредственно перед перезаписью таблицы определенными действиями команд ALTER TABLE и ALTER TYPE. Хотя другие управляющие операторы, например CLUSTER и VACUUM, тоже способны перезаписать таблицу, событие table_rewrite для них не запускается.

Триггеры событий (как и другие функции) нельзя выполнить в прерванной транзакции. Таким образом, если команда DDL завершается с ошибкой, любые связанные триггеры ddl_command_end выполнены не будут. И наоборот, если триггер ddl_command_start завершается с ошибкой, дальнейшие триггеры событий не сработают, и попыток выполнить саму команду тоже предпринято не будет. Аналогичным образом если триггер ddl_command_end завершается с ошибкой, действия оператора DDL откатятся, как и в любом другом случае прерывания содержащей их транзакции.

Полный список команд, поддерживаемых механизмом запуска событий, см. в разделе Матрица срабатывания триггеров событий.

Триггеры событий создаются с помощью команды CREATE EVENT TRIGGER. Чтобы создать триггер события, сначала следует создать функцию со специальным возвращаемым типом event_trigger. Эта функция не должна возвращать значение (и может этого не делать); возвращаемый тип служит просто сигналом, что функция будет вызываться как триггер события.

Если для определенного события определено более одного триггера события, они будут срабатывать в алфавитном порядке по имени триггера.

В определении триггера также можно указать условие WHEN, чтобы, например, триггер ddl_command_start мог срабатывать только для определенных команд, которые пользователь желает перехватить. Обычно такие триггеры используются для ограничения диапазона операций DDL, которые могут осуществлять пользователи.

Матрица срабатывания триггеров событий

В Таблице 1 перечислены все команды, для которых поддерживаются триггеры событий.

Таблица 1. Поддержка триггеров событий по тегам команд

Командный тег | ddl_command_start | ddl_command_end | sql_drop | table_rewrite | Примечания
ALTER AGGREGATE | X | X | - | - |
ALTER COLLATION | X | X | - | - |
ALTER CONVERSION | X | X | - | - |
ALTER DOMAIN | X | X | - | - |
ALTER DEFAULT PRIVILEGES | X | X | - | - |
ALTER EXTENSION | X | X | - | - |
ALTER FOREIGN DATA WRAPPER | X | X | - | - |
ALTER FOREIGN TABLE | X | X | X | - |
ALTER FUNCTION | X | X | - | - |
ALTER LANGUAGE | X | X | - | - |
ALTER LARGE OBJECT | X | X | - | - |
ALTER MATERIALIZED VIEW | X | X | - | - |
ALTER OPERATOR | X | X | - | - |
ALTER OPERATOR CLASS | X | X | - | - |
ALTER OPERATOR FAMILY | X | X | - | - |
ALTER POLICY | X | X | - | - |
ALTER PROCEDURE | X | X | - | - |
ALTER PUBLICATION | X | X | - | - |
ALTER ROUTINE | X | X | - | - |
ALTER SCHEMA | X | X | - | - |
ALTER SEQUENCE | X | X | - | - |
ALTER SERVER | X | X | - | - |
ALTER STATISTICS | X | X | - | - |
ALTER SUBSCRIPTION | X | X | - | - |
ALTER TABLE | X | X | X | X |
ALTER TEXT SEARCH CONFIGURATION | X | X | - | - |
ALTER TEXT SEARCH DICTIONARY | X | X | - | - |
ALTER TEXT SEARCH PARSER | X | X | - | - |
ALTER TEXT SEARCH TEMPLATE | X | X | - | - |
ALTER TRIGGER | X | X | - | - |
ALTER TYPE | X | X | - | X |
ALTER USER MAPPING | X | X | - | - |
ALTER VIEW | X | X | - | - |
COMMENT | X | X | - | - | Только для локальных объектов
CREATE ACCESS METHOD | X | X | - | - |
CREATE AGGREGATE | X | X | - | - |
CREATE CAST | X | X | - | - |
CREATE COLLATION | X | X | - | - |
CREATE CONVERSION | X | X | - | - |
CREATE DOMAIN | X | X | - | - |
CREATE EXTENSION | X | X | - | - |
CREATE FOREIGN DATA WRAPPER | X | X | - | - |
CREATE FOREIGN TABLE | X | X | - | - |
CREATE FUNCTION | X | X | - | - |
CREATE INDEX | X | X | - | - |
CREATE LANGUAGE | X | X | - | - |
CREATE MATERIALIZED VIEW | X | X | - | - |
CREATE OPERATOR | X | X | - | - |
CREATE OPERATOR CLASS | X | X | - | - |
CREATE OPERATOR FAMILY | X | X | - | - |
CREATE POLICY | X | X | - | - |
CREATE PROCEDURE | X | X | - | - |
CREATE PUBLICATION | X | X | - | - |
CREATE RULE | X | X | - | - |
CREATE SCHEMA | X | X | - | - |
CREATE SEQUENCE | X | X | - | - |
CREATE SERVER | X | X | - | - |
CREATE STATISTICS | X | X | - | - |
CREATE SUBSCRIPTION | X | X | - | - |
CREATE TABLE | X | X | - | - |
CREATE TABLE AS | X | X | - | - |
CREATE TEXT SEARCH CONFIGURATION | X | X | - | - |
CREATE TEXT SEARCH DICTIONARY | X | X | - | - |
CREATE TEXT SEARCH PARSER | X | X | - | - |
CREATE TEXT SEARCH TEMPLATE | X | X | - | - |
CREATE TRIGGER | X | X | - | - |
CREATE TYPE | X | X | - | - |
CREATE USER MAPPING | X | X | - | - |
CREATE VIEW | X | X | - | - |
DROP ACCESS METHOD | X | X | X | - |
DROP AGGREGATE | X | X | X | - |
DROP CAST | X | X | X | - |
DROP COLLATION | X | X | X | - |
DROP CONVERSION | X | X | X | - |
DROP DOMAIN | X | X | X | - |
DROP EXTENSION | X | X | X | - |
DROP FOREIGN DATA WRAPPER | X | X | X | - |
DROP FOREIGN TABLE | X | X | X | - |
DROP FUNCTION | X | X | X | - |
DROP INDEX | X | X | X | - |
DROP LANGUAGE | X | X | X | - |
DROP MATERIALIZED VIEW | X | X | X | - |
DROP OPERATOR | X | X | X | - |
DROP OPERATOR CLASS | X | X | X | - |
DROP OPERATOR FAMILY | X | X | X | - |
DROP OWNED | X | X | X | - |
DROP POLICY | X | X | X | - |
DROP PROCEDURE | X | X | X | - |
DROP PUBLICATION | X | X | X | - |
DROP ROUTINE | X | X | X | - |
DROP RULE | X | X | X | - |
DROP SCHEMA | X | X | X | - |
DROP SEQUENCE | X | X | X | - |
DROP SERVER | X | X | X | - |
DROP STATISTICS | X | X | X | - |
DROP SUBSCRIPTION | X | X | X | - |
DROP TABLE | X | X | X | - |
DROP TEXT SEARCH CONFIGURATION | X | X | X | - |
DROP TEXT SEARCH DICTIONARY | X | X | X | - |
DROP TEXT SEARCH PARSER | X | X | X | - |
DROP TEXT SEARCH TEMPLATE | X | X | X | - |
DROP TRIGGER | X | X | X | - |
DROP TYPE | X | X | X | - |
DROP USER MAPPING | X | X | X | - |
DROP VIEW | X | X | X | - |
GRANT | X | X | - | - | Только для локальных объектов
IMPORT FOREIGN SCHEMA | X | X | - | - |
REFRESH MATERIALIZED VIEW | X | X | - | - |
REVOKE | X | X | - | - | Только для локальных объектов
SECURITY LABEL | X | X | - | - | Только для локальных объектов
SELECT INTO | X | X | - | - |

Написание триггерных функций событий на C/RUST

В этом разделе описываются низкоуровневые детали интерфейса для триггерной функции события. Эта информация необходима только при написании функций триггера событий на C/RUST. Если вы используете язык более высокого уровня, эти детали обрабатываются незаметно для вас. В большинстве случаев вам следует рассмотреть возможность использовании процедурного языка, прежде чем писать свои триггеры событий на C/RUST. В документации каждого процедурного языка объясняется, как написать на нем триггер события.

Триггерные функции событий должны использовать интерфейс менеджера функций «version 1».

Когда функция вызывается менеджером триггеров событий, ей не передаются никакие обычные аргументы, но передается указатель «context», указывающий на структуру EventTriggerData. Функции на C/RUST могут проверить, были ли они вызваны из менеджера триггеров событий или нет, выполнив следующий макрос:

CALLED_AS_EVENT_TRIGGER(fcinfo)

который разворачивается в:

((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))

Если эта запись возвращает true, то можно безопасно привести fcinfo->context к типу EventTriggerData * и использовать структуру EventTriggerData. Функция не должна изменять структуру EventTriggerData или любые данные, на которые она указывает.

struct EventTriggerData определена в commands/event_trigger.h:

typedef struct EventTriggerData
{
    NodeTag     type;
    const char *event;      /* имя события */
    Node       *parsetree;  /* дерево синтаксического анализа */
    const char *tag;        /* тег команды */
} EventTriggerData;

где члены определены следующим образом:

type
Всегда T_EventTriggerData.

event
Описывает событие, для которого вызывается функция: "ddl_command_start", "ddl_command_end", "sql_drop" или "table_rewrite" (значения этих событий см. в разделе Обзор поведения триггеров событий).

parsetree
Указатель на дерево синтаксического анализа команды. Детали можно уточнить, просмотрев исходный код QHB. Структура дерева анализа может быть изменена без предварительного уведомления.

tag
Тег команды, связанный с событием, для которого запускается триггер события, например "CREATE FUNCTION".

Триггерная функция события должна возвращать указатель NULL (но не SQL-значение NULL, т. е. не устанавливать isNull равным true).

Полный пример триггера события

Вот очень простой пример триггерной функции события, написанной на C/RUST. (Примеры триггеров, написанных на процедурных языках, можно найти в документации по процедурным языкам.)

Функция noddl выдает ошибку при каждом вызове. Определение триггера события связало эту функцию с событием ddl_command_start. В результате все команды DDL (за исключением упомянутых в разделе Обзор поведения триггеров событий) невозможно запустить.

Это исходный код триггерной функции:

#include "qhb.h"
#include "commands/event_trigger.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(noddl);

Datum
noddl(PG_FUNCTION_ARGS)
{
    EventTriggerData *trigdata;

    if (!CALLED_AS_EVENT_TRIGGER(fcinfo))  /* внутренняя ошибка */
        elog(ERROR, "not fired by event trigger manager");
        /* ОШИБКА, "не запускается менеджером триггеров событий" */

    trigdata = (EventTriggerData *) fcinfo->context;

    ereport(ERROR,
        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 errmsg("command \"%s\" denied", trigdata->tag)));

    PG_RETURN_NULL();
}

После компиляции исходного кода (см. подраздел Компиляция и связывание динамически загружаемых функций), объявляем функцию и триггеры:

CREATE FUNCTION noddl() RETURNS event_trigger
    AS 'noddl' LANGUAGE C;

CREATE EVENT TRIGGER noddl ON ddl_command_start
    EXECUTE FUNCTION noddl();

Теперь можно проверить работу триггера:

=# \dy
                     List of event triggers
 Name  |       Event       | Owner | Enabled | Function | Tags
-------+-------------------+-------+---------+----------+------
 noddl | ddl_command_start | dim   | enabled | noddl    |
(1 row)

=# CREATE TABLE foo(id serial);
ERROR:  command "CREATE TABLE" denied
/* ОШИБКА: команда "CREATE TABLE" отклонена */

В этой ситуации, чтобы иметь возможность при необходимости запускать некоторые команды DDL, следует либо удалить триггер события, либо выключить его. Также может быть удобно выключить триггер только на время транзакции:

BEGIN;
    ALTER EVENT TRIGGER noddl DISABLE;
    CREATE TABLE foo (id serial);
    ALTER EVENT TRIGGER noddl ENABLE;
COMMIT;

(Напомним, что триггеры событий не действуют на команды DDL самих триггеров событий.)

Пример триггера события перезаписи таблицы

Благодаря событию table_rewrite можно реализовать политику перезаписи таблицы, разрешив перезапись только в определенные моменты обслуживания.

Вот пример реализации такой политики.

CREATE OR REPLACE FUNCTION no_rewrite()
 RETURNS event_trigger
 LANGUAGE plpgsql AS
$$
---
--- Реализация локальной политики перезаписи таблицы:
---   перезапись public.foo не допускается ни при каких обстоятельствах
---   другие таблицы могут перезаписываться только между 1 часом ночи и 6 часами утра,
---   если только их размер не превышает 100 блоков
---
DECLARE
  table_oid oid := pg_event_trigger_table_rewrite_oid();
  current_hour integer := extract('hour' from current_time);
  pages integer;
  max_pages integer := 100;
BEGIN
  IF pg_event_trigger_table_rewrite_oid() = 'public.foo'::regclass
  THEN
        RAISE EXCEPTION 'you''re not allowed to rewrite the table %',
                        table_oid::regclass;
  END IF;

  SELECT INTO pages relpages FROM pg_class WHERE oid = table_oid;
  IF pages > max_pages
  THEN
        RAISE EXCEPTION 'rewrites only allowed for table with less than % pages',
                        max_pages;
  END IF;

  IF current_hour NOT BETWEEN 1 AND 6
  THEN
        RAISE EXCEPTION 'rewrites only allowed between 1am and 6am';
  END IF;
END;
$$;

CREATE EVENT TRIGGER no_rewrite_allowed
                  ON table_rewrite
   EXECUTE FUNCTION no_rewrite();

Интерфейс программирования сервера

Интерфейс программирования сервера (Server Programming Interface, SPI) дает разработчикам пользовательских функций на C/RUST возможность запускать команды SQL внутри своих функций или процедур. SPI — это набор функций интерфейса, упрощающих доступ к синтаксическому анализатору, планировщику и исполнителю. Также SPI в некоторой степени управляет памятью.

Примечание
Доступные процедурные языки предоставляют различные средства для выполнения команд SQL из функций. Большинство этих средств основаны на SPI, поэтому эта документация может быть полезна и для использующих эти языки.

Обратите внимание, что если команда, вызванная через SPI, завершается с ошибкой, управление не вернется в функцию на C/RUST. Скорее всего, транзакция или субтранзакция, в которой выполняется эта функция на C/RUST, откатится. (Это может показаться удивительным, учитывая, что у большинства функций SPI имеются документированные соглашения о возврате ошибок. Однако эти соглашения применяются только к ошибкам, обнаруженным в самих функциях SPI). Управление после ошибки можно восстановить, создав собственную субтранзакцию, окружающую вызовы SPI, в которых может возникнуть ошибка.

В случае успеха функции SPI возвращают неотрицательный результат (либо через возвращаемое целочисленное значение, либо через глобальную переменную SPI_result, как описано ниже). В случае ошибки будет возвращен отрицательный результат или NULL.

Файлы исходного кода, использующие SPI, должны включать заголовочный файл executor/spi.h.

Функции интерфейса

SPI_connect — подключить функцию на C/RUST к менеджеру SPI
SPI_finish — отключить функцию на C/RUST от менеджера SPI
SPI_execute — выполнить команду
SPI_exec — выполнить команду чтения/записи
SPI_execute_extended — выполнить команду с внешними параметрами
SPI_execute_with_args — выполнить команду с внешними параметрами
SPI_prepare — подготовить оператор, но пока не выполнять его
SPI_prepare_cursor — подготовить оператор, но пока не выполнять его
SPI_prepare_extended — подготовить оператор, но пока не выполнять его
SPI_prepare_params — подготовить оператор, но пока не выполнять его
SPI_getargcount — вернуть количество аргументов, необходимых для оператора, подготовленного функцией SPI_prepare
SPI_getargtypeid — вернуть OID типа данных для аргумента оператора, подготовленного функцией SPI_prepare
SPI_is_cursor_plan — вернуть true, если оператор, подготовленный функцией SPI_prepare, можно использовать с функцией SPI_cursor_open
SPI_execute_plan — выполнить оператор, подготовленный функцией SPI_prepare
SPI_execute_plan_extended — выполнить оператор, подготовленный функцией SPI_prepare
SPI_execute_plan_with_paramlist — выполнить оператор, подготовленный функцией SPI_prepare
SPI_execp — выполнить оператор в режиме чтения/записи
SPI_cursor_open — установить курсор, используя оператор, созданный с помощью функции SPI_prepare
SPI_cursor_open_with_args — установить курсор, используя запрос и параметры
SPI_cursor_open_with_paramlist — установить курсор, используя параметры
SPI_cursor_parse_open — установить курсор, используя строку запроса и параметры
SPI_cursor_find — найти существующий курсор по имени
SPI_cursor_fetch — извлечь несколько строк через курсор
SPI_cursor_move — переместить курсор
SPI_scroll_cursor_fetch — извлечь несколько строк через курсор
SPI_scroll_cursor_move — переместить курсор
SPI_cursor_close — закрыть курсор
SPI_keepplan — сохранить подготовленный оператор
SPI_saveplan — сохранить подготовленный оператор
SPI_register_relation — сделать эфемерное именованное отношение доступным по имени в запросах SPI
SPI_unregister_relation — удалить эфемерное именованное отношение из реестра
SPI_register_trigger_data — сделать эфемерные данные триггера доступными в запросах SPI

SPI_connect

SPI_connect, SPI_connect_ext — подключить функцию на C/RUST к менеджеру SPI

Синтаксис

int SPI_connect(void)

int SPI_connect_ext(int options)

Описание

Функция SPI_connect устанавливает подключение вызова функции на C/RUST к менеджеру SPI. Эту функцию следует вызывать, если вы хотите выполнять команды через SPI. Некоторые служебные SPI-функции можно вызывать из неподключенных функций на C/RUST.

Функция SPI_connect_ext делает то же самое, но принимает аргумент, позволяющий передавать дополнительные флаги. В настоящее время доступны следующие дополнительные значения:

SPI_OPT_NONATOMIC
Устанавливает подключение SPI в неатомарном режиме, то есть разрешает использовать вызовы функций для управления транзакциями (SPI_commit, SPI_rollback). В противном случае вызов данных функций приведет к немедленной ошибке.

Функция SPI_connect() равнозначна функции SPI_connect_ext(0).

Возвращаемое значение

SPI_OK_CONNECT — при успешном выполнении

SPI_ERROR_CONNECT — при ошибке

SPI_finish

SPI_finish — отключить функцию на C/RUST от менеджера SPI

Синтаксис

int SPI_finish (void)

Описание

Функция SPI_finish закрывает существующее соединение с менеджером SPI. Эту функцию следует вызвать после завершения всех операций SPI, необходимых во время текущего вызова функции на C/RUST. Однако при прерывании транзакции с помощью elog(ERROR) о закрытии соединения можно не беспокоиться. В этом случае SPI очистится автоматически.

Возвращаемое значение

SPI_OK_FINISH — при корректном отключении

SPI_ERROR_UNCONNECTED — при вызове из неподключенной функции на C/RUST

SPI_execute

SPI_execute — выполнить команду

Синтаксис

int SPI_execute(const char * command, bool read_only, long count)

Описание

Функция SPI_execute выполняет указанную команду SQL для count строк. Если параметр read_only имеет значение true, команда должна только читать данные, тем самым несколько снижая издержки на ее выполнение.

Эту функцию можно вызвать только из подключенной функции на C/RUST.

Если count равен нулю, то команда выполняется для всех строк, удовлетворяющих условиями запроса. Если count больше нуля, то будет получено не больше count строк; выполнение остановится при достижении этого предела, почти так же, как при добавлении к запросу предложения LIMIT. Например,

SPI_execute("SELECT * FROM foo", true, 5);

получит из таблицы не более 5 строк. Обратите внимание, что такой предел действует, только когда команда действительно возвращает строки. Например,

SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5);

добавляет все строки из bar, игнорируя параметр count. Однако запрос

SPI_execute("INSERT INTO foo SELECT * FROM bar RETURNING *", false, 5);

добавит не более 5 строк, поскольку выполнение будет остановлено после получения пятой строки результата RETURNING.

Возможна передача несколько команд в одной строке; SPI_execute возвращает результат для команды, выполненной последней. Ограничение count применяется к каждой команде по отдельности (даже если на самом деле будет возвращен только последний результат). Этот предел не применяется к скрытым командам, сгенерированным правилами.

Когда параметр read_only имеет значение false, SPI_execute увеличивает счетчик команд и вычисляет новый снимок перед выполнением каждой команды в строке. Этот снимок фактически не изменяется, если текущий уровень изоляции транзакции SERIALIZABLE или REPEATABLE READ, но в режиме READ COMMITTED обновление снимка позволяет каждой команде видеть результаты только что зафиксированных транзакций из других сеансов. Это важно для согласованного поведения, когда команды изменяют базу данных.

Когда параметр read_only имеет значение true, SPI_execute не обновляет ни снимок, ни счетчик команд, и допускает в командной строке только простые команды SELECT. Эти команды выполняются с использованием снимка, предварительно сформированного для окружающего запроса. Этот режим выполнения несколько быстрее, чем режим чтения/записи, вследствие устранения издержек на каждую команду. Он также позволяет создавать действительно стабильные функции: поскольку все последующие выполнения будут использовать один и тот же снимок, в результатах не будет никаких изменений.

Обычно неразумно смешивать в одной функции с использованием SPI команды только для чтения и чтения/записи; это может привести к очень запутанному поведению, поскольку запросы только на чтение не будут видеть результаты каких-либо изменений базы данных, выполненных запросами на чтение/запись.

Фактическое количество строк, для которых была выполнена (последняя) команда, возвращается в глобальной переменной SPI_processed. Если эта функция возвращает значение SPI_OK_SELECT, SPI_OK_INSERT_RETURNING, SPI_OK_DELETE_RETURNING или SPI_OK_UPDATE_RETURNING, то для доступа к строкам результата можно воспользоваться глобальным указателем SPITupleTable *SPI_tuptable. Некоторые служебные команды (например, EXPLAIN) также возвращают наборы строк, и SPI_tuptable будет содержать их результат и в этих случаях. Некоторые служебные команды (COPY, CREATE TABLE AS) не возвращают набор строк, поэтому SPI_tuptable равен NULL, но они по-прежнему возвращают количество обработанных строк в SPI_processed.

Структура SPITupleTable определяется следующим образом:

typedef struct SPITupleTable
{
    /* Открытые члены */
    TupleDesc   tupdesc;        /* дескриптор кортежа */
    HeapTuple  *vals;           /* массив кортежей */
    uint64      numvals;        /* количество допустимых кортежей */

    /* Закрытые члены, не предназначенные для внешних вызывающиъ функций */
    uint64      alloced;        /* зарезервированная в памяти длина массива vals */
    MemoryContext tuptabcxt;    /* контекст таблицы результатов в памяти */
    slist_node  next;           /* ссылка для внутреннего учета системных ресурсов */
    SubTransactionId subid;     /* субтранзакция, в которой была создана структура tuptable */
} SPITupleTable;

Поля tupdesc, vals и numvals могут использоваться кодом, вызывающим SPI; остальные поля являются внутренними. vals представляет собой массив указателей на строки. Количество строк задается numvals (по некоторым историческим причинам это число тоже возвращается в SPI_processed). tupdesc является дескриптором строки, который можно передать SPI-функциям, работающим со строками.

SPI_finish освобождает все структуры SPITupleTable, выделенные для текущей функции на C/RUST. Вызвав SPI_freetuptable, вы можете досрочно освободить конкретную результирующую таблицу, если закончили с ней работать.

Аргументы

const char * command
строка, содержащая команду для выполнения

bool read_only
true для выполнения запросов в режиме «только чтение»

long count
максимальное количество строк, которые нужно вернуть, или 0, если ограничений нет

Возвращаемое значение

Если выполнение команды было успешным, то будет возвращено одно из следующих (неотрицательных) значений:

SPI_OK_SELECT — если была выполнена команда SELECT (но не SELECT INTO)

SPI_OK_SELINTO — если была выполнена команда SELECT INTO

SPI_OK_INSERT — если была выполнена команда INSERT

SPI_OK_DELETE — если была выполнена команда DELETE

SPI_OK_UPDATE — если была выполнена команда UPDATE

SPI_OK_MERGE — если была выполнена команда MERGE

SPI_OK_INSERT_RETURNING — если была выполнена команда INSERT RETURNING

SPI_OK_DELETE_RETURNING — если была выполнена команда DELETE RETURNING

SPI_OK_UPDATE_RETURNING — если была выполнена команда UPDATE RETURNING

SPI_OK_UTILITY — если была выполнена служебная команда (например CREATE TABLE)

SPI_OK_REWRITTEN — если команда была преобразована правилом в команду другого вида (например UPDATE стала INSERT).

При ошибке возвращается одно из следующих отрицательных значений:

SPI_ERROR_ARGUMENT — если параметр command равен NULL или параметр count меньше 0

SPI_ERROR_COPY — при попытке выполнить COPY TO stdout или COPY FROM stdin

SPI_ERROR_TRANSACTION — при попытке выполнить команду управления транзакцией (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, PREPARE TRANSACTION, COMMIT PREPARED, ROLLBACK PREPARED или любой их вариант)

SPI_ERROR_OPUNKNOWN — если тип команды неизвестен (такого происходить не должно)

SPI_ERROR_UNCONNECTED — при вызове из неподключенной функции на C/RUST

Примечания

Все функции SPI, выполняющие запросы, устанавливают значения как SPI_processed, так и SPI_tuptable (только указатель, а не содержимое структуры). Сохраните значения этих двух глобальных переменных в локальные переменные функции на C/RUST, если при последующих вызовах вам нужно получить доступ к таблице результатов выполнения SPI_execute или другой функции, выполняющей запросы.

SPI_exec

SPI_exec — выполнить команду чтения/записи

Синтаксис

int SPI_exec(const char * command, long count)

Описание

SPI_exec работает так же, как SPI_execute, но параметр read_only всегда воспринимается как имеющий значение false.

Аргументы

const char * command
строка, содержащая запрос для выполнения

long count
максимальное количество возвращаемых строк или 0, если ограничения нет

Возвращаемое значение

См. описание функции SPI_execute.

SPI_execute_extended

SPI_execute_extended — выполнить команду с внешними параметрами

Синтаксис

int SPI_execute_extended(const char *command,
                         const SPIExecuteOptions * options)

Описание

Функция SPI_execute_extended выполняет команду, которая может включать ссылки на параметры, передаваемые извне. В тексте команды параметры обозначаются символами $n, а объект options->params (если передается) предоставляет информацию о значениях и типе для каждого такого символа. Также в структуре options можно задавать различные параметры выполнения.

Как правило, в объекте options->params все параметры должны помечаться флагом PARAM_FLAG_CONST, поскольку для запроса всегда используется одноразовый план.

Если options->dest отличен от NULL, то результирующие кортежи не накапливаются в SPI_tuptable, а передаются в этот объект по мере их генерирования исполнителем. Переданный вызывающей функцией объект DestReceiver бывает особенно полезно использовать для запросов, которые могут сгенерировать много кортежей, поскольку данные можно обрабатывать на ходу, а не накапливать в памяти.

Аргументы

const char * command
командная строка

const SPIExecuteOptions * options
структура, содержащая необязательные аргументы

Вызывающая функция всегда должна сначала полностью заполнять структуру options нулями, а потом уже заполнять поля, которые хочет установить. Это гарантирует восходящую совместимость кода, так как если все поля, добавляемые в эту структуру в будущем, содержат ноль, они будут определены как обратно совместимые. В настоящее время в options имеются следующие поля:

ParamListInfo params
структура данных, содержащая типы и значения параметров запроса; NULL, если таковых нет

bool read_only
true для выполнения запросов в режиме «только чтение»

bool allow_nonatomic
true разрешает неатомарное выполнение операторов CALL и DO

bool must_return_tuples
если равно true, то вызывает ошибку для типов запросов, не возвращающих кортежи (не реагирует на случаи, когда возвращаются нулевые кортежи)

uint64 tcount
максимальное количество возвращаемых строк или 0, если ограничения нет

DestReceiver * dest
Объект DestReceiver, который будет получать все кортежи, выданные запросом; если равен NULL, то результирующие кортежи накапливаются в структуре SPI_tuptable, как в SPI_execute

ResourceOwner owner
Это поле присутствует для согласованности с SPI_execute_plan_extended, но игнорируется, так как используемый SPI_execute_extended план никогда не сохраняется.

Возвращаемое значение

Возвращаемое значение то же, что и для функции SPI_execute.

Когда options->dest равен NULL, SPI_processed и SPI_tuptable устанавливаются как в SPI_execute. Когда options->dest отличен от NULL, SPI_processed устанавливается в ноль, а SPI_tuptable — в NULL. Если требуется число кортежей, его должен вычислять объект DestReceiver вызывающей функции.

SPI_execute_with_args

SPI_execute_with_args — выполнить команду с внешними параметрами

Синтаксис

int SPI_execute_with_args(const char *command,
                          int nargs, Oid *argtypes,
                          Datum *values, const char *nulls,
                          bool read_only, long count)

Описание

Функция SPI_execute_with_args выполняет команду, которая может включать ссылки на параметры, передаваемые извне. В тексте команды параметры обозначаются символами $n, а в вызове указываются типы данных и значения для каждого такого символа. Параметры read_only и count интерпретируются так же, как и в SPI_execute.

Основное преимущество этой функции по сравнению с SPI_execute состоит в том, что значения данных можно вставить в команду без громоздких кавычек/экранирования и тем самым значительно снизить риск атак с SQL-инъекцией.

Аналогичных результатов можно достичь, вызвав SPI_prepare, а затем SPI_execute_plan; однако при использовании этой функции план запроса всегда настраивается на предоставленные конкретные значения параметров. Для одноразового выполнения запроса предпочтительней применять SPI_execute_with_args. Если одну и ту же команду нужно выполнять со множеством различных параметров, любой из этих методов может оказаться быстрее, в зависимости от того, что перевесит: стоимость повторного планирования или преимущества специализированных планов.

Аргументы

const char * command
командная строка

int nargs
количество входных параметров ($1, $2 и т. д.)

Oid * argtypes
массив длиной nargs, содержащий OID типов данных этих параметров

Datum * values
массив длиной nargs, содержащий фактические значения этих параметров

const char * nulls
массив длиной nargs, описывающий, какие параметры равны NULL
Если параметр nulls равен NULL, то SPI_execute_with_args предполагает, что ни один из параметров не содержит NULL. В противном случае каждая запись массива nulls должна содержать ' ', если значение соответствующего параметра не равно NULL, или 'n', если равно. (В последнем случае фактическое значение соответствующей записи в параметре values неважно.) Обратите внимание, что nulls — это не текстовая строка, а просто массив; завершающий символ '\0' не нужен.

bool read_only
true для выполнения запросов в режиме «только чтение»

long count
максимальное количество возвращаемых строк или 0, если ограничения нет

Возвращаемое значение

Возвращаемое значение такое же, как и для SPI_execute.

При успешном вызове переменные SPI_processed и SPI_tuptable устанавливаются так же, как в SPI_execute.

SPI_prepare

SPI_prepare — подготовить оператор, пока не выполняя его

Синтаксис

SPIPlanPtr SPI_prepare(const char * command, int nargs, Oid * argtypes)

Описание

Функция SPI_prepare создает и возвращает подготовленный оператор для указанной команды, но не выполняет ее. Впоследствии подготовленный оператор может быть многократно выполнен с помощью SPI_execute_plan.

Когда одна и та же или схожая команда должна выполняться многократно, обычно выгоднее провести синтаксический анализ только один раз и еще более выгодным может быть повторное использование плана выполнения этой команды. SPI_prepare преобразует командную строку в подготовленный оператор, инкапсулирующий результаты синтаксического анализа. Кроме того, подготовленный оператор предоставляет место для кэширования плана выполнения, если выясняется, что создание специализированного плана для каждого выполнения не приносит пользы.

Подготовленную команду можно обобщить, записав параметры ($1, $2 и т. д.) вместо значений, задаваемых константами в обычной команде. Тогда фактические значения параметров будут задаваться при вызове SPI_execute_plan. Это позволяет использовать подготовленную команду в более широком диапазоне ситуаций, чем это было бы возможно без параметров.

Оператор, возвращаемый SPI_prepare, можно использовать только в текущем вызове функции на C/RUST, поскольку SPI_finish освобождает память, выделенную для такого оператора. Но этот оператор можно сохранить на потом с помощью функций SPI_keepplan или SPI_saveplan.

Аргументы

const char * command
командная строка

int nargs
количество входных параметров ($1, $2 и т. д.)

Oid * argtypes
указатель на массив, содержащий OID типов данных этих параметров

Возвращаемое значение

SPI_prepare возвращает отличный от NULL указатель на SPIPlan, которая является скрытой структурой, представляющей подготовленный оператор. В случае ошибки будет возвращен NULL, а переменная SPI_result будет содержать один из тех кодов ошибок, какие используются и в SPI_execute, за исключением того, что значение SPI_ERROR_ARGUMENT устанавливается, если параметр command равен NULL, или параметр nargs меньше 0, или параметр nargs больше 0, а параметр argtypes равен NULL.

Примечания

Если параметры не определены, общий план будет создан при первом применении SPI_execute_plan, и он же будет использоваться для всех последующих выполнений. С параметрами первые несколько вызовов SPI_execute_plan создадут специализированные планы, специфические для переданных значений параметров. После достаточного количества использований одного и того же подготовленного оператора SPI_execute_plan построит общий план, и, если тот ненамного дороже специализированных планов, начнет использовать его, вместо того чтобы каждый раз создавать новый. Если это поведение по умолчанию не подходит, его можно изменить, передав в SPI_prepare_cursor флаг CURSOR_OPT_GENERIC_PLAN или CURSOR_OPT_CUSTOM_PLAN, чтобы принудительно использовать общие или специализированные планы соответственно.

Хотя основной смысл подготовленного оператора заключается в том, чтобы избежать повторного анализа и планирования оператора, QHB будет принудительно повторять анализ и планирование оператора перед его использованием в случаях, если с момента предыдущего использования подготовленного оператора изменяются определения (DDL) объектов базы данных, используемых в этом операторе. Кроме того, если между использованиями изменяется значение параметра search_path, оператор будет повторно проанализирован с новым значением search_path. Дополнительную информацию о поведении подготовленных операторов см. на справочной странице команды PREPARE.

Эту функции следует вызывать только из подключенной функции на C/RUST.

SPIPlanPtr объявляется как указатель на скрытый тип структуры в spi.h. Пытаться получить доступ к его содержимому напрямую неразумно, так как это повышает вероятность того, что ваш код сломается в будущих версиях QHB.

Имя SPIPlanPtr в некотором смысле сохранено по историческим причинам, поскольку теперь этой структуре данных необязательно содержать план выполнения.

SPI_prepare_cursor

SPI_prepare_cursor — подготовить оператор, пока не выполняя его

Синтаксис

SPIPlanPtr SPI_prepare_cursor(const char * command, int nargs,
                              Oid * argtypes, int cursorOptions)

Описание

Функция SPI_prepare_cursor идентична функции SPI_prepare, но позволяет дополнительно задавать «параметры курсора» планировщика. Это битовая маска, имеющая значения, перечисленные в nodes/parsenodes.h для поля options структуры DeclareCursorStmt. SPI_prepare всегда принимает параметры курсора равными нулю.

Сейчас эта функция устарела и вместо нее рекомендуется использовать функцию SPI_prepare_extended.

Аргументы

const char * command
командная строка

int nargs
количество входных параметров ($1, $2 и т. д.)

Oid * argtypes
указатель на массив, содержащий OID типов данных этих параметров

int cursorOptions
целочисленная битовая маска параметров курсора; ноль выбирает поведение по умолчанию

Возвращаемое значение

SPI_prepare_cursor имеет те же соглашения о возвращаемом результате, что и SPI_prepare.

Примечания

Полезные биты для установки в cursorOptions включают: CURSOR_OPT_SCROLL, CURSOR_OPT_NO_SCROLL, CURSOR_OPT_FAST_PLAN, CURSOR_OPT_GENERIC_PLAN и CURSOR_OPT_CUSTOM_PLAN. В частности, следует отметить, что CURSOR_OPT_HOLD игнорируется.

SPI_prepare_extended

SPI_prepare_extended — подготовить оператор, пока не выполняя его

Синтаксис

SPIPlanPtr SPI_prepare_extended(const char * command,
                                const SPIPrepareOptions * options)

Описание

Функция SPI_prepare_extended создает и возвращает подготовленный оператор для указанной команды, но не выполняет ее. Эта функция равнозначна SPI_prepare, но дополнительно позволяет вызывающей функции задавать параметры для управления синтаксическим анализом ссылок на внешние параметры, а также другими аспектами анализа и планирования запросов.

Аргументы

const char * command
командная строка

const SPIPrepareOptions * options
структура, содержащая необязательные аргументы

Вызывающая функция всегда должна сначала полностью заполнять структуру options нулями, а потом уже заполнять поля, которые хочет установить. Это гарантирует восходящую совместимость кода, так как если все поля, добавляемые в эту структуру в будущем, содержат ноль, они будут определены как обратно совместимые. В настоящее время в options имеются следующие поля:

ParserSetupHook parserSetup
Функция настройки обработчиков синтаксического анализа

void * parserSetupArg
аргумент для сквозной передачи в parserSetup

RawParseMode parseMode
режим для простого синтаксического анализа; RAW_PARSE_DEFAULT (ноль) выбирает поведение по умолчанию

int cursorOptions
целочисленная битовая маска для параметров курсора; ноль выбирает поведение по умолчанию

Возвращаемое значение

SPI_prepare_extended имеет те же соглашения о возвращаемом результате, что и SPI_prepare.

SPI_prepare_params

SPI_prepare_params — подготовить оператор, пока не выполняя его

Синтаксис

SPIPlanPtr SPI_prepare_params(const char * command,
                              ParserSetupHook parserSetup,
                              void * parserSetupArg,
                              int cursorOptions)

Описание

Функция SPI_prepare_params создает и возвращает подготовленный оператор для указанной команды, но не выполняет ее. Эта функция равнозначна SPI_prepare_cursor, но дополнительно позволяет вызывающей функции задавать функции обработки синтаксического анализа для управления анализом ссылок на внешние параметры.

Сейчас эта функция устарела и вместо нее рекомендуется использовать функцию SPI_prepare_extended.

Аргументы

const char * command
командная строка

ParserSetupHook parserSetup
функция настройки обработчиков синтаксического анализа

void * parserSetupArg
сквозной аргумент для parserSetup

int cursorOptions
целочисленная битовая маска параметров курсора; ноль выбирает поведение по умолчанию

Возвращаемое значение

SPI_prepare_params имеет те же соглашения о возвращаемом результате, что и SPI_prepare.

SPI_getargcount

SPI_getargcount — вернуть количество аргументов, необходимых оператору, подготовленному функцией SPI_prepare

Синтаксис

int SPI_getargcount(SPIPlanPtr plan)

Описание

Функция SPI_getargcount возвращает количество аргументов, необходимых для выполнения оператора, подготовленного функцией SPI_prepare.

Аргументы

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

Возвращаемое значение

Количество ожидаемых аргументов для параметра plan. Если значение параметра plan равно NULL или неправильное, в SPI_result устанавливается значение SPI_ERROR_ARGUMENT, а функция возвращает -1.

SPI_getargtypeid

SPI_getargtypeid — вернуть OID типа данных для аргумента оператора, подготовленного функцией SPI_prepare

Синтаксис

Oid SPI_getargtypeid(SPIPlanPtr plan, int argIndex)

Описание

Функция SPI_getargtypeid возвращает OID, представляющий типа аргумента с индексом argIndex для оператора, подготовленного функцией SPI_prepare. Нумерация аргументов начинается с нуля.

Аргументы

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

int argIndex
индекс аргумента, начиная с нуля

Возвращаемое значение

OID типа аргумента с заданным индексом. Если значение параметра plan равно NULL или неправильное, либо значение параметра argIndex меньше 0 или больше или равно количеству аргументов, объявленных для параметра plan, в SPI_result устанавливается значение SPI_ERROR_ARGUMENT, а функция возвращает значение InvalidOid.

SPI_is_cursor_plan

SPI_is_cursor_plan — вернуть true, если оператор, подготовленный функцией SPI_prepare, можно использовать с функцией SPI_cursor_open

Синтаксис

bool SPI_is_cursor_plan(SPIPlanPtr plan)

Описание

Функция SPI_is_cursor_plan возвращает true, если оператор, подготовленный функцией SPI_prepare, можно передать в качестве аргумента функции SPI_cursor_open, или false, если это не так. Чтобы выполнялись критерии true, в параметре plan должна быть представлена только одна команда, и эта команда должна возвращать кортежи вызывающей функции. Например, команда SELECT подходит, если не содержит предложение INTO, а команда UPDATE — только если содержит предложение RETURNING.

Аргументы

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

Возвращаемое значение

Значение true или false, показывающее, может ли plan создать курсор, при SPI_result равном нулю. Если определить ответ невозможно (например, если значение параметра plan равно NULL или неправильное, или при вызове без подключения к SPI), тогда в SPI_result устанавливается подходящий код ошибки, а функция возвращает значение false.

SPI_execute_plan

SPI_execute_plan — выполнить оператор, подготовленный функцией SPI_prepare

Синтаксис

int SPI_execute_plan(SPIPlanPtr plan, Datum * values, const char * nulls,
                     bool read_only, long count)

Описание

Функция SPI_execute_plan выполняет оператор, подготовленный функцией SPI_prepare или родственными ей. Параметры read_only и count имеют то же назначение, что и в SPI_execute.

Аргументы

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

Datum * values
Массив фактических значений параметров. Его длина должна быть равна количеству аргументов оператора.

const char * nulls
Массив, описывающий, какие параметры содержат NULL. Его длина должна быть равна количеству аргументов оператора.
Если параметр nulls равен NULL, то SPI_execute_plan предполагает, что ни один из параметров не равен NULL. В противном случае каждая запись массива nulls должна содержать ' ', если значение соответствующего параметра не равно NULL, или 'n', если равно. (В последнем случае фактическое значение в соответствующей записи values неважно.) Обратите внимание, что nulls — это не текстовая строка, а просто массив; завершающий символ '\0' не нужен.

bool read_only
true для выполнения запроса в режиме «только чтение»

long count
максимальное количество возвращаемых строк или 0, если ограничения нет

Возвращаемое значение

Возвращаемое значение такое же, как и для SPI_execute, со следующими дополнительными вариантами ошибочных (отрицательных) результатов:

SPI_ERROR_ARGUMENT — если значение параметра plan равно NULL или неправильное или если значение параметра count меньше 0

SPI_ERROR_PARAM — если значение параметра values равно NULL, и параметр plan был подготовлен с другими параметрами

В случае успешного вызова переменные SPI_processed и SPI_tuptable устанавливаются так же, как в SPI_execute.

SPI_execute_plan_extended

SPI_execute_plan_extended — выполнить оператор, подготовленный функцией SPI_prepare

Синтаксис

int SPI_execute_plan_extended(SPIPlanPtr plan,
                              const SPIExecuteOptions * options)

Описание

Функция SPI_execute_plan_extended выполняет оператор, подготовленный функцией SPI_prepare или родственными ей. Эта функция равнозначна SPI_execute_plan, но информация о значениях параметров, передаваемых в запрос, представляется по-другому, а кроме того ей могут передаваться дополнительные параметры, управляющие выполнением запроса.

Значения параметров запроса представляются через структуру ParamListInfo, что удобно для передачи значений, уже доступных в этом формате. Также посредством функций обработки, заданных в ParamListInfo, можно использовать динамические наборы параметров.

Также вместо того чтобы постоянно накапливать результирующие кортежи в структуре SPI_tuptable, эти кортежи можно передавать в предоставляемый вызывающим кодом объект DestReceiver по мере их генерирования исполнителем. Это особенно полезно для запросов, которые могут генерировать много кортежей, поскольку данные можно обрабатывать на ходу, а не копить в памяти.

Аргументы

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

const SPIExecuteOptions * options
структура, содержащая необязательные аргументы

Вызывающая функция всегда должна сначала полностью заполнять структуру options нулями, а потом уже заполнять поля, которые хочет установить. Это гарантирует восходящую совместимость кода, так как если все поля, добавляемые в эту структуру в будущем, содержат ноль, они будут определены как обратно совместимые. В настоящее время в options имеются следующие поля:

ParamListInfo params
структура данных, содержащая типы и значения параметров запроса; NULL, если таковых нет

bool read_only
true для выполнения запроса в режиме «только чтение»

bool allow_nonatomic
true разрешает неатомарное выполнение операторов CALL и DO

bool must_return_tuples
если равно true, то вызывает ошибку для типов запросов, не возвращающих кортежи (не реагирует на случаи, когда возвращаются нулевые кортежи)

uint64 tcount
максимальное количество возвращаемых строк или 0, если ограничения нет

DestReceiver * dest
Объект DestReceiver, который будет получать все кортежи, выданные запросом; если равен NULL, то результирующие кортежи накапливаются в структуре SPI_tuptable, как в SPI_execute_plan

ResourceOwner owner
Владелец источника, который будет содержать счетчик ссылок на план во время его выполнения. Если равно NULL, используется текущий владелец ресурса (CurrentResourceOwner). Игнорируется для несохраненных планов, поскольку SPI не получает на них счетчики ссылок.

Возвращаемое значение

Возвращаемое значение такое же, как у SPI_execute_plan.

Когда options->dest равен NULL, SPI_processed и SPI_tuptable устанавливаются как в SPI_execute_plan. Когда options->dest отличен от NULL, SPI_processed устанавливается в ноль, а SPI_tuptable — в NULL. Если требуется число кортежей, его должен вычислять объект DestReceiver вызывающей функции.

SPI_execute_plan_with_paramlist

SPI_execute_plan_with_paramlist — выполнить оператор, подготовленный функцией SPI_prepare

Синтаксис

int SPI_execute_plan_with_paramlist(SPIPlanPtr plan,
                                    ParamListInfo params,
                                    bool read_only,
                                    long count)

Описание

Функция SPI_execute_plan_with_paramlist выполняет оператор, подготовленный функцией SPI_prepare. Эта функция равнозначна SPI_execute_plan, но информация о значениях параметров, передаваемых в запрос, представляется по-другому. Представление ParamListInfo может быть удобно для передачи значений, которые уже доступны в этом формате. Также эта функция поддерживает использование динамических наборов параметров посредством функций обработки, заданных в ParamListInfo.

Сейчас эта функция устарела и вместо нее рекомендуется использовать функцию SPI_execute_plan_extended.

Аргументы

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

ParamListInfo params
структура данных, содержащая типы и значения параметров; NULL, если параметров нет

bool read_only
true для выполнения запроса в режиме «только чтение»

long count
максимальное количество возвращаемых строк или 0, если ограничения нет

Возвращаемое значение

Возвращаемое значение такое же, как у функции SPI_execute_plan.

В случае успешного вызова переменные SPI_processed и SPI_tuptable устанавливаются так же, как в SPI_execute_plan.

SPI_execp

SPI_execp — выполнить оператор в режиме чтения/записи

Синтаксис

int SPI_execp(SPIPlanPtr plan, Datum * values, const char * nulls, long count)

Описание

Функция SPI_execp похожа на функцию SPI_execute_plan, но ее параметр read_only всегда имеет значение false.

Аргументы

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

Datum * values
Массив фактических значений параметров. Его длина должна быть равна количеству аргументов оператора.

const char * nulls
Массив, описывающий, какие параметры равны NULL. Его длина должна быть равна количеству аргументов оператора.
Если параметр nulls равен NULL, то SPI_execp предполагает, что ни один из параметров не содержит NULL. В противном случае каждая запись массива nulls должна содержать ' ', если значение соответствующего параметра не равно NULL, или 'n', если равно. (В последнем случае фактическое значение в соответствующем элементе values неважно.) Обратите внимание, что nulls — это не текстовая строка, а просто массив; завершающий символ '\0' не нужен.

long count
максимальное количество возвращаемых строк или 0, если ограничения нет

Возвращаемое значение

См. SPI_execute_plan.

В случае успешного вызова переменные SPI_processed и SPI_tuptable устанавливаются так же, как в SPI_execute.

SPI_cursor_open

SPI_cursor_open — установить курсор для оператора, созданного функцией SPI_prepare

Синтаксис

Portal SPI_cursor_open(const char * name, SPIPlanPtr plan,
                       Datum * values, const char * nulls,
                       bool read_only)

Описание

Функция SPI_cursor_open устанавливает курсор (называемый внутренне порталом), который будет выполнять оператор, подготовленный функцией SPI_prepare. Параметры имеют тот же смысл, что и соответствующие параметры функции SPI_execute_plan.

Использование курсора вместо непосредственного выполнения оператора имеет два преимущества. Во-первых, результирующие строки можно получать небольшими порциями, что позволяет избежать переполнения памяти при выполнении запросов, возвращающих много строк. Во-вторых, портал может существовать дольше текущей функции на C/RUST (фактически он может просуществовать до конца текущей транзакции). Возврат имени портала в код, вызывающий функцию на C/RUST, дает возможность выдавать результат в виде набора строк.

Данные переданных параметров будут скопированы в портал курсора, поэтому их можно освободить даже во время существования курсора.

Аргументы

const char * name
имя для портала или NULL, чтобы система сама выбрала имя

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

Datum * values
Массив фактических значений параметров. Его длина должна быть равна количеству аргументов оператора.

const char * nulls
Массив, описывающий, какие параметры равны NULL. Его длина должна быть равна количеству аргументов оператора.
Если параметр nulls равен NULL, то SPI_cursor_open предполагает, что ни один из параметров не содержит NULL. В противном случае каждый элемент массива nulls должен содержать ' ', если значение соответствующего параметра не равно NULL, или 'n', если равно. (В последнем случае фактическое значение в соответствующей записи values неважно.) Обратите внимание, что nulls — это не текстовая строка, а просто массив; завершающий символ '\0' не нужен.

bool read_only
true для выполнения запроса в режиме «только чтение»

Возвращаемое значение

Указатель на портал, содержащий курсор. Обратите внимание, что нет соглашения о возврате ошибок; все ошибки будут выдаваться через elog.

SPI_cursor_open_with_args

SPI_cursor_open_with_args — установить курсор, используя запрос и параметры

Синтаксис

Portal SPI_cursor_open_with_args(const char *name,
                                 const char *command,
                                 int nargs, Oid *argtypes,
                                 Datum *values, const char *nulls,
                                 bool read_only, int cursorOptions)

Описание

Функция SPI_cursor_open_with_args устанавливает курсор (называемый внутренне порталом), который будет выполнять указанный запрос. Большинство параметров имеют те же значения, что и соответствующие параметры для функций SPI_prepare_cursor и SPI_cursor_open.

Для одноразового выполнения запроса эту функцию следует предпочесть последовательным вызовам SPI_prepare_cursor и SPI_cursor_open. Если одну и ту же команду нужно выполнять со множеством различных параметров, любой из этих методов может оказаться быстрее, в зависимости от того, что перевесит: стоимость повторного планирования или преимущества специализированных планов.

Данные переданных параметров будут скопированы в портал курсора, поэтому их можно освободить даже во время существования курсора.

В настоящее время эта функция устарела и вместо нее рекомендуется использовать функцию SPI_cursor_parse_open, предоставляющую равнозначную функциональность, но применяющую более современный API для обработки параметров запроса.

Аргументы

const char * name
имя для портала или NULL, чтобы система сама выбрала имя

const char * command
командная строка

int nargs
количество входных параметров ($1, $2 и т. д.)

Oid * argtypes
массив длиной nargs, содержащий OID типов данных этих параметров

Datum * values
массив длиной nargs, содержащий фактические значения этих параметров

const char * nulls
массив длиной nargs, описывающий, какие параметры равны NULL
Если параметр nulls равен NULL, то SPI_cursor_open_with_args предполагает, что ни один из параметров не содержит NULL. В противном случае каждый элемент массива nulls должен содержать ' ', если значение соответствующего параметра не равно NULL, или 'n', если равно. (В последнем случае фактическое значение в соответствующей записи values неважно.) Обратите внимание, что nulls — это не текстовая строка, а просто массив; завершающий символ '\0' не нужен.

bool read_only
true для выполнения запроса в режиме «только чтение»

int cursorOptions
целочисленная битовая маска параметров курсора; ноль выбирает поведение по умолчанию

Возвращаемое значение

Указатель на портал, содержащий курсор. Обратите внимание, что нет соглашения о возврате ошибок; все ошибки будут выдаваться через elog.

SPI_cursor_open_with_paramlist

SPI_cursor_open_with_paramlist — установить курсор, используя параметры

Синтаксис

Portal SPI_cursor_open_with_paramlist(const char *name,
                                      SPIPlanPtr plan,
                                      ParamListInfo params,
                                      bool read_only)

Описание

Функция SPI_cursor_open_with_paramlist устанавливает курсор (называемый внутренне порталом), который будет выполнять оператор, подготовленный функцией SPI_prepare. Эта функция равнозначна SPI_cursor_open, но информация о значениях параметров, передаваемых в запрос, представляется по-другому. Представление ParamListInfo может быть удобным для передачи значений, которые уже доступны в этом формате. Также эта функция поддерживает использование динамических наборов параметров посредством функций обработки, заданных в ParamListInfo.

Данные переданных параметров будут скопированы в портал курсора, поэтому их можно освободить даже во время существования курсора.

Аргументы

const char * name
имя для портала или NULL, чтобы система сама выбрала имя

SPIPlanPtr plan
подготовленный оператор (возвращаемый функцией SPI_prepare)

ParamListInfo params
структура данных, содержащая типы и значения параметров; NULL, если таковых нет

bool read_only
true для выполнения запроса в режиме «только чтение»

Возвращаемое значение

Указатель на портал, содержащий курсор. Обратите внимание, что нет соглашения о возврате ошибок; все ошибки будут выдаваться через elog.

SPI_cursor_parse_open

SPI_cursor_parse_open — установить курсор, используя строку запроса и параметры

Синтаксис

Portal SPI_cursor_parse_open(const char *name,
                             const char *command,
                             const SPIParseOpenOptions * options)

Описание

Функция SPI_cursor_parse_open устанавливает курсор (называемый внутренне порталом), который выполнит заданную строку запроса. Это сравнимо с последовательным вызовом функций SPI_prepare_cursor и SPI_cursor_open_with_paramlist, но все ссылки на параметры в строке запроса обрабатываются путем предоставления объекта ParamListInfo.

Для одноразового выполнения запроса эту функцию следует предпочесть последовательным вызовам SPI_prepare_cursor и SPI_cursor_open_with_paramlist. Если одну и ту же команду нужно выполнять со множеством различных параметров, любой из этих методов может оказаться быстрее, в зависимости от того, что перевесит: стоимость повторного планирования или преимущества специализированных планов.

Как правило, объект options->params должен пометить каждый параметр флагом PARAM_FLAG_CONST, поскольку для этого запроса всегда используется одноразовый план.

Данные переданных параметров будут скопированы в портал курсора, поэтому их можно освободить даже во время существования курсора.

Аргументы

const char * name
имя для портала или NULL, чтобы система сама выбрала имя

const char * command
командная строка

const SPIParseOpenOptions * options
структура, содержащая необязательные аргументы

Вызывающая функция всегда должна сначала полностью заполнять структуру options нулями, а потом уже заполнять поля, которые хочет установить. Это гарантирует восходящую совместимость кода, так как если все поля, добавляемые в эту структуру в будущем, содержат ноль, они будут определены как обратно совместимые. В настоящее время в options имеются следующие поля:

ParamListInfo params
структура данных, содержащая типы и значения параметров запроса; NULL, если таковых нет

int cursorOptions
целочисленная битовая маска параметров курсора; ноль выбирает поведение по умолчанию

bool read_only
true для выполнения запроса в режиме «только чтение»

Возвращаемое значение

Указатель на портал, содержащий курсор. Обратите внимание, что нет соглашения о возврате ошибок; все ошибки будут выдаваться через elog.

SPI_cursor_find

SPI_cursor_find — найти существующий курсор по имени

Синтаксис

Portal SPI_cursor_find(const char * name)

Описание

Функция SPI_cursor_find находит существующий портал по имени. Это главным образом полезно для разрешения имени курсора, возвращенного в виде текста какой-то другой функцией.

Аргументы

const char * name
имя портала

Возвращаемое значение

Указатель на портал с заданным именем или NULL, если такой портал не найден.

SPI_cursor_fetch

SPI_cursor_fetch — извлечь несколько строк через курсор

Синтаксис

void SPI_cursor_fetch(Portal portal, bool forward, long count)

Описание

Функция SPI_cursor_fetch извлекает несколько строк через курсор. Это равнозначно подмножеству возможностей команды SQL FETCH (расширенную функциональность предоставляет функция SPI_scroll_cursor_fetch).

Аргументы

Portal portal
портал, содержащий курсор

bool forward
true для извлечения с перемещением вперед, false — в обратном направлении

long count
максимальное количество извлекаемых строк

Возвращаемое значение

В случае успешного вызова переменные SPI_processed и SPI_tuptable устанавливаются так же, как в SPI_execute.

Примечания

Извлечение в обратном направлении может завершиться ошибкой, если план курсора был создан без параметра CURSOR_OPT_SCROLL.

SPI_cursor_move

SPI_cursor_move — переместить курсор

Синтаксис

void SPI_cursor_move(Portal portal, bool forward, long count)

Описание

Функция SPI_cursor_move пропускает некоторое количество строк в курсоре. Это равнозначно подмножеству возможностей команды SQL MOVE (расширенную функциональность предоставляет функция SPI_scroll_cursor_move).

Аргументы

Portal portal
портал, содержащий курсор

bool forward
true для перемещения вперед, false — в обратном направлении

long count
максимальное количество перемещаемых строк

Примечания

Перемещение в обратном направлении может завершиться ошибкой, если план курсора был создан без параметра CURSOR_OPT_SCROLL.

SPI_scroll_cursor_fetch

SPI_scroll_cursor_fetch — извлечь несколько строк через курсор

Синтаксис

void SPI_scroll_cursor_fetch(Portal portal, FetchDirection direction,
                             long count)

Описание

Функция SPI_scroll_cursor_fetch извлекает несколько строк через курсора. Это равнозначно команде SQL FETCH.

Аргументы

Portal portal
портал, содержащий курсор

FetchDirection direction
варианты: FETCH_FORWARD, FETCH_BACKWARD, FETCH_ABSOLUTE или FETCH_RELATIVE

long count
количество извлекаемых строк для FETCH_FORWARD или FETCH_BACKWARD, абсолютный номер извлекаемой строки для FETCH_ABSOLUTE или относительный номер извлекаемой строки для FETCH_RELATIVE

Возвращаемое значение

В случае успешного вызова переменные SPI_processed и SPI_tuptable устанавливаются так же, как в SPI_execute.

Примечания

Подробную интерпретацию параметров direction и count см. на справочной странице команды SQL FETCH.

При значении параметра direction (направление извлечения), отличном от FETCH_FORWARD, функция может завершиться ошибкой, если план курсора был создан без параметра CURSOR_OPT_SCROLL.

SPI_scroll_cursor_move

SPI_scroll_cursor_move — переместить курсор

Синтаксис

void SPI_scroll_cursor_move(Portal portal, FetchDirection direction,
                            long count)

Описание

Функция SPI_scroll_cursor_move перемещает курсор на некоторое количество строк. Это равнозначно команде SQL MOVE.

Аргументы

Portal portal
портал, содержащий курсор

FetchDirection direction
варианты: FETCH_FORWARD, FETCH_BACKWARD, FETCH_ABSOLUTE или FETCH_RELATIVE

long count
количество строк, на которое перемещается курсор, для FETCH_FORWARD или FETCH_BACKWARD, абсолютный номер строки, к которой перемещается курсор, для FETCH_ABSOLUTE или относительный номер строки, к которой перемещается курсор, для FETCH_RELATIVE

Возвращаемое значение

В случае успешного вызова переменная SPI_processed устанавливается так же, как в SPI_execute. Переменная SPI_tuptable устанавливается в NULL, поскольку эта функция не возвращает строк.

Примечания

Подробную интерпретацию параметров direction и count см. на справочной странице команды SQL FETCH.

При значении параметра direction (направление перемещения курсора), отличном от FETCH_FORWARD, функция может завершиться ошибкой, если план курсора был создан без параметра CURSOR_OPT_SCROLL.

SPI_cursor_close

SPI_cursor_close — закрыть курсор

Синтаксис

void SPI_cursor_close(Portal portal)

Описание

Функция SPI_cursor_close закрывает ранее созданный курсор и освобождает ресурсы, занятые его порталом.

Все открытые курсоры автоматически закрываются в конце транзакции. Функцию SPI_cursor_close нужно вызывать только в случае, если желательно освободить ресурсы раньше.

Аргументы

Portal portal
портал, содержащий курсор

SPI_keepplan

SPI_keepplan — сохранить подготовленный оператор

Синтаксис

int SPI_keepplan(SPIPlanPtr plan)

Описание

Функция SPI_keepplan сохраняет переданный оператор (подготовленный функцией SPI_prepare), чтобы его не освободила функция SPI_finish или менеджер транзакций. Это дает возможность повторно использовать подготовленные операторы в последующих вызовах функции на C/RUST в текущем сеансе.

Аргументы

SPIPlanPtr plan
сохраняемый подготовленный оператор

Возвращаемое значение

0 в случае успеха; SPI_ERROR_ARGUMENT, если значение параметра plan равно NULL или неправильное.

Примечания

Переданный оператор перемещается в постоянное хранилище посредством корректировки указателя (копирование данных не требуется). Если позже понадобится его удалить, воспользуйтесь функцией SPI_freeplan.

SPI_saveplan

SPI_saveplan — сохранить подготовленный оператор

Синтаксис

SPIPlanPtr SPI_saveplan(SPIPlanPtr plan)

Описание

Функция SPI_saveplan копирует переданный оператор (подготовленный функцией SPI_prepare) в память, которая не будет освобождена функцией SPI_finish или менеджером транзакций, и возвращает указатель на скопированный оператор. Это дает возможность повторно использовать подготовленные операторы при последующих вызовах функции на C/RUST в текущем сеансе.

Аргументы

SPIPlanPtr plan
сохраняемый подготовленный оператор

Возвращаемое значение

Указатель на скопированный оператор или NULL в случае ошибки. При ошибке SPI_result принимает одно из следующих значений:

SPI_ERROR_ARGUMENT — если значение параметра plan равно NULL или неправильное

SPI_ERROR_UNCONNECTED — при вызове из неподключенной функции на C/RUST

Примечания

Первоначально переданный оператор не освобождается, поэтому при желании его можно освободить вызовом функции SPI_freeplan, чтобы избежать утечки памяти до выполнения функции SPI_finish.

В большинстве случаев SPI_keepplan предпочтительнее этой функции, поскольку она дает примерно такой же результат без необходимости физического копирования структур данных подготовленного оператора.

SPI_register_relation

SPI_register_relation — сделать эфемерное именованное отношение доступным по имени в запросах SPI

Синтаксис

int SPI_register_relation(EphemeralNamedRelation enr)

Описание

Функция SPI_register_relation делает эфемерное именованное отношение, вместе со связанной информацией, доступным для запросов, планируемых и выполняемых через текущее подключение SPI.

Аргументы

EphemeralNamedRelation enr
запись эфемерного именованного отношения в реестре

Возвращаемое значение

Если выполнение команды было успешным, то будет возвращено следующее (неотрицательное) значение:

SPI_OK_REL_REGISTER — если отношение было успешно зарегистрировано по имени

При ошибке возвращается одно из следующих отрицательных значений:

SPI_ERROR_ARGUMENT — если значение параметра enr или его поля name равно NULL

SPI_ERROR_UNCONNECTED — при вызове из неподключенной функции на C/RUST

SPI_ERROR_REL_DUPLICATE — если имя, указанное в поле name параметра enr, уже зарегистрировано для этого подключения

SPI_unregister_relation

SPI_unregister_relation — удалить эфемерное именованное отношение из реестра

Синтаксис

int SPI_unregister_relation(const char * name)

Описание

Функция SPI_unregister_relation удаляет эфемерное именованное отношение из реестра для текущего подключения.

Аргументы

const char * name
имя записи отношения в реестре

Возвращаемое значение

Если выполнение команды было успешным, то будет возвращено следующее (неотрицательное) значение:

SPI_OK_REL_UNREGISTER — если блок кортежей был успешно удален из реестра

При ошибке возвращается одно из следующих отрицательных значений:

SPI_ERROR_ARGUMENT — если значение параметра name равно NULL

SPI_ERROR_UNCONNECTED — при вызове из неподключенной функции на C/RUST

SPI_ERROR_REL_NOT_FOUND — если name не обнаруживается в реестре для текущего подключения

SPI_register_trigger_data

SPI_register_trigger_data — сделать эфемерные данные триггера доступными в запросах SPI

Синтаксис

int SPI_register_trigger_data(TriggerData *tdata)

Описание

Функция SPI_register_trigger_data делает любые эфемерный отношения, захваченные триггером, доступными для запросов, планируемых и выполняемых через текущее подключение SPI. В настоящее время это подразумевает таблицы переходов, захваченные триггером AFTER, определенным с помощью предложения REFERENCING OLD/NEW TABLE AS .... Эта функция должна вызываться функцией обработки триггера на процедурном языке после подключения.

Аргументы

TriggerData * tdata
объект TriggerData, передаваемый функции обработки триггера через fcinfo->context

Возвращаемое значение

Если выполнение команды было успешным, то будет возвращено следующее (неотрицательное) значение:

SPI_OK_TD_REGISTER — если захваченные данные триггера (если таковые имеются) были успешно зарегистрированы

При ошибке возвращается одно из следующих отрицательных значений:

SPI_ERROR_ARGUMENT — если значение параметра tdata равно NULL

SPI_ERROR_UNCONNECTED — при вызове из неподключенной функции на C/RUST

SPI_ERROR_REL_DUPLICATE — если имя любого переходного отношения в данных триггера уже зарегистрировано для этого подключения

Вспомогательные функции интерфейса

SPI_fname — определить имя столбца по указанному номеру столбца
SPI_fnumber — определить номер столбца по указанному имени столбца
SPI_getvalue — вернуть строковое значение указанного столбца
SPI_getbinval — вернуть двоичное значение указанного столбца
SPI_gettype — вернуть имя типа данных указанного столбца
SPI_gettypeid — вернуть OID типа данных указанного столбца
SPI_getrelname — вернуть имя указанного отношения
SPI_getnspname — вернуть пространство имен указанного отношения
SPI_result_code_string — вернуть код ошибки в виде строки

Описанные здесь функции предоставляют интерфейс для извлечения информации из наборов результатов, возвращаемых SPI_execute и другими функциями SPI.

Все функции, описанные в этом разделе, могут использоваться как в подключенных, так и в неподключенных функциях на C/RUST.

SPI_fname

SPI_fname — определить имя столбца по указанному номеру столбца

Синтаксис

char * SPI_fname(TupleDesc rowdesc, int colnumber)

Описание

Функция SPI_fname возвращает копию имени столбца с указанным номером. (Когда эта копия имени будет не нужна, ее можно освободить с помощью pfree.)

Аргументы

TupleDesc rowdesc
описание входной строки

int colnumber
номер столбца (начиная с 1)

Возвращаемое значение

Название столбца; NULL, если значение параметра colnumber выходит за пределы допустимого диапазона. В случае ошибки в SPI_result устанавливается значение SPI_ERROR_NOATTRIBUTE.

SPI_fnumber

SPI_fnumber — определить номер столбца по указанному имени столбца

Синтаксис

int SPI_fnumber(TupleDesc rowdesc, const char * colname)

Описание

Функция SPI_fnumber возвращает номер для столбца с указанным именем.

Если параметр colname ссылается на системный столбец (например, ctid), будет возвращен соответствующий отрицательный номер столбца. Вызывающая функция должна провести тщательную проверку возвращаемого значения на точное соответствие значению SPI_ERROR_NOATTRIBUTE, чтобы выявить ошибку; проверка результата по условию «меньше или равно 0» будет корректной, только если нужно исключить системные столбцы.

Аргументы

TupleDesc rowdesc
описание входной строки

const char * colname
имя столбца

Возвращаемое значение

Номер столбца (начиная с 1 для пользовательских столбцов) или SPI_ERROR_NOATTRIBUTE, если именованный столбец не найден.

SPI_getvalue

SPI_getvalue — вернуть строковое значение указанного столбца

Синтаксис

char * SPI_getvalue(HeapTuple row, TupleDesc rowdesc, int colnumber)

Описание

Функция SPI_getvalue возвращает строковое представление значения указанного столбца.

Результат возвращается в памяти, выделенной с помощью palloc. (Когда он станет не нужен, можно будет освободить память с помощью pfree.)

Аргументы

HeapTuple row
проверяемая входная строка

TupleDesc rowdesc
описание входной строки

int colnumber
номер столбца (начиная с 1)

Возвращаемое значение

Значение столбца или NULL, если столбец содержит NULL, значение параметра colnumber выходит за пределы допустимого диапазона (в SPI_result устанавливается значение SPI_ERROR_NOATTRIBUTE) или нет доступной выходной функции (в SPI_result устанавливается значение SPI_ERROR_NOOUTFUNC).

SPI_getbinval

SPI_getbinval — вернуть двоичное значение указанного столбца

Синтаксис

Datum SPI_getbinval(HeapTuple row, TupleDesc rowdesc, int colnumber,
                    bool * isnull)

Описание

Функция SPI_getbinval возвращает значение указанного столбца во внутренней форме (как тип Datum).

Эта функция не выделяет новое пространство для данных. В случае типа данных, передаваемого по ссылке, возвращаемое значение будет указателем на переданную строку.

Аргументы

HeapTuple row
проверяемая входная строка

TupleDesc rowdesc
описание входной строки

int colnumber
номер столбца (начиная с 1)

bool * isnull
флаг для значения NULL в столбце

Возвращаемое значение

Возвращается двоичное значение столбца. Если этот столбец содержит NULL, переменной, на которую указывает параметр isnull, присваивается значение true, иначе — false.

При ошибке в SPI_result устанавливается значение SPI_ERROR_NOATTRIBUTE.

SPI_gettype

SPI_gettype — вернуть имя типа данных указанного столбца

Синтаксис

char * SPI_gettype(TupleDesc rowdesc, int colnumber)

Описание

Функция SPI_gettype возвращает копию имени типа данных указанного столбца. (Когда копия имени станет не нужна, можно будет освободить ее с помощью pfree.)

Аргументы

TupleDesc rowdesc
описание входной строки

int colnumber
номер столбца (начиная с 1)

Возвращаемое значение

Имя типа данных указанного столбца или NULL при ошибке. При ошибке в SPI_result устанавливается значение SPI_ERROR_NOATTRIBUTE.

SPI_gettypeid

SPI_gettypeid — вернуть OID типа данных указанного столбца

Синтаксис

Oid SPI_gettypeid(TupleDesc rowdesc, int colnumber)

Описание

Функция SPI_gettypeid возвращает OID типа данных указанного столбца.

Аргументы

TupleDesc rowdesc
описание входной строки

int colnumber
номер столбца (начиная с 1)

Возвращаемое значение

OID типа данных указанного столбца или InvalidOid при ошибке. При ошибке в SPI_result устанавливается значение SPI_ERROR_NOATTRIBUTE.

SPI_getrelname

SPI_getrelname — вернуть имя указанного отношения

Синтаксис

char * SPI_getrelname(Relation rel)

Описание

Функция SPI_getrelname возвращает копию имени указанного отношения. (Когда копия имени станет не нужна, можно будет освободить ее с помощью pfree.)

Аргументы

Relation rel
входное отношение

Возвращаемое значение

Имя указанного отношения.

SPI_getnspname

SPI_getnspname — вернуть пространство имен указанного отношения

Синтаксис

char * SPI_getnspname(Relation rel)

Описание

Функция SPI_getnspname возвращает копию имени пространства имен, которому принадлежит указанное отношение (Relation). Это равнозначно схеме отношения. По окончании работы со значением, возвращенным этой функцией, следует освободить его с помощью pfree.

Аргументы

Relation rel
входное отношение

Возвращаемое значение

Имя пространства имен указанного отношения.

SPI_result_code_string

SPI_result_code_string — вернуть код ошибки в виде строки

Синтаксис

const char * SPI_result_code_string(int code);

Описание

Функция SPI_result_code_string выдает строковое представление кода результата, возвращенного различными функциями SPI или сохраненного в SPI_result.

Аргументы

int code
код результата

Возвращаемое значение

Строковое представление кода результата.

Управление памятью

SPI_palloc — выделить память в верхнем контексте исполнителя
SPI_repalloc — перераспределить память в верхнем контексте исполнителя
SPI_pfree — освободить память в верхнем контексте исполнителя
SPI_copytuple — сделать копию строки в верхнем контексте исполнителя
SPI_returntuple — подготовить кортеж к возврату в виде Datum
SPI_modifytuple — создать строку, заменив выбранные поля заданной строки
SPI_freetuple — освободить строку, выделенную в верхнем контексте исполнителя
SPI_freetuptable — освободить набор строк, созданных SPI_execute или подобной функцией
SPI_freeplan — освободить ранее сохраненный подготовленный оператор

QHB выделяет память в контекстах памяти, предоставляющих удобный метод управления распределением памяти по многим различным местам и с различными сроками жизни этой памяти. Уничтожение контекста освобождает всю выделенную в нем память. Таким образом, во избежание утечек памяти нет необходимости отслеживать отдельные объекты; вместо этого достаточно управлять только относительно небольшим количеством контекстов. Функция palloc и родственные ей выделяют память из «текущего» контекста.

Функция SPI_connect создает новый контекст памяти и делает его текущим. Функция SPI_finish восстанавливает контекст, который был текущим ранее, и уничтожает контекст, созданный SPI_connect. Эти действия гарантируют, что временные выделения памяти, сделанные внутри вашей функции на C/RUST, будут освобождены при выходе из этой функции, предотвращая утечки памяти.

Однако если вашей функции на C/RUST нужно вернуть объект в выделенной памяти (например значение типа данных с передачей по ссылке), эту память нельзя выделить, используя palloc, по крайней мере, пока вы подключены к SPI. При попытке сделать это объект будет освобожден посредством SPI_finish, и функция на C/RUST не будет работать надежно. Для решения этой проблемы воспользуйтесь SPI_palloc, чтобы выделить память для возвращаемого объекта. SPI_palloc выделяет память в «верхнем контексте исполнителя», то есть в контексте памяти, который был текущим при вызове SPI_connect и является абсолютно верным контекстом для значения, возвращаемого функцией на C/RUST. Некоторые другие служебные функции, описанные в этом разделе, также возвращают объекты, созданные в верхнем контексте исполнителя.

Когда вызывается SPI_connect, создаваемый ей частный контекст функции на C/RUST становится текущим контекстом. Все выделения, сделанные функциями palloc, repalloc или служебными функциями SPI (за исключением случаев, описанных в этом разделе), выполняются в этом контексте. Когда функция на C/RUST отключается от менеджера SPI (через SPI_finish), текущим контекстом снова становится верхний контекст исполнителя, а вся память, выделенная в контексте памяти функции на C/RUST, освобождается и больше не может быть использована.

SPI_palloc

SPI_palloc — выделить память в верхнем контексте исполнителя

Синтаксис

void * SPI_palloc(Size size)

Описание

Функция SPI_palloc выделяет память в верхнем контексте исполнителя

Эту функцию можно использовать только при подключении к SPI. В противном случае она выдает ошибку.

Аргументы

Size size
размер выделяемой памяти (в байтах)

Возвращаемое значение

указатель на новый блок памяти заданного размера

SPI_repalloc

SPI_repalloc — перераспределить память в верхнем контексте исполнителя

Синтаксис

void * SPI_repalloc(void * pointer, Size size)

Описание

Функция SPI_repalloc изменяет размер сегмента памяти, ранее выделенного с помощью функции SPI_palloc.

Эта функция больше не отличается от обычной repalloc. Она сохранена только для обратной совместимости существующего кода.

Аргументы

void * pointer
указатель на изменяемое существующее хранилище

Size size
размер выделяемой памяти (в байтах)

Возвращаемое значение

указатель на новый блок памяти заданного размера, в который скопировано содержимое существующего блока

SPI_pfree

SPI_pfree — освободить память в верхнем контексте исполнителя

Синтаксис

void SPI_pfree(void * pointer)

Описание

Функция SPI_pfree освобождает память, ранее выделенную с помощью функции SPI_palloc или SPI_repalloc.

Эта функция больше не отличается от обычной pfree. Она сохранена только для обратной совместимости существующего кода.

Аргументы

void * pointer
указатель на освобождаемый существующий блок памяти

SPI_copytuple

SPI_copytuple — сделать копию строки в верхнем контексте исполнителя

Синтаксис

HeapTuple SPI_copytuple(HeapTuple row)

Описание

Функция SPI_copytuple создает копию строки в верхнем контексте исполнителя. Обычно это используется для возврата измененной строки из триггера. В функции, объявленной как возвращающая составной тип, используйте вместо нее SPI_returntuple.

Эту функцию можно использовать только при подключении к SPI. В противном случае она возвращает NULL и устанавливает в SPI_result значение SPI_ERROR_UNCONNECTED.

Аргументы

HeapTuple row
копируемая строка

Возвращаемое значение

скопированная строка или NULL в случае ошибки (код ошибки см. в SPI_result)

SPI_returntuple

SPI_returntuple — подготовить кортеж к возврату в виде Datum

Синтаксис

HeapTupleHeader SPI_returntuple(HeapTuple row, TupleDesc rowdesc)

Описание

Функция SPI_returntuple создает копию строки в верхнем контексте исполнителя, возвращая ее в виде типа Datum. Перед возвратом полученный указатель нужно лишь преобразовать в Datum посредством PointerGetDatum.

Эту функцию можно использовать только при подключении к SPI. В противном случае она возвращает NULL и устанавливает в SPI_result значение SPI_ERROR_UNCONNECTED.

Обратите внимание, что данный метод следует использовать для функций, которые объявлены как возвращающие составные типы. Для триггеров он не применяется; чтобы вернуть измененную строку в триггере, используйте функцию SPI_copytuple.

Аргументы

HeapTuple row
копируемая строка

TupleDesc rowdesc
дескриптор для строки (для наиболее эффективного кэширования передавайте каждый раз один и тот же дескриптор)

Возвращаемое значение

HeapTupleHeader, указывающий на скопированную строку, или NULL при ошибке (код ошибки см. в SPI_result)

SPI_modifytuple

SPI_modifytuple — создать строку, заменив выбранные поля заданной строки

Синтаксис

HeapTuple SPI_modifytuple(Relation rel, HeapTuple row, int ncols,
                          int * colnum, Datum * values, const char * nulls)

Описание

Функция SPI_modifytuple создает новую строку, подставляя новые значения для выбранных столбцов и копируя остальные столбцы исходной строки. Входная строка не изменяется. Новая строка возвращается в верхнем контексте исполнителя.

Эту функцию можно использовать только при подключении к SPI. В противном случае она возвращает NULL и устанавливает в SPI_result значение SPI_ERROR_UNCONNECTED.

Аргументы

Relation rel
Используется только как источник дескриптора строки. (Передача отношения вместо дескриптора строки является ошибкой.)

HeapTuple row
изменяемая строка

int ncols
количество изменяемых столбцов

int * colnum
массив длиной ncols, содержащий номера изменяемых столбцов (номера столбцов начинаются с 1)

Datum * values
массив длиной ncols, содержащий новые значения для заданных столбцов

const char * nulls
массив длиной ncols, описывающий, какие из новых значений равны NULL
Если параметр nulls равен NULL, то SPI_modifytuple предполагает, что ни одно новое значение не равно NULL. В противном случае каждый элемент массива nulls должен содержать ' ', если соответствующее новое значение не равно NULL, или 'n', если равно. (В последнем случае фактическое значение в соответствующем элементе values неважно.) Обратите внимание, что nulls — это не текстовая строка, а просто массив; завершающий символ '\0' не нужен.

Возвращаемое значение

новая строка с изменениями, размещенная в верхнем контексте исполнителя, или NULL при ошибке (код ошибки см. в SPI_result)

В случае ошибки в SPI_result устанавливаются следующие значения:

SPI_ERROR_ARGUMENT — если параметр rel равен NULL, или параметр row равен NULL, или параметр ncols меньше или равен 0, или параметр colnum равен NULL, или параметр values равен NULL

SPI_ERROR_NOATTRIBUTE — если параметр colnum содержит недопустимый номер столбца (меньше или равен 0 или больше, чем количество столбцов в параметре row)

SPI_ERROR_UNCONNECTED — если SPI неактивен

SPI_freetuple

SPI_freetuple — освободить строку, выделенную в верхнем контексте исполнителя

Синтаксис

void SPI_freetuple(HeapTuple row)

Описание

Функция SPI_freetuple освобождает строку, ранее выделенную в верхнем контексте исполнителя.

Эта функция больше не отличается от обычной heap_freetuple. Она сохранена только для обратной совместимости существующего кода.

Аргументы

HeapTuple row
освобождаемая строка

SPI_freetuptable

SPI_freetuptable — освободить набор строк, созданный SPI_execute или подобной функцией

Синтаксис

void SPI_freetuptable(SPITupleTable * tuptable)

Описание

Функция SPI_freetuptable освобождает набор строк, созданный предыдущим вызовом функции SPI, выполняющей команды, например SPI_execute. Поэтому данная функция часто вызывается с глобальной переменной SPI_tuptable в качестве аргумента.

Эта функция полезна, если функция на C/RUST, использующая SPI, должна выполнять несколько команд и не хочет сохранять результаты предыдущих команд до завершения. Обратите внимание, что все наборы строк будут в любом случае освобождены при вызове SPI_finish. Кроме того, если субтранзакция запускается, а затем прерывается в ходе выполнения использующей SPI функции на C/RUST, то SPI автоматически освобождает все наборы строк, созданные в рамках субтранзакции.

Аргументы

SPITupleTable * tuptable
указатель на набор освобождаемых строк или NULL, чтобы ничего не делать

SPI_freeplan

SPI_freeplan — освободить ранее сохраненный подготовленный оператор

Синтаксис

int SPI_freeplan(SPIPlanPtr plan)

Описание

Функция SPI_freeplan освобождает подготовленный оператор, ранее возвращенный функцией SPI_prepare или сохраненный функцией SPI_keepplan или SPI_saveplan.

Аргументы

SPIPlanPtr plan
указатель на освобождаемый оператор

Возвращаемое значение

0 в случае успеха; SPI_ERROR_ARGUMENT, если значение параметра plan равно NULL или неправильное.

Управление транзакциями

SPI_commit — зафиксировать текущую транзакцию
SPI_rollback — прервать текущую транзакцию
SPI_start_transaction — устаревшая функция

Выполнить команды управления транзакциями, например COMMIT и ROLLBACK, через функции SPI, такие как SPI_execute, невозможно. Однако существуют отдельные функции интерфейса, позволяющие управлять транзакциями через SPI.

Обычно небезопасно и нецелесообразно запускать и завершать транзакции в произвольных пользовательских функциях, вызываемых из SQL, без учета контекста, в котором они вызываются. Например, граница транзакции в середине функции, которая является частью сложного SQL-выражения, являющегося частью какой-либо другой команды SQL, скорее всего приведет к скрытым внутренним ошибкам или сбоям. Представленные здесь функции интерфейса предназначены, в первую очередь, для использования реализациями процедурных языков в целях поддержки управления транзакциями в процедурах уровня SQL, которые вызываются командой CALL (с учетом контекста ее вызова). Процедуры, реализованные на C/RUST и использующие SPI, могут реализовывать ту же логику, но подробное освещение этой темы выходит за рамки данной документации.

SPI_commit

SPI_commit, SPI_commit_and_chain — зафиксировать текущую транзакцию

Синтаксис

void SPI_commit(void)

void SPI_commit_and_chain(void)

Описание

Функция SPI_commit фиксирует текущую транзакцию. Это примерно равнозначно выполнению команды SQL COMMIT. После фиксации транзакции автоматически начинается новая транзакция с характеристиками по умолчанию, так что вызывающая функция может продолжать использовать средства SPI. Если во время фиксации происходит ошибка, текущая транзакция откатывается и начинается новая, после чего ошибка выдается обычным способом.

Функция SPI_commit_and_chain делает то же самое, но новая транзакция запускается с теми же характеристиками, что и только что завершенная, как при выполнении команды SQL COMMIT AND CHAIN.

Эти функции можно выполнить, только если SPI-подключение было установлено в неатомарном режиме путем вызова функции SPI_connect_ext.

SPI_rollback

SPI_rollback, SPI_rollback_and_chain — прервать текущую транзакцию

Синтаксис

void SPI_rollback(void)

void SPI_rollback_and_chain(void)

Описание

Функция SPI_rollback откатывает текущую транзакцию. Это примерно равнозначно выполнению команды SQL ROLLBACK. После отката транзакции автоматически начинается новая транзакция с характеристиками по умолчанию, так что вызывающая функция может продолжать использовать средства SPI.

Функция SPI_rollback_and_chain делает то же самое, но новая транзакция запускается с теми же характеристиками, что и только что завершенная, как при выполнении команды SQL ROLLBACK AND CHAIN.

Эти функции можно выполнить, только если SPI-подключение было установлено в неатомарном режиме путем вызова функции SPI_connect_ext.

SPI_start_transaction

SPI_start_transaction — устаревшая функция

Синтаксис

void SPI_start_transaction(void)

Описание

Функция SPI_start_transaction ничего не делает и существует только для совместимости кода с более ранними версиями QHB. Ранее она требовалась после вызова SPI_commit или SPI_rollback, но теперь эти функции начинают новую транзакцию автоматически.

Видимость изменений данных

Следующие правила регулируют видимость изменений данных в функциях, использующих SPI (или любых других функциях на C/RUST):

Во время выполнения команды SQL любые изменения данных, сделанные этой командой, невидимы для самой команды. Например, в:

INSERT INTO a SELECT * FROM a;

вставленные строки невидимы для части SELECT.

Изменения, произведенные командой К, видны всем командам, которые запускаются после нее, независимо от того, были ли эти команды запущены внутри К (во время ее выполнения) или после ее завершения.

Команды, выполняемые через SPI внутри функции, вызванной командой SQL (обычной функцией или триггером), следуют одному или другому из приведенных выше правил в зависимости от флага чтения/записи, передаваемого в SPI. Команды, выполняемые в режиме «только чтение», следуют первому правилу: они не могут видеть изменения, произведенные вызывающей командой. Команды, выполняемые в режиме «чтение-запись», следуют второму правилу: они могут видеть все произведенные к этому моменту изменения.

Все стандартные процедурные языки устанавливают режим SPI «чтение-запись» в зависимости от атрибута изменчивости функции. Команды функций STABLE и IMMUTABLE выполняются в режиме «только чтение», тогда как команды функций VOLATILE выполняются в режиме «чтение-запись». Хотя авторы функций на C/RUST могут нарушить это соглашение, вряд ли это будет хорошей идеей.

Следующий раздел содержит пример, иллюстрирующий применение этих правил.

Примеры

Этот раздел содержит очень простой пример использования SPI. Функция на C execq принимает команду SQL в качестве первого аргумента и число строк в качестве второго, выполняет команду с помощью функции SPI_exec и возвращает количество строк, обработанных командой. Более сложные примеры для SPI можно найти в модуле spi.

#include "qhb.h"

#include "executor/spi.h"
#include "utils/builtins.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(execq);

Datum
execq(PG_FUNCTION_ARGS)
{
    char *command;
    int cnt;
    int ret;
    uint64 proc;

    /* Преобразовать данный текстовый объект в строку C */
    command = text_to_cstring(PG_GETARG_TEXT_PP(0));
    cnt = PG_GETARG_INT32(1);

    SPI_connect();

    ret = SPI_exec(command, cnt);

    proc = SPI_processed;

    /*
     * Если были извлечены какие-то строки, вывести их через elog(INFO).
     */
    if (ret > 0 && SPI_tuptable != NULL)
    {
        TupleDesc tupdesc = SPI_tuptable->tupdesc;
        SPITupleTable *tuptable = SPI_tuptable;
        char buf[8192];
        uint64 j;

        for (j = 0; j < proc; j++)
        {
            HeapTuple tuple = tuptable->vals[j];
            int i;

            for (i = 1, buf[0] = 0; i <= tupdesc->natts; i++)
                snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %s%s",
                        SPI_getvalue(tuple, tupdesc, i),
                        (i == tupdesc->natts) ? " " : " |");
            elog(INFO, "EXECQ: %s", buf);
        }
    }

    SPI_finish();
    pfree(command);

    PG_RETURN_INT64(proc);
}

Так эта функция будет объявлена после ее компиляции в разделяемую библиотеку (подробную информацию см. в подразделе Компиляция и связывание динамически загружаемых функций):

CREATE FUNCTION execq(text, integer) RETURNS int8
    AS 'имя_файла'
    LANGUAGE C STRICT;

Пример сеанса:

=> SELECT execq('CREATE TABLE a (x integer)', 0);
 execq
-------
     0
(1 row)

=> INSERT INTO a VALUES (execq('INSERT INTO a VALUES (0)', 0));
INSERT 0 1
=> SELECT execq('SELECT * FROM a', 0);
INFO:  EXECQ:  0    -- добавлено функцией execq
INFO:  EXECQ:  1    -- возвращено функцией execq и добавлено верхней командой INSERT

 execq
-------
     2
(1 row)

=> SELECT execq('INSERT INTO a SELECT x + 2 FROM a RETURNING *', 1);
INFO:  EXECQ:  2    -- 0 + 2, затем выполнение было остановлено при достижении предела count
 execq
-------
     1
(1 row)

=> SELECT execq('SELECT * FROM a', 10);
INFO:  EXECQ:  0
INFO:  EXECQ:  1
INFO:  EXECQ:  2

 execq
-------
     3              -- 10 — всего лишь максимальное значение, 3 — реальное число строк
(1 row)

=> SELECT execq('INSERT INTO a SELECT x + 10 FROM a', 1);
 execq
-------
     3              -- обработаны все строки; обработки не останавливается по достижении count, поскольку ничего не возвращается
(1 row)

=> SELECT * FROM a;
 x
----
  0
  1
  2
 10
 11
 12
(6 rows)

=> DELETE FROM a;
DELETE 3
=> INSERT INTO a VALUES (execq('SELECT * FROM a', 0) + 1);
INSERT 0 1
=> SELECT * FROM a;
 x
---
 1                  -- 0 (нет строк в a) + 1
(1 row)

=> INSERT INTO a VALUES (execq('SELECT * FROM a', 0) + 1);
INFO:  EXECQ:  1
INSERT 0 1
=> SELECT * FROM a;
 x
---
 1
 2                  -- 1 (в a была только одна строка) + 1
(2 rows)

-- Этот пример демонстрирует правило видимости изменений данных.
-- execq вызывается дважды и каждый раз видит разное количество строк:

=> INSERT INTO a SELECT execq('SELECT * FROM a', 0) * x FROM a;
INFO:  EXECQ:  1    -- результаты из первой execq
INFO:  EXECQ:  2
INFO:  EXECQ:  1    -- результаты из второй execq
INFO:  EXECQ:  2
INFO:  EXECQ:  2
INSERT 0 2
=> SELECT * FROM a;
 x
---
 1
 2
 2                  -- 2 строки * 1 (x в первой строке)
 6                  -- 3 строки (2 + 1 только что вставленная) * 2 (x во второй строке)
(4 rows)

Фоновые рабочие процессы

QHB можно расширить для выполнения пользовательского кода в отдельных процессах. Такие процессы запускаются, останавливаются и контролируются процессом qhb, что позволяет им тесно связать время существования со статусом сервера. Эти процессы привязаны к области разделяемой памяти QHB и имеют возможность подключаться к базам данных; кроме того, они могут запускать последовательно несколько транзакций, в точности как обычный серверный процесс, подключенный к клиенту. Также, связываясь с libpq, они могут подключаться к серверу и вести себя как обычное клиентское приложение.

ПРЕДУПРЕЖДЕНИЕ
С использованием фоновых рабочих процессов сопряжены значительные угрозы стабильности и безопасности, поскольку, будучи написанными на языке C/RUST, они имеют неограниченный доступ к данным. Администраторам, желающим применять модули, включающие в себя фоновые рабочие процессы, нужно проявлять крайнюю осторожность. Запускать фоновые рабочие процессы следует разрешать только тщательно проверенным модулям.

Фоновые процессы можно инициализировать во время запуска QHB путем включения имени модуля в shared_preload_libraries. Модуль, желающий запустить фоновый рабочий процесс, может зарегистрировать его, вызвав функцию RegisterBackgroundWorker(BackgroundWorker *worker) из своей функции _PG_init(). Также фоновые рабочие процессы можно запустить после запуска системы, вызвав функцию RegisterDynamicBackgroundWorker(BackgroundWorker *worker, BackgroundWorkerHandle **handle). В отличие от RegisterBackgroundWorker, которую можно вызывать только из процесса qhbmaster, RegisterDynamicBackgroundWorker нужно вызывать из обычного обслуживающего процесса или другого фонового процесса.

Структура BackgroundWorker определяется таким образом:

typedef void (*bgworker_main_type)(Datum main_arg);
typedef struct BackgroundWorker
{
    char        bgw_name[BGW_MAXLEN];
    char        bgw_type[BGW_MAXLEN];
    int         bgw_flags;
    BgWorkerStartTime bgw_start_time;
    int         bgw_restart_time;       /* время в секундах или BGW_NEVER_RESTART */
    char        bgw_library_name[BGW_MAXLEN];
    char        bgw_function_name[BGW_MAXLEN];
    Datum       bgw_main_arg;
    char        bgw_extra[BGW_EXTRALEN];
    int         bgw_notify_pid;
} BackgroundWorker;

Поля bgw_name и bgw_type — это строки, которые будут использоваться в сообщениях журнала, списках процессов и тому подобных контекстах. Поле bgw_type должно быть одинаковым для всех фоновых процессов одного типа, чтобы, например, можно было сгруппировать такие рабочие процессы в список процессов. С другой стороны, поле bgw_name может содержать дополнительную информацию о конкретном процессе. (Как правило, строка для bgw_name будет некоторым образом содержать тип, но строго это не требуется.)

Поле bgw_flags — это побитовая битовая маска, указывающая возможности, запрашиваемые модулем. Возможные значения:

BGWORKER_SHMEM_ACCESS
Запрашивает доступ к разделяемой памяти. Это обязательный флаг.

BGWORKER_BACKEND_DATABASE_CONNECTION
Запрашивает возможность установить соединение с базой данных, с помощью которого она потом может выполнять транзакции и запросы. Фоновый рабочий процесс, использующий BGWORKER_BACKEND_DATABASE_CONNECTION для подключения к базе данных, должен также подключить разделяемую память с помощью BGWORKER_SHMEM_ACCESS, иначе рабочий процесс не запустится.

Поле bgw_start_time — это состояние сервера, в течение которого qhb должен запустить процесс; это может быть BgWorkerStart_QhbmasterStart (запускаться сразу после того, как сама QHB завершила собственную инициализацию; процессы, требующие этого, не подходят для подключений к базе данных), BgWorkerStart_ConsistentState (запускаться сразу после того, как было достигнуто согласованное состояние горячего резерва, позволяя процессам подключаться к базам данных и выполнять запросы только на чтение) и BgWorkerStart_RecoveryFinished (запускаться сразу после того, как система вошла в нормальное состояние чтения-записи). Обратите внимание, что последние два значения равнозначны для сервера, который не находится в режиме горячего резерва. Также обратите внимание, что этот параметр указывает только, когда эти процессы должны запускаться; они не будут останавливаться при переходе в другое состояние.

Поле bgw_restart_time — это интервал в секундах, который qhb должен выждать перед перезапуском процесса в случае его сбоя. Это может быть любое положительное значение или флаг BGW_NEVER_RESTART, указывающий не перезапускать процесс в случае сбоя.

Поле bgw_library_name — это имя библиотеки, в которой следует искать начальную точку входа для фонового процесса. Именованная библиотека будет динамически загружена рабочим процессом, а поле bgw_function_name будет использовано для идентификации вызываемой функции. При загрузке функции из основного кода в этом поле должно быть установлено значение «qhb».

Поле bgw_function_name — это имя той функции в динамически загружаемой библиотеке, которая должна использоваться в качестве начальной точки входа для нового фонового процесса.

Поле bgw_main_arg — это аргумент типа Datum для основной функции фонового рабочего процесса. Основная функция должна принимать один аргумент типа Datum и возвращать void. Поле bgw_main_arg будет передано в качестве этого аргумента. Кроме того, глобальная переменная MyBgworkerEntry указывает на копию структуры BackgroundWorker, переданную во время регистрации; рабочему процессу может быть полезно проверить эту структуру.

Везде, где определяется EXEC_BACKEND, или в динамических рабочих процессах, небезопасно передавать Datum по ссылке, нужно передавать его по значению. Если требуется аргумент, безопаснее всего будет передать int32 или другое маленькое значение и использовать его как индекс в массиве, размещенном в разделяемой памяти. Если же передается значение типа cstring или text, то этот указатель не будет работать в новом фоновом рабочем процессе.

Поле bgw_extra может содержать дополнительные данные для передачи фоновому рабочему процессу. В отличие от bgw_main_arg, эти данные не передаются в качестве аргумента основной функции рабочего процесса, но к ним можно получить доступ через MyBgworkerEntry, как было сказано выше.

Поле bgw_notify_pid — это PID обслуживающего процесса QHB, которому qhbmaster должен отправить SIGUSR1 при запуске или окончании этого рабочего процесса. Оно должно содержать 0 для рабочих процессов, зарегистрированных во время запуска qhbmaster или когда обслуживающий процесс, регистрирующий рабочий процесс, не хочет ждать его запуска. В противном случае его следует инициализировать как MyProcPid.

После запуска процесс может подключиться к базе данных, вызвав функцию BackgroundWorkerInitializeConnection(char *dbname, char *username, uint32 flags) или BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, uint32 flags). Это позволяет процессу выполнять транзакции и запросы с использованием интерфейса SPI. Если аргумент dbname равен NULL или dboid имеет значение InvalidOid, сеанс не подключен к какой-либо конкретной базе данных, но может обращаться к разделяемым каталогам. Если аргумент username равен NULL или useroid имеет значение InvalidOid, процесс будет запускаться от имени суперпользователя, созданного во время qhb_bootstrap (или initdb). Если в качестве flags указано BGWORKER_BYPASS_ALLOWCONN, можно обойти ограничение на подключение к базам данных, не допускающим подключения от пользователей. Фоновый рабочий процесс может вызвать только одну из этих двух функций и только один раз. Переключаться между базами данных невозможно.

Сигналы изначально блокируются, когда управление достигает основной функции фонового рабочего процесса, и должны быть разблокированы ей; это позволяет процессу при необходимости настраивать свои обработчики сигналов. В новом процессе сигналы могут быть разблокированы путем вызова BackgroundWorkerUnblockSignals и заблокированы путем вызова BackgroundWorkerBlockSignals.

Если поле bgw_restart_time для фонового рабочего процесса сконфигурировано как BGW_NEVER_RESTART, или если он завершается с кодом выхода 0 или уничтожается TerminateBackgroundWorker, при выходе он автоматически перестанет регистрироваться процессом qhbmaster. В противном случае он будет перезапущен по истечении периода времени, сконфигурированного посредством bgw_restart_time, или немедленно, если qhbmaster повторно инициализирует кластер из-за сбоя обслуживающего процесса. Для обслуживающих процессов, выполнение которых должно приостановиться лишь на время, вместо выхода следует использовать прерываемый режим ожидания; это можно сделать путем вызова WaitLatch(). Убедитесь, что при вызове этой функции установлен флаг WL_POSTMASTER_DEATH, и проверьте код завершения для быстрого выхода в экстренной ситуации, когда был уничтожен сам qhb.

Когда фоновый рабочий процесс регистрируется с помощью функции RegisterDynamicBackgroundWorker, обслуживающий процесс, выполняющий регистрацию, может получить информацию о его статусе. Обслуживающие процессы, желающие сделать это, должны передать в качестве второго аргумента RegisterDynamicBackgroundWorker адрес BackgroundWorkerHandle *. Если процесс успешно зарегистрирован, этот указатель будет инициализирован со скрытым обработчиком, который впоследствии можно будет передать в GetBackgroundWorkerPid(BackgroundWorkerHandle *, pid_t *) или в TerminateBackgroundWorker(BackgroundWorkerHandle *). GetBackgroundWorkerPid можно использовать для опроса статуса рабочего процесса: возвращаемое значение BGWH_NOT_YET_STARTED указывает, что процесс еще не был запущен qhbmaster, BGWH_STOPPED — что он был запущен, но больше не работает, а BGWH_STARTED — что он в данный момент работает. В последнем случае через второй аргумент также будет возвращен PID. TerminateBackgroundWorker заставляет qhbmaster отправлять рабочему процессу SIGTERM, если он работает, и отменять его регистрацию, как только он завершится.

В некоторых случаях процесс, регистрирующий фоновый рабочий процесс, может пожелать дождаться его запуска. Это можно сделать путем инициализации bgw_notify_pid для MyProcPid и последующей передачи BackgroundWorkerHandle *, полученного во время регистрации, в WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t*). Эта функция будет блокировать выполнение до тех пор, пока qhbmaster не попытается запустить фоновый рабочий процесс или не прекратит работу. Если фоновый процесс работает, будет возвращено значение BGWH_STARTED, а PID будет записан по указанному адресу. В противном случае будет возвращено значение BGWH_STOPPED или BGWH_POSTMASTER_DIED.

Процесс также может ожидать выключения фонового рабочего процесса, используя функцию WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle) и передавая BackgroundWorkerHandle *, полученный при регистрации. Эта функция будет блокировать выполнение до тех пор, пока не завершится либо фоновый рабочий процесс, либо qhbmaster. Когда завершает работу фоновый процесс, будет возвращено значение BGWH_STOPPED, если завершается qhbmaster, она вернет BGWH_POSTMASTER_DIED.

Фоновые рабочие процессы могут отправлять асинхронные уведомления либо с помощью команды NOTIFY через SPI, либо напрямую вызвав функцию Async_Notify(). Такие уведомления будут передаваться при фиксации транзакции. Фоновые рабочие процессы не должны регистрироваться командой LISTEN для получения асинхронных уведомлений, поскольку для них не предусмотрена инфраструктура, позволяющая получать подобные уведомления.

Максимальное количество регистрируемых фоновых рабочих процессов ограничивается параметром max_worker_processes.

Логическое декодирование

QHB предоставляет инфраструктуру для потоковой передачи модификаций, производимых через SQL, внешним потребителям. Эту функциональность можно использовать для различных целей, в том числе для решений по репликации и аудита.

Изменения передаются в потоках, идентифицируемых по слотам логической репликации.

Формат, в котором передаются эти изменения, определяется используемым плагином вывода. Пример плагина включен в дистрибутив QHB. Чтобы расширить выбор доступных форматов, не модифицируя код ядра, можно написать дополнительные плагины. Каждый плагин вывода имеет доступ к каждой отдельной новой строке, выдаваемой командой INSERT и новой версии строки, создаваемой командой UPDATE. Доступность старых версий строк для команд UPDATE и DELETE зависит от сконфигурированного репликационного идентификатора (см. REPLICA IDENTITY).

Изменения могут быть получены либо по протоколу потоковой репликации (см. разделы Протокол потоковой репликации и Интерфейс протокола потоковой репликации), либо вызовом функций через SQL (см. раздел SQL-интерфейс логического декодирования). Также можно написать дополнительные методы получения вывода из слота репликации, не модифицируя код ядра (см. раздел Запись вывода логического декодирования).

Примеры логического декодирования

Следующий пример демонстрирует управление логическим декодированием посредством SQL-интерфейса.

Прежде чем вы сможете использовать логическое декодирование, вам следует установить в параметре wal_level значение logical а в max_replication_slots — число не меньше 1. Затем вы должны подключиться к целевой базе данных (в данном примере это qhb) как суперпользователь.

qhb=# -- Создать слот с именем 'regression_slot', использующий плагин вывода 'test_decoding'
qhb=# SELECT * FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding', false, true);
    slot_name    |    lsn
-----------------+-----------
 regression_slot | 0/16B1970
(1 row)

qhb=# SELECT slot_name, plugin, slot_type, database, active, restart_lsn, confirmed_flush_lsn FROM pg_replication_slots;
    slot_name    |    plugin     | slot_type | database | active | restart_lsn | confirmed_flush_lsn
-----------------+---------------+-----------+----------+--------+-------------+-----------------
 regression_slot | test_decoding | logical   | qhb      | f      | 0/16A4408   | 0/16A4440
(1 row)

qhb=# -- Пока никакие изменения не видны
qhb=# SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
 lsn | xid | data
-----+-----+------
(0 rows)

qhb=# CREATE TABLE data(id serial primary key, data text);
CREATE TABLE

qhb=# -- DDL не реплицируется, поэтому вы увидите только транзакцию
qhb=# SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
    lsn    |  xid  |     data
-----------+-------+--------------
 0/BA2DA58 | 10297 | BEGIN 10297
 0/BA5A5A0 | 10297 | COMMIT 10297
(2 rows)

qhb=# -- После прочтения изменения считаются полученными и уже не выдаются
qhb=# -- в следующем вызове:
qhb=# SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
 lsn | xid | data
-----+-----+------
(0 rows)

qhb=# BEGIN;
qhb=*# INSERT INTO data(data) VALUES('1');
qhb=*# INSERT INTO data(data) VALUES('2');
qhb=*# COMMIT;

qhb=# SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
    lsn    |  xid  |                          data
-----------+-------+---------------------------------------------------------
 0/BA5A688 | 10298 | BEGIN 10298
 0/BA5A6F0 | 10298 | table public.data: INSERT: id[integer]:1 data[text]:'1'
 0/BA5A7F8 | 10298 | table public.data: INSERT: id[integer]:2 data[text]:'2'
 0/BA5A8A8 | 10298 | COMMIT 10298
(4 rows)

qhb=# INSERT INTO data(data) VALUES('3');

qhb=# -- Также вы можете заглянуть вперед в потоке изменений, не получая эти изменения
qhb=# SELECT * FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL);
    lsn    |  xid  |                          data
-----------+-------+---------------------------------------------------------
 0/BA5A8E0 | 10299 | BEGIN 10299
 0/BA5A8E0 | 10299 | table public.data: INSERT: id[integer]:3 data[text]:'3'
 0/BA5A990 | 10299 | COMMIT 10299
(3 rows)

qhb=# -- Следующий вызов pg_logical_slot_peek_changes() снова возвращает те же изменения
qhb=# SELECT * FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL);
    lsn    |  xid  |                          data
-----------+-------+---------------------------------------------------------
 0/BA5A8E0 | 10299 | BEGIN 10299
 0/BA5A8E0 | 10299 | table public.data: INSERT: id[integer]:3 data[text]:'3'
 0/BA5A990 | 10299 | COMMIT 10299
(3 rows)

qhb=# -- Плагину вывода можно передать параметры, влияющие на форматирование
qhb=# SELECT * FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include-timestamp', 'on');
    lsn    |  xid  |                          data
-----------+-------+---------------------------------------------------------
 0/BA5A8E0 | 10299 | BEGIN 10299
 0/BA5A8E0 | 10299 | table public.data: INSERT: id[integer]:3 data[text]:'3'
 0/BA5A990 | 10299 | COMMIT 10299 (at 2017-05-10 12:07:21.272494-04)
(3 rows)

qhb=# -- Не забудьте удалить слот, который вам больше не нужен, чтобы он перестал
qhb=# -- потреблять ресурсы сервера:
qhb=# SELECT pg_drop_replication_slot('regression_slot');
 pg_drop_replication_slot
-----------------------

(1 row)

Следующий пример показывает, как можно управлять логическим декодированием с помощью протокола потоковой репликации, используя программу qhb_recvlogical, включенную в дистрибутив QHB. Для этого нужно, чтобы система аутентификация клиентов была настроена допускать подключения репликации (см. подраздел Аутентификация) и чтобы в max_wal_senders было установлено достаточно большое значение, позволяющее установить дополнительное соединение. Во втором примере показано, как транслировать двухфазные транзакции. Прежде чем использовать двухфазные команды, следует установить в параметре max_prepared_transactions значение не меньше 1.

Пример 1:
$ pg_recvlogical -d qhb --slot=test --create-slot
$ pg_recvlogical -d qhb --slot=test --start -f -
Control+Z
$ psql -d qhb -c "INSERT INTO data(data) VALUES('4');"
$ fg
BEGIN 693
table public.data: INSERT: id[integer]:4 data[text]:'4'
COMMIT 693
Control+C
$ pg_recvlogical -d qhb --slot=test --drop-slot

Пример 2:
$ pg_recvlogical -d qhb --slot=test --create-slot --two-phase
$ pg_recvlogical -d qhb --slot=test --start -f -
Control+Z
$ psql -d qhb -c "BEGIN;INSERT INTO data(data) VALUES('5');PREPARE TRANSACTION 'test';"
$ fg
BEGIN 694
table public.data: INSERT: id[integer]:5 data[text]:'5'
PREPARE TRANSACTION 'test', txid 694
Control+Z
$ psql -d qhb -c "COMMIT PREPARED 'test';"
$ fg
COMMIT PREPARED 'test', txid 694
Control+C
$ pg_recvlogical -d qhb --slot=test --drop-slot

Следующий пример показывает SQL-интерфейс, который можно использовать для декодирования подготовленных транзакций. Прежде чем использовать команды двухфазной фиксации, следует установить в параметре max_prepared_transactions значение не меньше 1. Также при создании слота с помощью функции pg_create_logical_replication_slot нужно установить в ее параметре two-phase значение 'true'. Обратите внимание что если транзакция еще не была декодирована, мы будем транслировать ее целиком после фиксации.

qhb=# BEGIN;
qhb=*# INSERT INTO data(data) VALUES('5');
qhb=*# PREPARE TRANSACTION 'test_prepared1';

qhb=# SELECT * FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL);
    lsn    | xid |                          data
-----------+-----+---------------------------------------------------------
 0/1689DC0 | 529 | BEGIN 529
 0/1689DC0 | 529 | table public.data: INSERT: id[integer]:3 data[text]:'5'
 0/1689FC0 | 529 | PREPARE TRANSACTION 'test_prepared1', txid 529
(3 rows)

qhb=# COMMIT PREPARED 'test_prepared1';
qhb=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NULL);
    lsn    | xid |                    data
-----------+-----+--------------------------------------------
 0/168A060 | 529 | COMMIT PREPARED 'test_prepared1', txid 529
(4 row)

qhb=# -- вы также можете откатить подготовленную транзакцию
qhb=# BEGIN;
qhb=*# INSERT INTO data(data) VALUES('6');
qhb=*# PREPARE TRANSACTION 'test_prepared2';
qhb=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NULL);
    lsn    | xid |                          data
-----------+-----+---------------------------------------------------------
 0/168A180 | 530 | BEGIN 530
 0/168A1E8 | 530 | table public.data: INSERT: id[integer]:4 data[text]:'6'
 0/168A430 | 530 | PREPARE TRANSACTION 'test_prepared2', txid 530
(3 rows)

qhb=# ROLLBACK PREPARED 'test_prepared2';
qhb=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NULL);
    lsn    | xid |                     data
-----------+-----+----------------------------------------------
 0/168A4B8 | 530 | ROLLBACK PREPARED 'test_prepared2', txid 530
(1 row)

Принципы логического декодирования

Логическое декодирование

Логическое декодирование — это процесс извлечения всех постоянных изменений, происходящих в таблицах базы данных, в согласованном и простом для понимания формате, который можно интерпретировать, не имея досконального представления о внутреннем состоянии базы данных.

В QHB логическое декодирование реализуется путем декодирования содержимого журнала упреждающей записи, описывающего изменения на уровне хранения, в специфичную для приложения форму, например, в поток кортежей или операторов SQL.

Слоты репликации

В контексте логической репликации слот представляет поток изменений, которые могут быть воспроизведены клиенту в том порядке, в каком они были сделаны на исходном сервере. Через каждый слот передается последовательность изменений из одной базы данных.

Примечание
В QHB также имеются слоты потоковой репликации (см. подраздел Потоковая репликация), но они используются несколько по-другому.

У слота репликации есть идентификатор, уникальный для всех баз данных в кластере QHB. Слоты сохраняются независимо от использующих их подключений и защищены от сбоев сервера.

В обычном режиме работы логический слот будет выдавать каждое изменение только один раз. Текущая позиция каждого слота сохраняется только в контрольной точке, поэтому в случае сбоя слот может вернуться к предыдущему LSN, из-за чего впоследствии при перезапуске сервера последние изменения могут быть отправлены повторно. За предотвращение нежелательных эффектов от повторной обработки одного и того же сообщения отвечают клиенты логического декодирования. При необходимости клиенты могут при декодировании записывать последний полученный ими LSN и пропускать повторяющиеся данные или (при использовании протокола репликации) запрашивать, чтобы декодирование началось с этого LSN, вместо того чтобы позволить серверу определить начальную точку. Для этой цели разработан механизм отслеживания хода выполнения репликации, о котором можно прочитать в описании источников репликации.

Для одной базы данных могут существовать несколько независимых слотов. Каждый слот имеет собственное состояние, позволяя разным потребителям получать изменения из разных точек в потоке изменений базы данных. Для большинства приложений каждому потребителю понадобится отдельный слот.

Слот логической репликации ничего не знает о состоянии получателя(ей). Можно даже иметь несколько различных получателей, использующих один слот в разные моменты времени; они просто будут получать изменения, начиная с того момента, когда их перестал потреблять предыдущий получатель. Но в любой отдельно взятый момент времени потреблять изменения из слота может только один получатель.

ВНИМАНИЕ!
Слоты репликации сохраняются при сбоях сервера и ничего не знают о состоянии их потребителя(ей). Они не дадут удалить требуемые ресурсы, даже когда не используются никаким подключением. Это занимает место в хранилище, поскольку ни требуемые записи WAL, ни требуемые строки из системных каталогов нельзя удалить командой VACUUM, пока они нужны слоту репликации. В исключительных случаях это может привести к отключению базы данных для предотвращения зацикливания идентификаторов транзакций (см. подраздел Предотвращение ошибок зацикливания идентификаторов транзакций). Поэтому если слот больше не нужен, его следует удалить.

Плагины вывода

Плагины вывода трансформируют данные из внутреннего представления журнала упреждающей записи в формат, запрашиваемый потребителем слота репликации.

Экспортированные снимки

Когда новый слот репликации создается через интерфейс потоковой репликации (см. CREATE_REPLICATION_SLOT), экспортируется снимок (см. подраздел Функции синхронизации снимков), который будет показывать ровно то состояние базы данных, все изменения после которого будут включаться в поток изменений. Это можно использовать для создания новой реплики с помощью команды SET TRANSACTION SNAPSHOT, чтобы прочитать состояние базы данных в момент создания слота. Затем эту транзакцию можно использовать для выгрузки состояния базы данных на этот момент времени, которое впоследствии можно обновлять с помощью содержимого слота, не теряя никаких изменений.

Создание снимка возможно не всегда. В частности, это не получится при подключении к горячему резерву. Приложения, которым не нужен экспорт снимка, могут подавить его, установив параметр NOEXPORT_SNAPSHOT.

Интерфейс протокола потоковой репликации

Команды

CREATE_REPLICATION_SLOT имя_слота LOGICAL плагин_вывода

DROP_REPLICATION_SLOT имя_слота [ WAIT ]

START_REPLICATION SLOT имя_слота LOGICAL ...

используются для создания, удаления и потоковой передачи изменений из слота репликации соответственно. Эти команды доступны только для соединения репликации; их нельзя использовать в обычном SQL. Подробную информацию об этих командах см. в разделе Протокол потоковой репликации.

Для управления логическим декодированием по соединению потоковой репликации можно воспользоваться утилитой qhb_recvlogical. (В ней используются эти команды.)

SQL-интерфейс логического декодирования

Подробное описание API уровня SQL для взаимодействия с логическим декодированием см. в подразделе Функции для управления репликацией.

Синхронная репликация (см. подраздел Синхронная репликация) поддерживается только для слотов репликации, используемых через интерфейс потоковой репликации. Интерфейс функций и дополнительные, не базовые интерфейсы не поддерживают синхронную репликацию.

Системные каталоги, связанные с логическим декодированием

В представлениях pg_replication_slots и pg_stat_replication выводится информация о текущем состоянии слотов репликации и соединений потоковой репликации соответственно. Эти представления относятся как к физической, так и к логической репликации. В представлении pg_stat_replication_slots выводится статистическая информация о слотах логической репликации.

Плагины вывода логического декодирования

Пример плагина вывода можно найти на справочной странице модуля test_decoding в Приложении данного руководства.

Функция инициализации

Плагин вывода загружается путем динамической загрузки разделяемой библиотеки, при этом в качестве базового имени библиотеки задается имя этого плагина. Для нахождения библиотеки используется обычный путь поиска библиотек. Чтобы предоставить требуемые функции обратного вызова плагина вывода и показать, что эта библиотека действительно является плагином вывода, она должна иметь функцию с именем _PG_output_plugin_init. Этой функции передается структура, которую нужно заполнить указателями на функции обратного вызова для определенных действий:

typedef struct OutputPluginCallbacks
{
    LogicalDecodeStartupCB startup_cb;
    LogicalDecodeBeginCB begin_cb;
    LogicalDecodeChangeCB change_cb;
    LogicalDecodeTruncateCB truncate_cb;
    LogicalDecodeCommitCB commit_cb;
    LogicalDecodeMessageCB message_cb;
    LogicalDecodeFilterByOriginCB filter_by_origin_cb;
    LogicalDecodeShutdownCB shutdown_cb;
    LogicalDecodeFilterPrepareCB filter_prepare_cb;
    LogicalDecodeBeginPrepareCB begin_prepare_cb;
    LogicalDecodePrepareCB prepare_cb;
    LogicalDecodeCommitPreparedCB commit_prepared_cb;
    LogicalDecodeRollbackPreparedCB rollback_prepared_cb;
    LogicalDecodeStreamStartCB stream_start_cb;
    LogicalDecodeStreamStopCB stream_stop_cb;
    LogicalDecodeStreamAbortCB stream_abort_cb;
    LogicalDecodeStreamPrepareCB stream_prepare_cb;
    LogicalDecodeStreamCommitCB stream_commit_cb;
    LogicalDecodeStreamChangeCB stream_change_cb;
    LogicalDecodeStreamMessageCB stream_message_cb;
    LogicalDecodeStreamTruncateCB stream_truncate_cb;
} OutputPluginCallbacks;

typedef void (*LogicalOutputPluginInit) (struct OutputPluginCallbacks *cb);

Функции обратного вызова begin_cb, change_cb и commit_cb являются обязательными, а startup_cb, filter_by_origin_cb, truncate_cb и shutdown_cb необязательны. Если truncate_cb не установлена, но нужно декодировать TRUNCATE, эта операция игнорируется.

Также плагин вывода может определять функции для поддержки потоковой передачи больших транзакций во время их выполнения. Функции stream_start_cb, stream_stop_cb, stream_abort_cb, stream_commit_cb, stream_change_cb и stream_prepare_cb являются обязательными, а stream_message_cb и stream_truncate_cb необязательны.

Также плагин вывода может определять функции для поддержки двухфазных фиксаций, позволяющие декодировать операции в PREPARE TRANSACTION. Функции обратного вызова begin_prepare_cb, prepare_cb, stream_prepare_cb, commit_prepared_cb и rollback_prepared_cb являются обязательными, а filter_prepare_cb необязательна.

Возможности

Для декодирования, форматирования и вывода изменений плагины вывода могут использовать практически всю обычную инфраструктуру сервера, включая вызов функций вывода. К отношениям разрешен доступ только на чтение, только если они были созданы утилитой qhb_bootstrap (или initdb) в схеме pg_catalog или были помечены как пользовательские таблицы каталога с помощью команд

ALTER TABLE user_catalog_table SET (user_catalog_table = true);
CREATE TABLE another_catalog_table(data text) WITH (user_catalog_table = true);

Обратите внимание, что обращение к пользовательским таблицам каталога или обычным системным таблицам каталога в плагинах вывода должно осуществляться только посредством функций сканирования systable_*. Обращение через функции сканирования heap_* вызовет ошибку. Кроме того, запрещены любые операции, ведущие к присвоению идентификатора транзакции. К таким операциями, среди прочих, относятся записи в таблицы, выполнение изменений DDL и вызов функции pg_current_xact_id().

Режимы вывода

Функции обратного вызова плагина вывода могут передавать данные потребителю практически в любых форматах. Для некоторых вариантов применения, например, просмотра изменений через SQL, возвращение данных в типах, которые могут содержать произвольные данные (например, bytea), может быть неудобным. Если плагин вывода выводит только текстовые данные в кодировке сервера, он может объявить это, установив в OutputPluginOptions.output_type значение OUTPUT_PLUGIN_TEXTUAL_OUTPUT вместо OUTPUT_PLUGIN_BINARY_OUTPUT в функции обратного вызова для запуска. В этом случае все данные должны быть в кодировке сервера, чтобы их могло содержать значение типа text. Это проверяется в сборках с включенными проверочными утверждениями.

Функции обратного вызова в плагине вывода

Плагин вывода уведомляется о происходящих изменениях через различные функции обратного вызова, которые он должен предоставить.

Параллельные транзакции декодируются в порядке фиксации, при этом между функциями обратного вызова begin и commit декодируются только изменения, относящиеся к определенной транзакции. Явно или неявно откатившиеся транзакции никогда не декодируются. Успешные точки сохранения помещаются в содержащую их транзакцию в том порядке, в котором они выполнялись в этой транзакции. Транзакция, подготовленная для двухфазной фиксации с помощью команды PREPARE TRANSACTION, тоже будет декодирована, если плагином вывода были предоставлены требуемые для декодирования функции обратного вызова. Возможно такое, что текущая декодируемая подготовленная транзакция параллельно прерывается командой ROLLBACK PREPARED. В этом случае логическое декодирование этой транзакции тоже будет прервано. Все изменения такой транзакции пропускаются после выявления прерывания и вызова функции обратного вызова prepare_cb. Таким образом, даже в случае параллельного прерывания плагину вывода предоставляется достаточно информации, чтобы он мог правильно обработать ROLLBACK PREPARED после ее декодирования.

Примечание
Декодироваться будут только те транзакции, которые уже были успешно сброшены на диск. В результате этого COMMIT может не декодироваться в непосредственно следующей за ним функции pg_logical_slot_get_changes(), когда в параметре synchronous_commit установлено значение off (выключен).

Функция обратного вызова для запуска

Необязательная функция обратного вызова startup_cb вызывается, когда слот репликации создается или через него запрашивается потоковая передача изменений, независимо от количества изменений, готовых к отправке.

typedef void (*LogicalDecodeStartupCB) (struct LogicalDecodingContext *ctx,
                                        OutputPluginOptions *options,
                                        bool is_init);

Параметр is_init будет равен true, когда слот репликации создается, и false в противном случае. Параметр options указывает на структуру параметров, которые могут устанавливать плагины вывода:

typedef struct OutputPluginOptions
{
    OutputPluginOutputType output_type;
    bool        receive_rewrites;
} OutputPluginOptions;

В output_type следует установить значение OUTPUT_PLUGIN_TEXTUAL_OUTPUT или OUTPUT_PLUGIN_BINARY_OUTPUT. См. также подраздел Режимы вывода. Если receive_rewrites равен true, плагин вывода также будет вызываться для изменений, произведенных перезаписями кучи при определенных операциях DDL. Эти изменения представляют интерес для плагинов, проводящих репликацию DDL, но они требуют особой обработки.

Функция запуска должна проверить параметры, представленные в ctx->output_plugin_options. Если плагину вывода нужно иметь состояние, он может хранить его в ctx->output_plugin_private.

Функция обратного вызова для выключения

Необязательная функция обратного вызова shutdown_cb вызывается, когда ранее активный слот репликации перестает использоваться и может освободить ресурсы, занятые плагином вывода. При этом слот не всегда удаляется, прекращается только потоковая передача через него.

typedef void (*LogicalDecodeShutdownCB) (struct LogicalDecodingContext *ctx);

Функция обратного вызова для начала транзакции

Обязательная функция обратного вызова begin_cb вызывается, когда декодируется начало зафиксированной транзакции. Прерванные транзакции и их содержимое никогда не декодируются.

typedef void (*LogicalDecodeBeginCB) (struct LogicalDecodingContext *ctx,
                                      ReorderBufferTXN *txn);

Параметр txn содержит метаинформацию о транзакции, например временную метку ее фиксации и ее идентификатор.

Функция обратного вызова для завершения транзакции

Обязательная функция обратного вызова commit_cb вызывается, когда декодируется фиксация транзакции. Перед этим для всех модифицированных строк (если таковые имеются) будут вызваны функции обратного вызова change_cb.

typedef void (*LogicalDecodeCommitCB) (struct LogicalDecodingContext *ctx,
                                       ReorderBufferTXN *txn,
                                       XLogRecPtr commit_lsn);

Функция обратного вызова для изменений

Обязательная функция обратного вызова change_cb вызывается для каждой отдельной модификации строки в транзакции, производимой командами INSERT, UPDATE или DELETE. Даже если исходная команда модифицировала несколько строк сразу, эта функция обратного вызова будет вызываться отдельно для каждой строки. Функция change_cb может обращаться к системным или пользовательским таблицам каталога, чтобы добавить в процессе вывода подробную информацию о модификации строки. В случае декодирования подготовленной (но еще не зафиксированной) транзакции или декодирования незафиксированной транзакции эта функция для изменений также может выдать ошибку из-за одновременного отката этой самой транзакции. В этом случае логическое декодирование этой прерванной транзакции корректно останавливается.

typedef void (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx,
                                       ReorderBufferTXN *txn,
                                       Relation relation,
                                       ReorderBufferChange *change);

Параметры ctx и txn имеют то же содержимое, что и для функций обратного вызова begin_cb и commit_cb, но с дополнительным дескриптором отношений relation, указывающим на отношение, которому принадлежит строка, и структурой change, описывающей передаваемую модификацию строки.

Примечание
С помощью логического декодирования могут быть извлечены изменения только в пользовательских таблицах, не являющихся нежурналируемыми (см. UNLOGGED) или временными (см. TEMPORARY или TEMP).

Функция обратного вызова для опустошения

Функция обратного вызова truncate_cb вызывается для команды TRUNCATE.

typedef void (*LogicalDecodeTruncateCB) (struct LogicalDecodingContext *ctx,
                                         ReorderBufferTXN *txn,
                                         int nrelations,
                                         Relation relations[],
                                         ReorderBufferChange *change);

Параметры такие же, как для функции обратного вызова change_cb. Однако поскольку операции TRUNCATE в таблицах, связанных внешними ключами, должны выполняться вместе, эта функция получает на вход не одно отношение, а массив отношений. Подробную информацию см. на справочной странице команды TRUNCATE.

Функция обратного вызова для фильтра источника

Необязательная функция обратного вызова filter_by_origin_cb вызывается, чтобы определить, интересуют ли плагин вывода данные, воспроизводимые из источника с заданным идентификатором (origin_id).

typedef bool (*LogicalDecodeFilterByOriginCB) (struct LogicalDecodingContext *ctx,
                                               RepOriginId origin_id);

Параметр ctx имеет то же содержимое, что и для других функций обратного вызова. Доступна информация только об источнике. Чтобы показать, что изменения, поступающие из переданного узла, не представляют интереса, плагин возвращает true, вследствие чего они отфильтровываются; в противном случае он возвращает false. Другие функции обратного вызова для отфильтрованных транзакций и изменений вызываться не будут.

Это полезно при реализации каскадных или разнонаправленных решений репликации. В таких конфигурациях фильтрация по источнику позволяет предотвратить репликацию туда-обратно одних и тех же изменений. Хотя в транзакциях и изменениях тоже переносится информация об источнике, фильтрация посредством этой функции гораздо более эффективна.

Функция обратного вызова для произвольных сообщений

Необязательная функция обратного вызова message_cb вызывается, когда декодируется сообщение логического декодирования.

typedef void (*LogicalDecodeMessageCB) (struct LogicalDecodingContext *ctx,
                                        ReorderBufferTXN *txn,
                                        XLogRecPtr message_lsn,
                                        bool transactional,
                                        const char *prefix,
                                        Size message_size,
                                        const char *message);

Параметр txn содержит метаинформацию о транзакции, например временную метку ее фиксации и ее идентификатор. Однако обратите внимание, что он может быть равен NULL, когда сообщение нетранзакционное, и транзакции, протоколирующей сообщение, еще не был присвоен идентификатор. В параметре lsn содержится позиция сообщения в WAL. Параметр transactional показывает, было ли сообщение передано как транзакционное или нет. Подобно функции обратного вызова для изменений, в случае декодирования подготовленной (но еще не зафиксированной) транзакции или декодирования незафиксированной транзакции, эта функция для сообщений тоже может выдать ошибку из-за одновременного отката этой самой транзакции. В этом случае логическое декодирование этой прерванной транзакции корректно останавливается. В параметре prefix передается произвольный префикс (завершающийся нулевым символом), который можно использовать для выделения сообщений, интересующих текущий плагин. И, наконец, в параметре message содержится само сообщение размером message_size байт.

Следует дополнительно позаботиться о том, чтобы префикс, интересующий плагин вывода, был уникальным. Обычно лучше всего использовать имя расширения или самого плагина вывода.

Функция обратного вызова для фильтра подготовки

Необязательная функция обратного вызова filter_prepare_cb вызывается, чтобы определить, следует ли декодировать данные, составляющие текущую транзакцию с двухфазной фиксацией, на этом этапе подготовки или позже, в виде обычной однофазной транзакции во время выполнения команды COMMIT PREPARED. Чтобы показать, что декодирование нужно пропустить, возвращает true; в противном случае — false. Когда функция обратного вызова не определена, предполагается false (т. е. фильтрация отсутствует, все транзакции, использующие двухфазную фиксацию, декодируются также в две фазы).

typedef bool (*LogicalDecodeFilterPrepareCB) (struct LogicalDecodingContext *ctx,
                                              TransactionId xid,
                                              const char *gid);

Параметр ctx имеет то же содержимое, что и для других функций обратного вызова. Параметры xid и gid предоставляют два разных способа идентификации транзакции. Последующая команда COMMIT PREPARED или ROLLBACK PREPARED передает оба идентификатора, давая плагину вывода выбор, какой из них использовать.

Эта функция может вызываться несколько раз в течение одной декодируемой транзакции и при каждом вызове должна предоставлять один и тот же постоянный ответ для данной пары xid и gid.

Функция обратного вызова для начала подготовленной транзакции

Обязательная функция обратного вызова begin_prepare_cb вызывается, когда декодируется начало подготовленной транзакции. Поле gid, входящее в параметр txn, в этой функции можно применять для проверки того, получал ли уже этот плагин данную команду PREPARE, и если да, он может либо выдать ошибку, либо пропустить оставшиеся изменения транзакции.

typedef void (*LogicalDecodeBeginPrepareCB) (struct LogicalDecodingContext *ctx,
                                             ReorderBufferTXN *txn);

Функция обратного вызова для подготовленной транзакции

Обязательная функция обратного вызова prepare_cb вызывается, когда декодируется транзакция, подготовленная для двухфазной фиксации. Перед этим для всех модифицированных строк (если таковые имеются) будет вызвана функция обратного вызова change_cb. В этой функции можно применять поле gid, входящее в параметр txn.

typedef void (*LogicalDecodePrepareCB) (struct LogicalDecodingContext *ctx,
                                        ReorderBufferTXN *txn,
                                        XLogRecPtr prepare_lsn);

Функция обратного вызова для фиксации подготовленной транзакции

Обязательная функция обратного вызова commit_prepared_cb вызывается, когда декодируется команда COMMIT PREPARED транзакции. В этой функции можно применять поле gid, входящее в параметр txn.

typedef void (*LogicalDecodeCommitPreparedCB) (struct LogicalDecodingContext *ctx,
                                               ReorderBufferTXN *txn,
                                               XLogRecPtr commit_lsn);

Функция обратного вызова для отката подготовленной транзакции

Обязательная функция обратного вызова rollback_prepared_cb вызывается, когда декодируется команда ROLLBACK PREPARED транзакции. В этой функции можно применять поле gid, входящее в параметр txn. Параметры prepare_end_lsn и prepare_time можно использовать для проверки того, получал ли этот плагин данную команду PREPARE TRANSACTION, и если да, он может применить откат, а в противном случае пропустить эту операцию. Одного поля gid для этого недостаточно, поскольку на нижестоящем узле может быть подготовленная транзакция с таким же идентификатором.

typedef void (*LogicalDecodeRollbackPreparedCB) (struct LogicalDecodingContext *ctx,
                                                 ReorderBufferTXN *txn,
                                                 XLogRecPtr prepare_end_lsn,
                                                 TimestampTz prepare_time);

Функция обратного вызова для начала потока

Функция обратного вызова stream_start_cb вызывается, когда открывается блок передаваемых в потоке изменений из выполняющейся транзакции.

typedef void (*LogicalDecodeStreamStartCB) (struct LogicalDecodingContext *ctx,
                                            ReorderBufferTXN *txn);

Функция обратного вызова для остановки потока

Функция обратного вызова stream_stop_cb вызывается, когда закрывается блок передаваемых в потоке изменений из выполняющейся транзакции.

typedef void (*LogicalDecodeStreamStopCB) (struct LogicalDecodingContext *ctx,
                                           ReorderBufferTXN *txn);

Функция обратного вызова для прерывания потока

Функция обратного вызова stream_abort_cb вызывается, чтобы прервать ранее переданную в потоке транзакцию.

typedef void (*LogicalDecodeStreamAbortCB) (struct LogicalDecodingContext *ctx,
                                            ReorderBufferTXN *txn,
                                            XLogRecPtr abort_lsn);

Функция обратного вызова для подготовки потока

Функция обратного вызова stream_prepare_cb вызывается, чтобы подготовить ранее переданную в потоке транзакцию как часть двухфазной фиксации.

typedef void (*LogicalDecodeStreamPrepareCB) (struct LogicalDecodingContext *ctx,
                                              ReorderBufferTXN *txn,
                                              XLogRecPtr prepare_lsn);

Функция обратного вызова для фиксации потока

Функция обратного вызова stream_commit_cb вызывается, чтобы зафиксировать ранее переданную в потоке транзакцию.

typedef void (*LogicalDecodeStreamCommitCB) (struct LogicalDecodingContext *ctx,
                                             ReorderBufferTXN *txn,
                                             XLogRecPtr commit_lsn);

Функция обратного вызова для изменений в потоке

Функция обратного вызова stream_change_cb вызывается, когда передается изменение в блоке поточно передаваемых изменений (границы которого устанавливают вызовы stream_start_cb и stream_stop_cb). Фактические изменения не отображаются, поскольку транзакция может позже прерваться, а изменения для прерванных транзакций не декодируются.

typedef void (*LogicalDecodeStreamChangeCB) (struct LogicalDecodingContext *ctx,
                                             ReorderBufferTXN *txn,
                                             Relation relation,
                                             ReorderBufferChange *change);

Функция обратного вызова для сообщений потока

Функция обратного вызова stream_message_cb вызывается, когда передается произвольное сообщение в блоке поточно передаваемых изменений (границы которого устанавливают вызовы stream_start_cb и stream_stop_cb). Содержимое транзакционных сообщений не отображается, поскольку может позже прерваться, а изменения для прерванных транзакций не декодируются.

typedef void (*LogicalDecodeStreamMessageCB) (struct LogicalDecodingContext *ctx,
                                              ReorderBufferTXN *txn,
                                              XLogRecPtr message_lsn,
                                              bool transactional,
                                              const char *prefix,
                                              Size message_size,
                                              const char *message);

Функция обратного вызова для опустошения потока

Функция обратного вызова stream_truncate_cb вызывается для команды TRUNCATE в блоке поточно передаваемых изменений (границы которого устанавливают вызовы stream_start_cb и stream_stop_cb).

typedef void (*LogicalDecodeStreamTruncateCB) (struct LogicalDecodingContext *ctx,
                                               ReorderBufferTXN *txn,
                                               int nrelations,
                                               Relation relations[],
                                               ReorderBufferChange *change);

Параметры аналогичны параметрам функции обратного вызова stream_change_cb. Однако поскольку операции TRUNCATE в таблицах, связанных внешними ключами, должны выполняться одновременно, эта функция получает на вход не одно отношение, а массив отношений. Подробную информацию см. на справочной странице команды TRUNCATE.

Функции для формирования вывода

Чтобы действительно сформировать вывод, плагины вывода могу записывать данные в выходной буфер StringInfo через ctx->out внутри функций обратного вызова begin_cb, commit_cb или change_cb. Прежде чем записывать данные в этот буфер, нужно вызвать OutputPluginPrepareWrite(ctx, last_write), а по завершении записи в буфер нужно вызвать OutputPluginWrite(ctx, last_write), чтобы сформировать запись. Параметр last_write показывает, была ли эта конкретная операция записи последней в данной функции обратного вызова.

Следующий пример показывает, как вывести данные для потребителя плагина вывода:

OutputPluginPrepareWrite(ctx, true);
appendStringInfo(ctx->out, "BEGIN %u", txn->xid);
OutputPluginWrite(ctx, true);

Запись вывода логического декодирования

Существует возможность добавлять другие методы вывода для логического декодирования. По сути, нужно предоставить три функции: одну для чтения WAL, одну для подготовки к записи и одну для записи вывода (см. подраздел Функции для формирования вывода).

/*-------------------------------------------------------------------------
 *     Впомогательные функции для использования логического декодирования и
 *     управления слотами логической репликации посредством SQL.
 *-------------------------------------------------------------------------
 */

#include "qhb.h"

#include <unistd.h>

#include "access/xact.h"
#include "access/xlog_internal.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "catalog/pg_type.h"
#include "fmgr.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "replication/decode.h"
#include "replication/logical.h"
#include "replication/message.h"
#include "storage/fd.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_lsn.h"
#include "utils/regproc.h"
#include "utils/resowner.h"

/* Внутренние данные для выписываемых данных */
typedef struct DecodingOutputState
{
   Tuplestorestate *tupstore;
   TupleDesc   tupdesc;
   bool        binary_output;
   int64       returned_rows;
} DecodingOutputState;

/*
 * Подготовка к записи плагина вывода.
 */
static void
LogicalOutputPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
                         bool last_write)
{
   resetStringInfo(ctx->out);
}
/*
 * Выполнение записи плагина вывода в хранилище кортежей.
 */
static void
LogicalOutputWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
                  bool last_write)
{
   Datum       values[3];
   bool        nulls[3];
   DecodingOutputState *p;
   /* Данные SQL могут быть только ограниченной длины... */
   if (ctx->out->len > MaxAllocSize - VARHDRSZ)
       elog(ERROR, "too much output for sql interface");

   p = (DecodingOutputState *) ctx->output_writer_private;

   memset(nulls, 0, sizeof(nulls));
   values[0] = LSNGetDatum(lsn);
   values[1] = TransactionIdGetDatum(xid);

   /*
    * Когда мы записываем текстовый вывод, добавление ctx->out происходит в
    * кодировке базы данных.
    */
   if (!p->binary_output)
       Assert(pg_verify_mbstr(GetDatabaseEncoding(),
                              ctx->out->data, ctx->out->len,
                              false));

   /* неприглядно, но cstring_to_text_with_len прекрасно работает для bytea */
   values[2] = PointerGetDatum(cstring_to_text_with_len(ctx->out->data, ctx->out->len));

   tuplestore_putvalues(p->tupstore, p->tupdesc, values, nulls);
   p->returned_rows++;
}

/*
 * Функция-помощник для различных вызываемых через SQL функций логического декодирования.
 */
static Datum
pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool binary)
{
   Name        name;
   XLogRecPtr  upto_lsn;
   int32       upto_nchanges;
   ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
   MemoryContext per_query_ctx;
   MemoryContext oldcontext;
   XLogRecPtr  end_of_wal;
   LogicalDecodingContext *ctx;
   ResourceOwner old_resowner = CurrentResourceOwner;
   ArrayType  *arr;
   Size        ndim;
   List       *options = NIL;
   DecodingOutputState *p;

   CheckSlotPermissions();

   CheckLogicalDecodingRequirements();

   if (PG_ARGISNULL(0))
       ereport(ERROR,
               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                errmsg("slot name must not be null")));
   name = PG_GETARG_NAME(0);

   if (PG_ARGISNULL(1))
       upto_lsn = InvalidXLogRecPtr;
   else
       upto_lsn = PG_GETARG_LSN(1);

   if (PG_ARGISNULL(2))
       upto_nchanges = InvalidXLogRecPtr;
   else
       upto_nchanges = PG_GETARG_INT32(2);

   if (PG_ARGISNULL(3))
       ereport(ERROR,
               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                errmsg("options array must not be null")));
   arr = PG_GETARG_ARRAYTYPE_P(3);

   /* состояние, в котором должен записываться вывод */
   p = palloc0(sizeof(DecodingOutputState));

   p->binary_output = binary;

   per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
   oldcontext = MemoryContextSwitchTo(per_query_ctx);

   /* Деконструировать массив параметров */
   ndim = ARR_NDIM(arr);
   if (ndim > 1)
   {
       ereport(ERROR,
               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("array must be one-dimensional")));
   }
   else if (array_contains_nulls(arr))
   {
       ereport(ERROR,
               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("array must not contain nulls")));
   }
   else if (ndim == 1)
   {
       int         nelems;
       Datum      *datum_opts;
       int         i;

       Assert(ARR_ELEMTYPE(arr) == TEXTOID);

       deconstruct_array_builtin(arr, TEXTOID, &datum_opts, NULL, &nelems);

       if (nelems % 2 != 0)
           ereport(ERROR,
                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("array must have even number of elements")));

       for (i = 0; i < nelems; i += 2)
       {
           char       *optname = TextDatumGetCString(datum_opts[i]);
           char       *opt = TextDatumGetCString(datum_opts[i + 1]);

           options = lappend(options, makeDefElem(optname, (Node *) makeString(opt), -1));
       }
   }

   InitMaterializedSRF(fcinfo, 0);
   p->tupstore = rsinfo->setResult;
   p->tupdesc = rsinfo->setDesc;

   /*
    * Вычисление текущего конца WAL.
    */
   if (!RecoveryInProgress())
       end_of_wal = GetFlushRecPtr(NULL);
   else
       end_of_wal = GetXLogReplayRecPtr(NULL);

   ReplicationSlotAcquire(NameStr(*name), true);

   PG_TRY();
   {
       /* перезапуск в функции confirmed_flush слота */
       ctx = CreateDecodingContext(InvalidXLogRecPtr,
                                   options,
                                   false,
                                   XL_ROUTINE(.page_read = read_local_xlog_page,
                                              .segment_open = wal_segment_open,
                                              .segment_close = wal_segment_close),
                                   LogicalOutputPrepareWrite,
                                   LogicalOutputWrite, NULL);

       MemoryContextSwitchTo(oldcontext);

       /*
        * Проверить, записывает ли плагин вывода текстовый вывод, если это то,
        * что нам нужно.
        */
       if (!binary &&
           ctx->options.output_type !=OUTPUT_PLUGIN_TEXTUAL_OUTPUT)
           ereport(ERROR,
                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("logical decoding output plugin \"%s\" produces binary output, but function \"%s\" expects textual data",
                           NameStr(MyReplicationSlot->data.plugin),
                           format_procedure(fcinfo->flinfo->fn_oid))));

       ctx->output_writer_private = p;

       /*
        * Декодирование WAL должно начаться в restart_lsn, чтобы все
        * xact, зафиксированные после выполнения confirmed_flush слота, могли быть
        * собраны в буферы переупорядочивания.
        */
       XLogBeginRead(ctx->reader, MyReplicationSlot->data.restart_lsn);

       /* аннулировать нереверсируемые строки */
       InvalidateSystemCaches();

       /* Декодировать, пока не кончатся записи */
       while (ctx->reader->EndRecPtr < end_of_wal)
       {
           XLogRecord *record;
           char       *errm = NULL;

           record = XLogReadRecord(ctx->reader, &errm);
           if (errm)
               elog(ERROR, "could not find record for logical decoding: %s", errm);

           /*
            * Показанные выше функции обратного вызова {begin_txn,change,commit_txn}_wrapper
            * сохранят описание в нашем хранилище кортежей.
            */
           if (record != NULL)
               LogicalDecodingProcessRecord(ctx, ctx->reader);

           /* check limits */
           if (upto_lsn != InvalidXLogRecPtr &&
               upto_lsn <= ctx->reader->EndRecPtr)
               break;
           if (upto_nchanges != 0 &&
               upto_nchanges <= p->returned_rows)
               break;
           CHECK_FOR_INTERRUPTS();
       }

       /*
        * Логическое декодирование могло бы затереть CurrentResourceOwner во время
        * управления транзакциями, поэтому восстанавливаем значение исполнителя.
        * (Это топорно, но прямо сейчас в очистке нет смысла.)
        */
       CurrentResourceOwner = old_resowner;

       /*
        * В следующий раз начнем там, где закончили.
        */
       if (ctx->reader->EndRecPtr != InvalidXLogRecPtr && confirm)
       {
           LogicalConfirmReceivedLocation(ctx->reader->EndRecPtr);

           /*
            * Если изменился только confirmed_flush_lsn, слот не будет помечен
            * функцией выше как «грязный». Ожидается, что вызывающие функции в
            * интерфейсе передатчика WAL будут отслеживать свой прогресс и не
            * нуждаются в том, чтобы его записывать. Но функции, использующие
            * SQL-интерфейс, не могут указать собственные начальные позиции, и им
            * труднее отслеживать свой прогресс, поэтому мы должны приложить больше
            * усилий, чтобы сохранить для них этот прогресс.
            *
            * «Загрязним» слот, чтобы он записался при следующей контрольной точке.
            * Мы все равно потеряем его позицию при сбое (это подтверждено), но это
            * лучше, чем постоянно терять эту позицию даже при обычном перезапуске.
            */
           ReplicationSlotMarkDirty();
       }

       /* освобожение контекста, вызов функции обратного вызова для выключения */
       FreeDecodingContext(ctx);

       ReplicationSlotRelease();
       InvalidateSystemCaches();
   }
   PG_CATCH();
   {
       /* очистка всех реверсируемых строк */
       InvalidateSystemCaches();

       PG_RE_THROW();
   }
   PG_END_TRY();

   return (Datum) 0;
}

/*
 * Функция SQL, возвращающая поток изменений в виде текста, потребляющая данные.
 */
Datum
pg_logical_slot_get_changes(PG_FUNCTION_ARGS)
{
   return pg_logical_slot_get_changes_guts(fcinfo, true, false);
}

/*
 * Функция SQL, возвращающая поток изменений в виде текста, только чуть позже.
 */
Datum
pg_logical_slot_peek_changes(PG_FUNCTION_ARGS)
{
   return pg_logical_slot_get_changes_guts(fcinfo, false, false);
}

/*
 * Функция SQL, возвращающая поток изменений в виде двоичных данных, потребляющая данные.
 */
Datum
pg_logical_slot_get_binary_changes(PG_FUNCTION_ARGS)
{
   return pg_logical_slot_get_changes_guts(fcinfo, true, true);
}

/*
 * Функция SQL, возвращающая поток изменений в виде двоичных данных, только чуть позже.
 */
Datum
pg_logical_slot_peek_binary_changes(PG_FUNCTION_ARGS)
{
   return pg_logical_slot_get_changes_guts(fcinfo, false, true);
}

/*
 * Функция SQL для записи сообщения логического декодирования в WAL.
 */
Datum
pg_logical_emit_message_bytea(PG_FUNCTION_ARGS)
{
   bool        transactional = PG_GETARG_BOOL(0);
   char       *prefix = text_to_cstring(PG_GETARG_TEXT_PP(1));
   bytea      *data = PG_GETARG_BYTEA_PP(2);
   bool        flush = PG_GETARG_BOOL(3);
   XLogRecPtr  lsn;

   lsn = LogLogicalMessage(prefix, VARDATA_ANY(data), VARSIZE_ANY_EXHDR(data),
                           transactional, flush);
   PG_RETURN_LSN(lsn);
}

Datum
pg_logical_emit_message_text(PG_FUNCTION_ARGS)
{
   /* bytea и text совместимы */
   return pg_logical_emit_message_bytea(fcinfo);
}

Поддержка синхронной репликации для логического декодирования

Обзор

Логическое декодирование можно применять для создания решений синхронной репликации с тем же пользовательским интерфейсом, что и у синхронной репликации поверх потоковой репликации. Для этого потоковая передача данных должна проводиться через интерфейс потоковой репликации (см. раздел Интерфейс протокола потоковой репликации). Клиенты должны передавать сообщения Standby status update (F) (см. раздел Протокол потоковой репликации), как это делают клиенты потоковой репликации.

Примечание
Синхронная реплика, получающая изменения через логическое декодирование, будет работать в рамках одной базы данных. Поскольку в настоящее время параметр synchronous_standby_names, напротив, устанавливается на уровне сервера, это означает, что данная методика не будет работать корректно при активном использовании нескольких баз данных.

Ограничения применимости

В конфигурации с синхронной репликацией может возникнуть взаимоблокировка, если транзакция в эксклюзивном режиме заблокирует таблицы каталога (в т. ч. пользовательские). Информацию о пользовательских таблицах каталога см. в подразделе Возможности. Это происходит потому, что логическое декодирование транзакций может заблокировать таблицы каталога при обращении к ним. Во избежание этого пользователи должны воздерживаться от эксклюзивной блокировки таблиц каталога (в т. ч. пользовательских). Такую блокировку могут вызвать следующие действия:

Установка явной блокировки (с помощью команды LOCK) для каталога pg_class в транзакции.

Выполнение CLUSTER для каталога pg_class в транзакции.

Выполнение PREPARE TRANSACTION после команды LOCK для каталога pg_class, с разрешенным логическим декодированием двухфазных транзакций.

Выполнение PREPARE TRANSACTION после команды CLUSTER для каталога pg_trigger, с разрешенным логическим декодированием двухфазных транзакций. Это приведет к взаимоблокировке, только если у публикуемой таблицы есть триггер.

Выполнение TRUNCATE для таблицы каталога (в т. ч. пользовательской) в транзакции.

Обратите внимание, что эти команды, способные вызвать взаимоблокировку, могут применяться не только к явно указанным выше таблицам системного каталога, но и к любой другой таблице каталога (в т. ч. пользовательской).

Потоковая передача больших транзакций для логического декодирования

Базовые функции обратного вызова в плагине вывода (например, begin_cb, change_cb, commit_cb и message_cb) вызываются, только когда транзакция действительно фиксируется. Изменения в любом случае декодируются из журнала транзакций, но передаются в плагин вывода только при фиксации (и отбрасываются, если транзакция прерывается).

Это означает, что, хотя декодирование происходит инкрементно и может сбрасывать данные на диск, чтобы контролировать потребление памяти, все декодированные изменения должны передаваться, когда транзакция окончательно фиксируется (или, точнее, когда фиксация декодируется из журнала транзакций). В зависимости от размера транзакции и пропускной способности сети время передачи может значительно увеличить задержку применения.

Чтобы уменьшить задержку применения, вызванную большими транзакциями, плагин вывода может предоставить дополнительную функцию обратного вызова для поддержки инкрементальной потоковой передачи выполняющихся транзакций. Имеется несколько обязательных функций потоковой передачи (stream_start_cb, stream_stop_cb, stream_abort_cb, stream_commit_cb и stream_change_cb) и две необязательных (stream_message_cb и stream_truncate_cb). Кроме того, если требуется поддержка потоковой передачи двухфазных комманд, нужно предоставить дополнительные функции обратного вызова. (Подробную информацию см. в разделе Поддержка двухфазной фиксации для логического декодирования.)

При потоковой передаче выполняющейся транзакции изменения (и сообщения) передаются блоками, границы которых устанавливают функции обратного вызова stream_start_cb и stream_stop_cb. По завершении передачи всех декодированных изменений транзакция может быть зафиксирована функцией обратного вызова stream_commit_cb (или, возможно, прервана функцией stream_abort_cb). Если поддерживается двухфазная фиксация, транзакция может быть подготовлена функцией обратного вызова stream_prepare_cb, зафиксирована (COMMIT PREPARED) функцией обратного вызова commit_prepared_cb или прервана rollback_prepared_cb.

Например, последовательность вызовов функций обратного вызова потоковой передачи для одной транзакции может выглядеть так:

stream_start_cb(...);   <-- начало первого блока изменений
  stream_change_cb(...);
  stream_change_cb(...);
  stream_message_cb(...);
  stream_change_cb(...);
  ...
  stream_change_cb(...);
stream_stop_cb(...);    <-- конец первого блока изменений

stream_start_cb(...);   <-- начало второго блока изменений
  stream_change_cb(...);
  stream_change_cb(...);
  stream_change_cb(...);
  ...
  stream_message_cb(...);
  stream_change_cb(...);
stream_stop_cb(...);    <-- конец второго блока изменений

[a. при использовании обычной фиксации]
stream_commit_cb(...);    <-- фиксация переданной транзакции

[b. при использовании двухфазной фиксации]
stream_prepare_cb(...);   <-- подготовка переданной транзакции
commit_prepared_cb(...);  <-- фиксация подготовленной транзакции

Разумеется, рабочая последовательность вызовов этих функций может быть более сложной. В ней могут быть блоки нескольких поточно передаваемых транзакций, некоторые транзакции могут прерываться и т. д.

Подобно поведению сброса на диск, потоковая передача запускается, когда общее количество изменений, декодированных из WAL (для всех выполняющихся транзакций), превышает предел, определенный параметром logical_decoding_work_mem. В этот момент выбирается и поточно передается самая большая транзакция верхнего уровня (это определяется по объему памяти, который на данный момент занимают декодированные изменения). Однако в некоторых случаях все равно приходится сбросить данные на диск, даже если включена поточная передача, поскольку порог памяти уже превышен, а кортеж еще полностью не декодирован (например, декодировано добавление только в таблицу TOAST, но не в основную таблицу).

Даже при потоковой передаче больших транзакций изменения по-прежнему применяются в порядке фиксации, сохраняя те же гарантии, что и не в потоковом режиме.

Поддержка двухфазной фиксации для логического декодирования

С базовыми функциями обратного вызова в плагине вывода (например, begin_cb, change_cb, commit_cb и message_cb) такие команды двухфазной фиксации, как PREPARE TRANSACTION, COMMIT PREPARED и ROLLBACK PREPARED, не декодируются. При этом PREPARE TRANSACTION игнорируется, COMMIT PREPARED декодируется как COMMIT, а ROLLBACK PREPARED декодируется как ROLLBACK.

Для поддержки потоковой передачи двухфазных команд плагин вывода должен предоставить дополнительные функции обратного вызова. Имеется несколько обязательных функций для двухфазной фиксации (begin_prepare_cb, prepare_cb, commit_prepared_cb, rollback_prepared_cb и stream_prepare_cb) и одна необязательная (filter_prepare_cb).

Если плагин предоставляет функции обратного вызова для декодирования команд двухфазной фиксации, то при выполнении PREPARE TRANSACTION изменения этой транзакции декодируются и передаются в плагин вывода, и вызывается функция обратного вызова prepare_cb. Это отличается от базовой схемы декодирования, где изменения передаются в плагин вывода только при фиксации транзакции. Начало подготовленной транзакции обозначается вызовом функции обратного вызова begin_prepare_cb.

Когда подготовленная транзакция откатывается командой ROLLBACK PREPARED, вызывается функция обратного вызова rollback_prepared_cb, а когда подготовленная команда фиксируется командой COMMIT PREPARED, вызывается функция обратного вызова commit_prepared_cb.

Плагин вывода может дополнительно определять правила фильтрации посредством filter_prepare_cb, чтобы декодировать в две фазы только определенную транзакцию. Этого можно добиться, сопоставляя с некоторым шаблоном в gid или производя поиск по xid.

Пользователям, желающим декодировать подготовленные транзакции, нужно учитывать следующие моменты:

Если подготовленная транзакция поставила эксклюзивные блокировки на таблицы каталога (в т. ч. пользовательские), то декодирование подготовки может заблокироваться и оставаться таковым вплоть до фиксации основной транзакции.

Решение логической репликации, которое формирует распределенную двухфазную фиксацию с использованием этой функциональности, может получить взаимоблокировку, если подготовленная транзакция поставила эксклюзивные блокировки на таблицы каталога (в т. ч. пользовательские). Во избежание этого в таких транзакциях следует воздержаться от блокировок таблиц каталога (например, явной командой LOCK). Подробную информацию см. в подразделе Ограничения применимости.

Отслеживание прогресса репликации

Источники репликации предназначены для упрощения реализации решений логической репликации поверх логического декодирования. Они обеспечивают решение двух распространенных проблем:

Как максимально надежно отслеживать прогресс репликации

Как менять поведение репликации в зависимости от источника строки; например, для предотвращения циклов в конфигурациях с двунаправленной репликацией

Источники репликации имеют только два свойства: имя и идентификатор. Имя, по которому следует обращаться к источнику из разных систем, является значением типа text в произвольной форме. Его следует задавать так, чтобы конфликты между источниками репликации, созданными различными решениями репликации, были маловероятны; например, добавлять к нему в качестве префикса имя решения репликации. Идентификатор используется только для того, чтобы не приходилось хранить длинное текстовое имя там, где критично эффективное использование памяти. Он не может совместно использоваться разными системами.

Источники репликации можно создать функцией pg_replication_origin_create(), удалить функцией pg_replication_origin_drop() и увидеть в системном каталоге pg_replication_origin.

Одной из нетривиальных задач при формировании решения репликации является отслеживание прогресса воспроизведения наиболее безопасным способом. К примеру, когда применяющий изменения процесс (или весь кластер) умирает, нужно иметь возможность выявить, до какой точки данные были успешно реплицированы. Наивные решения этого вопроса, такие как изменение строки в некоторой таблице для каждой воспроизведенной транзакции, имеют свои недостатки вроде издержек во время выполнения и «разбухания» базы данных.

При использовании инфраструктуры источников репликации сеанс можно пометить как воспроизводящий изменения из удаленного узла (с помощью функции pg_replication_origin_session_setup()). Кроме того, с помощью функции pg_replication_origin_xact_setup() для каждой транзакции из источника можно сконфигурировать LSN и временную метку фиксации. Если проделать все это, отслеживание прогресса репликации будет защищено от сбоев. Прогресс воспроизведения для всех источников репликации можно увидеть в представлении pg_replication_origin_status. Прогресс отдельного источника, например, при возобновлении репликации, можно получить для любого источника, вызвав функцию pg_replication_origin_progress(), или для источника, сконфигурированного в текущем сеансе, вызвав функцию pg_replication_origin_session_progress().

В более сложных топологиях репликации, чем простая репликация из одной системы в другую, возможна еще одна проблема: трудно избежать повторной репликации уже воспроизведенных строк. Это может привести к зацикливанию репликации и снижению эффективности. Источники репликации обеспечивают оптимальный механизм распознавания и предотвращения этой проблемы. Если при конфигурации применить функции, упомянутые в предыдущем параграфе, во все сгенерированные сеансом изменения и транзакции, передаваемые функциям обратного вызова в плагине вывода (см. раздел Плагины вывода логического декодирования), добавляется пометка источника репликации генерирующего сеанса. Это позволяет обрабатывать их в плагине вывода по-разному, например, игнорировать все строки, кроме имеющих локальное происхождение. Кроме того, можно воспользоваться функцией обратного вызова filter_by_origin_cb и отфильтровать поток изменений логического декодирования в зависимости от источника. Хотя фильтрация посредством этой функции не такая гибкая, как проверка меток внутри плагина вывода, она гораздо более эффективна.

Модули архивирования

QHB предоставляет инфраструктуру для создания пользовательских модулей для непрерывного архивирования (см. раздел Непрерывное архивирование и восстановление на момент времени (PITR)). Хотя архивировать с помощью команды оболочки (т. е. archive_command) гораздо проще, пользовательский модуль архивирования зачастую будет гораздо более производительным и устойчивым к ошибкам.

Если задать в параметре archive_library библиотеку архивирования пользовательского модуля, QHB будет передавать завершенные файлы WAL этому модулю, а сервер будет избегать повторного использования или удаления этих файлов WAL до тех пор, пока модуль не просигнализирует, что файлы были успешно заархивированы. По большому счету модуль сам решает, что делать с каждым файлом WAL, но многие рекомендации перечислены в подразделе Настройка архивирования WAL.

Модули архивирования должны состоять как минимум из функции инициализации (см. раздел Функции инициализации) и требуемых функций обратного вызова (см. раздел Функции обратного вызова модулей архивирования). Тем не менее модулям архивирования разрешено также делать гораздо большее (например, объявлять GUC (Global User Configuration, серверные конфигурационные параметры) и регистрировать фоновые рабочие процессы).

Функции инициализации

Библиотека архивирования загружается путем динамической загрузки разделяемой библиотеки с базовым именем, указанным в archive_library. Для нахождения библиотеки используется обычный путь поиска библиотек. Чтобы предоставить необходимые функции обратного вызова модуля архивирования и показать, что библиотека в действительности является модулем архивирования, она должна предоставить функцию с именем _PG_archive_module_init. Этой функции передается структура, заполняемая указателями на функции обратного вызова для отдельных действий.

typedef struct ArchiveModuleCallbacks
{
    ArchiveCheckConfiguredCB check_configured_cb;
    ArchiveFileCB archive_file_cb;
    ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);

Обязательной является только функция обратного вызова archive_file_cb. Остальные функции необязательны.

Функции обратного вызова модулей архивирования

Функции обратного вызова архивирования определяют фактическое поведение модуля при архивировании. Сервер будет вызывать их по мере надобности для обработки каждого отдельного файла WAL.

Функция обратного вызова для проверки

Функция обратного вызова check_configured_cb вызывается, чтобы определить, полностью ли настроен модуль и готов ли он принимать файлы WAL (например, что в его параметрах конфигурации установлены допустимые значения). Если функция check_configured_cb не определена, сервер всегда предполагает, что модуль настроен.

typedef bool (*ArchiveCheckConfiguredCB) (void);

Если возвращается true, сервер перейдет к архивированию файла путем вызова функции обратного вызова archive_file_cb. Если возвращается false, архивирование не выполняется, и архиватор выдаст в журнал сервера следующее сообщение:

WARNING:  archive_mode enabled, yet archiving is not configured
-- ПРЕДУПРЕЖДЕНИЕ: включен archive_mode, но архивирование не настроено

В последнем случае сервер будет периодически вызывать эту функцию, и архивирование начнется, только когда она вернет true.

Функция обратного вызова для архивирования

Функция обратного вызова archive_file_cb вызывается для архивирования одного файла WAL.

typedef bool (*ArchiveFileCB) (const char *file, const char *path);

Если возвращается true, сервер переходит к следующему этапу, как если бы файл был успешно заархивирован, то есть может переработать или удалить исходный файл WAL. Если возвращается false, сервер сохранит оригинальный файл WAL и повторит попытку архивирования позже. Аргумент file будет содержать только имя архивируемого файла WAL, тогда как аргумент path содержит полный путь к файлу WAL (в том числе имя файла).

Функция обратного вызова для выключения

Функция обратного вызова shutdown_cb вызывается, когда завершается процесс архиватора (например, после ошибки) или изменяется значение archive_library. Если функция shutdown_cb не определена, в таких ситуациях никакие специальные действия не предпринимаются.

typedef void (*ArchiveShutdownCB) (void);

Пример

В следующем примере продемонстрирована реализация базовой библиотеки архивирования, примерно равнозначная следующей команде оболочки:

test ! -f /путь/к/месту/назначения && cp /путь/к/источнику /путь/к/месту/назначения

Первым существенным отличием этого модуля от приведенной выше команды является то, что модуль сначала копирует файл во временное место назначения, синхронизирует его с диском, а затем надежно перемещает его в конечное место назначение. Другим существенным отличием является то, что если /путь/к/месту/назначения уже существует, но его содержимое идентично таковому /пути/к/источнику, архивирование будет успешным, тогда как команда выше завершится ошибкой. Это позволяет избежать проблем в случае, если файл успешно архивируется, а затем, прежде чем произведется надежная запись об этом успехе, в системе происходит сбой.

#include "qhb.h"

#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
#include "utils/memutils.h"

PG_MODULE_MAGIC;

typedef struct BasicArchiveData
{
   MemoryContext context;
} BasicArchiveData;

static char *archive_directory = NULL;

static void basic_archive_startup(ArchiveModuleState *state);
static bool basic_archive_configured(ArchiveModuleState *state);
static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);

static const ArchiveModuleCallbacks basic_archive_callbacks = {
   .startup_cb = basic_archive_startup,
   .check_configured_cb = basic_archive_configured,
   .archive_file_cb = basic_archive_file,
   .shutdown_cb = basic_archive_shutdown
};

/*
 * _PG_init
 *
 * Определяет GUC модуля.
 */
void
_PG_init(void)
{
   DefineCustomStringVariable("basic_archive.archive_directory",
                              gettext_noop("Archive file destination directory."),
                              NULL,
                              &archive_directory,
                              "",
                              PGC_SIGHUP,
                              0,
                              check_archive_directory, NULL, NULL);

   MarkGUCPrefixReserved("basic_archive");
}

/*
 * _PG_archive_module_init
 *
 * Возвращает имеющиеся в модуле функции обратного вызова для архивирования.
 */
const ArchiveModuleCallbacks *
_PG_archive_module_init(void)
{
   return &basic_archive_callbacks;
}

/*
 * basic_archive_startup
 *
 * Создает контекст памяти модуля.
 */
void
basic_archive_startup(ArchiveModuleState *state)
{
   BasicArchiveData *data;

   data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
                                                        sizeof(BasicArchiveData));
   data->context = AllocSetContextCreate(TopMemoryContext,
                                         "basic_archive",
                                         ALLOCSET_DEFAULT_SIZES);
   state->private_data = (void *) data;
}

/*
 * check_archive_directory
 *
 * Проверяет, что предоставленный архивный каталог существует.
 */
static bool
check_archive_directory(char **newval, void **extra, GucSource source)
{
   struct stat st;

     /*
      * Значение по умолчанию — пустая строка, поэтому нам приходится принять это значение.
      * Наша функция обратного вызова check_configured также проверяет это и не
      * позволяет перейти к архивированию, если строка по-прежнему пуста.
      */
   if (*newval == NULL || *newval[0] == '\0')
       return true;

   /*
    * Убедимся, что путь к файлу не будет слишком длинным.  Документы показывают,
    * что имена архивируемых файлов могут иметь длину до 64 символов.
    */
   if (strlen(*newval) + 64 + 2 >= MAXPGPATH)
   {
       GUC_check_errdetail("Archive directory too long.");
       return false;
   }

   /*
    * Проводим базовую проверку работоспособности — что указанный архивный каталог
    * существует. В будущем он может быть удален, поэтому нам все равно нужно быть
    * готовыми к тому, что он не существует в актуальной логике архивирования.
    */
   if (stat(*newval, &st) != 0 || !S_ISDIR(st.st_mode))
   {
       GUC_check_errdetail("Specified archive directory does not exist.");
       return false;
   }

   return true;
}

/*
 * basic_archive_configured
 *
 * Проверяет, что каталог archive_directory не пуст.
 */
static bool
basic_archive_configured(ArchiveModuleState *state)
{
   return archive_directory != NULL && archive_directory[0] != '\0';
}

/*
 * basic_archive_file
 *
 * Архивирует один файл.
 */
static bool
basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
   sigjmp_buf  local_sigjmp_buf;
   MemoryContext oldcontext;
   BasicArchiveData *data = (BasicArchiveData *) state->private_data;
   MemoryContext basic_archive_context = data->context;

   /*
    * Мы выполняем basic_archive_file_internal() в собственном контексте памяти,
    * чтобы его можно было с легкостью сбросить во время восстановления после
    * ошибки (тем самым избежав утечек памяти).
    */
   oldcontext = MemoryContextSwitchTo(basic_archive_context);

   /*
    * Поскольку архиватор оперирует на самом нижнем уровне стека исключений, ошибки
    * уровня ERROR превращаются в FATAL и приводят к перезапуску процесса архиватора.
    * Однако при использовании в случае проблем функции ereport(ERROR, ...) их легко
    * кодировать и решать.  Таким образом, мы создает свой обработчик исключений,
    * чтобы ловить ошибки ERROR и возвращать false, вместо того чтобы перезапускать
    * архиватор при каждой неудаче.
    */
   if (sigsetjmp(local_sigjmp_buf, 1) != 0)
   {
       /* Поскольку мы не используем PG_TRY, нужно сбросить стек ошибок вручную */
       error_context_stack = NULL;

       /* Предотвращаем прерывания во время очистки */
       HOLD_INTERRUPTS();

       /* Сообщаем об ошибке и очищаем ErrorContext для следующего раза */
       EmitErrorReport();
       FlushErrorState();

       /* Закрываем все файлы, оставленные открытыми copy_file() или compare_files() */
       AtEOSubXact_Files(false, InvalidSubTransactionId, InvalidSubTransactionId);

       /* Сбрасываем наш контекст памяти и опять переключаемся на оригинальный */
       MemoryContextSwitchTo(oldcontext);
       MemoryContextReset(basic_archive_context);

       /* Удаляем наш обработчик исключений */
       PG_exception_stack = NULL;

       /* Теперь мы снова можем разрешить прерывания */
       RESUME_INTERRUPTS();

       /* Сообщаем об ошибке, чтобы архиватор снова попытался обработать этот файл */
       return false;
   }

   /* Включаем наш обработчик исключений */
   PG_exception_stack = &local_sigjmp_buf;

   /* Архивируем файл! */
   basic_archive_file_internal(file, path);

   /* Удаляем наш обработчик исключений */
   PG_exception_stack = NULL;

   /* Сбрасываем наш контекст памяти и опять переключаемся на оригинальный */
   MemoryContextSwitchTo(oldcontext);
   MemoryContextReset(basic_archive_context);

   return true;
}

static void
basic_archive_file_internal(const char *file, const char *path)
{
   char        destination[MAXPGPATH];
   char        temp[MAXPGPATH + 256];
   struct stat st;
   struct timeval tv;
   uint64      epoch;          /* миллисекунды*/

   ereport(DEBUG3,
           (errmsg("archiving \"%s\" via basic_archive", file)));

   snprintf(destination, MAXPGPATH, "%s/%s", archive_directory, file);

   /*
    * Сначала проверяем, не архивировался ли уже этот файл. Если он уже существует
    * и имеет то же содержимое, что и файл, который мы пытаемся заархивировать,
    * мы можем вернуть успешный результат (убедившись, что файл хранится на диске).
    * Такой сценарий возможен, если сбой на сервере произошел после архивирования
    * файла но до переименования его файла .ready в .done.
    *
    * Если архивный файл уже существует, но у него другое содержимое,
    * что-то может быть не так, поэтому мы просто выдадим ошибку.
    */
   if (stat(destination, &st) == 0)
   {
       if (compare_files(path, destination))
       {
           ereport(DEBUG3,
                   (errmsg("archive file \"%s\" already exists with identical contents",
                           destination)));

           fsync_fname(destination, false);
           fsync_fname(archive_directory, true);

           return;
       }

       ereport(ERROR,
               (errmsg("archive file \"%s\" already exists", destination)));
   }
   else if (errno != ENOENT)
       ereport(ERROR,
               (errcode_for_file_access(),
                errmsg("could not stat file \"%s\": %m", destination)));

   /*
    * Выберем достаточно уникальное имя для временного файла, чтобы исключить
    * вероятность конфликта. Это помогает избежать проблем в случае, если временный
    * файл не был удален после сбоя или если так вышло, что другой сервер архивирует
    * файлы в тот же каталог.
    */
   gettimeofday(&tv, NULL);
   if (pg_mul_u64_overflow((uint64) 1000, (uint64) tv.tv_sec, &epoch) ||
       pg_add_u64_overflow(epoch, (uint64) (tv.tv_usec / 1000), &epoch))
       elog(ERROR, "could not generate temporary file name for archiving");

   snprintf(temp, sizeof(temp), "%s/%s.%s.%d." UINT64_FORMAT,
            archive_directory, "archtemp", file, MyProcPid, epoch);

   /*
    * Копируем файл во временное место назначения. Обратите внимание, что эта
    * операция завершится ошибкой, если temp уже существует.
    */
   copy_file(path, temp);

   /*
    * Синхронизируем временный файл с диском и переносим его в конечное место назначения.
    * Обратите внимание, что это перепишет любой уже существующий файл, но это возможно,
    * только если кто-то создал этот файл с момента выполнения вышеуказанной функции stat().
    */
   (void) durable_rename(temp, destination, ERROR);

   ereport(DEBUG1,
           (errmsg("archived \"%s\" via basic_archive", file)));
}

/*
 * compare_files
 *
 * Возвращает сведения о том, совпадает ли содержимое файлов.
 */
static bool
compare_files(const char *file1, const char *file2)
{
#define CMP_BUF_SIZE (4096)
   char        buf1[CMP_BUF_SIZE];
   char        buf2[CMP_BUF_SIZE];
   int         fd1;
   int         fd2;
   bool        ret = true;

   fd1 = OpenTransientFile(file1, O_RDONLY | PG_BINARY);
   if (fd1 < 0)
       ereport(ERROR,
               (errcode_for_file_access(),
                errmsg("could not open file \"%s\": %m", file1)));

   fd2 = OpenTransientFile(file2, O_RDONLY | PG_BINARY);
   if (fd2 < 0)
       ereport(ERROR,
               (errcode_for_file_access(),
                errmsg("could not open file \"%s\": %m", file2)));

   for (;;)
   {
       int         nbytes = 0;
       int         buf1_len = 0;
       int         buf2_len = 0;

       while (buf1_len < CMP_BUF_SIZE)
       {
           nbytes = read(fd1, buf1 + buf1_len, CMP_BUF_SIZE - buf1_len);
           if (nbytes < 0)
               ereport(ERROR,
                       (errcode_for_file_access(),
                        errmsg("could not read file \"%s\": %m", file1)));
           else if (nbytes == 0)
               break;

           buf1_len += nbytes;
       }

       while (buf2_len < CMP_BUF_SIZE)
       {
           nbytes = read(fd2, buf2 + buf2_len, CMP_BUF_SIZE - buf2_len);
           if (nbytes < 0)
               ereport(ERROR,
                       (errcode_for_file_access(),
                        errmsg("could not read file \"%s\": %m", file2)));
           else if (nbytes == 0)
               break;

           buf2_len += nbytes;
       }

       if (buf1_len != buf2_len || memcmp(buf1, buf2, buf1_len) != 0)
       {
           ret = false;
           break;
       }
       else if (buf1_len == 0)
           break;
   }

   if (CloseTransientFile(fd1) != 0)
       ereport(ERROR,
               (errcode_for_file_access(),
                errmsg("could not close file \"%s\": %m", file1)));

   if (CloseTransientFile(fd2) != 0)
       ereport(ERROR,
               (errcode_for_file_access(),
                errmsg("could not close file \"%s\": %m", file2)));

   return ret;
}

/*
 * basic_archive_shutdown
 *
 * Освобождает наше выделенное состояние.
 */
static void
basic_archive_shutdown(ArchiveModuleState *state)
{
   BasicArchiveData *data = (BasicArchiveData *) state->private_data;
   MemoryContext basic_archive_context;

   /*
    * Если мы не сохранили указатель на наше выделенное состояние, нам не нужно
    * ничего очищать.
    */
   if (data == NULL)
       return;

   basic_archive_context = data->context;
   Assert(CurrentMemoryContext != basic_archive_context);

   if (MemoryContextIsValid(basic_archive_context))
       MemoryContextDelete(basic_archive_context);
   data->context = NULL;

   /*
    * Наконец освобождаем состояние.
    */
   pfree(data);
   state->private_data = NULL;
}

Процесс qhb

qhb — сервер баз данных QHB

Синтаксис

qhb [параметр...]

или

qhbmaster [параметр...]

Описание

qhb — это сервер баз данных QHB. Чтобы получить доступ к базе данных, клиентское приложение подключается (по сети или локально) к работающему экземпляру qhb. Затем экземпляр qhb запускает отдельный серверный процесс для обработки соединения.

Один экземпляр qhb всегда управляет данными только одного кластера баз данных. Кластер баз данных — это набор баз данных, который хранится в определенном месте общей файловой системы («область данных»). В системе одновременно может работать несколько экземпляров qhb, если они используют разные области данных и разные коммуникационные порты (см. ниже). Когда qhb запускается, он должен знать местоположение области данных. Местоположение следует указать с помощью параметра -D или переменной среды PGDATA; значение по умолчанию отсутствует. Как правило, -D или PGDATA указывает непосредственно на каталог области данных, созданный qhb_bootstrap (или initdb). Другие возможные шаблоны файлов описаны в разделе Расположение файлов.

По умолчанию qhb запускается в приоритетном режиме и выводит сообщения журнала в стандартный поток ошибок. При практическом применении qhb должен запускаться как фоновый процесс, возможно, во время загрузки системы.

Также команду qhb можно вызвать в однопользовательском режиме. В основном этот режим используется во время начального создания базы при помощи qhb_bootstrap, но иногда применяется и для отладки или аварийного восстановления. Обратите внимание, что использование однопользовательского режима не совсем подходит для полноценной отладки сервера, поскольку в нем не будет реалистичного межпроцессного взаимодействия и блокировок. При вызове в однопользовательском режиме из командной оболочки пользователь может вводить запросы, и результаты будут выводиться на экран, но в форме, которая более полезна для разработчиков, чем для конечных пользователей. В этом режиме пользователем сеанса будет считаться пользователь с номером идентификатора 1, и ему будут предоставлены неявные полномочия суперпользователя. Этому пользователю необязательно существовать на самом деле, поэтому однопользовательский режим можно использовать для ручного восстановления после некоторых видов случайного повреждения системных каталогов.

Параметры

qhb принимает ряд аргументов командной строки. Подробно эти параметры рассматриваются в главе Конфигурация сервера. Настроив файл конфигурации, можно сэкономить время на ввод большинства этих параметров. Некоторые (безопасные) параметры также можно установить со стороны подключающегося клиента (в зависимости от приложения), чтобы они применялись только для этого сеанса. Например, если установлена переменная среды PGOPTIONS, клиенты на основе libpq передадут эту строку на сервер, который будет интерпретировать ее как параметры командной строки qhb.

Общие параметры

-B количество-буферов
Устанавливает количество разделяемых буферов для использования процессами сервера. Значение по умолчанию для этого параметра автоматически выбирается qhb_bootstrap. Указание этого параметра равнозначно установке параметра конфигурации shared_buffers.

-c имя=значение
Устанавливает именованный параметр времени выполнения. Параметры конфигурации, поддерживаемые QHB, описаны в главе Конфигурация сервера. Большинство других параметров командной строки на самом деле являются краткими формами такого назначения параметров. Указав -c несколько раз, можно установить несколько параметров.

-C имя
Выводит значение именованного параметра времени выполнения и завершает работу. (Подробности см. выше в описании параметра -c.) Это возвращает из qhb.conf значения с изменениями, вызванными любыми параметрами в этом вызове. Не отражает параметры, предоставленные при запуске кластера.
Для большинства параметров это можно использовать на работающем сервере. Однако для некоторых параметров, вычисляемых во время выполнения, (например, для shared_memory_size, shared_memory_size_in_huge_pages и wal_segment_size) сервер необходимо выключать.
Этот параметр предназначен для других программ, которые взаимодействуют с экземпляром сервера (как, например, qhb_ctl) и запрашивают значения параметров конфигурации. Пользовательские приложения должны вместо этого использовать команду SHOW или представление pg_settings.

-d уровень-отладки
Устанавливает уровень отладки. Чем выше это значение, тем больше выходных данных отладки записывается в журнал сервера. Значения находятся в диапазоне от 1 до 5. Также для отдельного сеанса возможно передать -d 0, что не даст уровню серверного журнала родительского процесса qhb распространиться на него.

-D каталог-данных
Задает расположение файловой системы файлов конфигурации базы данных. Подробную информацию см. в разделе Расположение файлов.

-e
Устанавливает формат даты по умолчанию в «European», то есть порядок вводимых значений даты вида ДМГ. Это также приводит к тому, что в определенных форматах вывода даты день отображается перед месяцем. Подробную информацию см. в разделе Типы даты/времени.

-F
Выключает вызовы fsync для повышения производительности, хотя и с риском повреждения данных в случае сбоя системы. Указание этого параметра равнозначно выключению параметра конфигурации fsync. Перед использованием этого параметра прочтите подробную документацию!

-h имя-хоста
Задает IP-адрес или имя хоста, по которому qhb должен принимать подключения по TCP/IP от клиентских приложений. Значением также может быть список адресов через запятую или символ *, означающий все доступные интерфейсы. Пустое значение указывает, что не принимается ни один IP-адрес, и в этом случае подключение к серверу возможно только через сокеты домена Unix. По умолчанию принимаются подключения только к локальной рабочей станции. Указание этого параметра равнозначно установке параметра конфигурации listen_addresses.

-i
Позволяет удаленным клиентам подключаться по TCP/IP (интернет-домен). Без этого параметра принимаются только локальные подключения. Он равнозначен установке в параметре listen_addresses значения * в qhb.conf или посредством параметра -h.
Этот параметр считается устаревшим, так как не позволяет получить доступ ко всем функциям listen_addresses. Обычно лучше устанавливать listen_addresses напрямую.

-k каталог
Задает каталог сокета домена Unix, в котором qhb должен принимать подключения от клиентских приложений. Значение также может быть разделенным запятыми списком каталогов. Пустое значение указывает, что не принимается ни один сокет домена Unix, и в этом случае подключение к серверу возможно только через сокеты TCP/IP. Значением по умолчанию обычно является /tmp, но его можно изменить во время сборки. Указание этого параметра равнозначно установке параметра конфигурации unix_socket_directories.

-l
Включает поддержку безопасных подключений с использованием SSL. Чтобы этот параметр был доступен, QHB нужно скомпилировать с поддержкой SSL. Более подробную информацию по использованию SSL см. в разделе Защита TCP/IP-соединений посредством SSL.

-N максимум-соединений
Задает максимальное количество клиентских подключений, которые будет принимать этот сервер. Значение по умолчанию для этого параметра автоматически выбирает qhb_bootstrap. Указание этого параметра равнозначно установке параметра конфигурации max_connections.

-p порт
Задает порт TCP/IP или расширение файла локального сокета домена Unix, на котором qhb должен принимать подключения от клиентских приложений. По умолчанию используется значение переменной среды PGPORT или, если оно не указано, то по умолчанию используется значение, установленное во время компиляции (обычно 5432). Если вы указываете порт, отличный от порта по умолчанию, то все клиентские приложения должны указывать один и тот же порт, используя либо параметры командной строки, либо PGPORT.

-s
Выводит информацию о времени и другую статистику в конце каждой команды. Это полезно для оценки производительности системы или для настройки количества буферов.

-S рабочая-память
Задает базовый объем памяти, который будет использоваться для сортировок и хеш-таблиц, прежде чем прибегнуть к применению временных файлов на диске. Описание аналогичного параметра конфигурации work_mem см. в подразделе Память.

-V
--version
Показать версию qhb и завершиться.

--имя=значение
Устанавливает именованный параметр времени выполнения; является более короткой формой параметра -c.

--describe-config
Этот параметр выгружает внутренние переменные конфигурации сервера, их описания и значения по умолчанию в формате команды COPY с разделителями в виде знаков табуляции. Он предназначен в первую очередь для использования инструментами администрирования.

-?
--help
Показать справку об аргументах командной строки qhb и завершиться.

Параметры для внутреннего использования

Описанные здесь параметры используются в основном для целей отладки, а в некоторых случаях — для помощи в восстановлении сильно поврежденных баз данных. Не должно быть никаких причин использовать их при настройке производственной базы данных. Они перечислены здесь только для использования системными разработчиками QHB. Кроме того, эти параметры могут быть изменены или удалены в будущих версиях без предварительного уведомления.

-f { s | i | o | b | t | n | m | h }
Запрещает использование определенных методов сканирования и объединения: s и i выключают последовательное и индексное сканирование соответственно, o, b и t — сканирование только по индексу, сканирование по индексу битовой карты и сканирование TID соответственно, а n, m и h — соединения с помощью вложенных циклов, слияния и хеширования соответственно.
Ни последовательное сканирование, ни соединение с вложенным циклом нельзя выключить полностью; параметры -fs и -fn просто не позволят оптимизатору использовать эти типы планов, если у того есть какая-либо другая альтернатива.

-n
Этот параметр предназначен для отладки проблем, которые приводят к аварийному завершению серверного процесса. Обычная стратегия в этой ситуации состоит в том, чтобы уведомить все остальные серверные процессы, что они должны завершиться, а затем повторно инициализировать разделяемую память и семафоры. Это связано с тем, что вызвавший ошибку серверный процесс перед завершением мог повредить некоторое разделяемое состояние. Этот параметр указывает, что qhb не будет повторно инициализировать разделяемые структуры данных. Опытный системный программист может затем использовать отладчик для проверки состояния разделяемой памяти и семафора.

-O
Позволяет изменять структуру системных таблиц. Этот параметр используется утилитой qhb_bootstrap.

-P
Игнорировать системные индексы при чтении системных таблиц, но по-прежнему обновлять индексы при изменении этих таблиц. Это полезно при восстановлении поврежденных системных индексов.

-t pa[rser] | pl[anner] | e[xecutor]
Выводить статистику по времени выполнения каждого запроса, относящегося к каждому из основных системных модулей. Этот параметр нельзя использовать совместно с параметром -s.

-T
Этот параметр предназначен для отладки проблем, которые приводят к аварийному завершению серверного процесса. Обычная стратегия в этой ситуации состоит в том, чтобы уведомить все другие серверные процессы, что они должны завершиться, а затем повторно инициализировать разделяемую память и семафоры. Это связано с тем, что вызвавший ошибку серверный процесс перед завершением мог повредить некоторое разделяемое состояние. Этот параметр указывает, что qhb остановит все остальные серверные процессы, отправив сигнал SIGSTOP, но не вызовет их завершение. Это позволит системным программистам вручную собрать дампы ядра всех процессов сервера.

-v протокол
Задает номер версии протокола взаимодействия клиент-сервер, который будет использоваться для конкретного сеанса. Этот параметр предназначен только для внутреннего использования.

-W секунды
При запуске нового серверного процесса после проведения процедуры аутентификации возникает задержка на заданное количество секунд. Назначение этого параметра — дать возможность подключиться к серверному процессу с помощью отладчика.

Параметры для однопользовательского режима

Следующие параметры применимы только к однопользовательскому режиму (см. параграф Однопользовательский режим ниже).

--single
Выбирает однопользовательский режим. Этот аргумент должен быть первым в командной строке.

база_данных
Задает имя базы данных, к которой открывается доступ. Этот аргумент должен быть последним в командной строке. Если он опущен, по умолчанию используется имя пользователя.

-E
Выводить все команды на стандартное устройство вывода перед их выполнением.

-j
Использовать в качестве завершающих символов ввода команды точку с запятой и два символа перевода строки, а не просто перевод строки.

-r имя_файла
Отправлять весь вывод журнала сервера в файл имя_файла. Этот параметр учитывается только в том случае, если он указан в командной строке.

Переменные среды

PGCLIENTENCODING
Кодировка символов по умолчанию, используемая клиентами. (Клиенты могут переопределить ее индивидуально.) Это значение также можно установить в файле конфигурации.

PGDATA
Местоположение по умолчанию для каталога данных.

PGDATESTYLE
Значение по умолчанию для параметра времени выполнения DateStyle. (Использование этой переменной среды считается устаревшим.)

PGPORT
Номер порта по умолчанию (желательно установить в файле конфигурации).

Диагностика

Сообщение об ошибке, в котором упоминается semget или shmget, скорее всего, означает необходимость сконфигурировать ядро для предоставления достаточного объема разделяемой памяти и семафоров. Более подробно это рассматривается в разделе Управление ресурсами ядра. Переконфигурирование ядра можно отложить, уменьшив значение параметра shared_buffers для снижения потребления разделяемой памяти QHB, и/или уменьшив значение параметра max_connections для снижения затрат на использование семафоров.

Сообщение об ошибке, указывающее на то, что другой сервер уже запущен, следует тщательно проверить, например, с помощью команды

$ ps ax | grep qhb

или

$ ps -ef | grep qhb

в зависимости от ОС. Если вы уверены, что конфликтующий сервер не запущен, можно удалить указанный в сообщении файл блокировки и повторить попытку.

Сообщение об ошибке, указывающее на невозможность привязки к порту, может означать, что этот порт уже используется некоторым процессом, не связанным с QHB. Также эту ошибку можно получить, если завершить работу qhb и немедленно перезапустить его, используя тот же порт; в этом случае следует просто подождать несколько секунд, пока операционная система не закроет порт, прежде чем попытаться снова. Наконец, эту ошибку можно получить, если указать номер порта, который ваша ОС считает зарезервированным. Например, многие версии Unix считают номера портов менее 1024 «доверенными» и разрешают доступ к ним только суперпользователю Unix.

Примечания

qhbmaster является устаревшим псевдонимом qhb.

Для безопасного и удобного запуска и выключения сервера qhb можно использовать утилиту qhb_ctl.

Если это вообще возможно, не используйте сигнал SIGKILL для остановки основного сервера qhb, иначе qhb не освободит системные ресурсы (например разделяемую память и семафоры), которые занимал перед остановкой. Такое поведение может вызвать проблемы при запуске нового экземпляра qhb.

Для нормального завершения работы сервера qhb можно использовать сигналы SIGTERM, SIGINT или SIGQUIT. При первом сервер перед выходом будет дожидаться завершения работы всех клиентов, при втором все клиенты будут принудительно отключены, а третий приведет к немедленному выходу без корректного завершения, вследствие чего во время перезапуска выполнится процедура восстановления.

Сигнал SIGHUP перезагрузит файлы конфигурации сервера. Также возможно отправить SIGHUP на отдельный процесс сервера, но это обычно не имеет смысла.

Чтобы отменить текущий запрос, отправьте сигнал SIGINT процессу, выполняющему эту команду. Чтобы завершить внутренний процесс, отправьте этому процессу сигнал SIGTERM. См. также в подразделе Функции для передачи сигналов серверу описание функций pg_cancel_backend и pg_terminate_backend, которые являются вызываемыми из SQL аналогами этих двух действий.

Сервер qhb использует SIGQUIT, чтобы приказать подчиненным серверным процессам завершиться без обычной очистки. Пользователям нельзя использовать этот сигнал. Также неразумно отправлять SIGKILL на серверный процесс — основной процесс qhb будет интерпретировать это как сбой и заставит все родственные процессы завершиться как часть стандартной процедуры восстановления после сбоя.

Ошибки

Параметры с -- не будут работать на FreeBSD или OpenBSD; используйте вместо них -c. Это ошибка ОС.

Однопользовательский режим

Чтобы запустить сервер в однопользовательском режиме, используйте команду вроде

qhb --single -D /var/lib/qhb/data other-options my_database

Укажите правильный путь к каталогу базы данных с помощью -D или задайте переменную среды PGDATA. Также укажите имя конкретной базы данных, в которой хотите работать.

Обычно в однопользовательском режиме сервер обрабатывает символ перевода строки как окончание ввода команды; точка с запятой не является синтаксическим разделителем, как в psql. Чтобы ввести команду, занимающую несколько строк, следует перед каждым символом перевода строки, кроме последнего, использовать обратный слэш. При этом обратный слэш и соседний символ перевода строки удаляются из вводимой команды. Обратите внимание, что это происходит даже внутри строкового литерала или комментария.

Но если воспользоваться параметром командной строки -j, одиночный символ перевода строки не завершит ввод команды; вместо него это будет делать последовательность из точки с запятой и двух символов перевода строки. То есть для завершения команды следует ввести точку с запятой, а следом совершенно пустую строку. Обратный слэш с последующим символом перевода строки в этом режиме не имеют специального значения. Опять же, внутри строкового литерала или комментария такая последовательность тоже не работает.

В любом из режимов ввода символ точки с запятой, который введен не сразу перед или в составе завершающей последовательности команды, воспринимается как разделитель команд. При введении завершающей последовательности команды несколько операторов, введенных с разделителями, будут выполняться как одна транзакция.

Для завершения сеанса введите символ конца файла (EOF, обычно это Ctrl+D). Если после введения последней завершающей последовательности команды вы вводили какой-либо текст, то EOF будет восприниматься как завершающая последовательность команды, и для выхода понадобится еще один EOF.

Обратите внимание, что однопользовательский режим не предоставляет продвинутых возможностей для редактирования строк (например, здесь нет истории команд). Также в однопользовательском режиме не производится фоновая обработка, например, не выполняются автоматические контрольные точки или репликация.

Примеры

Чтобы запустить qhb в фоновом режиме, используя значения по умолчанию, введите:

$ nohup qhb >logfile 2>&1 </dev/null &

Чтобы запустить qhb с определенным портом, например 1234, введите:

$ qhb -p 1234

Чтобы подключиться к этому серверу с помощью psql, укажите этот порт в параметре -p:

$ psql -p 1234

или установите переменную среды PGPORT:

$ export PGPORT=1234
$ psql

Именованные параметры времени выполнения можно установить в любом из следующих стилей:

$ qhb -c work_mem=1234
$ qhb --work-mem=1234

Любая из этих форм переопределяет любое значение параметра work_mem в qhb.conf. Обратите внимание, что подчеркивания в именах параметров в командной строке могут быть записаны как подчеркивание или тире. За исключением краткосрочных экспериментов, вероятно, лучше отредактировать параметр в qhb.conf, чем задавать его в аргументах командной строки.

См. также

qhb_bootstrap, qhb_ctl

Обзор внутренних компонентов QHB

Автор:
Эта глава возникла как часть магистерской диссертации Стефана Симковича, подготовленной в Венском технологическом университете под руководством профессора университета доктора Георга Готтлоба (Georg Gottlob) и его ассистентки магистра Катрин Сейр (Katrin Seyr).¹

В этой главе дается обзор внутренней структуры серверной части QHB. После прочтения следующих разделов вы получите представление о том, как обрабатывается запрос. Эта глава предназначена для того, чтобы помочь читателю понять общую последовательность операций, которые происходят на сервере с момента получения запроса до момента, когда результаты возвращаются клиенту.

Путь запроса

Здесь мы даем краткий обзор этапов, которые должен пройти запрос, чтобы получить результат.

Необходимо установить соединение между прикладной программой и сервером QHB. Прикладная программа передает запрос на сервер и ожидает получения от него результатов.

На этапе синтаксического анализа запрос, переданный прикладной программой, проверяется на предмет корректного синтаксиса, и создается дерево запросов.

Система переписывания принимает дерево запросов, созданное на этапе синтаксического анализа, и ищет любые правила (хранящиеся в системных каталогах), которые будут применяться к этому дереву запросов. Она выполняет преобразования, заданные в телах правил.
Одно из применений системы перезаписи заключается в реализации представлений. Всякий раз, когда выполняется запрос к представлению (т. е. виртуальной таблице), система переписывания переписывает запрос пользователя в запрос, который обращается к базовым таблицам, указанным в определении представления.

Планировщик/оптимизатор берет дерево запросов (переписанное) и создает план запроса, который будет передан на вход исполнителю.
Для этого сначала создаются все возможные пути, ведущие к одному и тому же результату. Например, если по сканируемому отношению есть индекс, существует два способа сканирования. Один — это простое последовательное сканирование, а второй — использовать индекс. Далее оценивается стоимость выполнения каждого пути и из них выбирается самый дешевый. Этот путь разворачивается в полный план, который может использовать исполнитель.

Исполнитель рекурсивно отрабатывает дерево плана и извлекает строки способом, представленным этим планом. Исполнитель сканирует отношения, пользуясь системой хранения, выполняет сортировки и соединения, вычисляет условия отбора и наконец возвращает полученные строки.

В следующих разделах мы рассмотрим каждый из перечисленных выше элементов более подробно, чтобы дать лучшее представление о внутреннем контроле и структурах данных QHB.

Как устанавливаются соединения

QHB реализует простую клиент-серверную модель «по процессу на пользователя». В этой модели каждый клиентский процесс подключен ровно к одному обслуживающему процессу. Поскольку мы не знаем заранее, сколько подключений будет сделано, мы должны использовать «процесс-супервизор», который порождает новый обслуживающий процесс каждый раз, когда запрашивается подключение. Этот процесс- супервизор называется qhbmaster и ожидает на указанном TCP/IP порту входящие подключения. Всякий раз, когда обнаруживается запрос на подключение, он порождает новый обслуживающий процесс. Эти обслуживающие процессы взаимодействуют друг с другом и с другими процессами экземпляра СУБД при помощи семафоров и разделяемой памяти для обеспечения целостности данных при одновременном обращении к ним.

Клиентским процессом может быть любая программа, которая понимает протокол QHB, описанный в главе Клиент-серверный протокол. Многие клиенты основаны на библиотеке libpq для языка C, но существует несколько независимых реализаций протокола, например драйвер Java JDBC.

После установления соединения клиентский процесс может отправить запрос обслуживающему процессу, к которому он подключился. Запрос передается в виде обычного текста, т. е. на клиенте синтаксический анализ не выполняется. Обслуживающий процесс анализирует запрос, создает план выполнения, выполняет его и возвращает полученные строки клиенту, передавая их через установленное соединение.

Этап синтаксического анализа

Этап синтаксического анализа состоит из двух частей:

Собственно синтаксический анализ, причем анализатор, определенный в gram.y и scan.l, создается с использованием инструментов Unix bison и flex.

Процесс трансформации вносит изменения и дополнения в структуры данных, возвращаемые синтаксическим анализатором.

Синтаксический анализ

Синтаксический анализатор должен проверить строку запроса (которая поступает в виде обычного текста) на допустимый синтаксис. Если синтаксис верен, строится дерево синтаксического анализа, которое передается обратно; в противном случае возвращается ошибка. Лексический и синтаксический анализаторы реализованы с использованием известных инструментов Unix bison и flex.

Лексический анализатор определяется в файле scan.l и отвечает за распознавание идентификаторов, ключевых слов SQL и т. д. Для каждого найденного ключевого слова или идентификатора создается синтаксическая единица, которая передается синтаксическому анализатору.

Синтаксический анализатор определяется в файле gram.y и состоит из набора правил грамматики и действий, которые выполняются всякий раз, когда правило срабатывает. Для построения дерева синтаксического анализа используется код действий (который фактически является кодом C/RUST).

Файл scan.l трансформируется в исходный файл scan.c на языке C с помощью программы flex, а gram.y трансформируется в gram.c с помощью bison. После того, как эти преобразования произошли, для создания синтаксического анализатора можно применить обычный компилятор C. Никогда не вносите никаких изменений в сгенерированные файлы C/RUST, так как они будут перезаписаны при следующем вызове flex или bison.

Примечание
Указанные трансформации и компиляции обычно выполняются автоматически с помощью сборочных файлов, поставляемых с исходным дистрибутивом QHB.

Подробное описание bison или грамматических правил, заданных в gram.y выходит за рамки этого руководства. Существует много книг и документов, касающихся flex и bison. Прежде чем начать изучать грамматику, приведенную в gram.y, следует ознакомиться с bison, иначе вы не поймете, что там происходит.

Процесс трансформации

Этап синтаксического анализа создает дерево синтаксического анализа, используя только фиксированные правила о синтаксической структуре SQL. Он не проводит никаких поисков в системных каталогах, поэтому нет никакой возможности понять детальную семантику запрашиваемых операций. После завершения синтаксического анализа процесс трансформации принимает на вход дерево, возвращенное синтаксическим анализатором, и выполняет семантическую интерпретацию, необходимую для понимания того, на какие таблицы, функции и операторы ссылается запрос. Структура данных, созданная для представления этой информации, называется деревом запросов.

Причина отделения исходного синтаксического анализа от семантического анализа заключается в том, что поиски в системном каталоге могут выполняться только в рамках транзакции, а нам нежелательно начинать транзакцию сразу после получения строки запроса. Этапа исходного синтаксического анализа достаточно для идентификации команд управления транзакциями (BEGIN, ROLLBACK и т. д.), И их можно выполнить корректно без какого-либо дальнейшего анализа. Как только мы убеждаемся, что имеем дело с настоящим запросом (например SELECT или UPDATE), можно начать транзакцию, если мы еще не находимся в ней. Только тогда можно будет вызвать процесс трансформации.

Дерево запросов, созданное в процессе трансформации, структурно во много похоже на исходное дерево синтаксического анализа, но имеет много отличий в деталях. Например, узел FuncCall в дереве синтаксического анализа представляет собой нечто, синтаксически похожее на вызов функции. Он может быть преобразован в узел FuncExpr или Aggref в зависимости от того, окажется ли функция с указанным именем обычной или агрегатной функцией. Кроме того, в дерево запросов добавляются сведения о фактических типах данных столбцов и результатах выражения.

Система правил QHB

QHB поддерживает мощную систему правил для подробного описания представлений и неоднозначных обновлений представлений. Система правил реализована методом, называемым переписыванием запроса. Система переписывания — это модуль, существующий между этапом синтаксического анализа и планировщиком/ оптимизатором.

В системе переписывания вводятся и выводятся деревья запросов, то есть изменения в представлении или уровне семантической детализации в деревьях отсутствуют. Переписывание можно рассматривать как форму расширения макросов.

Планировщик/оптимизатор

Задача планировщика/оптимизатора заключается в создании оптимального плана выполнения. Заданный SQL-запрос (а следовательно, и дерево запросов) на самом деле можно выполнить множеством различных способов, каждый из которых приведет к одному и тому же набору результатов. Если это осуществимо путем вычислений, оптимизатор запросов будет проверять каждый из этих возможных планов выполнения, в итоге выбирая план выполнения, который предположительно будет выполняться быстрее всего.

Примечание
В некоторых ситуациях изучение каждого возможного способа выполнения запроса может занимать слишком много времени и объема памяти. В частности, это происходит при выполнении запросов, включающих большое количество операций соединения. Чтобы определить разумный (необязательно оптимальный) план запроса за разумное время, QHB использует генетический оптимизатор запросов (см. главу Генетический оптимизатор запросов), когда число соединений превышает пороговое значение (см. описание параметра geqo_threshold).

В действительности процедура поиска планировщика работает со структурами данных, называемыми путями, которые являются просто сокращенными представлениями планов, содержащими ровно столько информации, сколько нужно планировщику для принятия решений. После определения наиболее дешевого пути строится полноценное дерево плана для передачи исполнителю. Оно представляет желаемый план выполнения достаточно подробно, чтобы исполнитель его запустил. В остальной части этого раздела мы будем игнорировать различие между путями и планами.

Генерирование возможных планов

Планировщик/оптимизатор начинает работу с формирования планов для сканирования каждого отдельного отношения (таблицы), используемого в запросе. Возможные планы определяются по имеющим в каждом отношении индексам. Всегда существует возможность выполнения последовательного сканирования отношения, поэтому план последовательного сканирования создается всегда. Предположим, что для таблицы определен индекс (например B-дерево) и запрос содержит ограничение отношение.атрибут ОПЕР константа. Если оказывается, что отношение.атрибут соответствует ключу индекса B-дерева, а ОПЕР является одним из операторов, перечисленных в классе операторов индекса, то создается другой план, использующий для сканирования отношения индекс B-дерево. Если существуют дополнительные индексы, а ограничения в запросе совпадают с ключом индекса, то будут рассмотрены дополнительные планы. Планы сканирования индексов также создаются для индексов, имеющих порядок сортировки, который может соответствовать предложению ORDER BY запроса (если таковое имеется) или порядок сортировки, который может быть полезен для соединения слиянием (см. ниже).

Если запрос требует соединения двух или более связей, планы для соединения отношений рассматриваются после того, как были найдены все возможные планы для сканирования отдельных отношений. Возможны три стратегии соединения:

соединение с вложенным циклом: правое отношение сканируется один раз для каждой строки, найденной в левом отношении. Эта стратегия проста в реализации, но может быть очень времязатратной. (Однако если правое отношение можно сканировать по индексу, это может быть хорошей стратегией, поскольку возможно использование значений из текущей строки левого отношения в качестве ключей для сканирования по индексу правого отношения.)

соединение слиянием: Каждое отношение сортируется по атрибутам соединения до начала соединения. Затем эти два отношения параллельно сканируются, и соответствующие строки соединяются, формируя строки соединения. Этот вид соединения более привлекателен, поскольку каждое отношение нужно просканировать только один раз. Требуемой сортировки можно добиться либо добавив явный этап сортировки, либо сканируя отношение в правильном порядке с использованием индекса по ключу соединения.

соединение по хешу: правое отношение сначала сканируется и загружается в хеш-таблицу, используя ее атрибуты соединения в качестве хеш-ключей. Затем выполняется сканирование отношения слева, и соответствующие значения каждой найденной строки используются в качестве хеш-ключей для поиска соответствующих строк в таблице.

Если запрос включает более двух отношений, конечный результат должен быть построен деревом этапов соединения, каждый из которых имеет два входных значения. Планировщик изучает различные возможные последовательности соединения, чтобы найти самый дешевый.

Если запрос использует меньше geqo_threshold отношений, проводится почти исчерпывающий поиск, чтобы найти лучшую последовательность соединения. Планировщик отдает предпочтение соединениям между любыми двумя отношениями, для которых существует соответствующее предложение соединения в условии WHERE (т. е. таких, для которых существует ограничение типа WHERE отн1.атр1=отн2.атр2.) Пары соединения без предложения соединения рассматриваются только тогда, когда нет другого выбора, то есть у конкретного отношения нет доступных предложений соединения к любому другому отношению. Для каждой пары соединения, рассматриваемой планировщиком, генерируются все возможные планы и выбирается тот, который (по расчетам) является самым дешевым.

Когда значение geqo_threshold превышается, рассматриваемые последовательности соединения определяются эвристически, как описано в главе Генетический оптимизатор запросов. В остальном процесс остается неизменным.

Готовое дерево плана состоит из узлов последовательного сканирования или сканирования по индексу базовых отношений, плюс (при необходимости) узлов соединений с вложенным циклом, слиянием или по хешу, а также любых необходимых вспомогательных этапов, например узлов сортировки или вычисления агрегатных функций. Большинство из этих типов узлов плана имеют дополнительную возможность делать выбор (отбрасывание строк, которые не удовлетворяют заданному логическому условию) и проекцию (вычисление производного набора столбцов на основе заданных значений столбцов, т. е. вычисление скалярных выражений там, где это необходимо). Одна из обязанностей планировщика заключается в том, чтобы добавить условия отбора из предложения WHERE и вычисление требуемых выходных выражений к наиболее подходящим узлам дерева плана.

Исполнитель

Исполнитель принимает план, созданный планировщиком/оптимизатором, и рекурсивно обрабатывает его для извлечения необходимого набора строк. По сути, это механизм конвейерного получения данных по требованию. Каждый раз, когда вызывается узел плана, он должен доставить еще одну строку или сообщить, что он выполнил доставку строк.

Чтобы привести конкретный пример, предположим, что верхний узел является узлом MergeJoin. Прежде чем произойдет какое-либо слияние, нужно извлечь две строки (по одной из каждого подплана). Таким образом, исполнитель рекурсивно вызывает себя для обработки подпланов (он начинает с подплана, добавленного к левому дереву). Новый верхний узел (верхний узел левого подплана) является, скажем, узлом Sort, и, опять же, для получения входной строки требуется рекурсия. Дочерний узел Sort может быть узлом SeqScan, представляющим фактическое чтение таблицы. Выполнение этого узла приводит к тому, что исполнитель извлекает строку из таблицы и возвращает ее вызывающему узлу. Узел Sort будет повторно вызывать свой дочерний элемент для получения всех строк, подлежащих сортировке. Когда входные строки заканчиваются (как сигнализирует дочерний узел, возвращающий вместо строки NULL), узел Sort выполняет сортировку и наконец может вернуть свою первую выходную строку, а именно первую в порядке сортировки. Он сохраняет оставшиеся строки, хранящиеся так, чтобы он мог доставить их в отсортированном порядке в ответ на более поздние требования.

Узел MergeJoin аналогичным образом требует первую строку из своего правого подплана. Затем он сравнивает две строки, чтобы увидеть, можно ли их соединить; если можно, он возвращает соединенную строку вызывающей функции. При следующем вызове, или сразу же, если он не может соединить текущую пару входных строк, этот узел переходит к следующей строке одной таблицы или другой (в зависимости от итога сравнения) и снова проверяет соответствие. В конце концов, строки в одном из подпланов кончаются, и узел MergeJoin возвращает NULL, чтобы указать, что больше нельзя сформировать ни одной строки соединения.

Сложные запросы могут включать в себя много уровней узлов плана, но общий подход одинаков: каждый узел вычисляет и возвращает свою следующую выходную строку при каждом вызове. Каждый узел также отвечает за применение любых выражений выбора или проекции, которые были назначены ему планировщиком.

Механизм исполнителя используется для вычисления всех пяти основных типов запросов SQL: SELECT, INSERT, UPDATE, DELETE и MERGE. Для команды SELECT узлу исполнителя верхнего уровня нужно только выдавать клиенту каждую строку, возвращенную деревом плана запроса. Команды INSERT ... SELECT, UPDATE, DELETE и MERGE по сути выполняются как SELECT под специальным узлом верхнего уровня плана с именем ModifyTable.

Команда INSERT ... SELECT подает строки в ModifyTable для добавления. Для команды UPDATE планировщик делает так, чтобы каждая вычисляемая строка включала все измененные значения столбцов, а также TID (идентификатор кортежа или строки) исходной целевой строки; эти данные подаются в узел ModifyTable, который использует эту информацию для создания новой измененной строки и пометки старой строки как удаленной. Для команды DELETE единственным столбцом, который фактически возвращается планом, является столбец, содержащий TID, и узел ModifyTable просто использует значения TID, чтобы посетить каждую целевую строку и пометить ее как удаленную. Для команды MERGE планировщик соединяет исходные и целевые отношения и включает все значения столбцов, требуемые для любого из предложений WHEN, плюс TID целевой строки; эти данные подаются в узел ModifyTable, который использует эту информацию, чтобы определить, какое из предложений WHEN выполнить, а затем при необходимости добавляет, изменяет или удаляет целевую строку.

Простая команда INSERT ... VALUES создает тривиальное дерево плана, состоящее из одного узла Result, который вычисляет только одну строку результата и подает ее узлу ModifyTable для выполнения добавления.

¹

Enhancement of the ANSI SQL Implementation of PostgreSQL. Stefan Simkovics. Department of Information Systems, Vienna University of Technology. Vienna, Austria. November 29, 1998.

Системные каталоги

Системные каталоги — это место, где система управления реляционными базами данных хранит метаданные схемы, например сведения о таблицах и столбцах, а также внутреннюю служебную информацию. Системные каталоги QHB — это обычные таблицы. Их можно удалить и пересоздать, добавить столбцы, вставить и изменить значения — и тем самым сильно испортить свою систему. В большинстве случаев не следует изменять системные каталоги вручную, для этого обычно существуют команды SQL. (Например, команда CREATE DATABASE добавляет строку в каталог pg_database — и действительно создает базу данных на диске.) Есть некоторые исключения для особенно сложных операций, но многие из них с течением времени становятся доступны в виде команд SQL, и поэтому необходимость в прямом манипулировании системными каталогами постоянно уменьшается.

Обзор

В Таблице 1 перечислены системные каталоги. Более подробная документация по каждому каталогу приводится ниже.

Большинство системных каталогов копируются из шаблона базы данных во время ее создания и затем зависят от конкретной базы данных. Несколько каталогов физически совместно используются во всех базах данных в кластере; они отмечаются в описаниях отдельных каталогов.

Таблица 1. Системные каталоги

Имя каталога | Назначение
pg_aggregate | агрегатные функции
pg_am | методы доступа отношений
pg_amop | операторы метода доступа
pg_amproc | вспомогательные функции метода доступа
pg_attrdef | значения столбцов по умолчанию
pg_attribute | столбцы таблицы («атрибуты»)
pg_authid | идентификаторы авторизации (роли)
pg_auth_members | отношения членства идентификаторов авторизации
pg_cast | приведения (преобразования типов данных)
pg_class | таблицы, индексы, последовательности, представления («отношения»)
pg_collation | правила сортировки (сведения о локали)
pg_constraint | проверочные ограничения, ограничения уникальности, ограничения первичного ключа, ограничения внешнего ключа
pg_conversion | информация о перекодировках
pg_database | базы данных в этом кластере
pg_db_role_setting | параметры на уровне ролей и баз данных
pg_default_acl | права доступа по умолчанию для типов объектов
pg_depend | зависимости между объектами базы данных
pg_description | описания или комментарии для объектов базы данных
pg_enum | определения перечислимых меток и значений
pg_event_trigger | триггеры событий
pg_extension | установленные расширения
pg_foreign_data_wrapper | определения оберток сторонних данных
pg_foreign_server | определения сторонних серверов
pg_foreign_table | дополнительная информация по сторонним таблицам
pg_index | дополнительная информация по индексам
pg_inherits | иерархия наследования таблиц
pg_init_privs | начальные права для объектов
pg_language | языки для написания функций
pg_largeobject | страницы данных для больших объектов
pg_largeobject_metadata | метаданные для больших объектов
pg_namespace | схемы
pg_opclass | классы операторов методов доступа
pg_operator | операторы
pg_opfamily | семейства операторов методов доступа
pg_parameter_acl | параметры конфигурации, для которых были предоставлены права
pg_partitioned_table | информация о ключе разбиения таблиц
pg_policy | политики защиты на уровне строк
pg_proc | функции и процедуры
pg_publication | публикации для логической репликации
pg_publication_namespace | сопоставление схем с публикациями
pg_publication_rel | сопоставление отношений с публикациями
pg_range | информация о типах диапазонов
pg_replication_origin | зарегистрированные источники репликации
pg_rewrite | правила переписывания запросов
pg_seclabel | метки безопасности для объектов базы данных
pg_sequence | информация о последовательностях
pg_shdepend | зависимости для разделяемых объектов
pg_shdescription | комментарии к разделяемым объектам
pg_shseclabel | метки безопасности для разделяемых объектов базы данных
pg_statistic | статистика планировщика
pg_statistic_ext | расширенная статистика планировщика (определение)
pg_statistic_ext_data | расширенная статистика планировщика (собранная статистика)
pg_subscription | подписки на логическую репликацию
pg_subscription_rel | состояние отношений для подписок
pg_tablespace | табличные пространства в этом кластере баз данных
pg_transform | трансформации (преобразования типов данных для процедурных языков)
pg_trigger | триггеры
pg_ts_config | конфигурации текстового поиска
pg_ts_config_map | сопоставления синтаксических единиц конфигураций текстового поиска
pg_ts_dict | словари текстового поиска
pg_ts_parser | синтаксические анализаторы текстового поиска
pg_ts_template | шаблоны текстового поиска
pg_type | типы данных
pg_user_mapping | сопоставления пользователей для сторонних серверов

pg_aggregate

В каталоге pg_aggregate хранится информация об агрегатных функциях. Агрегатная функция — это функция, которая работает с набором значений (как правило, с одним столбцом из каждой строки, которая соответствует условию запроса) и возвращает одно значение, вычисленное из всех этих значений. Типичными агрегатными функциями являются sum, count и max. Каждая запись в pg_aggregate представляет собой дополнение записи в pg_proc. Запись в pg_proc содержит имя агрегата, типы входных и выходных данных и другую информацию, аналогичную имеющейся у обычных функций.

Таблица 2. Столбцы pg_aggregate

Столбец Тип

Описание

aggfnoid regproc (ссылается на pg_proc.oid)

OID агрегатной функции в каталоге pg_proc

aggkind char

Вид агрегата: n для обычного (normal), o для сортирующего (ordered-set) или h для гипотезирующего (hypothetical-set)

aggnumdirectargs int2

Количество непосредственных (неагрегированных) аргументов сортирующего или гипотезирующего агрегата, считая переменный массив аргументов за один аргумент. Если равняется pronargs, агрегат должен принимать переменный массив, и этот массив описывает как агрегированные аргументы, так и конечные непосредственные аргументы. Всегда равно нулю для обычных агрегатов.

aggtransfn regproc (ссылается на pg_proc.oid)

Функция перехода

aggfinalfn regproc (ссылается на pg_proc.oid)

Функция завершения (ноль, если ее нет)

aggcombinefn regproc (ссылается на pg_proc.oid)

Функция совмещения (ноль, если ее нет)

aggserialfn regproc (ссылается на pg_proc.oid)

Функция сериализации (ноль, если ее нет)

aggdeserialfn regproc (ссылается на pg_proc.oid)

Функция десериализации (ноль, если ее нет)

aggmtransfn regproc (ссылается на pg_proc.oid)

Функция прямого перехода для режима движущегося агрегата (ноль, если ее нет)

agminvtransfn regproc (ссылается на pg_proc.oid)

Функция обратного перехода для режима движущегося агрегата (ноль, если ее нет)

aggmfinalfn regproc (ссылается на pg_proc.oid)

Функция завершения для режима движущегося агрегата (ноль, если ее нет)

aggfinalextra bool

При значении true в aggfinalfn передаются дополнительные фиктивные аргументы

aggmfinalextra bool

При значении true в aggmfinalfn передаются дополнительные фиктивные аргументы

aggfinalmodify char

Изменяет ли функция aggfinalfn значение состояния перехода: r, если состояние доступно только для чтения (read), s, если aggtransfn нельзя применять после aggfinalfn, или w, если состояние перезаписывается (write).

aggmfinalmodify char

Схоже с aggfinalmodify, но для самого aggmfinalfn

aggsortop oid (ссылается на pg_operator.oid)

Связанный оператор сортировки (ноль, если его нет)

aggtranstype oid (ссылается на pg_type.oid)

Тип данных внутреннего перехода (состояния) агрегатной функции

aggtransspace int4

Приблизительный средний размер (в байтах) данных о состоянии перехода или 0 для использования расчетного значения по умолчанию

aggmtranstype oid (ссылается на pg_type.oid)

Тип данных внутреннего перехода (состояния) агрегатной функции для режима движущегося агрегата (ноль, если его нет)

aggmtransspace int4

Приблизительный средний размер (в байтах) данных о состоянии перехода для режима движущегося агрегата или 0 для использования расчетного значения по умолчанию

agginitval text

Начальное значение состояния перехода. Это текстовое поле, содержащее начальное значение во внешнем строковом представлении. Если это поле равно NULL, то значение состояния перехода начинается с NULL.

aggminitval text

Начальное значение состояния перехода для режима движущегося агрегата. Это текстовое поле, содержащее начальное значение во внешнем строковом представлении. Если это поле равно NULL, то значение состояния перехода начинается с NULL.

Новые агрегатные функции регистрируются с помощью команды CREATE AGGREGATE. Дополнительную информацию о написании агрегатных функций и значении функций перехода см. в разделе Пользовательские агрегаты.

pg_am

В каталоге pg_am хранится информация о методах доступа к отношениям. Для каждого метода доступа, поддерживаемого системой, существует одна строка. В настоящее время только таблицы и индексы имеют методы доступа. Требования к табличным и индексным методам доступа подробно рассматриваются в главах Определение интерфейса табличных методов доступа и Определение интерфейса индексных методов доступа соответственно.

Таблица 3. Столбцы pg_am

Столбец Тип

Описание

oid oid

Идентификатор строки

amname name

Имя метода доступа

amhandler regproc (ссылается на pg_proc.oid)

OID функции обработки, ответственной за предоставление информации о методе доступа

amtype char

t = таблица (включая материализованные представления), i = индекс.

Примечание
Данные о свойствах индексных методов доступа напрямую видны только на уровне кода С. Тем не менее запросы SQL могут проверять эти свойства с помощью функции pg_index_column_has_property() и связанных функций; см. таблицу Функции для получения информации из системных каталогов.

pg_amop

В каталоге pg_amop хранится информация об операторах, связанных с семействами операторов метода доступа. Для каждого оператора, который является членом семейства операторов, существует одна строка. Членом семейства может быть либо оператор поиска, либо оператор упорядочивания. Оператор может принадлежать нескольким семействам, но в пределах одного семейства не может находиться более чем в одной позиции поиска или позиции упорядочивания. (Допустимо, хотя и маловероятно, что оператор будет использоваться как для поиска, так и для сравнения.)

Таблица 4. Столбцы pg_amop

Столбец Тип

Описание

oid oid

Идентификатор строки

amopfamily oid (ссылается на pg_opfamily.oid)

Семейство операторов, для которого сделана эта запись

amoplefttype oid (ссылается на pg_type.oid)

Тип входных данных левого операнда оператора

amoprighttype oid (ссылается на pg_type.oid)

Тип входных данных правого операнда оператора

amopstrategy int2

Номер стратегии оператора

amoppurpose char

Назначение оператора: s для поиска, о для упорядочивания

amopopr oid (ссылается на pg_operator.oid)

OID оператора

amopmethod oid (ссылается на pg_am.oid)

Индексный метод доступа, для которого предназначено семейство операторов

amopsortfamily oid (ссылается на pg_opfamily.oid)

Семейство операторов B-дерева, в соответствии с которым данный оператор сортирует, если это оператор упорядочивания; 0, если это оператор поиска

Запись оператора «поиска» указывает, что индекс этого семейства операторов можно использовать для поиска всех строк, удовлетворяющих условию WHERE столбец_индекса оператор константа. Очевидно, что такой оператор должен возвращать тип boolean, а тип его левого операнда должен соответствовать типу данных столбца индекса.

Запись оператора «упорядочивания» указывает, что индекс этого семейства операторов можно отсканировать для возврата строк в порядке, представленном в предложении ORDER BY столбец_индекса оператор константа. Такой оператор может возвращать любой сортируемый тип данных, хотя, опять же, тип его левого операнда должен соответствовать типу данных столбца индекса. Точная семантика ORDER BY определяется по столбцу amopsortfamily, который должен ссылаться на семейство операторов B-дерева для типа результата оператора.

Примечание
В настоящее время предполагается, что порядок сортировки для оператора упорядочивания является значением по умолчанию для указанного семейства операторов, т. е. ASC NULLS LAST. Возможно, когда-нибудь для придания большей гибкости будут добавлены дополнительные столбцы, чтобы явно указать параметры сортировки.

Поле amopmethod в записи оператора должно соответствовать полю opfmethod содержащего его семейства операторов (включение сюда amopmethod является преднамеренной денормализацией структуры каталога из соображений производительности). Кроме того, поля amoplefttype и amoprighttype должны соответствовать полям oprleft и oprright ссылочной записи в каталоге pg_operator.

pg_amproc

В каталоге pg_amproc хранится информация о вспомогательных функциях, связанных с семействами операторов метода доступа. Для каждой вспомогательной функции, принадлежащей семейству операторов, существует одна строка.

Таблица 5. Столбцы pg_amproc

Столбец Тип

Описание

oid oid

Идентификатор строки

amprocfamily oid (ссылается на pg_opfamily.oid)

Семейство операторов, для которого сделана эта запись

amproclefttype oid (ссылается на pg_type.oid)

Тип входных данных левого операнда связанного оператора

amprocrighttype oid (ссылается на pg_type.oid)

Тип входных данных правого операнда связанного оператора

amprocnum int2

Номер вспомогательной функции

amproc regproc (ссылается на pg_proc.oid)

OID этой функции

Обычная интерпретация полей amproclefttype и amprocrighttype состоит в том, что они определяют типы входных данных левого и правого операнда оператора (или операторов), который поддерживает определенная вспомогательная функция. Для некоторых методов доступа они соответствуют типу(ам) входных данных самой вспомогательной функции, для других — нет. Существует такое понятие, как «стандартные» вспомогательные функции для индекса, то есть функции, у которых поля amproclefttype и amprocrighttype равны полю opcintype класса операторов индекса.

pg_attrdef

В каталоге pg_attrdef хранятся значения столбцов по умолчанию. Основная информация о столбцах находится в каталоге pg_attribute. Здесь же содержатся записи только о тех столбцах, для которых было явно установлено значение по умолчанию.

Таблица 6. Столбцы pg_attrdef

Столбец Тип

Описание

oid oid

Идентификатор строки

adrelid oid (ссылается на pg_class.oid)

Таблица, которой принадлежит этот столбец

adnum int2 (ссылается на pg_attribute.attnum)

Номер столбца

adbin pg_node_tree

Значение столбца по умолчанию в представлении nodeToString(). Чтобы преобразовать его в выражение SQL, воспользуйтесь функцией pg_get_expr(adbin, adrelid).

pg_attribute

В каталоге pg_attribute хранится информация о столбцах таблицы. Для каждого столбца в каждой таблице базы данных будет храниться ровно одна строка pg_attribute. (Также здесь будут храниться записи атрибутов для индексов и вообще всех объектов, которые имеют записи в каталоге pg_class.)

Термин «атрибут» равнозначен столбцу и используется по историческим причинам.

Таблица 7. Столбцы pg_attribute

Столбец Тип

Описание

attrelid oid (ссылается на pg_class.oid)

Таблица, которой принадлежит этот столбец

attname name

Имя столбца

atttypid oid (ссылается на pg_type.oid)

Тип данных этого столбца (ноль для удаленного столбца)

attstattarget int4

Столбец attstattarget управляет уровнем детализации статистики, собираемой для данного столбца при помощи команды ANALYZE. Нулевое значение указывает на то, что статистика не должна собираться. Отрицательное значение указывает использовать системный целевой показатель статистики по умолчанию. Точное значение положительных значений зависит от типа данных. Для скалярных типов данных значение attstattarget является как целевым числом собираемых «наиболее распространенных значений», так и целевым числом создаваемых интервалов гистограммы.

attlen int2

Копия поля pg_type.typlen из записи типа этого столбца

attnum int2

Номер столбца. Обычные столбцы нумеруются по возрастанию, начиная с 1. Системные столбцы, такие как ctid, имеют отрицательные номера (произвольные).

attndims int4

Количество размерностей, если столбец имеет тип массива; в противном случае ноль. (В настоящее время количество размерностей массива не регулируется, поэтому любое ненулевое значение в сущности означает «это массив».)

attcacheof int4

Всегда -1 в хранилище, но при загрузке в дескриптор строки в памяти это поле может измениться, чтобы кэшировать смещение атрибута в строке

atttypmod int4

В поле atttypmod записываются специфичные для определенного типа данные, предоставляемые при создании таблицы (например максимальная длина столбца varchar). Эти данные передаются специфичным для типа функциям ввода и функциям ограничения длины. Для типов, которые не нуждаются в atttypmod, значение обычно будет равняться -1.

attbyval bool

Копия поля pg_type.typbyval из записи типа этого столбца

attalign char

Копия поля pg_type.typalign из записи типа этого столбца

attstorage char

Обычно это копия поля pg_type.typstorage из записи типа этого столбца. Для типов данных, поддерживающих TOAST, после создания столбца этот атрибут можно изменить в целях управления политикой хранения.

attcompression char

Текущий метод сжатия столбца. Обычно это '\0', указывающий использовать метод, установленный по умолчанию на данный момент (см. default_toast_compression). В ином случае при значении 'p' выбирается алгоритм сжатия pglz, тогда как при 'l' выбирается алгоритм LZ4. Однако если attstorage не допускает сжатие, это поле игнорируется.

attnotnull bool

Это поле представляет ограничение NOT NULL.

atthasdef bool

Этот столбец содержит выражение по умолчанию или генерирующее выражение; в этом случае в каталоге pg_attrdef, который фактически определяет данное выражение, появится соответствующая запись. (Определить, какое это выражение: по умолчанию или генерирующее, можно, проверив столбец attgenerated.)

atthasmissing bool

Этот столбец содержит значение, которое используется, когда столбец полностью отсутствует в строке (это происходит, когда столбец добавляется с неизменяющимся значением DEFAULT после создания строки). Используемое фактическое значение хранится в столбце attmissingval.

attidentity char

Пустой символ ('') указывает, что это не столбец идентификации. В ином случае a = значение генерируется всегда (always), d = значение генерируется по умолчанию (by default).

attgenerated char

Пустой символ ('') указывает, что это не генерируемый столбец. В ином случае s = генерируемое значение хранится (stored). (В будущем могут быть добавлены и другие значения.)

attisdropped bool

Этот столбец был удален и стал нерабочим. Удаленный столбец все еще физически присутствует в таблице, но игнорируется синтаксическим анализатором, и поэтому к нему нельзя обратиться через SQL.

attislocal bool

Этот столбец определяется локально в отношении. Обратите внимание, что столбец может одновременно быть локально определен и унаследован.

attinhcount int4

Число прямых предков этого столбца. Столбец с ненулевым числом предков нельзя удалить или переименовать.

attcollation oid (ссылается на pg_collation.oid)

Определенные для столбца правила сортировки или ноль, если столбец имеет несортируемый тип данных.

attacl aclitem[]

Права доступа на уровне столбца, если они были предоставлены специально для этого столбца

attoptions text[]

Параметры уровня атрибута в виде строк «ключ=значение»

attfdwoptions text[]

Параметры уровня атрибута для обертки сторонних данных в виде строк «ключ=значение»

attmissingval anyarray

Этот столбец включает массив из одного элемента, содержащий значение, используемое, когда столбец полностью отсутствует в строке (это происходит, когда столбец добавляется с неизменяющимся значением DEFAULT после создания строки). Это значение используется, только если поле atthasmissing содержит true. Если значение отсутствует, данный столбец содержит NULL.

В записи удаленного столбца в каталоге pg_attribute поле atttypid сбрасывается до нуля, но поле attlen и остальные поля, копируемые из pg_type, все еще действительны. Это необходимо, чтобы справиться с ситуацией, когда после удаления столбца удаляется и его тип данных, и поэтому соответствующей строки в каталоге pg_type тоже больше нет. Тогда для интерпретации содержимого строки таблицы можно использовать поле attlen и остальные поля.

pg_authid

В каталоге pg_authid содержится информация об идентификаторах авторизации (ролях) базы данных. Роль включает в себя понятия «пользователи» и «группы». Пользователь — это просто роль с установленным флагом rolcanlogin. Любая роль (с rolcanlogin или без него) может иметь в качестве членов другие роли; см. pg_auth_members.

Поскольку этот каталог содержит пароли, он не должен быть общедоступным. Общедоступным представлением на базе pg_authid является pg_roles, в котором прикрыто поле пароля.

В главе Роли в базе данных содержатся подробные сведения об управлении пользователями и их правами.

Поскольку идентификационные характеристики пользователей являются кластерными, каталог pg_authid совместно используется всеми базами данных кластера: существует только одна копия pg_authid на кластер, а не отдельная для каждой базы данных.

Таблица 8. Столбцы pg_authid

Столбец Тип

Описание

oid oid

Идентификатор строки

rolname name

Имя роли

rolsuper bool

Роль обладает правами суперпользователя

rolinherit bool

Роль автоматически наследует права ролей, членом которых является

rolcreaterole bool

Роль может создавать другие роли

rolcreatedb bool

Роль может создавать базы данных

rolcanlogin bool

Роль может начинать сеанс. То есть эту роль можно задать в качестве начального идентификатора авторизации сеанса

rolreplication bool

Роль является ролью репликации. Роль репликации может инициировать подключения репликации и создавать и удалять слоты репликации.

rolbypassrls bool

Роль обходит политику защиты на уровне строк; подробную информацию см. в разделе Политики защиты строк.

rolconnlimit int4

Для ролей, которые могут начинать сеанс, этот столбец устанавливает максимальное число одновременных подключений, которые может создать эта роль. -1 означает отсутствие ограничений.

rolpassword text

Пароль (возможно зашифрованный); NULL, если его нет. Формат зависит от используемой формы шифрования.

rolvaliduntil timestamptz

Срок действия пароля (используется только для аутентификации по паролю); NULL, если срок действия не ограничен

Для пароля, зашифрованного MD5, столбец rolpassword будет начинаться со строки md5, за которой следует 32-символьный шестнадцатеричный хеш MD5. Хеш MD5 будет содержать пароль пользователя, конкатенированный с его именем. Например, если у пользователя joe пароль xyzzy, QHB будет хранить хеш md5 строки xyzzyjoe.

Если пароль зашифрован с помощью SCRAM-SHA-256, он имеет следующий формат:

SCRAM-SHA-256$<число итераций>:<соль>$<Сохраненный_Ключ>:<Ключ_Сервера>

где соль, Сохраненный_Ключ и Ключ_Сервера кодируются в формате Base64. Этот формат совпадает с форматом, указанным в стандарте RFC 5803.

Пароль, который не соответствует ни одному из этих форматов, считается незашифрованным.

pg_auth_members

В каталоге pg_auth_members показаны отношения членства между ролями. Допускается любой нециклический набор отношений.

Поскольку идентификационные характеристики пользователей являются кластерными, каталог pg_auth_members совместно используется всеми базами данных кластера: существует только одна копия pg_auth_members на кластер, а не отдельная для каждой базы данных.

Таблица 9. Столбцы pg_auth_members

Столбец Тип

Описание

roleid oid (ссылается на pg_authid.oid)

Идентификатор роли, имеющей члена

member oid (ссылается на pg_authid.oid)

Идентификатор роли, являющейся членом роли из roleid

grantor oid (ссылается на pg_authid.oid)

Идентификатор роли, предоставившей это членство

admin_option bool

True, если member может предоставлять членство в roleid другим ролям

pg_cast

В каталоге pg_cast хранятся как встроенные, так и пользовательские пути преобразования типов данных.

Следует отметить, что pg_cast содержит не все преобразования типов, которые умеет выполнять система, а только те, которые нельзя вывести из некоторого общего правила. Например, приведение между доменом и его базовым типом в pg_cast явно не представлено. Еще одним важным исключением является то, что в каталоге pg_cast явно не представлены «автоматические приведения ввода/вывода», которые выполняются, используя для преобразования в/из типа text или других строковых типов собственные функции ввода/вывода типа данных.

Таблица 10. Столбцы pg_cast

Столбец Тип

Описание

oid oid

Идентификатор строки

castsource oid (ссылается на pg_type.oid)

OID исходного типа данных

casttarget oid (ссылается на pg_type.oid)

OID целевого типа данных

castfunc oid (ссылается на pg_proc.oid)

OID функции, используемой для выполнения этого приведения. Ноль, если для данного метода приведения функция не требуется.

castcontext char

Указывает, в каких контекстах можно вызвать приведение. e означает только явное приведение (explicit) с использованием синтаксиса CAST или ::. а означает неявное присваивание целевому столбцу (assignment), а также явное приведение. i означает неявное приведение в выражениях (implicitly), а также все остальные случаи.

castmethod char

Указывает, как выполняется приведение. f означает, что используется функция (function), указанная в поле castfunc. i означает, что используются функции ввода/вывода (input/output). b означает, что типы являются двоично-совместимыми (binary-coercible), поэтому преобразование не требуется.

Функции приведения, перечисленные в каталоге pg_cast, должны всегда принимать исходный тип приведения в качестве типа первого аргумента и возвращать результат, имеющий целевой тип приведения. Функция приведения может иметь до трех аргументов. Второй аргумент, если он присутствует, должен иметь тип integer; он получает модификатор типа, связанный с целевым типом, или -1, если модификатора нет. Третий аргумент, если он присутствует, должен иметь тип boolean; он получает значение true, если приведение является явным, в противном случае — false.

Есть возможность создать в pg_cast запись, в которой исходные и целевые типы совпадают, если связанная функция принимает более одного аргумента. Такие записи представляют «функции приведения длины», которые преобразуют значения типа так, чтобы они подходили определенному значению модификатора типа.

Когда запись в pg_cast имеет различные исходный и целевой типы, а функция принимает более одного аргумента, эта запись представляет преобразование из одного типа в другой и применение приведения длины за один шаг. Если такая запись отсутствует, приведение к типу, использующему модификатор типа, включает два шага: один для преобразования между типами данных и второй для применения модификатора.

pg_class

В каталог pg_class вносятся таблицы и почти все объекты, которые имеют столбцы или иным образом похожи на таблицу. Сюда входят индексы (см. также pg_index), последовательности (см. также pg_sequence), представления, материализованные представления, составные типы и таблицы TOAST; см. описание столбца relkind. Ниже, говоря об «отношениях», мы подразумеваем все эти виды объектов. Не все столбцы в каталоге значимы для всех типов отношений.

Таблица 11. Столбцы pg_class

Столбец Тип

Описание

oid oid

Идентификатор строки

relname name

Имя таблицы, индекса, представления и т. д.

relnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего это отношение

reltype oid (ссылается на pg_type.oid)

OID типа данных, который соответствует типу строки этой таблицы, если таковой имеется; ноль для индексов, у которых нет записи в каталоге pg_type

reloftype oid (ссылается на pg_type.oid)

OID нижележащего составного типа для типизированных таблиц; ноль для всех остальных отношений

relowner oid (ссылается на pg_authid.oid)

Владелец отношения

relam oid (ссылается на pg_am.oid)

Если это таблица или индекс, то используемый метод доступа (куча, B-дерево, хеш и т. д.); иначе ноль (ноль проставляется для последовательностей, а также для нехранимых отношений, например представлений)

relfilenode oid

Имя файла на диске для этого отношения; ноль означает, что это «сопоставленное» отношение, для которого имя файла на диске определяется состоянием на нижнем уровне

reltablespace oid (ссылается на pg_tablespace.oid)

Табличное пространство, в котором хранится это отношение. При нулевом значении подразумевается табличное пространство базы данных по умолчанию. (Не имеет смысла, если отношение не имеет файла на диске.)

relpages int4

Размер представления этой таблицы на диске в страницах (в размере BLCKSZ). Это только расчетная оценка, используемая планировщиком. Она обновляется с помощью команд VACUUM, ANALYZE и нескольких команд DDL, таких как CREATE INDEX.

reltuples float4

Количество активных строк в таблице. Это только расчетная оценка, используемая планировщиком. Она обновляется с помощью команд VACUUM, ANALYZE и нескольких команд DDL, таких как CREATE INDEX. Если таблица никогда не подвергалась очистке или анализу, поле reltuples содержит -1, указывая, что количество строк неизвестно.

relallvisible int4

Количество страниц, отмеченных как полностью видимые на карте видимости таблицы. Это только расчетная оценка, используемая планировщиком. Она обновляется с помощью команд VACUUM, ANALYZE и нескольких команд DDL, таких как CREATE INDEX.

reltoastrelid oid (ссылается на pg_class.oid)

OID таблицы TOAST, связанной с этой таблицей; ноль, если такой таблицы нет. Таблица TOAST хранит большие атрибуты «вынесенными» во вторичную таблицу.

relhasindex bool

True, если это таблица и она имеет (или недавно имела) какие-либо индексы

relisshared bool

True, если эта таблица совместно используется всеми базами данных в кластере. Совместно используются только определенные системные каталоги (например pg_database).

relpersistence char

p = постоянная таблица (permanent), u = незарегистрированная таблица (unlogged), t = временная таблица (temporary)

relkind char

r = Обычная таблица (regular), i = индекс (index), s = последовательность (sequence), t = таблица TOAST, v = представление (view), m = материализованное представление (materialized), c = составной тип (composite), f = сторонняя таблица (foreign), p = партиционированная таблица (partitioned), I = партиционированный индекс (index)

relnatts int2

Количество пользовательских столбцов в отношении (системные столбцы не учитываются). В каталоге pg_attribute должно быть такое же количество соответствующих записей. См. также описание столбца pg_attribute.attnum.

relchecks int2

Количество проверочных ограничений (CHECK) у таблицы; см. каталог pg_constraint

relhasrules bool

True, если таблица имеет (или когда-то имела) правила; см. каталог pg_rewrite

relhastriggers bool

True, если таблица имеет (или когда-то имела) триггеры; см. каталог pg_trigger

relhassubclass bool

True, если таблица или индекс имеют (или когда-то имели) потомков в иерархии наследования либо партиции

relrowsecurity bool

True, если в таблице включена защита на уровне строк; см. каталог pg_policy

relforcerowsecurity bool

True, если защита уровня строк (если она включена) будет также применяться и к владельцу таблицы; см. каталог pg_policy

relispopulated bool

True, если отношение заполнено (это справедливо для всех отношений, кроме некоторых материализованных представлений)

relreplident char

Столбцы, используемые для формирования «идентификатора реплики» для строк: d = по умолчанию (default) (первичный ключ, если таковой имеется), n = ничего (nothing), f = все столбцы, i = индекс со свойством indisreplident (если ранее использовавшийся индекс был удален, действует так же, как n)

relispartition bool

True, если таблица или индекс является партицией

relrewrite oid (ссылается на pg_class.oid)

Для новых отношений, записываемых во время операции DDL, которая требует перезаписи таблицы, это поле содержит OID исходного отношения; в противном случае ноль. Это состояние видимо только внутри; данное поле всегда должно содержать 0 для видимого пользователю отношения.

relfrozenxid xid

В этой таблице все идентификаторы транзакций, предшествующие данному, были заменены на постоянный («замороженный») идентификатор транзакции. Это используется для отслеживания того, нужно ли очистить таблицу для предотвращения зацикливания идентификаторов транзакций или уменьшения размера pg_xact. Ноль (InvalidTransactionId), если данное отношение не является таблицей.

relminmxid xid

В этой таблице все идентификаторы мультитранзакций, предшествующие данному, были заменены на постоянный («замороженный») идентификатор транзакции. Это используется для отслеживания того, нужно ли очистить таблицу для предотвращения зацикливания идентификаторов мультитранзакций или уменьшения размера pg_multixact. Ноль (InvalidMultiXactId), если данное отношение не является таблицей.

relacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

reloptions text[]

Параметры, специфичные для методов доступа, в виде строк «ключ=значение»

relpartbound pg_node_tree

Если таблица является партицией (см. relispartition), внутреннее представление границ партиции

Несколько логических флагов в каталоге pg_class поддерживаются не строго: они гарантированно будут установлены в true, если это правильное состояние, но не сбрасываются на false сразу же, как только условие поменяется. Например, флаг relhasindex устанавливается с помощью команды CREATE INDEX, но никогда не сбрасывается с помощью DROP INDEX. Вместо этого relhasindex сбрасывает команда VACUUM, если обнаруживает, что таблица не имеет индексов. Этот механизм позволяет избежать состояния гонки и улучшает параллельное использование.

pg_collation

В каталоге pg_collation описываются доступные правила сортировки, которые в сущности представляют собой сопоставления между именем SQL и категориями локалей операционной системы. Дополнительную информацию см. в разделе Поддержка правил сортировки.

Таблица 12. Столбцы pg_collation

Столбец Тип

Описание

oid oid

Идентификатор строки

collname name

Имя правила сортировки (уникальное для каждого пространства имен и кодировки)

collnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего это правило сортировки

collowner oid (ссылается на pg_authid.oid)

Владелец правила сортировки

collprovider char

Поставщик правила сортировки: d = установленный в базе данных по умолчанию (default), c = libc, i = icu

collisdeterministic bool

Является ли правило сортировки детерминированным?

collencoding int4

Кодировка, в которой применимо правило сортировки, или -1, если оно работает для любой кодировки

collcollate text

LC_COLLATE для этого объекта правила сортировки

collctype text

LC_CTYPE для этого объекта правила сортировки

colliculocale text

Идентификатор локали ICU для этого объекта правила сортировки

collversion text

Версия правила сортировки, специфическая для поставщика данных. Записывается при создании правила сортировки, а затем проверяется при его использовании для обнаружения изменений в его определении, которые могут привести к повреждению данных.

Обратите внимание, что уникальными ключами в этом каталоге являются (collname, collencoding, collnamespace), а не только (collname, collnamespace). QHB обычно игнорирует все правила сортировки, у которых поле collencoding не равно либо кодировке текущей базы данных, либо -1; кроме того, создание новых записей с тем же именем, что и запись с collencoding = -1, запрещено. Поэтому для обозначения правила сортировки достаточно использовать полное имя SQL (схема.имя), даже если оно не является уникальным согласно определению каталога. Такое определение каталога обусловлено тем, что во время инициализации кластера qhb_bootstrap (или initdb) заполняет его записями для всех доступных в системе локалей, поэтому каталог должен иметь возможность хранить записи для всех кодировок, которые могут когда-либо использоваться в кластере.

В базе данных template0 может быть полезно создать правила сортировки, кодировка которых не соответствует кодировке базы данных, так как они могут соответствовать кодировкам баз данных, впоследствии клонированных из template0. В настоящее время это придется сделать вручную.

pg_constraint

В каталоге pg_constraint хранятся проверочные и исключающие ограничения, ограничения уникальности, первичного и внешнего ключей для таблиц. (Ограничения столбцов не обрабатываются отдельно. Каждое ограничение столбца эквивалентно некоторому ограничению таблицы.) Ограничения NOT NULL представлены не здесь, а в каталоге pg_attribute.

Определяемые пользователем триггеры ограничений (созданные с помощью команды CREATE CONSTRAINT TRIGGER) тоже приводят к появлению записи в этой таблице.

Также здесь хранятся проверочные ограничения для доменов.

Таблица 13. Столбцы pg_constraint

Столбец Тип

Описание

oid oid

Идентификатор строки

conname name

Имя ограничения (необязательно уникальное!)

connamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего это ограничение

contype char

c = проверочное ограничение (check), f = ограничение внешнего ключа (foreign), p = ограничение первичного ключа (primary), u = ограничение уникальности (unique), t = триггер ограничения (trigger), x = исключающее ограничение (exclusion)

condeferrable bool

Является ли ограничение откладываемым?

condeferred bool

Является ли ограничение отложенным по умолчанию?

convalidated bool

Было ли это ограничение проверено? В настоящее время может иметь значение false только для внешних ключей и проверочных ограничений (CHECK)

conrelid oid (ссылается на pg_class.oid)

Таблица, на которую наложено это ограничение; ноль, если это не ограничение таблицы

contypid oid (ссылается на pg_type.oid)

Домен, на который наложено это ограничение; ноль, если это не ограничение домена

conindid oid (ссылается на pg_class.oid)

Индекс, поддерживающий это ограничение, если это ограничение уникальности, первичного ключа, внешнего ключа или исключающее ограничение; в остальных случаях ноль

conparentid oid (ссылается на pg_constraint.oid)

Соответствующее ограничение в родительской партиционированной таблице, если это ограничение для партиции; в остальных случаях ноль

confrelid oid (ссылается на pg_class.oid)

Если это внешний ключ, то его ссылочная таблица; в остальных случаях ноль

confupdtype char

Код действия при обновлении внешнего ключа: a = нет действия (no action), r = ограничить (restrict), c = каскадное действие (cascade), n = установить NULL, d = поведение по умолчанию (default)

confdeltype char

Код действия при удалении внешнего ключа: a = нет действия, r = ограничить, c = каскадное действие, n = установить NULL, d = поведение по умолчанию

confmatchtype char

Тип сопоставления для внешнего ключа: f = полное (full), p = частичное (partial), s = простое (simple)

conislocal bool

Это ограничение определяется локально для данного отношения. Обратите внимание, что ограничение может одновременно быть локально определено и унаследовано.

coninhcount int4

Количество предков прямого наследования от ограничения. Ограничение с ненулевым количеством предков нельзя удалить или переименовать.

connoinherit bool

Это ограничение определено локально для данного отношения и является ненаследуемым.

conkey int2[] (ссылается на pg_attribute.attnum)

Для ограничения таблицы (включая внешние ключи, но не триггеры ограничений) — список столбцов с ограничением

confkey int2[] (ссылается на pg_attribute.attnum)

Для внешнего ключа — список ссылочных столбцов

conpfeqop oid[] (ссылается на pg_operator.oid)

Для внешнего ключа — список операторов равенства для сравнений PK (первичный ключ) = FK (внешний ключ)

conppeqop oid[] (ссылается на pg_operator.oid)

Для внешнего ключа — список операторов равенства для сравнений PK = PK

conffeqop oid[] (ссылается на pg_operator.oid)

Для внешнего ключа — список операторов равенства для сравнений FK = FK

confdelsetcols int2[] (ссылается на pg_attribute.attnum)

Для внешнего ключа с указанием действия SET NULL или SET DEFAULT при удалении — столбцы, которые будут изменены. При значении NULL будут изменены все столбцы, на которые ссылается ключ.

conexclop oid[] (ссылается на pg_operator.oid)

Для исключающего ограничения — список операторов исключения по столбцам

conbin pg_node_tree

Для проверочного ограничения — внутреннее представление выражения. (Чтобы извлечь определение проверочного ограничения, рекомендуется использовать функцию pg_get_constraintdef().)

В случае исключающего ограничения поле conkey полезно только для элементов ограничения, которые являются простыми ссылками на столбец. Для других случаев в conkey находится ноль, и чтобы обнаружить выражение, определяющее ограничение, необходимо просмотреть связанный индекс. (Следовательно, поле conkey имеет то же содержимое, что и поле pg_index.indkey для индекса.)

Примечание
Поле pg_class.relchecks необходимо согласовать с количеством записей проверочного ограничения, имеющихся в этой таблице для каждого отношения.

pg_conversion

В каталоге pg_conversion описываются функции преобразования кодировки. Подробную информацию см. на справочной странице команды CREATE CONVERSION.

Таблица 14. Столбцы pg_conversion

Столбец Тип

Описание

oid oid

Идентификатор строки

conname name

Имя преобразования (уникальное в пределах пространства имен)

connamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего это преобразование

conowner oid (ссылается на pg_authid.oid)

Владелец преобразования

conforencoding int4

Идентификатор исходной кодировки (функция pg_encoding_to_char() может преобразовать этот номер в имя кодировки)

contoencoding int4

Идентификатор целевой кодировки (функция pg_encoding_to_char() может преобразовать этот номер в имя кодировки)

conproc regproc (ссылается на pg_proc.oid)

Функция преобразования

condefault bool

True, если это преобразование по умолчанию

pg_database

В каталоге pg_database хранится информация о доступных базах данных. Базы данных создаются с помощью команды CREATE DATABASE. Подробную информацию о значении некоторых параметров см. в главе Управление базами данных.

В отличие от большинства системных каталогов, pg_database совместно используется всеми базами данных кластера: существует только одна копия pg_database на кластер, а не отдельная для каждой базы данных.

Таблица 15. Столбцы pg_database

Столбец Тип

Описание

oid oid

Идентификатор строки

datname name

Имя базы данных

datdba oid (ссылается на pg_authid.oid)

Владелец базы данных (как правило, пользователь, который ее создал)

encoding int4

Кодировка символов для этой базы данных (функция pg_encoding_to_char() может преобразовать этот номер в имя кодировки)

datlocprovider char

Провайдер локали для этой базы данных: c = libc, i = icu

datistemplate bool

Если true, то эту базу данных может клонировать любой пользователь, обладающий правом CREATEDB; если false, то клонировать ее могут только суперпользователи или владелец этой базы данных.

datallowconn bool

Если false, то никто не может подключиться к этой базе данных. Используется для защиты базы-шаблона template0 от изменений.

datconnlimit int4

Устанавливает максимальное число одновременных подключений, которые могут быть выполнены к этой базе данных. -1 означает отсутствие ограничений.

datfrozenxid xid

В этой базе данных все идентификаторы транзакций, предшествующие данному, были заменены на постоянный («замороженный») идентификатор транзакции. Это используется для отслеживания того, нужно ли очистить таблицу для предотвращения зацикливания идентификаторов транзакций или уменьшения размера pg_xact. Это минимальные значения поля pg_class.relfrozenxid для всех таблиц.

datminmxid xid

В этой базе данных все идентификаторы мультитранзакций, предшествующие данному, были заменены на другой идентификатор транзакции. Это используется для отслеживания того, нужно ли очистить таблицу для предотвращения зацикливания идентификаторов мультитранзакций или уменьшения размера pg_multixact. Это минимальные значения поля pg_class.relminmxid для всех таблиц.

dattablespace oid (ссылается на pg_tablespace.oid)

Табличное пространство по умолчанию для этой базы данных. В пределах этой базы данных все таблицы, у которых поле pg_class.reltablespace равно нулю, будут храниться в этом табличном пространстве (в частности, там будут находиться все системные каталоги, кроме разделяемых).

datcollate text

LC_COLLATE для этой базы данных

datctype text

LC_CTYPE для этой базы данных

daticulocale text

Идентификатор локали ICU для этой базы данных

datcollversion text

Версия правила сортировки, специфическая для поставщика данных. Записывается при создании правила сортировки, а затем проверяется при его использовании для обнаружения изменений в его определении, которые могут привести к повреждению данных.

datacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

pg_db_role_setting

В каталоге pg_db_role_setting для каждой комбинации роли и базы данных записываются значения по умолчанию, заданные для переменных конфигурации времени выполнения.

В отличие от большинства системных каталогов, pg_db_role_setting совместно используется всеми базами данных кластера: существует только одна копия pg_db_role_setting на кластер, а не отдельная для каждой базы данных.

Таблица 16. Столбцы pg_db_role_setting

Столбец Тип

Описание

setdatabase oid (ссылается на pg_database.oid)

OID базы данных, к которой применяется эта настройка переменных, или ноль, если она не специфична для конкретной базы данных

setrole oid (ссылается на pg_authid.oid)

OID роли, к которой применяется эта настройка переменных, или ноль, если она не специфична для конкретной роли

setconfig text[]

Значения по умолчанию для переменных конфигурации времени выполнения

pg_default_acl

В каталоге pg_default_acl хранятся права, изначально присваиваемые только что созданным объектам.

Таблица 17. Столбцы pg_default_acl

Столбец Тип

Описание

oid oid

Идентификатор строки

defaclrole oid (ссылается на pg_authid.oid)

OID роли, связанной с этой записью

defaclnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, связанного с этой записью, или ноль, если такового нет

defaclobjtype char

Тип объекта, для которого эта запись предназначена: r = отношение (relation) (таблица, представление), S = последовательность (sequence), f = функция (function), T = тип (type), n = схема

defaclacl aclitem[]

Права доступа, которые этот тип объекта должен иметь при создании

Запись в pg_default_acl отображает начальные права, которые будут присвоены объекту, принадлежащему указанному пользователю. В настоящее время существует два типа записей: «глобальные» записи с defaclnamespace = нулю и записи «схемы», ссылающиеся на конкретную схему. Если глобальная запись присутствует, то она переопределяет обычные фиксированные права по умолчанию для типа объекта. Посхемная запись, если она присутствует, представляет права, которые будут добавлены к глобальным или фиксированным правам по умолчанию.

Обратите внимание, что если запись ACL в другом каталоге имеет значение NULL, то она используется для представления фиксированных прав по умолчанию для ее объекта, а не тех, что могут находиться в pg_default_acl в данный момент. Каталог pg_default_acl учитывается только при создании объекта.

pg_depend

В каталоге pg_depend записываются отношения зависимости между объектами базы данных. Эта информация позволяет командам DROP находить, какие другие объекты должны быть удалены с помощью DROP CASCADE, или предотвращать удаление в случае DROP RESTRICT.

См. также описание каталога pg_shdepend, который выполняет аналогичную функцию для зависимостей, включающих разделяемые объекты в кластере баз данных.

Таблица 18. Столбцы pg_depend

Столбец Тип

Описание

classid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится зависимый объект

objid oid (ссылается на какой-либо столбец OID)

OID конкретного зависимого объекта

objsubid int4

Для столбца таблицы это номер столбца (поля objid и classid ссылаются на саму таблицу). Для всех остальных типов объектов этот столбец содержит ноль.

refclassid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится ссылочный объект

refobjid oid (ссылается на какой-либо столбец OID)

OID конкретного ссылочного объекта

refobjsubid int4

Для столбца таблицы это номер столбца (поля refobjid и refclassid ссылаются на саму таблицу). Для всех остальных типов объектов этот столбец содержит ноль.

deptype char

Код, определяющий конкретную семантику этого отношения зависимости; см. текст ниже

Во всех случаях запись в pg_depend указывает, что ссылочный объект нельзя удалить, не удалив зависимый объект. Тем не менее есть несколько подвидов зависимостей, задаваемых в поле deptype:

DEPENDENCY_NORMAL (n)
Обычное отношение между раздельно созданными объектами. Зависимый объект можно удалить, не затрагивая ссылочный объект. Последний можно удалить только путем указания CASCADE, и в этом случае зависимый объект также удаляется. Пример: столбец таблицы имеет нормальную зависимость от своего типа данных.

DEPENDENCY_AUTO (a)
Зависимый объект можно быть удален отдельно от ссылочного и должен быть автоматически удален (вне зависимости от режима RESTRICT или CASCADE) при удалении ссылочного объекта. Пример: именованное ограничение для таблицы становится автоматически зависимым от таблицы, поэтому если таблица будет удалена, оно исчезнет.

DEPENDENCY_INTERNAL (i)
Зависимый объект был создан как часть ссылочного и на самом деле является только частью его внутренней реализации. Удаление такого зависимого объекта напрямую при помощи DROP будет сразу же запрещено (вместо этого пользователю будет выдано сообщение о необходимости вызвать DROP для ссылочного объекта). Удаление ссылочного объекта приведет к автоматическому удалению зависимого объекта вне зависимости от наличия указания CASCADE. Если зависимый объект нужно удалить из-за зависимости от какого-либо другого удаляемого объекта, его удаление преобразуется в удаление ссылочного объекта, поэтому зависимости NORMAL и AUTO зависимого объекта ведут себя так же, как и зависимости ссылочного объекта. Пример: правило ON SELECT для представления становится внутренне зависимым от этого представления, что предотвращает удаление правила, пока существует представление. Зависимости для этого правила (например таблицы, на которые оно ссылается) действуют так, как если бы они были зависимостями представления.

DEPENDENCY_PARTITION_PRI (P)
DEPENDENCY_PARTITION_SEC (S)
Зависимый объект был создан как часть ссылочного и на самом деле является только частью его внутренней реализации; однако, в отличие от INTERNAL, существует более одного такого ссылочного объекта. Зависимый объект не должен удаляться, пока не будет удален хотя бы один из этих объектов; если какой-либо из них удаляется, зависимый объект должен удаляться вне зависимости от наличия указания CASCADE. Также, в отличие от INTERNAL, удаление некоторого другого объекта, от которого зависит объект, не приводит к автоматическому удалению какого-либо партиционно- ссылочного объекта. Следовательно, если удаление каскадно не доходит хотя бы до одного из этих объектов по какому-то другому пути, оно не произойдет. (В большинстве случаев зависимый объект совместно использует все свои непартиционированные зависимости, по крайней мере, с одним партиционно-ссылочным объектом, так что это ограничение не приведет к блокированию каскадного удаления.) Первичные (PRI) и вторичные (SEC) партиционные зависимости ведут себя одинаково, за исключением того, что первичная зависимость предпочтительна для использования в сообщениях об ошибках — следовательно, партиционно-зависимый объект должен иметь одну первичную партиционную зависимость и одну или несколько вторичных. Обратите внимание, что партиционные зависимости создаются в дополнение к любым зависимостям, которые обычно имеются у объекта, а не вместо них. Это упрощает операции ATTACH/DETACH PARTITION: необходимо только добавить или удалить партиционные зависимости. Пример: дочерний партиционированный индекс становится партиционно-зависимым как от своей партиционированной таблицы, так и от родительского партиционированного индекса, поэтому он будет удален при удалении любого из этих объектов, но не в других случаях. Зависимость от родительского индекса является первичной, так что если пользователь попытается удалить дочерний партиционированный индекс, в сообщении об ошибке будет предложено вместо этого удалить родительский индекс (а не таблицу).

DEPENDENCY_EXTENSION (е)
Зависимый объект является элементом расширения, которое является его ссылочным объектом (см. pg_extension). Зависимый объект можно удалить только с помощью команды DROP EXTENSION для ссылочного объекта. Функционально этот тип зависимости действует так же, как и зависимость INTERNAL, но он хранится отдельно для ясности и упрощения qhb_dump.

DEPENDENCY_AUTO_EXTENSION (x)
Зависимый объект не является элементом расширения, которое является его ссылочным объектом (и поэтому не должен игнорироваться qhb_dump), но он не может функционировать без расширения и должен автоматически удаляться при удалении расширения. Кроме того, зависимый объект можно удалить сам по себе. Функционально этот тип зависимости действует так же, как и зависимость AUTO, но он хранится отдельно для ясности и упрощения qhb_dump.

В будущем могут понадобиться другие разновидности зависимости.

Обратите внимание, что два объекта вполне могут быть связаны более чем одной записью в pg_depend. Например, дочерний партиционированный индекс будет иметь зависимость партиционного типа от связанной партиционированной таблицы и автозависимость от каждого столбца той таблицы, которую он индексирует. Такая ситуация выражает объединение семантики множественной зависимости. Зависимый объект можно удалить без указания CASCADE, если какая-либо из его зависимостей удовлетворяет его условию автоматического удаления. И наоборот, должны быть выполнены все ограничения зависимостей, определяющие, какие объекты должны быть удалены вместе.

Большинство объектов, созданных во время qhb_bootstrap (или initdb), считаются «закрепленными», что означает, что сама система зависит от них. Поэтому их ни в коем случае нельзя удалять. Кроме того, зная, что закрепленные объекты не будут удалены, механизм зависимостей не утруждается созданием в pg_depend записей, показывающих зависимости от этих объектов. Таким образом, например, столб таблицы типа numeric номинально имеет зависимость NORMAL от типа данных numeric, но фактически такая запись в pg_depend не появляется.

pg_description

В каталоге pg_description хранятся дополнительные описания (комментарии) для каждого объекта базы данных. Описания можно обрабатывать с помощью команды COMMENT и просматривать с помощью команд psql \d. В начальном содержимом pg_description представлены описания многих встроенных системных объектов.

См. также описание каталога pg_shdescription, который выполняет аналогичную функцию в отношении описаний разделяемых объектов в кластере баз данных.

Таблица 19. Столбцы pg_description

Столбец Тип

Описание

objoid oid (ссылается на какой-либо столбец OID)

OID объекта, к которому относится данное описание

classoid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится этот объект

objsubid int4

Для комментария к столбцу таблицы это номер столбца (поля objoid и classoid ссылаются на саму таблицу). Для всех остальных типов объектов этот столбец содержит ноль.

description text

Произвольный текст, который служит описанием данного объекта

pg_enum

В каталоге pg_enum содержатся записи, показывающие значения и метки для всех перечислимых типов. Внутренним представлением заданного значения перечисления в действительности является OID соответствующей ему строки в pg_enum.

Таблица 20. Столбцы pg_enum

Столбец Тип

Описание

oid oid

Идентификатор строки

enumtypid oid (ссылается на pg_type.oid)

OID записи в каталоге pg_type, содержащей это перечислимое значение

enumsortorder float4

Позиция этого перечислимого значения по порядку сортировки внутри его перечислимого типа

enumlabel name

Текстовая метка для этого перечислимого значения

OID для строк в pg_enum следуют специальному правилу: четные OID гарантированно упорядочиваются аналогично порядку сортировки их перечислимого типа. То есть если два четных OID принадлежат к одному и тому же перечислимому типу, меньший OID должен иметь меньшее значение enumsortorder. Нечетные значения OID могут быть никак не связаны с порядком сортировки. Это правило во многих случаях позволяет подпрограммам сравнения перечислений обходиться без поиска в каталоге. Подпрограммы, которые создают и изменяют перечислимые типы, по возможности пытаются назначать значениям перечислений четные OID.

При создании перечислимого типа его членам присваиваются позиции по порядку сортировки 1..n. Но членам, добавляемым позже, могут присваиваться отрицательные или дробные значения enumsortorder. Единственное требование к этим значениям в том, чтобы они были правильно упорядочены и уникальны в каждом перечислимом типе.

pg_event_trigger

В каталоге pg_event_trigger хранятся триггеры событий. Подробную информацию см. в главе Триггеры событий.

Таблица 21. Столбцы pg_event_trigger

Столбец Тип

Описание

oid oid

Идентификатор строки

evtname name

Имя триггера (должно быть уникальным)

evtevent name

Характеризует событие, для которого срабатывает этот триггер

evtowner oid (ссылается на pg_authid.oid)

Владелец триггера события

evtfoid oid (ссылается на pg_proc.oid)

Вызываемая функция

evtenabled char

Задает режимы session_replication_role, в которых срабатывает триггер события. О = триггер срабатывает в режимах «origin» (источник) и «local» (локально), D = триггер выключен (disabled), R = триггер срабатывает в режиме «replica» (реплика), A = триггер срабатывает всегда (always).

evttags text[]

Теги команд, для которых этот триггер будет срабатывать. Если NULL, то срабатывание этого триггера не ограничено на основании тега команды.

pg_extension

В каталоге pg_extension хранится информация об установленных расширениях. Подробную информацию о расширениях см. в разделе Упаковка связанных объектов в расширение.

Таблица 22. Столбцы pg_extension

Столбец Тип

Описание

oid oid

Идентификатор строки

extname name

Имя расширения

extowner oid (ссылается на pg_authid.oid)

Владелец расширения

extnamespace oid (ссылается на pg_namespace.oid)

Схема, содержащая экспортируемые объекты расширения

extrelocatable bool

True, если расширение можно переместить в другую схему

extversion text

Имя версии расширения

extconfig oid[] (ссылается на pg_class.oid)

Массив с OID regclass для таблиц(ы) конфигурации расширения или NULL, если у расширения нет таких таблиц

extcondition text[]

Массив с условиями фильтра предложения WHERE для таблиц(ы) конфигурации расширения или NULL, если условий нет

Обратите внимание, что, в отличие от большинства каталогов со столбцом «namespace», столбец extnamespace не подразумевает, что расширение принадлежит этой схеме. Имена расширений никогда не дополняются схемой. Скорее, extnamespace указывает схему, содержащую большинство или все объекты расширения. Если поле extrelocatable имеет значение true, то эта схема фактически должна содержать все дополненные схемой объекты, принадлежащие данному расширению.

pg_foreign_data_wrapper

В каталоге pg_foreign_data_wrapper хранятся определения оберток сторонних данных. Обертка сторонних данных — это механизм, с помощью которого осуществляется доступ к внешним данным, находящимся на сторонних серверах.

Таблица 23. Столбцы pg_foreign_data_wrapper

Столбец Тип

Описание

oid oid

Идентификатор строки

fdwname name

Имя обертки сторонних данных

fdwowner oid (ссылается на pg_authid.oid)

Владелец обертки сторонних данных

fdwhandler oid (ссылается на pg_proc.oid)

Ссылается на функцию обработки, которая отвечает за предоставление исполняемых подпрограмм для этой обертки сторонних данных. Ноль, если обработчик отсутствует

fdwvalidator oid (ссылается на pg_proc.oid)

Ссылается на функцию-валидатор, которая отвечает за проверку допустимости параметров, заданных обертке сторонних данных, а также параметров для сторонних серверов и сопоставлений пользователей, использующих эту обертку. Ноль, если валидатор отсутствует

fdwacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

fdwoptions text[]

Параметры, специфичные для обертки сторонних данных, в виде строк «ключ=значение»

pg_foreign_server

В каталоге pg_foreign_server хранятся определения сторонних серверов. Сторонний сервер описывает источник внешних данных, например удаленный сервер. Доступ к сторонним серверам осуществляется через обертки сторонних данных.

Таблица 24. Столбцы pg_foreign_server

Столбец Тип

Описание

oid oid

Идентификатор строки

srvname name

Имя стороннего сервера

srvowner oid (ссылается на pg_authid.oid)

Владелец стороннего сервера

srvfdw oid (ссылается на pg_foreign_data_wrapper.oid)

OID обертки сторонних данных этого стороннего сервера

srvtype text

Тип сервера (необязательно)

srvversion text

Версия сервера (необязательно)

srvacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

srvoptions text[]

Параметры, специфичные для стороннего сервера, в виде строк «ключ=значение»

pg_foreign_table

В каталоге pg_foreign_table содержится дополнительная информация о сторонних таблицах. Сторонняя таблица в основном представляется записью в каталоге pg_class аналогично обычной таблице. Запись о ней в pg_foreign_table содержит информацию, относящуюся только к сторонним таблицам, а не к какому-либо другому виду отношений.

Таблица 25. Столбцы pg_foreign_table

Столбец Тип

Описание

ftrelid oid (ссылается на pg_class.oid)

OID записи в каталоге pg_class для этой сторонней таблицы

ftserver oid (ссылается на pg_foreign_server.oid)

OID стороннего сервера для этой сторонней таблицы

ftoptions text[]

Параметры сторонней таблицы в виде строк «ключ=значение»

pg_index

В каталоге pg_index содержится часть информации об индексах. Остальное находится по большей части в каталоге pg_class.

Таблица 26. Столбцы pg_index

Столбец Тип

Описание

indexrelid oid (ссылается на pg_class.oid)

OID записи в каталоге pg_class для этого индекса

indrelid oid (ссылается на pg_class.oid)

OID записи в каталоге pg_class для таблицы, к которой относится этот индекс

indnatts int2

Общее количество столбцов в индексе (повторяет поле pg_class.relnatts); это число включает в себя как ключевые, так и включенные атрибуты

indnkeyatts int2

Количество ключевых столбцов в индексе, не считая каких-либо включенных столбцов, которые просто хранятся и не участвуют в семантике индекса

indisunique bool

Если true, то это уникальный индекс

indnullsnotdistinct bool

Это значение используется только для уникальных индексов. Если false, то этот уникальный индекс будет считать значения NULL различными (поэтому индекс сможет содержать в столбце несколько значений NULL; это поведение QHB по умолчанию). Если true, то уникальный индекс будет считать значения NULL одинаковыми (поэтому индекс сможет содержать в столбце только одно значение NULL).

indisprimary bool

Если true, то этот индекс представляет первичный ключ таблицы (в этом случае поле indisunique всегда должно иметь значение true)

indisexclusion bool

Если true, то этот индекс поддерживает исключающее ограничение

indimmediate bool

Если true, то проверка уникальности применяется сразу после вставки (неприменимо, если поле indisunique имеет значение false)

indisclustered bool

Если true, то последний раз данная таблица кластеризовалась по этому индексу

indisvalid bool

Если true, то индекс в настоящее время является допустимым для запросов. False означает, что индекс может быть неполным: он по-прежнему должен изменяться с помощью операций INSERT/UPDATE, но безопасно использовать его для запросов нельзя. Если индекс уникальный, свойство уникальности также не гарантируется.

indcheckxmin bool

Если true, запросы не должны использовать индекс до тех пор, пока поле xmin этой строки pg_index находится ниже их горизонта событий TransactionXmin, поскольку таблица может содержать разорванные цепочки HOT с видимыми для этих запросов несовместимыми строками

indisready bool

Если true, то индекс в настоящее время готов к вставкам. False означает, что индекс должен игнорироваться операциями INSERT/UPDATE.

indislive bool

Если false, индекс находится в процессе удаления и должен игнорироваться при любой попытке использования (включая решения о безопасности HOT)

indisreplident bool

Если true, то этот индекс был выбран в качестве «идентификатора реплики» с помощью команды ALTER TABLE ... REPLICA IDENTITY USING INDEX ...

indkey int2vector (ссылается на pg_attribute.attnum)

Это массив из значений indnatts, указывающий на столбцы таблицы, которые этот индекс обрабатывает. Например, значение 1 3 будет означать, что первый и третий столбцы таблицы составляют записи индекса. Ключевые столбцы предшествуют неключевым (включенным) столбцам. Ноль в этом массиве указывает, что соответствующий атрибут индекса является выражением по столбцам таблицы, а не просто ссылкой на столбец.

indcollation oidvector (ссылается на pg_collation.oid)

Для каждого столбца в ключе индекса (значения indnkeyatts) это поле содержит OID правила сортировки, используемого для индекса, или ноль, если столбец имеет несортируемый тип данных.

indclass oidvector (ссылается на pg_opclass.oid)

Для каждого столбца в ключе индекса (значения indnkeyatts) это поле содержит OID используемого класса операторов. Дополнительную информацию см. в описании каталога pg_opclass

indoption int2vector

Это массив из значений indnkeyatts, хранящих биты флагов для каждого столбца. Значение этих битов определяется индексным методом доступа.

indexprs pg_node_tree

Деревья выражений (в представлении nodeToString()) для атрибутов индекса, которые не являются простыми ссылками на столбец. Это список с одним элементом для каждой нулевой записи в поле indkey. NULL, если все атрибуты индекса являются простыми ссылками.

indpred pg_node_tree

Дерево выражений (в представлении nodeToString()) для предиката частичного индекса. NULL, если это не частичный индекс.

pg_inherits

В каталог pg_inherits записывается информация об иерархиях наследования таблицы и индекса. Для каждой прямой связи «родитель-потомок» таблицы или индекса в базе данных существует одна запись. (Косвенное наследование можно определить, проследив цепочки записей.)

Таблица 27. Столбцы pg_inherits

Столбец Тип

Описание

inhrelid oid (ссылается на pg_class.oid)

OID дочерней таблицы или индекса

inparent oid (ссылается на pg_class.oid)

OID родительской таблицы или индекса

inhseqno int4

Если у дочерней таблицы существует более одного прямого родителя (множественное наследование), это число указывает порядок, в котором должны располагаться наследуемые столбцы. Отсчет начинается с 1.
У индексов не может быть множественного наследования, так как они могут наследоваться только при декларативном партиционировании.

inhdetachpending bool

true для партиции, находящейся в процессе отсоединения; иначе — false.

pg_init_privs

В каталог pg_init_privs записывается информация об изначальных правах объектов в системе. Для каждого объекта в базе данных, имеющего нестандартный (отличный от NULL) начальный набор прав, существует одна запись.

Начальные права объектов могут задаваться либо при инициализации системы (с помощью qhb_bootstrap), либо при создании объекта во время выполнения команды CREATE EXTENSION, а скрипт расширения устанавливает начальные права с помощью системы GRANT. Обратите внимание, что эта система автоматически записывает права во время выполнения скрипта расширения, поэтому для записи прав авторам расширения достаточно использовать в своем скрипте только команды GRANT и REVOKE. Столбец privtype показывает, было ли начальное право установлено qhb_bootstrap или в процессе выполнения команды CREATE EXTENSION.

Объекты с начальными правами, установленными qhb_bootstrap, будут иметь записи, где privtype содержит 'i', тогда как у объектов с начальными правами, установленными CREATE EXTENSION, privtype будет иметь значение 'e'.

Таблица 28. Столбцы pg_init_privs

Столбец Тип

Описание

objoid oid (ссылается на какой-либо столбец OID)

OID конкретного объекта

classoid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится этот объект

objsubid int4

Для столбца таблицы это номер столбца (поля objoid и classoid ссылаются на саму таблицу). Для всех остальных типов объектов этот столбец содержит ноль.

privtype char

Код, определяющий тип начального права этого объекта; см. текст выше

initprivs aclitem[]

Начальные права доступа; подробную информацию см. в разделе Права

pg_language

В каталоге pg_language регистрируются языки, на которых можно писать функции или хранимые процедуры. Более подробную информацию об обработчиках языка см. на справочной странице команды CREATE LANGUAGE и в главе Процедурные языки.

Таблица 29. Столбцы pg_language

Столбец Тип

Описание

oid oid

Идентификатор строки

lanname name

Имя языка

lanowner oid (ссылается на pg_authid.oid)

Владелец языка

lanispl bool

Для внутренних языков (таких как SQL) имеет значение false, для пользовательских — true. В настоящее время qhb_dump все еще использует это, чтобы определить, какие языки нужно выгружать в дамп, но в будущем этот механизм может быть заменен на другой.

lanplusted bool

True, если это доверенный язык (это означает, что он предположительно не предоставляет доступ к чему-либо за пределами обычной среды выполнения SQL). Только суперпользователи могут создавать функции на недоверенных языках.

lanplcallfoid oid (ссылается на pg_proc.oid)

Для не внутренних языков это ссылка на обработчик языка, то есть специальную функцию, отвечающую за выполнение всех функций, написанных конкретно на этом языке. Ноль для внутренних языков.

laninline oid (ссылается на pg_proc.oid)

Ссылка на функцию, отвечающую за выполнение «подставляемых» анонимных блоков кода (блоков DO). Ноль, если подставляемые блоки не поддерживаются.

lanvalidator oid (ссылается на pg_proc.oid)

Ссылка на функцию-валидатор языка, отвечающую за проверку синтаксиса и допустимости новых функций при их создании. Ноль, если валидатор отсутствует.

lanacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

pg_largeobject

В каталоге pg_largeobject содержатся данные, составляющие «большие объекты». Большой объект идентифицируется OID, назначенным при его создании. Каждый большой объект разбивается на сегменты или «страницы», достаточно маленькие для удобного хранения в виде строк в pg_largeobject. Количество данных на странице определяется как LOBLKSIZE (который в настоящее время равен BLCKSZ/4, т. е. обычно 2 Кб).

Для получения списка OID больших объектов следует обратиться к каталогу pg_largeobject_metadata.

Таблица 30. Столбцы pg_largeobject

Столбец Тип

Описание

loid oid (ссылается на pg_largeobject_metadata.oid)

Идентификатор большого объекта, включающего эту страницу

pageno int4

Номер этой страницы в большом объекте (начиная с нуля)

data bytea

Непосредственно данные, хранящиеся в большом объекте. Их объем никогда не будет превышать LOBLKSIZE байт, но может быть меньше.

В каждой строке каталога pg_largeobject содержатся данные для одной страницы большого объекта, начиная со смещения (pageno * LOBLKSIZE) внутри него в байтах. Эта реализация допускает разреженное хранилище: страницы могут отсутствовать и быть короче LOBLKSIZE байт, даже если это не последние страницы объекта. Отсутствующие области внутри большого объекта считываются как нули.

pg_largeobject_metadata

В каталоге pg_largeobject_metadata содержатся метаданные, связанные с большими объектами. Фактические данные больших объектов хранятся в каталоге pg_largeobject.

Таблица 31. Столбцы pg_largeobject_metadata

Столбец Тип

Описание

oid oid

Идентификатор строки

lomowner oid (ссылается на pg_authid.oid)

Владелец большого объекта

lomacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

pg_namespace

В каталоге pg_namespace хранятся пространства имен. Пространство имен — это структура, лежащая в основе схем SQL: каждое пространство имен может содержать отдельный набор отношений, типов и т. д. без конфликтов имен.

Таблица 32. Столбцы pg_namespace

Столбец Тип

Описание

oid oid

Идентификатор строки

nspname name

Имя пространства имен

nspowner oid (ссылается на pg_authid.oid)

Владелец пространства имен

nspacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

pg_opclass

В каталоге pg_opclass определяются классы операторов для индексных методов доступа. Каждый класс операторов определяет семантику для индексных столбцов определенного типа данных и конкретного индексного метода доступа. Класс операторов по существу показывает, что конкретное семейство операторов применимо к конкретному индексируемому типу данных столбца. Набор операторов из семейства, который на самом деле можно использовать с индексированным столбцом, составляют операторы, принимающие в качестве левого операнда тип данных столбца.

Классы операторов подробно описываются в разделе Интерфейсные расширения для индексов.

Таблица 33. Столбцы pg_opclass

Столбец Тип

Описание

oid oid

Идентификатор строки

opcmethod oid (ссылается на pg_am.oid)

Индексный метод доступа, для которого предназначен этот класс операторов

opcname name

Имя класса операторов

opcnamespace oid (ссылается на pg_namespace.oid)

Пространство имен этого класса операторов

opcowner oid (ссылается на pg_authid.oid)

Владелец класса операторов

opcfamily oid (ссылается на pg_opfamily.oid)

Семейство операторов, содержащее этот класс операторов

opcintype oid (ссылается на pg_type.oid)

Тип данных, который индексируется этим классом операторов

opcdefault bool

True, если этот класс операторов используется по умолчанию для opcintype

opckeytype oid (ссылается на pg_type.oid)

Тип данных, хранящихся в индексе, или ноль, если он совпадает с opcintype

Значение поля opfmethod класса операторов должно совпадать с таковым у содержащего его семейства операторов. Кроме того, в каталоге pg_opclass должно быть не более одной строки, в которой opcdefault равно true для любой заданной комбинации opcmethod и opcintype.

pg_operator

В каталоге pg_operator хранится информация об операторах. Подробную информацию см. на справочной странице команды CREATE OPERATOR и в разделе Пользовательские операторы.

Таблица 34. Столбцы pg_operator

Столбец Тип

Описание

oid oid

Идентификатор строки

oprname name

Имя оператора

oprnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего этот оператор

oprowner oid (ссылается на pg_authid.oid)

Владелец оператора

oprkind char

b = инфиксный оператор («both») или l = префиксный оператор («left»)

oprcanmerge bool

Этот оператор поддерживает соединения слиянием

oprcanhash bool

Этот оператор поддерживает хеш-соединения

oprleft oid (ссылается на pg_type.oid)

Тип левого операнда (ноль для префиксного оператора)

oprright oid (ссылается на pg_type.oid)

Тип правого операнда

oprresult oid (ссылается на pg_type.oid)

Тип результата (ноль для еще не определенного «пустого» оператора)

oprcom oid (ссылается на pg_operator.oid)

Коммутатор этого оператора (ноль, если такового нет)

oprnegate oid (ссылается на pg_operator.oid)

Инвертор этого оператора (ноль, если такового нет)

oprcode regproc (ссылается на pg_proc.oid)

Функция, реализующая этот оператор (ноль для еще не определенного «пустого» оператора)

oprrest regproc (ссылается на pg_proc.oid)

Функция оценки избирательности ограничения для этого оператора (ноль, если таковой нет)

oprjoin regproc (ссылается на pg_proc.oid)

Функция оценки избирательности соединения для этого оператора (ноль, если таковой нет)

pg_opfamily

В каталоге pg_opfamily определяются семейства операторов. Каждое семейство операторов представляет собой набор операторов и связанных с ними вспомогательных процедур, реализующих семантику, специфичную для конкретного индексного метода доступа. Более того, все операторы в семействе «совместимы» в том смысле, который определяется методом доступа. Концепция семейства операторов позволяет применять с индексами операторы разного типа и рассуждать о них, используя знания о семантике метода доступа.

Семейства операторов подробно описаны в разделе Интерфейсные расширения для индексов.

Таблица 35. Столбцы pg_opfamily

Столбец Тип

Описание

oid oid

Идентификатор строки

opfmethod oid (ссылается на pg_am.oid)

Индексный метод доступа, для которого предназначено это семейство операторов

opfname name

Имя семейства операторов

opfnamespace oid (ссылается на pg_namespace.oid)

Пространство имен этого семейства операторов

opfowner oid (ссылается на pg_authid.oid)

Владелец семейства операторов

Большая часть информации, определяющей семейство операторов, находится не в строке каталога pg_opfamily, а в связанных строках каталогов pg_amop, pg_amproc и pg_opclass.

pg_parameter_acl

В каталоге pg_parameter_acl записываются параметры конфигурации, для которых предоставлены права одной или нескольким ролям. Для параметров, для которых имеются права по умолчанию, записей не делается.

В отличие от большинства системных каталогов, pg_parameter_acl совместно используется всеми базами данных кластера: существует только одна копия pg_parameter_acl на кластер, а не отдельная для каждой базы данных.

Таблица 36. Столбцы pg_parameter_acl

Столбец Тип

Описание

oid oid

Идентификатор строки

parname text

Имя параметра конфигурации, для которого были предоставлены права

paracl aclitem[]

Права доступа; подробную информацию см. в разделе Права

pg_partitioned_table

В каталоге pg_partitioned_table хранится информация о том, как партиционируются таблицы.

Таблица 37. Столбцы pg_partitioned_table

Столбец Тип

Описание

partrelid oid (ссылается на pg_class.oid)

OID записи в каталоге pg_class для этой партиционированной таблицы

partstrat char

Стратегия партиционирования: h = по хешу (hash), l = по списку (list), r = по диапазону (range)

partnatts int2

Количество столбцов в ключе разбиения

partdefid oid (ссылается на pg_class.oid)

OID записи в каталоге pg_class для партиции по умолчанию этой партиционированной таблицы или ноль, если эта партиционированная таблица не имеет партиции по умолчанию.

partattrs int2vector (ссылается на pg_attribute.attnum)

Это массив из значений поля partnatts, обозначающих, какие столбцы таблицы являются частью ключа разбиения. Например, значение 1 3 будет означать, что первый и третий столбцы таблицы составляют ключ разбиения. Ноль в этом массиве означает, что соответствующий столбец ключа разбиения является выражением, а не простой ссылкой на столбец.

partclass oidvector (ссылается на pg_opclass.oid)

Для каждого столбца в ключе разбиения это поле содержит OID используемых классов операторов. Подробную информацию см. в описании каталога pg_opclass.

partcollation oidvector (ссылается на pg_opclass.oid)

Для каждого столбца в ключе разбиения это поле содержит OID правила сортировки, используемого для партиционирования, или ноль, если столбец не относится к сортируемому типу данных.

partexprs pg_node_tree

Деревья выражений (в представлении nodeToString()) для столбцов ключа разбиения, которые не являются простыми ссылками на столбцы. Это список с одним элементом для каждой нулевой записи в поле partattrs. NULL, если все столбцы ключа разбиения являются простыми ссылками.

pg_policy

В каталоге pg_policy хранятся политики защиты на уровне строк для таблиц. Политика включает тип команды, к которой она применяется (возможно, это все команды), роли, к которым она применяется, выражение, вводимое в качестве требования барьера безопасности в запросы, включающие данную таблицу, и выражение, вводимое в качестве параметра WITH CHECK для запросов, которые пытаются добавить в таблицу новые записи.

Таблица 38. Столбцы pg_policy

Столбец Тип

Описание

oid oid

Идентификатор строки

polname name

Имя политики

polrelid oid (ссылается на pg_class.oid)

Таблица, к которой применяется политика

polcmd char

Тип команды, к которой применяется политика: r для SELECT, a для INSERT, w для UPDATE, d для DELETE или * для всех

polpermissive bool

Является ли эта политика разрешительной или ограничительной?

polroles oid[] (ссылается на pg_authid.oid)

Роли, к которым применяется политика; ноль означает PUBLIC (которая обычно является единственным элементом массива)

polqual pg_node_tree

Дерево выражений, добавляемое к требованиям барьеров безопасности для запросов, использующих данную таблицу

polwithcheck pg_node_tree

Дерево выражений, добавляемое к требованиям WITH CHECK для запросов, пытающихся добавить строки в таблицу

Примечание
Политики, хранящиеся в каталоге pg_policy, применяются, только когда для их таблицы установлен признак pg_class.relrowsecurity.

pg_proc

В каталоге pg_proc хранится информация о простых функциях, процедурах, агрегатных и оконных функциях (в совокупности также известных как подпрограммы). Более подробную информацию см. на справочных страницах команд CREATE FUNCTION и CREATE PROCEDURE и в разделе Пользовательские функции.

Если prokind указывает, что эта запись предназначена для агрегатной функции, в каталоге pg_aggregate должна иметься соответствующая строка.

Таблица 39. Столбцы pg_proc

Столбец Тип

Описание

oid oid

Идентификатор строки

proname name

Имя функции

pronamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего эту функцию

proowner oid (ссылается на pg_authid.oid)

Владелец функции

prolang oid (ссылается на pg_language.oid)

Язык реализации или интерфейс вызова этой функции

procost float4

Предполагаемая стоимость исполнения (в единицах cpu_operator_cost); если установлен признак proretset, это стоимость одной возвращенной строки

prorows float4

Предполагаемое количество возвращаемых строк (ноль, если не установлен признак proretset)

provariadic oid (ссылается на pg_type.oid)

Тип данных элементов переменного массива параметров или ноль, если функция не принимает переменное количество параметров

prosupport regproc (ссылается на pg_proc.oid)

Вспомогательная функция планировщика для этой функции (см. раздел Информация по оптимизации функций) или ноль, если таковой нет

prokind char

f для обычной функции (function), p для процедуры (procedure), a для агрегатной функции (aggregate) или w для оконной функции (window)

prosecdef bool

Функция является определителем контекста безопасности (т. е. функцией «setuid»)

proleakproof bool

Функция не имеет побочных эффектов. Никакая информация о ее аргументах не передается иначе, чем через возвращаемое значение. Любая функция, которая может вызвать ошибку, в зависимости от значений ее аргументов, не является герметичной.

proisstrict bool

Функция возвращает NULL, если любой аргумент вызова имеет значение NULL. В этом случае функция по факту не будет вызвана вообще. Функции, которые не являются «строгими», должны быть подготовлены для обработки входных значений NULL.

proretset bool

Функция возвращает множество (т. е. несколько значений указанного типа данных)

provolatile char

Поле provolatile показывает, зависит ли результат функции только от ее входных аргументов, или на него влияют внешние факторы. Для «неизменяемых» функций, которые всегда дают один и тот же результат для одних и тех же входных данных, устанавливается значение i (immutable). Для «стабильных» функций, результаты которых (для фиксированных входных аргументов) не изменяются в пределах сканирования, устанавливается значение s (stable). Для «изменчивых» функций, результаты которых могут измениться в любое время, устанавливается значение v (volatile). (Также v используется для функций с побочными эффектами, чтобы их вызовы нельзя было оптимизировать.)

proparallel char

Поле proparallel показывает, можно ли безопасно запускать функцию в параллельном режиме. Для функций, которые безопасно запускать в параллельном режиме без ограничений, устанавливается значение s (safe). Для функций, которые могут выполняться в параллельном режиме, но их выполнение ограничено ведущим процессом группы (параллельные рабочие процессы не могут вызывать эти функции), устанавливается значение r (restricted). Для функций, которые небезопасны в параллельном режиме (наличие такой функции заставляет оптимизатор строить план последовательного выполнения запроса), устанавливается значение u (unsafe).

pronargs int2

Количество входных аргументов

pronargdefaults int2

Количество аргументов, имеющих значения по умолчанию

prorettype oid (ссылается на pg_type.oid)

Тип данных возвращаемого значения

proargtypes oidvector (ссылается на pg_type.oid)

Массив с типами данных аргументов функции. Включает только входные аргументы (в том числе аргументы INOUT и VARIADIC) и тем самым представляет сигнатуру вызова функции.

proallargtypes oid[] (ссылается на pg_type.oid)

Массив с типами данных аргументов функции. Включает в себя все аргументы (в том числе аргументы OUT и INOUT), однако если все аргументы являются IN, это поле будет иметь значение NULL. Обратите внимание, что индексы массивов обычно начинаются с 1, тогда как по историческим причинам proargtypes индексируются, начиная с 0.

proargmodes char[]

Массив с режимами аргументов функции, закодированными как i для аргументов IN, о для аргументов OUT, b для аргументов INOUT, v для аргументов VARIADIC, t для аргументов TABLE. Если все аргументы являются IN, это поле будет иметь значение NULL. Обратите внимание, что индексы соответствуют позициям в proallargtypes, а не в proargtypes.

proargnames text[]

Массив с именами аргументов функции. Аргументы без имени устанавливаются в массиве как пустые строки. Если ни один из аргументов не имеет имени, это поле будет иметь значение NULL. Обратите внимание, что индексы соответствуют позициям в proallargtypes, а не в proargtypes.

proargdefaults pg_node_tree

Деревья выражений (в представлении nodeToString()) для значений по умолчанию. Это список, содержащий pronargdefaults элементов, соответствующих последним N входным аргументам (т. е. последним N позициям в поле proargtypes). Если ни один из аргументов не имеет значения по умолчанию, это поле будет иметь значение NULL.

protrftypes oid[] (ссылается на pg_type.oid)

Массив типов данных аргументов/результатов, к которым необходимо применить преобразования (из предложения TRANSFORM объявления функции). NULL, если таковых нет.

prosrc text

Это значение говорит обработчику функции, как ее вызвать. Это может быть фактический исходный код функции для интерпретируемых языков, символ ссылки, имя файла или что-нибудь еще, в зависимости от языка реализации/соглашения о вызовах.

probin text

Дополнительная информация о том, как вызвать эту функцию. Интерпретация этого значения тоже зависит от языка.

prosqlbody pg_node_tree

Предварительно проанализированное тело функции SQL. Используется для функций на языке SQL, когда тело функции задается в виде стандартной нотации SQL, а не строкового литерала. В остальных случаях — NULL.

proconfig text[]

Локальные настройки переменных конфигурации времени выполнения, действующие для функции

proacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

Для скомпилированных функций, как встроенных, так и динамически загружаемых, поле prosrc содержит имя функции на языке C/RUST (символ ссылки). Для функции на языке SQL prosrc содержит исходный текст функции, если та задана в виде строкового литерала, но если тело функции записано в стиле стандарта SQL, поле prosrc не используется (обычно это пустая строка), а поле prosqlbody содержит предварительно проанализированное определение. Для всех остальных известных на данный момент типов языков поле prosrc содержит исходный текст функции. Поле probin всегда имеет значение NULL, за исключением динамически загружаемых функций на языке C/RUST, для которых оно задает имя файла разделяемой библиотеки, содержащей эти функции.

pg_publication

В каталоге pg_publication содержатся все публикации, созданные в базе данных. Подробнее о публикациях можно узнать в разделе Публикация.

Таблица 40. Столбцы pg_publication

Столбец Тип

Описание

oid oid

Идентификатор строки

pubname name

Имя публикации

pubname oid (ссылается на pg_authid.oid)

Владелец публикации

puballtables bool

Если true, эта публикация автоматически включает все таблицы в базе данных, в том числе те, что будут созданы в будущем.

pubinsert bool

Если true, операции INSERT реплицируются для таблиц в публикации.

pubupdate bool

Если true, операции UPDATE реплицируются для таблиц в публикации.

pubdelete bool

Если true, операции DELETE реплицируются для таблиц в публикации.

pubtruncate bool

Если true, операции TRUNCATE реплицируются для таблиц в публикации.

pubviaroot bool

Если true, операции с листовой партицией реплицируются с использованием идентификатора и схемы включенной в данную публикацию партиционированной таблицы-родителя самого верхнего уровня, а не ее собственных.

pg_publication_namespace

В каталоге pg_publication_namespace содержатся сопоставления между схемами и публикациями в базе данных. Это сопоставления вида многие-ко-многим.

Таблица 41.Столбцы pg_publication_namespace

Столбец Тип

Описание

oid oid

Идентификатор строки

pnpubid oid (ссылается на pg_publication.oid)

Ссылка на публикацию

pnnspid oid (ссылается на pg_namespace.oid)

Ссылка на схему

pg_publication_rel

В каталоге pg_publication_rel содержатся сопоставления между отношениями и публикациями в базе данных. Это сопоставления вида многие-ко-многим. В более понятном пользователю виде эта информация выводится в представлении pg_publication_tables.

Таблица 42. Столбцы pg_publication_rel

Столбец Тип

Описание

oid oid

Идентификатор строки

prpubid oid (ссылается на pg_publication.oid)

Ссылка на публикацию

prrelid oid (ссылается на pg_class.oid)

Ссылка на отношение

prqual pg_node_tree

Дерево выражений (в представлении nodeToString()) для квалифицирующего условия публикации отношения. NULL, если у публикации нет такого условия.

prattrs int2vector (ссылается на pg_attribute.attnum)

Это массив значений, показывающих, какие столбцы таблицы являются частью публикации. Например, значение 1 3 будет означать, что публикуются первый и третий столбцы таблицы. Значение NULL показывает, что публикуются все столбцы.

pg_range

В каталоге pg_range хранится информация о диапазонных типах. Это дополнительная информация к записям типов в каталоге pg_type.

Таблица 43. Столбцы pg_range

Столбец Тип

Описание

rngtypid oid (ссылается на pg_type.oid)

OID диапазонного типа

rngsubtype oid (ссылается на pg_type.oid)

OID типа элемента (подтипа) этого диапазонного типа

rngmultitypid oid (ссылается на pg_type.oid)

OID мультидиапазонного типа для этого диапазонного типа

rngcollation oid (ссылается на pg_collation.oid)

OID правила сортировки, используемого для сравнения диапазонов, или ноль, если такового нет

rngsubopc oid (ссылается на pg_opclass.oid)

OID класса операторов подтипа, используемого для сравнения диапазонов

rngcanonic regproc (ссылается на pg_proc.oid)

OID функции, преобразующей значение диапазона в каноничную форму, или ноль, если таковой нет

rngsubdiff regproc (ссылается на pg_proc.oid)

OID функции, возвращающей разницу между значениями двух элементов в виде значения double precision, или ноль, если таковой нет

Значение поля rngsubopc (плюс rngcollation, если тип элемента является сортируемым) определяет правило сортировки, используемое диапазонным типом. Значение поля rngcanonic используется, когда тип элемента является дискретным. Значение поля rngsubdiff является необязательным, но его стоит задавать для повышения производительности индексов GiST с диапазонным типом.

pg_replication_origin

В каталоге pg_replication_origin содержатся все созданные источники репликации. Подробную информацию об источниках репликации см. в главе Отслеживание прогресса репликации.

В отличие от большинства системных каталогов, pg_replication_origin совместно используется всеми базами данных кластера: существует только одна копия pg_replication_origin на кластер, а не отдельная для каждой базы данных.

Таблица 44. Столбцы pg_replication_origin

Столбец Тип

Описание

roident oid

Уникальный в пределах кластера идентификатор источника репликации. Никогда не должен выходить за границы системы.

roname text

Определяемое пользователем внешнее имя источника репликации.

pg_rewrite

В каталоге pg_rewrite хранятся правила перезаписи для таблиц и представлений.

Таблица 45. Столбцы pg_rewrite

Столбец Тип

Описание

oid oid

Идентификатор строки

rulename name

Имя правила

ev_class oid (ссылается на pg_class.oid)

Таблица, для которой предназначено это правило

ev_type char

Тип события, для которого используется это правило: 1 = SELECT, 2 = UPDATE, 3 = INSERT, 4 = DELETE

ev_enabled char

Задает режимы session_replication_role, при которых срабатывает это правило. О = правило срабатывает в режимах «origin» (источник) и «local» (локально), D = правило выключено (disabled), R = правило срабатывает в режиме «replica» (реплика), A = правило срабатывает всегда (always).

is_instead bool

True, если это правило INSTEAD

ev_qual pg_node_tree

Дерево выражений (в представлении nodeToString()) для условия применения правила

ev_action pg_node_tree

Дерево запросов (в форме представления nodeToString()) для действия правила

Примечание
Если таблица имеет какие-либо правила в этом каталоге, поле pg_class. relhasrules должно содержать true.

pg_seclabel

В каталоге pg_seclabel хранятся метки безопасности для объектов базы данных. Метками безопасности можно управлять с помощью команды SECURITY LABEL. Более простой способ просмотра меток безопасности см. в описании представления pg_seclabels.

См. также описание каталога pg_shseclabel, который выполняет ту же функцию для меток безопасности разделяемых объектов в кластере баз данных.

Таблица 46. Столбцы pg_seclabel

Столбец Тип

Описание

objoid oid (ссылается на какой-либо столбец OID)

OID объекта, к которому относится эта метка безопасности

classoid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится этот объект

objsubid int4

Для метки безопасности по столбцу таблицы это номер столбца (поля objoid и classoid ссылаются на саму таблицу). Для всех остальных типов объектов этот столбец содержит ноль.

provider text

Поставщик меток безопасности, связанный с этой меткой.

label text

Метка безопасности, применяемая к этому объекту.

pg_sequence

В каталоге pg_sequence содержится информация о последовательностях. Некоторые сведения о последовательностях, такие как имя и схема, находятся в каталоге pg_class.

Таблица 47. Столбцы pg_sequence

Столбец Тип

Описание

seqrelid oid (ссылается на pg_class.oid)

OID записи в каталоге pg_class для этой последовательности

seqtypid oid (ссылается на pg_type.oid)

Тип данных последовательности

seqstart int8

Начальное значение последовательности

seqincrement int8

Значение приращения последовательности

seqmax int8

Максимальное значение последовательности

seqmin int8

Минимальное значение последовательности

seqcache int8

Размер кэша последовательности

seqcycle bool

Зацикливается ли последовательность

pg_shdepend

В каталог pg_shdepend записываются отношения зависимости между объектами базы данных и разделяемыми объектами, например ролями. Эта информация позволяет QHB перед попыткой удаления этих объектов убедиться в том, что они не используются.

См. также описание каталога pg_depend, который выполняет ту же функцию для зависимостей объектов в пределах одной базы данных.

В отличие от большинства системных каталогов, pg_shdepend совместно используется всеми базами данных кластера: существует только одна копия pg_shdepend на кластер, а не отдельная для каждой базы данных.

Таблица 48. Столбцы pg_shdepend

Столбец Тип

Описание

dbid oid (ссылается на pg_database.oid)

OID базы данных, в которой находится зависимый объект, или ноль для разделяемого объекта

classid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится зависимый объект

objid oid (ссылается на какой-либо столбец OID)

OID конкретного зависимого объекта

objsubid int4

Для столбца таблицы это номер столбца (поля objid и classid ссылаются на саму таблицу). Для всех остальных типов объектов этот столбец содержит ноль.

refclassid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится ссылочный объект (это должен быть разделяемый каталог)

refobjid oid (ссылается на какой-либо столбец OID)

OID конкретного ссылочного объекта

deptype char

Код, определяющий конкретную семантику этого отношения зависимости; см. текст ниже

Во всех случаях запись в pg_shdepend показывает, что ссылочный объект нельзя удалить, не удалив также зависимый объект. Однако есть несколько подвидов зависимостей, которые задаются в поле deptype:

SHARED_DEPENDENCY_OWNER (о)
Ссылочный объект (который должен быть ролью) является владельцем зависимого объекта.

SHARED_DEPENDENCY_ACL (a)
Ссылочный объект (который должен быть ролью) упоминается в списке ACL (список управления доступом (access control list), т. е. список прав) зависимого объекта. (Запись SHARED_DEPENDENCY_ACL не создается для владельца объекта, так как у того в любом случае будет запись SHARED_DEPENDENCY_OWNER.)

SHARED_DEPENDENCY_POLICY (r)
Ссылочный объект (который должен быть ролью) является целью для объекта зависимой политики.

SHARED_DEPENDENCY_TABLESPACE (t)
Ссылочный объект (который должен быть табличным пространством) является табличным пространством для отношения, у которого нет хранилища.

В будущем могут потребоваться и другие подвиды зависимостей. В частности, обратите внимание, что текущее определение поддерживает в качестве ссылочных объектов только роли и табличные пространства.

Как и в каталоге pg_depend, большинство объектов, созданных во время qhb_bootstrap (или initdb) считаются «закрепленными». В pg_shdepend не создаются никакие записи, в которых закрепленный объект мог бы быть ссылочным или зависимым.

pg_shdescription

В каталоге pg_shdescription хранятся необязательные описания (комментарии) для разделяемых объектов базы данных. Описаниями можно манипулировать с помощью команды COMMENT и просматривать с помощью команд psql \d.

См. также описание каталога pg_description, который выполняет ту же функцию для описаний объектов в пределах одной базы данных.

В отличие от большинства системных каталогов, pg_shdescription совместно используется всеми базами данных кластера: существует только одна копия pg_shdescription на кластер, а не отдельная для каждой базы данных.

Таблица 49. Столбцы pg_shdescription

Столбец Тип

Описание

objoid oid (ссылается на какой-либо столбец OID)

OID объекта, к которому относится это описание

classoid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится этот объект

description text

Произвольный текст, который служит описанием этого объекта

pg_shseclabel

В каталоге pg_shseclabel хранятся метки безопасности по разделяемым объектам базы данных. Метками безопасности можно управлять с помощью команды SECURITY LABEL. Более простой способ просмотра меток безопасности см. в описании представления pg_seclabels.

См. также описание каталога pg_seclabel, который выполняет ту же функцию для меток безопасности объектов в пределах одной базы данных.

В отличие от большинства системных каталогов, pg_shseclabel совместно используется всеми базами данных кластера: существует только одна копия pg_shseclabel на кластер, а не отдельная для каждой базы данных.

Таблица 50. Столбцы pg_shseclabel

Столбец Тип

Описание

objoid oid (ссылается на какой-либо столбец OID)

OID объекта, к которому относится эта метка безопасности

classoid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится этот объект

provider text

Поставщик меток безопасности, связанный с этой меткой

label text

Метка безопасности, применяемая к этому объекту

pg_statistic

В каталоге pg_statistic хранятся статистические данные о содержимом базы данных. Записи создаются при помощи команды ANALYZE и впоследствии используются планировщиком запросов. Обратите внимание, что все статистические данные по своей сути приблизительны, даже если предполагается, что они актуальны.

Обычно для каждого столбца таблицы, который был проанализирован, есть одна запись со значением stainherit = false. Если таблица имеет потомков в иерархии наследования, то создается также вторая запись со значением stainherit = true. Эта строка представляет статистику по столбцу по всему дереву наследования, т. е. статистику по данным, которые выводятся с помощью команды SELECT столбец FROM таблица*, в то время как строка stainherit = false представляет результаты выполнения команды SELECT столбец FROM ONLY таблица.

Также в каталоге pg_statistic хранятся статистические данные о значениях индексных выражений. Они описываются так, как если бы они были реальными столбцами данных; в частности, starelid ссылается на индекс. Однако для обычного столбца индекса, не содержащего выражения, запись не производится, поскольку она дублировала бы запись для нижележащего столбца таблицы. В настоящее время записи для индексных выражений всегда имеют значение stainherit = false.

Поскольку различные виды статистики подходят для различных видов данных, pg_statistic предназначен не для того, чтобы строить конкретные предположения о том, какая статистика в нем хранится. В отдельных столбцах pg_statistic приводятся только самые общие статистические данные (например процент значений NULL). Все остальное хранится в «слотах», представляющих собой группы связанных столбцов, содержимое которых определяется кодовым номером в одном из слотов.

Каталог pg_statistic не должен быть доступен для чтения всеми, поскольку даже статистическая информация о содержимом таблицы может считаться конфиденциальной. (Например, могут представлять интерес минимальные и максимальные значения столбца зарплаты.) Доступным для чтения всеми представлением каталога pg_statistic является pg_stats, которое выводит информацию только о тех таблицах, которые может читать текущий пользователь.

Таблица 51. Столбцы pg_statistic

Столбец Тип

Описание

starelid oid (ссылается на pg_class.oid)

Таблица или индекс, которому принадлежит описываемый столбец

staattnum int2 (ссылается на pg_attribute.attnum)

Номер описываемого столбца

stainherit bool

Если true, то в статистику включаются значения из дочерних таблиц иерархии наследования, а не только значения в указанном отношении

stanullfrac float4

Доля записей столбца, которые содержат NULL

stawidth int4

Средняя хранимая ширина записей, отличных от NULL, в байтах

stadistinct float4

Количество уникальных значений данных, отличных от NULL, в столбце. Значение больше нуля — это фактическое количество уникальных значений. Значение меньше нуля — это множитель для количества строк в таблице, взятый с отрицательным знаком; например, столбец, в котором около 80% значений отличны от NULL и каждое такое значение появляется в среднем примерно два раза, может быть представлен как stadistinct = -0.4. Нулевое значение означает, что количество уникальных значений неизвестно.

stakindN int2

Кодовый номер, указывающий вид статистики, хранящейся в N-м «слоте» строки каталога pg_statistic.

staopN oid (ссылается на pg_operator.oid)

Оператор, используемый для получения статистики, хранящейся в N-м «слоте». Например, для слота гистограммы это будет оператор <, определяющий порядок сортировки данных. Ноль, если для этого вида статистики не требуется оператор.

stacollN oid (ссылается на pg_collation.oid)

Правило сортировки, используемое для получения статистики, хранящейся в N-м «слоте». Например, для слота гистограммы для сортируемого столбца это будет правило сортировки, определяющее порядок сортировки данных. Ноль для несортируемых данных.

stanumbersN float4[]

Числовая статистика соответствующего вида для N-го «слота» или NULL, если этот вид слота не содержит числовых значений

stavaluesN anyarray

Значения данных столбца соответствующего вида для N-го «слота» или NULL, если этот вид слота не хранит никаких значений данных. Значения каждого элемента массива фактически относятся к определенному типу данных столбца или связанному типу, например типу элемента массива, поэтому нет способа определить тип этих столбцов более конкретно, чем anyarray.

pg_statistic_ext

В каталоге pg_statistic_ext содержатся определения расширенной статистики планировщика. Каждая строка в этом каталоге соответствует объекту статистики, созданному с помощью команды CREATE STATISTICS.

Таблица 52. Столбцы pg_statistic_ext

Столбец Тип

Описание

oid oid

Идентификатор строки

stxrelid oid (ссылается на pg_class.oid)

Таблица, содержащая столбцы, описываемые этим объектом

stxname name

Имя объекта статистики

stxnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего этот объект статистики

stxowner oid (ссылается на pg_authid.oid)

Владелец объекта статистики

stxstattarget int4

Поле stxstattarget определяет уровень детализации статистики, собранной для данного объекта статистики командой ANALYZE. Нулевое значение показывает, что статистику собирать не нужно. Отрицательное значение указывает использовать максимум из ориентиров статистики для целевых столбцов, если таковые установлены, или системный ориентир статистики по умолчанию. Положительные значения stxstattarget определяют ориентировочное количество «наиболее распространенных значений», которое нужно собрать.

stxkeys int2vector (ссылается на pg_attribute.attnum)

Массив номеров атрибутов, показывающий, какие столбцы таблицы охвачены этим объектом статистики; например, значение 1 3 будет означать, что охвачены первый и третий столбцы таблицы

stxkind char[]

Массив, содержащий коды для включенных типов статистики; допустимыми значениями являются: d для статистики по количеству уникальных значений (n-distinct), f для статистики функциональной зависимости (functional dependency), m для статистики списка наиболее распространенных значений (most common values, MCV) и e для статистики выражений (expression)

stxexprs pg_node_tree

Деревья выражений (в представлении nodeToString()) для атрибутов объекта статистики, которые не являются простыми ссылками на столбец. Это список с одним элементом для каждого выражения. NULL, если все атрибуты объекта статистики являются простыми ссылками.

Запись в каталоге pg_statistic_ext полностью заполняется во время выполнения команды CREATE STATISTICS, но фактические статистические значения при этом не вычисляются. Последующие команды ANALYZE вычисляют требуемые значения и заполняют запись в каталоге pg_statistic_ext_data.

pg_statistic_ext_data

В каталоге pg_statistic_ext_data хранятся данные для расширенной статистики планировщика, определенной в каталоге pg_statistic_ext. Каждая строка в этом каталоге соответствует объекту статистики, созданному с помощью команды CREATE STATISTICS.

Обычно для каждого проанализированного объекта статистики здесь имеется одна запись со значением stxdinherit = false. Если у таблицы есть потомки в иерархии наследования или партиции, также создается вторая запись со значением stxdinherit = true. Эта строка представляет объект статистики по дереву наследования, т. е. статистику для данных, которые можно увидеть, выполнив запрос SELECT * FROM таблица*, тогда как строка stxdinherit = false представляет результаты запроса SELECT * FROM ONLY таблица.

Как и каталог pg_statistic, pg_statistic_ext_data не должен быть доступен для чтения всем, поскольку его содержимое может считаться конфиденциальным. (Пример: определенный интерес могут предоставлять наиболее распространенных комбинации значений в столбцах базы данных.) Доступным для всех представлением каталога pg_statistic_ext_data является pg_stats_ext (после соединения с каталогом pg_statistic_ext), которое предоставляет информацию только о тех таблицах и столбцах, которые может прочитать текущий пользователь.

Таблица 53. Столбцы pg_statistic_ext_data

Столбец Тип

Описание

stxoid oid (ссылается на pg_statistic_ext.oid)

Объект расширенной статистики, содержащий определение для этих данных

stxdinherit bool

Если true, то в статистику включаются значения из дочерних таблиц иерархии наследования, а не только значения в указанном отношении

stxdndistinct pg_ndistinct

Количество уникальных значений, сериализованное в виде типа pg_ndistinct

stxddependencies pg_dependencies

Статистика функциональных зависимостей, сериализованная в виде типа pg_dependencies

stxdmcv pg_mcv_list

Статистика списка MCV (most-common values, наиболее распространенных значений), сериализованная в виде типа pg_mcv_list

stxdexpr pg_statistic[]

Статистика по каждому выражению, сериализованная в виде массива типа pg_statistic

pg_subscription

В каталоге pg_subscription содержатся все существующие подписки на логическую репликацию. Подробную информацию о логической репликации см. в главе Логическая репликация.

В отличие от большинства системных каталогов, pg_subscription совместно используется всеми базами данных кластера: существует только одна копия pg_subscription на кластер, а не отдельная для каждой базы данных.

Обычным пользователям запрещен доступ к столбцу subconninfo, поскольку он может содержать пароли в открытом виде.

Таблица 54. Столбцы pg_subscription

Столбец Тип

Описание

oid oid

Идентификатор строки

subdbid oid (ссылается на pg_database.oid)

OID базы данных, в которой находится подписка

subskiplsn pg_lsn

LSN конца транзакции, изменения которой должны быть пропущены, если это правильный LSN; в противном случае 0/0.

subname name

Имя подписки

subowner oid (ссылается на pg_authid.oid)

Владелец подписки

subenabled bool

Если true, то подписка включена и должна реплицироваться.

subbinary bool

Если true, то подписка будет запрашивать данные у публикующего сервера в двоичном формате

substream bool

Если true, то для подписки будет разрешена потоковая передача данных еще выполняющихся транзакций

subtwophasestate char

Коды состояния для двухфазного режима: d = выключен (disabled), p = ожидается включение (pending), e = включен (enabled)

subdisableonerr bool

Если true, подписка будет выключена, если один из ее рабочих процессов обнаружит ошибку

subconninfo text

Строка подключения к вышестоящей базе данных

subslotname name

Имя слота репликации в вышестоящей базе данных (также используется в качестве локального имени источника репликации); NULL представляет NONE.

subsynccommit text

Значение параметра synchronous_commit для рабочих процессов подписки

subpublications text[]

Массив имен публикаций, на которые оформлена подписка. Эти имена представляют публикации, определенные в вышестоящей базе данных. Подробнее публикации описаны в разделе Публикация.

pg_subscription_rel

В каталоге pg_subscription_rel содержится состояние для каждого реплицируемого отношения в каждой подписке. Это сопоставление вида многие-ко-многим.

Этот каталог содержит только таблицы, известные подписке после выполнения команды CREATE SUBSCRIPTION или ALTER SUBSCRIPTION ... REFRESH PUBLICATION.

Таблица 55. Столбцы pg_subscription_rel

Столбец Тип

Описание

srsubid oid (ссылается на pg_subscription.oid)

Ссылка на подписку

srrelid oid (ссылается на pg_class.oid)

Ссылка на отношение

srsubstate char

Код состояния: i = инициализация (initialize), d = копирование данных (data), f = копирование таблицы завершено (finished), s = синхронизация выполнена (synchronized), r = готовность (ready) (нормальная репликация)

srsublsn pg_lsn

LSN изменения состояния на удаленной стороне, используемый для координации синхронизации при состояниях s или r; в иных случаях NULL

pg_tablespace

В каталоге pg_tablespace хранится информация о доступных табличных пространствах. Таблицы можно размещать в конкретных табличных пространствах с целью управления структурой диска.

В отличие от большинства системных каталогов, pg_tablespace совместно используется всеми базами данных кластера: существует только одна копия pg_tablespace на кластер, а не отдельная для каждой базы данных.

Таблица 56. Столбцы pg_tablespace

Столбец Тип

Описание

oid oid

Идентификатор строки

spcname name

Имя табличного пространства

spcowner oid (ссылается на pg_authid.oid)

Владелец табличного пространства (обычно это пользователь, который его создал)

spcacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

spcoptions text[]

Параметры уровня табличного пространства в виде строк «ключ=значение»

pg_transform

В каталоге pg_transform хранится информация о преобразованиях, которые являются механизмом адаптации типов данных к процедурным языкам. Подробную информацию см. на справочной странице команды CREATE TRANSFORM.

Таблица 57. Столбцы pg_transform

Столбец Тип

Описание

oid oid

Идентификатор строки

trftype oid (ссылается на pg_type.oid)

OID типа данных, для которого предназначено это преобразование

trflang oid (ссылается на pg_language.oid)

OID языка, для которого предназначено это преобразование

trffromsql regproc (ссылается на pg_proc.oid)

OID функции, используемой при преобразовании типа данных для передачи в процедурный язык (например, параметры функции). Содержит ноль, если должно применяться поведение по умолчанию.

trftosql regproc (ссылается на pg_proc.oid)

OID функции, используемой при преобразовании значений, полученных из процедурного языка (например, возвращаемых значений), в тип данных. Содержит ноль, если должно применяться поведение по умолчанию.

pg_trigger

В каталоге pg_trigger хранится информация о триггерах в таблицах и представлениях. Дополнительную информацию см. на справочной странице команды CREATE TRIGGER.

Таблица 58. Столбцы pg_trigger

Столбец Тип

Описание

oid oid

Идентификатор строки

tgrelid oid (ссылается на pg_class.oid)

Таблица, на которую действует этот триггер

tgparentid oid (ссылается на pg_trigger.oid)

Родительский триггер, из которого клонируется данный (это происходит при создании партиций или присоединении их к партиционированной таблице); ноль, если данный триггер — не клон

tgname name

Имя триггера (должно быть уникальным среди триггеров одной таблицы)

tgfoid oid (ссылается на pg_proc.oid)

Вызываемая функция

tgtype int2

Битовая маска, определяющая условия срабатывания триггера

tgenabled char

Задает режимы session_replication_role, при которых срабатывает триггер. О = триггер срабатывает в режимах «origin» (источник) и «local» (локально), D = триггер выключен (disabled), R = триггер срабатывает в режиме «replica» (реплика), A = триггер срабатывает всегда (always).

tgisinternal bool

True, если триггер генерируется внутренне (обычно для принудительного применения ограничения, определенного в поле tgconstraint)

tgconstrrelid oid (ссылается на pg_class.oid)

Таблица, на которую ссылается ограничение ссылочной целостности (ноль, если триггер предназначен не для реализации ограничения ссылочной целостности)

tgconstrindid oid (ссылается на pg_class.oid)

Индекс, поддерживающий ограничение уникальности, первичного ключа, ссылочной целостности или исключения (ноль, если триггер предназначен не для реализации этих типов ограничений)

tgconstraint oid (ссылается на pg_constraint.oid)

Связанная с триггером запись в каталоге pg_constraint (ноль, если триггер предназначен не для ограничения)

tgdeferrable bool

True, если триггер ограничения является откладываемым

tginitdeferred bool

True, если триггер ограничения изначально отложен

tgnargs int2

Количество строк аргументов, переданных в триггерную функцию

tgattr int2vector (ссылается на pg_attribute.attnum)

Номера столбцов, если триггер является специфичным для столбцов; в противном случае пустой массив

tgargs bytea

Строки аргументов для передачи триггеру, каждая завершается NULL

tgqual pg_node_tree

Дерево выражений (в представлении nodeToString()) для условия триггера WHEN или NULL, если такового нет

tgoldtable name

Имя предложения REFERENCING для OLD TABLE или NULL, если такового нет

tgnewtable name

Имя предложения REFERENCING для NEW TABLE или NULL, если такового нет

В настоящее время триггеры, специфичные для столбцов, поддерживаются только для событий UPDATE, поэтому поле tgattr актуально только для этого типа событий. Поле tgtype может также содержать биты для других типов событий, но считается, что они распространяются только на таблицы, независимо от значения поля tgattr.

Примечание
Когда поле tgconstraint имеет отличное от нуля значение, поля tgconstrrelid, tgconstrindid, tgdeferrable и tginitdeferred в основном дублируют ссылочную запись в каталоге pg_constraint. Тем не менее неоткладываемый триггер может быть связан с откладываемым ограничением: ограничения внешнего ключа могут иметь как откладываемые, так и неоткладываемые триггеры.

Примечание
Если отношение имеет какие-либо триггеры в этом каталоге, поле pg_class. relhastriggers должно содержать true.

pg_ts_config

В каталоге pg_ts_config содержатся записи, представляющие конфигурации текстового поиска. Конфигурация задает определенный синтаксический анализатор текстового поиска и список словарей, которые будут использоваться для каждого из типов выходных синтаксических единиц синтаксического анализатора. Анализатор отображается в записи каталога pg_ts_config, но сопоставление синтаксическая единица-словарь определяется подчиненными записями в каталоге pg_ts_config_map.

Возможности текстового поиска QHB подробно описаны в главе Полнотекстовый поиск.

Таблица 59. Столбцы pg_ts_config

Столбец Тип

Описание

oid oid

Идентификатор строки

cfgname name

Имя конфигурации текстового поиска

cfgnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего эту конфигурацию

cfgowner oid (ссылается на pg_authid.oid)

Владелец конфигурации

cfgparser oid (ссылается на pg_ts_parser.oid)

OID синтаксического анализатора текстового поиска для этой конфигурации

pg_ts_config_map

В каталоге pg_ts_config_map содержатся записи, показывающие, к каким словарям текстового поиска следует обращаться и в каком порядке, для каждого типа синтаксических единиц, выдаваемого каждым синтаксическим анализатором конфигурации текстового поиска.

Возможности текстового поиска QHB подробно описаны в главе Полнотекстовый поиск.

Таблица 60. Столбцы pg_ts_config_map

Столбец Тип

Описание

mapcfg oid (ссылается на pg_ts_config.oid)

OID записи в каталоге pg_ts_config, которой принадлежит эта запись сопоставления

maptokentype int4

Тип синтаксических единиц, генерируемых синтаксическим анализатором конфигурации

mapseqno int4

Порядок, в котором следует обращаться к этой записи (сначала меньшие mapseqno)

mapdict oid (ссылается на pg_ts_dict.oid)

OID словаря текстового поиска, к которому нужно обращаться

pg_ts_dict

В каталоге pg_ts_dict содержатся записи, определяющие словари текстового поиска. Словарь зависит от шаблона текстового поиска, который задает все необходимые функции реализации; сам словарь предоставляет значения для настраиваемых пользователем параметров, поддерживаемых шаблоном. Такое разделение позволяет создавать словари пользователям без прав. Параметры задаются текстовой строкой dictinitoption, формат и значение которой различаются в зависимости от шаблона.

Возможности текстового поиска QHB подробно описаны в главе Полнотекстовый поиск.

Таблица 61. Столбцы pg_ts_dict

Столбец Тип

Описание

oid oid

Идентификатор строки

dictname name

Имя словаря текстового поиска

dictnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего этот словарь

dictowner oid (ссылается на pg_authid.oid)

Владелец словаря

dicttemplate oid (ссылается на pg_ts_template.oid)

OID шаблона текстового поиска для этого словаря

dictinitoption text

Строка параметров инициализации для этого шаблона

pg_ts_parser

В каталоге pg_ts_parser содержатся записи, определяющие синтаксические анализаторы текстового поиска. Синтаксический анализатор отвечает за разбиение входного текста на лексемы и присвоение каждой лексеме типа синтаксической единицы. Поскольку синтаксический анализатор должен быть реализован с помощью функций на языке уровня C/RUST, создавать новые синтаксические анализаторы могут только суперпользователи базы данных.

Возможности текстового поиска QHB подробно описаны в главе Полнотекстовый поиск.

Таблица 62. Столбцы pg_ts_parser

Столбец Тип

Описание

oid oid

Идентификатор строки

prsname name

Имя синтаксического анализатора текстового поиска

prsnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего этот синтаксический анализатор

prsstart regproc (ссылается на pg_proc.oid)

OID функции запуска этого синтаксического анализатора

prstoken regproc (ссылается на pg_proc.oid)

OID функции этого синтаксического анализатора, выдающей следующую синтаксическую единицу

prsend regproc (ссылается на pg_proc.oid)

OID функции этого синтаксического анализатора, завершающей анализ

prsheadline regproc (ссылается на pg_proc.oid)

OID функции этого синтаксического анализатора, выдающей заголовки (ноль, если таковой нет)

prslextype regproc (ссылается на pg_proc.oid)

OID функции лексических типов этого синтаксического анализатора

pg_ts_template

В каталоге pg_ts_template содержатся записи, определяющие шаблоны текстового поиска. Шаблон — это основа реализации для класса словарей текстового поиска. Поскольку шаблон должен быть реализован с помощью функций на языке уровня C/RUST, создавать новые шаблоны могут только суперпользователи базы данных.

Возможности текстового поиска QHB подробно описаны в главе Полнотекстовый поиск.

Таблица 63. Столбцы pg_ts_template

Столбец Тип

Описание

oid oid

Идентификатор строки

tmplname name

Имя шаблона текстового поиска

tmplnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего этот шаблон

tmplinit regproc (ссылается на pg_proc.oid)

OID функции инициализации этого шаблона (ноль, если таковой нет)

tmpllexize regproc (ссылается на pg_proc.oid)

OID функции выделения лексем этого шаблона

pg_type

В каталоге pg_type хранится информация о типах данных. Базовые и перечислимые (скалярные) типы создаются с помощью команды CREATE TYPE, а домены — с помощью CREATE DOMAIN. Составной тип автоматически создается для каждой таблицы в базе данных, чтобы представлять структуру строк таблицы. Кроме того, составные типы можно создать с помощью команды CREATE TYPE AS.

Таблица 64. Столбцы pg_type

Столбец Тип

Описание

oid oid

Идентификатор строки

typname name

Имя типа данных

typnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен, содержащего этот тип

typowner oid (ссылается на pg_authid.oid)

Владелец типа

typlen int2

Для типа фиксированного размера поле typlen содержит число байтов во внутреннем представлении типа. Но для типа переменной длины typlen будет отрицательным. -1 обозначает тип «varlena» (он содержит машинное слово, указывающее длину), -2 обозначает строку в стиле C/RUST, завершающуюся значением NULL.

typbyval bool

Поле typbyval определяет, будут ли внутренние подпрограммы передавать значение этого типа по значению или по ссылке. typbyval предпочтительно иметь значение false, если typlen не равен 1, 2 или 4 (или 8 на 64-битных машинах). Типы переменной длины всегда передаются по ссылке. Обратите внимание, что typbyval может иметь значение false, даже если длина типа позволила бы передачу по значению.

typtype char

Поле typtype имеет значение b для базового типа (base), c для составного типа (composite) (например, типа строки таблицы), d для домена (domain), e для перечислимого типа (enum), p для псевдотипа (pseudo), r для диапазонного типа (range) или m для мультидиапазонного типа. См. также описания столбцов typrelid и typbasetype.

typcategory char

В поле typcategory задается произвольная классификация типов данных, которая используется синтаксическим анализатором для определения того, какие неявные приведения должны быть «предпочтительными». См. Таблицу 65.

typispreferred bool

True, если тип является предпочтительной целью приведения внутри своей категории (typcategory)

typisdefined bool

True, если тип определен, false, если это запись-«заглушка» для еще не определенного типа. Когда поле typisdefined имеет значение false, ни на что, кроме имени типа, пространства имен и OID, полагаться нельзя.

typdelim char

Символ, который разделяет два значения этого типа при синтаксическом анализе вводимого массива. Обратите внимание, что разделитель связан с типом данных элемента массива, а не с типом самого массива.

typrelid oid (ссылается на pg_class.oid)

Если это составной тип (см. typtype), то данный столбец указывает на запись в каталоге pg_class, которая определяет соответствующую таблицу. (Для отдельно стоящего составного типа запись в pg_class на самом деле не представляет таблицу, но она все равно необходима для записей типа в каталоге pg_attribute, чтобы сослаться на них.) Для несоставных типов столбец содержит ноль.

typsubscript regproc (ссылается на pg_proc.oid)

OID функции обработки индексации или ноль, если этот тип не поддерживает индексацию. Для «настоящих» типов массивов typsubscript = array_subscript_handler, но у других типов могут быть и другие функции обработки для реализации особого способа индексации.

typelem oid (ссылается на pg_type.oid)

Если поле typelem не содержит ноль, то оно указывает на другую строку в каталоге pg_type, определяя тип, возвращаемый при индексации. Если поле typsubscript содержит ноль, то и в этом столбце должен быть ноль. Однако typelem может содержать ноль и когда значение typsubscript ненулевое, если при определении типа результата индексации обработчику не нужен typelem. Обратите внимание, что зависимость typelem подразумевает физическое включение типа элемента в этот тип, поэтому изменения DDL типа элемента могут быть ограничены присутствием этого типа.

typarray oid (ссылается на pg_type.oid)

Если поле typarray не содержит 0, то оно ссылается на другую строку в каталоге pg_type, описывающую «настоящий» тип массива, где этот тип является элементом

typinput regproc (ссылается на pg_proc.oid)

Функция преобразования ввода (из текстового формата)

typoutput regproc (ссылается на pg_proc.oid)

Функция преобразования вывода (в текстовый формат)

typreceive regproc (ссылается на pg_proc.oid)

Функция преобразования ввода (из двоичного формата) или 0, если такой функции нет

typsend regproc (ссылается на pg_proc.oid)

Функция преобразования вывода (в двоичный формат) или 0, если такой функции нет

typmodin regproc (ссылается на pg_proc.oid)

Функция ввода модификатора типа или 0, если тип не поддерживает модификаторы

typmodout regproc (ссылается на pg_proc.oid)

Функция вывода модификатора типа или 0 для использования стандартного формата

typanalyze regproc (ссылается на pg_proc.oid)

Пользовательская функция ANALYZE или 0 для использования стандартной функции

typalign char

Поле typalign определяет выравнивание, необходимое при хранении значения этого типа. Это относится к хранилищу на диске, а также к большинству представлений значения внутри QHB. Когда несколько значений хранятся последовательно, например, в представлении полной строки данных на диске, заполнение дополнительными байтами происходит перед значением этого типа, чтобы тот начинался на указанной границе. Ссылка на выравнивание является началом первого значения в последовательности.
Возможные значения:

c = выравнивание по символам (char), т. е. выравнивание не требуется.

s = выравнивание по границе слова (short) (2 байта на большинстве процессоров).

i = выравнивание по границе целого (int) (4 байта на большинстве процессоров).

d = выравнивание по границе двойного слова (double) (8 байт на многих процессорах, но далеко не на всех).

typstorage char

Значение поля typstorage для типов varlena (т. е. с typlen = -1) сообщает, подготовлен ли тип для помещения в TOAST и какова должна быть стратегия по умолчанию для атрибутов этого типа.
Возможные значения:

p (plain, простое): Значения всегда должны храниться простым образом (этот вариант используется для всех типов постоянной длины).

e (external, внешнее): Значения могут храниться во вторичном отношении «TOAST» (если в отношении это значение есть, см. pg_class.reltoastrelid).

m (main, основное): Значения могут быть сжаты и храниться внутристрочно.

x (extended, расширенное): Значения могут быть сжаты и/или перемещены во вторичное отношение.

Для типов, которые могут быть помещены в TOAST, обычно применяется стратегия x. Обратите внимание, что значения m также можно переместить во вторичное хранилище, но только в крайнем случае (значения e и x перемещаются первыми).

typnotnull bool

Поле typnotnull представляет собой ограничение NOT NULL для типа. Используется только для доменов.

typbasetype oid (ссылается на pg_type.oid)

Если это домен (см. typtype), то поле typbasetype определяет тип, на котором он основан. Ноль, если этот тип не является доменом.

typtypmod int4

Домены используют поле typtypmod, чтобы записать модификатор typmod, применяемый к их базовому типу (-1, если базовый тип не использует typtypmod). -1, если этот тип не является доменом.

typndims int4

Значение поля typndims представляет собой число размерностей массива для домена, определенного поверх массива (т. е. когда typbasetype является типом массива). Ноль для типов, отличных от доменов поверх типов массивов.

typcollation oid (ссылается на pg_collation.oid)

Поле typcollation задает правило сортировки типа. Если тип не поддерживает правила сортировки, столбец содержит ноль. Базовый тип, который поддерживает правила сортировки, здесь будет иметь ненулевое значение, как правило, DEFAULT_COLLATION_OID. Домен поверх сортируемого типа может иметь OID правила сортировки, отличающийся от такового его базового типа, если он был указан для домена.

typdefaultbin pg_node_tree

Если поле typdefaultbin содержит не NULL, то это представление nodeToString() выражения по умолчанию для данного типа. Используется только для доменов.

typdefault text

Поле typdefault содержит NULL, если с типом не связано значение по умолчанию. Если поле typdefaultbin содержит не NULL, то поле typdefault должно содержать понятную человеку версию выражения по умолчанию, представленного в typdefaultbin. Если поле typdefaultbin содержит NULL, а typdefault — нет, значит, в typdefault находится внешнее представление значения по умолчанию этого типа, которое можно передать в принадлежащую типу функцию преобразования ввода для получения константы.

typacl aclitem[]

Права доступа; подробную информацию см. в разделе Права

Примечание
Для типов фиксированного размера, используемых в системных таблицах, крайне важно, чтобы размер и выравнивание, определенные в каталоге pg_type, были согласованы с тем, как компилятор расположит этот столбец в структуре, представляющей строку таблицы.

В Таблице 65 перечислены определяемые системой значения typcategory. Любые будущие дополнения к этому списку также будут записаны прописными буквами ASCII. Все остальные символы ASCII зарезервированы для пользовательских категорий.

Таблица 65. Коды typcategory

Код | Категория
A | Типы массивов
B | Логические типы
C | Составные типы
D | Типы даты/времени
E | Перечислимые типы
G | Геометрические типы
I | Типы сетевых адресов
N | Числовые типы
P | Псевдотипы
R | Диапазонные типы
S | Строковые типы
T | Интервальные типы
U | Пользовательские типы
V | Типы битовых строк
X | Неизвестный тип (unknown)
Z | Типы для внутреннего использования

pg_user_mapping

В каталоге pg_user_mapping хранятся сопоставления локальных пользователей с удаленными. Обычные пользователи не имеют доступа к этому каталогу, вместо этого им следует использовать представление pg_user_mappings.

Таблица 66. Столбцы pg_user_mapping

Столбец Тип

Описание

oid oid

Идентификатор строки

umuser oid (ссылается на pg_authid.oid)

OID сопоставляемой локальной роли или ноль, если пользовательское сопоставление является общедоступным

umserver oid (ссылается на pg_foreign_server.oid)

OID стороннего сервера, содержащего это сопоставление

umoptions text[]

Специальные параметры сопоставления пользователей в виде строк «ключ=значение»

Системные представления

Помимо системных каталогов, QHB предоставляет ряд встроенных представлений. Некоторые системные представления обеспечивают удобный доступ к ряду часто используемых запросов в системных каталогах. Другие представления обеспечивают доступ к внутреннему состоянию сервера.

Информационная схема предоставляет альтернативный набор представлений, которые перекрывают функциональные возможности системных представлений. Поскольку информационная схема является частью стандарта SQL, а описанные здесь представления специфичны для QHB, обычно лучше использовать информационную схему, если та выдает всю необходимую информацию.

Обзор

В Таблице 1 перечислены описываемые здесь системные представления. Более подробная информация по каждому представлению приведена ниже. Существует несколько дополнительных представлений, обеспечивающих доступ к результатам работы сборщика статистики; они описаны в таблице Представления по собранной статистике.

Кроме явно отмеченных исключений, все описанные здесь представления доступны только для чтения.

Таблица 1. Системные представления

Имя представления | Назначение
pg_available_extensions | доступные расширения
pg_available_extension_versions | доступные версии расширений
pg_backend_memory_contexts | контексты памяти обслуживающего процесса
pg_config | параметры конфигурации времени компиляции
pg_cursors | открытые курсоры
pg_file_settings | сводка содержимого файла конфигурации
pg_group | группы пользователей баз данных
pg_hba_file_rules | сводка содержимого файла конфигурации аутентификации клиентов
pg_ident_file_mappings | сводка содержимого файла конфигурации с сопоставлением имен пользователей клиентов
pg_indexes | индексы
pg_locks | удерживаемые или ожидаемые в настоящий момент блокировки
pg_matviews | материализованные представления
pg_policies | политики
pg_prepared_statements | подготовленные операторы
pg_prepared_xacts | подготовленные транзакции
pg_publication_tables | публикации и связанные с ними таблицы
pg_replication_origin_status | сведения об источниках репликации, включая ход выполнения репликации
pg_replication_slots | сведения о слотах репликации
pg_roles | роли базы данных
pg_rules | правила
pg_seclabels | метки безопасности
pg_sequences | последовательности
pg_settings | настройки параметров
pg_shadow | пользователи базы данных
pg_shmem_allocations | блоки, выделенные в разделяемой памяти
pg_stats | статистика планировщика
pg_stats_ext | расширенная статистика планировщика
pg_stats_ext_exprs | расширенная статистика планировщика для выражений
pg_tables | таблицы
pg_timezone_abbrevs | аббревиатуры часовых поясов
pg_timezone_names | названия часовых поясов
pg_user | пользователи базы данных
pg_user_mappings | сопоставления пользователей
pg_views | представления

pg_available_extensions

Представление pg_available_extensions содержит список доступных для установки расширений. См. также каталог pg_extension, в котором отображаются уже установленные расширения.

Таблица 2. Столбцы pg_available_extensions

Столбец Тип

Описание

name name

Имя расширения

default_version text

Имя версии по умолчанию или NULL, если версия не указана

installed_version text

Версия расширения, установленная в настоящий момент, или NULL, если расширение не установлено

comment text

Строка комментария из управляющего файла расширения

Представление pg_available_extensions доступно только для чтения.

pg_available_extension_versions

Представление pg_available_extension_versions содержит список конкретных версий расширений, доступных для установки. См. также каталог pg_extension, в котором отображаются уже установленные на данный момент расширения.

Таблица 3. Столбцы pg_available_extension_versions

Столбец Тип

Описание

name name

Имя расширения

version text

Имя версии

installed bool

True, если эта версия данного расширения уже установлена

superuser bool

True, если это расширение разрешено устанавливать только суперпользователям (но см. trusted)

trusted bool

True, если это расширение могут устанавливать обычные пользователи с надлежащими правами

relocatable bool

True, если расширение можно переместить в другую схему

schema name

Имя схемы, в которую необходимо установить расширение, или NULL, если оно частично или полностью переместимо

requires name[]

Имена расширений, заранее необходимых для данного, или NULL, если таковых нет

comment text

Строка комментария из управляющего файла расширения

Представление pg_available_extension_versions доступно только для чтения.

pg_backend_memory_contexts

В представлении pg_backend_memory_contexts отображаются все контексты памяти серверного процесса, назначенного текущему сеансу.

Для каждого контекста памяти pg_backend_memory_contexts содержит одну строку.

Таблица 4. Столбцы pg_backend_memory_contexts

Столбец Тип

Описание

name text

Имя контекста памяти

ident text

Идентификационная информация о контексте памяти. Размер этого поля ограничен 1024 байтами

parent text

Имя родителя этого контекста памяти

level int4

Расстояние от верхнего контекста памяти (TopMemoryContext) в дереве контекстов

total_bytes int8

Общее количество байтов, выделенных для этого контекста памяти

total_nblocks int8

Общее количество блоков памяти, выделенных для этого контекста памяти

free_bytes int8

Объем свободного пространства в байтах

free_chunks int8

Общее количество свободных участков памяти

used_bytes int8

Объем использованного пространства в байтах

По умолчанию представление pg_backend_memory_contexts могут прочитать только суперпользователи или роли с правами роли pg_read_all_stats.

pg_config

В представлении pg_config описываются параметры конфигурации времени компиляции текущей установленной версии QHB. Оно предназначено, например, для использования программными пакетами, которым может понадобиться содействие QHB в поиске необходимых заголовочных файлов и библиотек. Оно предоставляет ту же базовую информацию, что и клиентское приложение QHB pg_config.

По умолчанию представление pg_config могут прочитать только суперпользователи.

Таблица 5. Столбцы pg_config

Столбец Тип

Описание

name text

Имя параметра

setting text

Значение параметра

pg_cursors

Представление pg_cursors содержит список курсоров, доступных в настоящий момент. Курсоры могут быть определены несколькими способами:

через оператор DECLARE в SQL

через сообщение Bind в клиент-серверном протоколе QHB, как описано в подразделе Расширенный запрос

через интерфейс программирования сервера (Server Programming Interface, SPI), как описано в разделе Функции интерфейса

В представлении pg_cursors отображаются курсоры, созданные любым из этих средств. Курсоры существуют только в течение транзакции, которая их определяет, если только они не были объявлены WITH HOLD. Таким образом, не удерживаемые курсоры присутствуют в представлении только до конца создавшей их транзакции.

Примечание
Курсоры используются внутри системы для реализации некоторых компонентов QHB, например процедурных языков. Поэтому представление pg_cursors может содержать курсоры, которые не были явно созданы пользователем.

Таблица 6. Столбцы pg_cursors

Столбец Тип

Описание

name text

Имя курсора

statement text

Дословная строка запроса, представившая объявление этого курсора

is_holdable bool

true, если курсор удерживаемый (т. е. может быть доступен после фиксации транзакции, в которой он был объявлен); иначе — false

is_binary bool

true, если курсор был объявлен с указанием BINARY; иначе — false

is_scrollable bool

true, если курсор прокручиваемый (т. е. позволяет извлекать строки непоследовательным образом); иначе — false

creation_time timestamptz

Время, когда был объявлен курсор

Представление pg_cursors доступно только для чтения.

pg_file_settings

В представлении pg_file_settings выводится сводка содержимого файла(ов) конфигурации сервера. Для каждой записи «имя = значение», фигурирующей в этих файлах, данное представление содержит строку с примечаниями, показывающими, может ли это значение быть успешно применено. Также для проблем, не связанных с записью «имя = значение», например синтаксических ошибок в файлах, в этом представлении может содержаться дополнительная строка (или строки).

Это представление полезно для проверки того, будут ли работать запланированные изменения в файлах конфигурации, или для диагностики предыдущего сбоя. Обратите внимание, что это представление сообщает о текущем содержимом файлов, а не о том, что было применено сервером в последний раз. (Для получения того состояния обычно достаточно представления pg_settings).

По умолчанию представление pg_file_settings могут прочитать только суперпользователи.

Таблица 7. Столбцы pg_file_settings

Столбец Тип

Описание

sourcefile text

Полный путь к файлу конфигурации

sourceline int4

Номер строки в файле конфигурации, где присутствует эта запись

seqno int4

Порядок, в котором обрабатываются записи (1..n)

name text

Имя параметра конфигурации

setting text

Значение, присваиваемое параметру

applied bool

True, если значение может быть применено успешно

error text

Сообщение об ошибке, указывающее, почему эта запись не может быть применена, или NULL

Если файл конфигурации содержит синтаксические ошибки или недопустимые имена параметров, сервер не будет пытаться применить какие-либо параметры из файла, а следовательно, все поля applied будут читаться как false. В таком случае в представлении будет одна или несколько строк с полями error, отличными от NULL, указывающими на проблему(ы). Иначе, если это возможно, будут применяться отдельные параметры. Если отдельный параметр применить нельзя (например, из-за недопустимого значения или если параметр нельзя изменить после запуска сервера), для него будет записано соответствующее сообщение в поле error. Другой вариант, при котором поле applied будет содержать false, означает, что эта запись переопределяется более поздней записью для того же имени параметра; этот случай не считается ошибкой, поэтому в поле error ничего не появляется.

Дополнительную информацию о различных способах изменения параметров времени выполнения см. в разделе Настройка параметров

pg_group

В представлении pg_group отображаются имена и члены всех ролей, отмеченных как не имеющие признака rolcanlogin, что примерно соответствует набору ролей, которые использовались как группы.

Таблица 8. Столбцы pg_group

Столбец Тип

Описание

groname name (ссылается на pg_authid.rolname)

Имя группы

grosysid oid (ссылается на pg_authid.oid)

Идентификатор этой группы

grolist oid[] (ссылается на pg_authid.oid)

Массив, содержащий идентификаторы ролей в этой группе

pg_hba_file_rules

В представлении pg_hba_file_rules выводится сводка содержимого файла конфигурации аутентификации клиентов, qhb_hba.conf. Для каждой непустой строки файла, не имеющей комментариев, это представление содержит строку с примечаниями, показывающими, может ли это правило быть успешно применено.

Это представление может быть полезно для проверки того, будут ли работать запланированные изменения в файле конфигурации аутентификации, или для диагностики предыдущего сбоя. Обратите внимание, что это представление сообщает о текущем содержимом файла, а не о том, что было загружено сервером в последний раз.

По умолчанию представление pg_hba_file_rules могут прочитать только суперпользователи.

Таблица 9. Столбцы pg_hba_file_rules

Столбец Тип

Описание

line_number int4

Номер строки этого правила в qhb_hba.conf

type text

Тип соединения

database text[]

Список имен баз данных, к которым применяется это правило

user_name text[]

Список имен пользователей и групп, к которым применяется это правило

address text

Имя или IP-адрес хоста, либо одно из значений: all (все), samehost (тот же хост) или samenet (та же сеть), либо NULL для локальных соединений

netmask text

Маска IP-адреса или NULL, если это неприменимо

auth_method text

Метод аутентификации

options text[]

Параметры, указанные для метода аутентификации, если таковые имеются

error text

Сообщение об ошибке, указывающее, почему эта строка не может быть обработана, или NULL

Обычно строка, отражающая неверную запись, будет иметь значения только для полей line_number и error.

Более подробную информацию о конфигурации аутентификации клиентов см. в главе Аутентификация клиентского приложения.

pg_ident_file_mappings

В представлении pg_ident_file_mappings выводится сводка содержимого файла конфигурации сопоставления имен пользователей клиентов, pg_ident.conf. Для каждой непустой строки файла, не имеющей комментариев, это представление содержит строку с примечаниями, показывающими, может ли это сопоставление быть успешно применено.

Это представление может быть полезно для проверки того, будут ли работать запланированные изменения в файле конфигурации аутентификации, или для диагностики предыдущего сбоя. Обратите внимание, что это представление сообщает о текущем содержимом файла, а не о том, что было загружено сервером в последний раз.

По умолчанию представление pg_ident_file_mappings могут прочитать только суперпользователи.

Таблица 10. Столбцы pg_ident_file_mappings

Столбец Тип

Описание

line_number int4

Номер строки этого сопоставления в pg_ident.conf

map_name text

Имя сопоставления

sys_name text

Обнаруженное имя пользователя клиента

pg_username text

Запрошенное имя пользователя QHB

error text

Сообщение об ошибке, указывающее, почему эта строка не может быть обработана, или NULL

Обычно строка, отражающая неверную запись, будет иметь значения только для полей line_number и error.

Более подробную информацию о конфигурации аутентификации клиентов см. в главе Аутентификация клиентского приложения.

pg_indexes

Представление pg_indexes предоставляет доступ к полезной информации о каждом индексе в базе данных.

Таблица 11. Столбцы pg_indexes

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей таблицу и индекс

tablename name (ссылается на pg_class.relname)

Имя таблицы, для которой предназначен индекс

indexname name (ссылается на pg_class.relname)

Имя индекса

tablespace name (ссылается на pg_tablespace.spcname)

Имя табличного пространства, содержащего индекс (NULL, если это пространство по умолчанию для базы данных)

indexdef text

Определение индекса (реконструированная команда CREATE INDEX)

pg_locks

Представление pg_locks предоставляет доступ к информации о блокировках, удерживаемых активными процессами на сервере баз данных. Подробно процесс блокировки рассматривается в главе Управление параллельным доступом.

Для каждого активного блокируемого объекта, запрошенного режима блокировки и соответствующего процесса pg_locks содержит одну строку. Таким образом, один и тот же блокируемый объект может фигурировать здесь много раз, если его блокируют или ожидают его блокировки несколько процессов. Однако объект, который в настоящее время не имеет блокировок, не будет отображаться вообще.

Существует несколько различных типов блокируемых объектов: отношения целиком (например, таблицы), отдельные страницы отношений, отдельные кортежи отношений, идентификаторы транзакций (как виртуальные, так и постоянные) и общие объекты базы данных (идентифицируемые по OID класса и OID объекта, так же как и в каталоге pg_description или pg_depend). Также в виде отдельного блокируемого объекта представляется право на расширение отношения, а также право изменения значения pg_database.datfrozenxid. Кроме того, могут встречаться «рекомендательные» блокировки, значения которых определяет пользователь.

Таблица 12. Столбцы pg_locks

Столбец Тип

Описание

locktype text

Тип блокируемого объекта: relation (отношение), extend (расширение), page (страница), tuple (кортеж), transactionid (идентификатор транзакции), virtualxid (виртуальный идентификатор), spectoken (спекулятивный маркер), object (объект), userlock (пользовательская блокировка) или advisory (рекомендательная блокировка). (См. также таблицу События ожидания типа Lock.)

database oid (ссылается на pg_database.oid)

OID базы данных, в которой существует цель блокировки, или ноль, если цель является разделяемым объектом, или NULL, если цель является идентификатором транзакции

relation oid (ссылается на pg_class.oid)

OID отношения, на которое нацелена блокировка, или NULL, если цель не является отношением или частью отношения

page int4

Номер страницы, на которую нацелена блокировка в пределах отношения, или NULL, если цель не является страницей или кортежем отношения

tuple int2

Номер кортежа, на который нацелена блокировка внутри страницы, или NULL, если цель не является кортежем

virtualxid text

Виртуальный идентификатор транзакции, на которую нацелена блокировка, или NULL, если цель не является виртуальным идентификатором транзакции

transactionid xid

Идентификатор транзакции, на которую нацелена блокировка, или NULL, если цель не является идентификатором транзакции

classid oid (ссылается на pg_class.oid)

OID системного каталога, содержащего цель блокировки, или NULL, если цель не является обычным объектом базы данных

objid oid (ссылается на какой-либо столбец OID)

OID цели блокировки в системном каталоге или NULL, если цель не является обычным объектом базы данных

objsubid int2

Номер столбца, на который нацелена блокировка (поля classid и objid ссылаются на саму таблицу), или ноль, если цель является каким-либо другим обычным объектом базы данных, или NULL, если цель не является обычным объектом базы данных

virtualtransaction text

Виртуальный идентификатор транзакции, которая удерживает или ожидает эту блокировку

pid int4

Идентификатор серверного процесса (PID), удерживающего или ожидающего эту блокировку, или NULL, если блокировка удерживается подготовленной транзакцией

mode text

Имя режима блокировки, удерживаемой или ожидаемой этим процессом (см. подразделы Блокировки на уровне таблицы и Уровень изоляции Serializable)

granted bool

True, если блокировка удерживается, false, если блокировка ожидается

fastpath bool

True, если блокировка была получена посредством быстрого пути, false, если она получена через главную таблицу блокировки

waitstart timestamptz

Время, когда серверный процесс начал ожидать эту блокировку, или NULL, если блокировка удерживается. Обратите внимание, что после начала ожидания в этом поле может кратковременно стоять NULL, несмотря на то, что поле granted равно false.

Поле granted имеет значение true в строке, представляющей блокировку, удерживаемую указанным процессом. Значение false показывает, что этот процесс в настоящее время ожидает получения данной блокировки, свидетельствуя о том, что как минимум еще один процесс удерживает или ожидает блокировку того же объекта в конфликтном режиме. Ожидающий процесс будет находиться в спящем режиме до тех пор, пока не будет снята другая блокировка (или обнаружена ситуация взаимоблокировки). Один процесс может ожидать получения не более одной блокировки за раз.

Во время выполнения транзакции серверный процесс удерживает эксклюзивную блокировку виртуального идентификатора транзакции. Если транзакции присваивается постоянный идентификатор (что обычно происходит только в том случае, если та изменяет состояние базы данных), он также удерживает эксклюзивную блокировку на постоянном идентификаторе транзакции до ее завершения. Когда процесс считает необходимым специально дождаться окончания другой транзакции, он делает это, пытаясь получить совместную блокировку идентификатора другой транзакции (виртуального или постоянного, в зависимости от ситуации). Это произойдет только тогда, когда другая транзакция завершится и освободит свои блокировки.

Хотя кортежи относятся к блокируемому типу объектов, информация о блокировках на уровне строк хранится на диске, а не в памяти, и поэтому обычно блокировки на уровне строк в этом представлении не отображаются. Если процесс ожидает блокировки уровня строки, он обычно фигурирует в представлении как ожидающий постоянного идентификатора транзакции текущего владельца этой блокировки.

Рекомендательные блокировки можно получить по ключам, состоящим из одного значения bigint или двух значений integer. При выведении ключа bigint его старшая часть отображается в столбце classid, младшая — в столбце objid, а objsubid равен 1. Исходное значение bigint можно собрать заново с помощью выражения (classid::bigint << 32) | objid::bigint. Ключи integer отображаются так: первый в столбце classid, второй — в столбце objid, а objsubid равен 2. Фактическое значение ключей определяет пользователь. Рекомендательные блокировки являются локальными для каждой базы данных, поэтому столбец database имеет для них значение.

pg_locks обеспечивает глобальное представление всех блокировок в кластере баз данных, а не только тех, которые относятся к текущей базе. Хотя через соединение столбцов relation и pg_class.oid можно определить заблокированные отношения, это будет правильно работать только для отношений в текущей базе данных (т. е. тех, для которых столбец database — это либо OID текущей базы данных, либо ноль).

Столбец pid можно соединить со столбцом pid представления pg_stat_activity, что позволит получить дополнительную информацию о сеансе, удерживающем или ожидающем каждую блокировку, например:

SELECT * FROM pg_locks pl LEFT JOIN pg_stat_activity psa
    ON pl.pid = psa.pid;

Кроме того, при использовании подготовленных транзакций столбец virtualtransaction можно соединить со столбцом transaction представления pg_prepared_xacts, что позволит получить дополнительную информацию об удерживающих блокировки подготовленных транзакциях. (Подготовленная транзакция не может ожидать блокировки, но продолжает удерживать блокировки, полученные ею в процессе выполнения.) Например:

SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
    ON pl.virtualtransaction = '-1/' || ppx.transaction;

Хотя и возможно получить информацию о том, какие из процессов блокируют другие процессы (и какие именно), соединив представление pg_locks с самим собой, крайне трудно сделать это правильно во всех деталях. В таком запросе пришлось бы закодировать знание о том, какие режимы блокировки конфликтуют с другими. Хуже того, представление pg_locks не выдает информацию ни о том, какие процессы опережают другие в очередях ожидания блокировки, ни о том, какие процессы являются параллельными рабочими процессами и от от имени каких других сеансов клиента они выполняются. Чтобы определить, каким процессом (или процессами) блокируется ожидающий процесс, лучше воспользоваться функцией pg_blocking_pids() (см. таблицу Функции для получения информации о сеансе).

Представление pg_locks отображает данные как из менеджера обычных блокировок, так и из менеджера предикатных блокировок, которые являются отдельными системами; кроме того, менеджер обычных блокировок подразделяет свои блокировки на обычные и полученные быстрым путем. Полная согласованность этих данных не гарантируется. При запросе к этому представлению данные о блокировках по быстрому пути (с fastpath = true) собираются с каждого серверного процесса по очереди, без замораживания состояние всего менеджера блокировок, поэтому существует вероятность того, что во время сбора этой информации блокировки будут получены или освобождены. Обратите внимание, однако, что известно, что эти блокировки не должны конфликтовать ни с какими другими действующими блокировками. После того как во все серверные процессы были сделаны запросы для получения быстрых блокировок, оставшаяся часть менеджера обычных блокировок блокируется как единое целое, и согласованный снимок всех оставшихся блокировок собирается атомарно. После разблокирования менеджера обычных блокировок аналогичным образом блокируется менеджер предикатных блокировок, и все предикатные блокировки собираются атомарно. Таким образом, за исключением быстрых блокировок, каждый менеджер блокировок будет выдавать согласованный набор результатов, но поскольку мы не блокируем оба менеджера блокировок одновременно, блокировки могут быть получены или освобождены после опроса менеджера обычных блокировок и до опроса менеджера предикатных блокировки.

Блокировка менеджера обычных и/или предикатных блокировок может некоторым образом повлиять на производительность базы данных, если очень часто обращаться к этому представлению. Эти блокировки удерживаются не дольше, чем необходимо для получения данных от менеджеров блокировок, но это не до конца исключает возможность влияния на производительность.

pg_matviews

Представление pg_matviews обеспечивает доступ к полезной информации о каждом материализованном представлении в базе данных.

Таблица 13. Столбцы pg_matviews

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей материализованное представление

matviewname name (ссылается на pg_class.relname)

Имя материализованного представления

matviewowner name (ссылается на pg_authid.rolname)

Имя владельца материализованного представления

tablespace name (ссылается на pg_tablespace.spcname)

Имя табличного пространства, содержащего материализованное представление (NULL, если это табличное пространство по умолчанию для базы данных)

hasindexes bool

True, если материализованное представление имеет (или недавно имело) какие-либо индексы

ispopulated bool

True, если материализованное представление в настоящий момент заполнено

definition text

Определение материализованного представления (реконструированный запрос SELECT)

pg_policies

Представление pg_policies обеспечивает доступ к полезной информации о каждой политике защиты на уровне строк в базе данных.

Таблица 14. Столбцы pg_policies

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей таблицу с этой политикой

tablename name (ссылается на pg_class.relname)

Имя таблицы с этой политикой

policyname name (ссылается на pg_policy.polname)

Имя политики

polpermissive text

Является ли эта политика разрешительной или ограничительной?

roles name[]

Роли, к которым применяется эта политика

cmd text

Тип команд, к которому применяется эта политика

qual text

Выражение, добавляемое к условиям барьера безопасности для запросов, к которым применяется эта политика

with_check text

Выражение, добавляемое к условиям WITH CHECK для запросов, которые пытаются добавить строки в эту таблицу

pg_prepared_statements

В представлении pg_prepared_statements отображаются все подготовленные операторы, доступные в текущем сеансе. Более подробную информацию о подготовленных операторах см. на справочной странице команды PREPARE.

Для каждого подготовленного оператора pg_prepared_statements содержит одну строку. Строки добавляются в представление при создании нового подготовленного оператора и удаляются при освобождении подготовленного оператора (например, с помощью команды DEALLOCATE).

Таблица 15. Столбцы pg_prepared_statements

Столбец Тип

Описание

name text

Идентификатор подготовленного оператора

statement text

Строка запроса, отправленная клиентом для создания этого подготовленного оператора. Для подготовленных операторов, созданных посредством SQL, это оператор PREPARE, предоставленный клиентом. Для подготовленных операторов, созданных посредством клиент-серверного протокола, это текст самого подготовленного оператора.

prepare_time timestamptz

Время, когда был создан подготовленный оператор

parameter_types regtype[]

Ожидаемые типы параметров для подготовленного оператора в виде массива regtype. OID, соответствующий элементу этого массива, можно получить путем приведения значения regtype к типу oid.

from_sql bool

true, если подготовленный оператор был создан посредством команды SQL PREPARE; false, если оператор был подготовлен посредством клиент-серверного протокола

generic_plans int8

Сколько раз был выбран типовой план

custom_plans int8

Сколько раз был выбран пользовательский план

Представление pg_prepared_statements доступно только для чтения.

pg_prepared_xacts

В представлении pg_prepared_xacts отображается информация о транзакциях, которые в настоящее время подготовлены для двухфазной фиксации (подробную информацию см. на справочной странице команды PREPARE TRANSACTION).

Для каждой подготовленной транзакции pg_prepared_xacts содержит одну строку. Запись удаляется при фиксации или откате транзакции.

Таблица 16. Столбцы pg_prepared_xacts

Столбец Тип

Описание

transaction xid

Числовой идентификатор подготовленной транзакции

gid text

Глобальный идентификатор транзакции, который был присвоен транзакции

prepared timestamptz

Время, когда транзакция была подготовлена к фиксации

owner name (ссылается на pg_authid.rolname)

Имя пользователя, выполнившего транзакцию

database name (ссылается на pg_database.datname)

Имя базы данных, в которой была выполнена транзакция

При обращении к представлению pg_prepared_xacts внутренние структуры данных менеджера транзакций на мгновение блокируются, и создается копия для отображения в представлении. Это гарантирует, что представление выдает согласованный набор результатов, не блокируя при этом обычные операции дольше необходимого. Тем не менее при частых обращениях к этому представлению это может некоторым образом повлиять на производительность базы данных.

pg_publication_tables

В представлении pg_publication_tables выводится информация о сопоставлении публикаций и информации о таблицах, которые они содержат. В отличие от нижележащего каталога pg_publication_rel, это представление расширяет список публикаций, определенных как FOR ALL TABLES и FOR TABLES IN SCHEMA, поэтому для таких публикаций здесь будет фигурировать строка для каждой подходящей таблицы.

Таблица 17. Столбцы pg_publication_tables

Столбец Тип

Описание

pubname name (ссылается на pg_publication.pubname)

Имя публикации

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей таблицу

tablename name (ссылается на pg_class.relname)

Имя таблицы

schemaname name[] (ссылается на pg_attribute.attname)

Имена столбцов таблицы, включенных в публикацию. Если пользователь не указывает список столбцов таблицы, здесь перечисляются все ее столбцы

rowfilter text

Выражение для условия, ограничивающего публикацию таблицы

pg_replication_origin_status

В представлении pg_replication_origin_status содержится информация о прогрессе воспроизведения записей репликации, достигнутом для определенного источника. Подробную информацию об источниках репликации см. в главе Отслеживание прогресса репликации.

Таблица 18. Столбцы pg_replication_origin_status

Столбец Тип

Описание

local_id oid (ссылается на pg_replication_origin.roident)

Внутренний идентификатор узла

external_id text (ссылается на pg_replication_origin.roname)

Внешний идентификатор узла

remote_lsn pg_lsn

LSN исходного узла, до которого были реплицированы данные.

local_lsn pg_lsn

LSN этого узла, в котором был реплицирован remote_lsn. Используется при асинхронной фиксации для сброса записей фиксации на диск перед сохранением данных.

pg_replication_slots

В представлении pg_replication_slots выводится список всех слотов репликации, которые в настоящее время существуют в кластере баз данных, а также их текущее состояние.

Подробную информацию о слотах репликации см. в подразделе Слоты репликации и главе Логическое декодирование.

Таблица 19. Столбцы pg_replication_slots

Столбец Тип

Описание

slot_name name

Уникальный на уровне кластера идентификатор для слота репликации

plugin name (ссылается на pg_database.oid)

Базовое имя разделяемого объекта, который содержит плагин вывода, используемого этим логическим слотом, или NULL для физических слотов.

slot_type text

Тип слота: physical (физический) или logical (логический)

datoid oid (ссылается на pg_database.oid)

OID базы данных, с которой связан этот слот, или NULL. Только логические слоты могут иметь связанную базу данных.

database text (ссылается на pg_database.datname)

Имя базы данных, с которой связан этот слот, или NULL. Только логические слоты могут иметь связанную базу данных.

temporary bool

True, если это слот временной репликации. Временные слоты не сохраняются на диске и автоматически удаляются при ошибке или по завершении сеанса.

active bool

True, если этот слот в настоящий момент активно используется

active_pid int4

Идентификатор процесса сеанса, использующего этот слот, если тот в настоящий момент активно используется. NULL, если слот неактивен.

xmin xid

Старейшая транзакция, которую должна сохранить база данных для этого слота. Команда VACUUM не может убрать кортежи, удаленные более поздними транзакциями.

catalog_xmin xid

Старейшая транзакция, влияющая на системные каталоги, которую должна сохранить база данных для этого слота. Команда VACUUM не сможет убрать кортежи каталогов, удаленные более поздними транзакциями.

restart_lsn pg_lsn

Адрес (LSN) старейшей записи в WAL, которая все еще может понадобиться пользователю этого слота и поэтому не будет автоматически удаляться во время контрольных точек, если только этот LSN не отстает более чем на max_slot_wal_keep_size от текущего. NULL, если LSN этого слота никогда не был зарезервирован.

confirmed_flush_lsn pg_lsn

Адрес (LSN), до которого потребитель логического слота подтвердил получение данных. Данные старше этого LSN уже не доступны. NULL для физических слотов.

wal_status text

Состояние файлов WAL, затребованных этим слотом. Возможные значения:

reserved (резерв) означает, что объем затребованных файлов не выходит за пределы max_wal_size.

extended (превышение) означает, что предел max_wal_size превышен, но файлы все еще сохраняются благодаря либо слоту репликации, либо тому, что не превышают wal_keep_size.

unreserved (нет резерва) означает, что слот больше не сохраняет требуемые файлы WAL и некоторые из них будут удалены при следующей контрольной точке. Это состояние может смениться на reserved или extended.

lost (потеря) означает, что какие-то из требуемых файлов WAL были удалены и данный слот уже нельзя использовать.

Последние два значения можно увидеть только при неотрицательном значении max_slot_wal_keep_size. Если restart_lsn содержит NULL, это поле тоже содержит NULL.

safe_wal_size int8

Объем в байтах, который может быть записан в WAL, чтобы этому слоту не грозила опасность оказаться в состоянии «lost». Для потерянных слотов, а также при max_slot_wal_keep_size равном -1 это поле содержит NULL.

two_phase bool

True, если этот слот задействован в декодировании подготовленных транзакций. Всегда false для физических слотов.

pg_roles

Представление pg_roles обеспечивает доступ к информации о ролях в базах данных. Это просто доступное для всех представление каталоге pg_authid, в котором прикрыто поле пароля.

Таблица 20. Столбцы pg_roles

Столбец Тип

Описание

rolname name

Имя роли

rolsuper bool

Роль обладает правами суперпользователя

rolinherit bool

Роль автоматически наследует права ролей, членом которых является

rolcreaterole bool

Роль может создавать другие роли

rolcreatedb bool

Роль может создавать базы данных

rolcanlogin bool

Роль может начинать сеанс. То есть эта роль может быть задана в качестве начального идентификатора авторизации сеанса

rolreplication bool

Роль является ролью репликации. Роль репликации может инициировать подключения репликации и создавать и удалять слоты репликации.

rolconnlimit int4

Для ролей, которые могут начинать сеанс, этот столбец устанавливает максимальное число одновременных подключений, которые может создать эта роль. -1 означает отсутствие ограничений.

rolpassword text

Не пароль (всегда читается как ********)

rolvaliduntil timestamptz

Срок действия пароля (используется только для аутентификации по паролю); NULL, если срок действия не ограничен

rolbypassrls bool

Роль обходит любую политику защиты на уровне строк; подробную информацию см. в разделе Политики защиты строк.

rolconfig text[]

Специфичные для роли значения по умолчанию переменных конфигурации времени выполнения

oid oid (ссылается на pg_authid.oid)

Идентификатор роли

pg_rules

Представление pg_rules обеспечивает доступ к полезной информации о правилах перезаписи запросов.

Таблица 21. Столбцы pg_rules

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей таблицу

tablename name (ссылается на pg_class.relname)

Имя таблицы, для которой предназначено это правило

rulename name (ссылается на pg_rewrite.rulename)

Имя правила

definition text

Определение правила (реконструированная команда создания)

Из представления pg_rules исключены правила ON SELECT для представлений и материализованных представлений; их можно найти в представлениях pg_views и pg_matviews.

pg_seclabels

Представление pg_seclabels предоставляет информацию о метках безопасности. Это более простая для запросов версия каталога pg_seclabel.

Таблица 22. Столбцы pg_seclabels

Столбец Тип

Описание

objoid oid (ссылается на какой-либо столбец OID)

OID объекта, которому принадлежит эта метка безопасности

classoid oid (ссылается на pg_class.oid)

OID системного каталога, в котором находится этот объект

objsubid int4

Для метки безопасности на столбце таблицы это номер столбца (поля objoid и classoid ссылаются на саму таблицу). Для всех остальных типов объектов этот столбец содержит ноль.

objtype text

Тип объекта, к которому применяется эта метка, в текстовом виде

objnamespace oid (ссылается на pg_namespace.oid)

OID пространства имен для этого объекта, если применимо; иначе NULL

objname text

Имя объекта, к которому применяется эта метка, в текстовом виде

provider text (ссылается на pg_seclabel.provider)

Поставщик меток безопасности, связанный с этой меткой

label text (ссылается на pg_seclabel.label)

Метка безопасности, применяемая к этому объекту

pg_sequences

Представление pg_sequences обеспечивает доступ к полезной информации о каждой последовательности в базе данных.

Таблица 23. Столбцы pg_sequences

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей последовательность

sequencename name (ссылается на pg_class.relname)

Имя последовательности

sequenceowner name (ссылается на pg_authid.rolname)

Имя владельца последовательности

data_type regtype (ссылается на pg_type.oid)

Тип данных последовательности

start_value int8

Начальное значение последовательности

min_value int8

Минимальное значение последовательности

max_value int8

Максимальное значение последовательности

increment_by int8

Значение приращения последовательности

cycle bool

Зацикливается ли последовательность

cache_size int8

Размер кэша последовательности

last_value int8

Последнее значение последовательности, записанное на диск. Если используется кэширование, это значение может быть больше, чем последнее значение, полученное из последовательности. Содержит NULL, если последовательность еще не была прочитана. Кроме того, значением будет NULL, если текущий пользователь не имеет для последовательности прав USAGE или SELECT.

pg_settings

Представление pg_setting обеспечивает доступ к параметрам времени выполнения сервера. В сущности это альтернативный интерфейс команд SHOW и SET. Также оно обеспечивает доступ к некоторым фактам о каждом параметре, которые нельзя получить напрямую при помощи команды SHOW, например минимальное и максимальное значения.

Таблица 24. Столбцы pg_settings

Столбец Тип

Описание

name text

Имя параметра конфигурации времени выполнения

setting text

Текущее значение параметра

unit text

Неявно подразумеваемая единица измерения параметра

category text

Логическая группа параметра

short_desc text

Краткое описание параметра

extra_desc text

Дополнительное, более детальное, описание параметра

context text

Контекст, необходимый для установки значения параметра (см. ниже)

vartype text

Тип параметра (bool, enum, integer, real или string)

source text

Источник текущего значения параметра

min_val text

Минимальное допустимое значение параметра (NULL для нечисловых значений)

max_val text

Максимально допустимое значение параметра (NULL для нечисловых значений)

enumvals text[]

Допустимые значения перечислимого параметра (NULL для неперечислимых значений)

boot_val text

Значение параметра, присваиваемое при запуске сервера, если параметр не задан иным образом

reset_val text

Значение, в которое параметр будет сброшен командой RESET в текущем сеансе

sourcefile text

Файл конфигурации, в котором было установлено текущее значение (NULL для значений, полученных не из файлов конфигурации, или при проверке пользователем, который не является ни суперпользователем, ни членом роли pg_read_all_settings); полезно при использовании предписаний include в файлах конфигурации

sourceline int4

Номер строки в файле конфигурации, в которой было задано текущее значение (NULL для значений, полученных не из файлов конфигурации, или при проверке пользователем, который не является ни суперпользователем, ни членом роли pg_read_all_settings).

pending_restart bool

true, если значение было изменено в файле конфигурации, но требуется перезапуск; иначе — false.

Существует несколько возможных значений поля context. В порядке уменьшения сложности изменения параметров это:

internal
Эти параметры нельзя изменить непосредственно; они отражают значения, определенные внутри системы. Некоторые из них можно изменить, пересобрав сервер с другими параметрами конфигурации или изменив параметры, передаваемые в qhb_bootstrap.

qhbmaster
Эти параметры можно применять только при запуске сервера, поэтому любое изменение требует его перезагрузки. Значения для этих параметров обычно хранятся в файле qhb.conf либо передаются в командной строке при запуске сервера. Разумеется, параметры любого из типов context более низкого уровня также можно задать во время запуска сервера.

sighup
Изменения этих параметров можно вносить в qhb.conf без перезагрузки сервера. Если послать в qhbmaster сигнал SIGHUP, тот перечитает qhb.conf и применит изменения. Также qhbmaster перешлет сигнал SIGHUP своим дочерним процессам, чтобы все они приняли новое значение.

superuser-backend
Изменения этих параметров можно вносить в qhb.conf без перезагрузки сервера. Также их можно установить для определенного сеанса в пакете запроса на подключение (например, через переменную среды PGOPTIONS), но только если подключающийся пользователь является суперпользователем. Однако эти параметры никогда не изменяются в сеансе после его запуска. Если вы измените их внутри qhb.conf, отправьте сигнал SIGHUP в qhbmaster, чтобы тот перечитал qhb.conf. Новые значения будут влиять только на сеансы, запускаемые впоследствии.

backend
Изменения этих параметров можно вносить в qhb.conf без перезагрузки сервера. Также их можно установить для определенного сеанса в пакете запроса на подключение (например, через переменную среды PGOPTIONS); любой пользователь может внести такое изменение для своего сеанса. Однако эти параметры никогда не изменяются в сеансе после его запуска. Если вы измените их внутри qhb.conf, отправьте сигнал SIGHUP в qhbmaster, чтобы тот перечитал qhb.conf. Новые значения будут влиять только на сеансы, запускаемые впоследствии.

superuser
Эти параметры можно задать из qhb.conf или в рамках сеанса с помощью команды SET, но только суперпользователи могут изменить их посредством SET. Изменения в qhb.conf будут влиять на существующие сеансы только в том случае, если в них командой SET не были заданы локальные в рамках сеанса значения.

user
Эти параметры можно задать из qhb.conf или в рамках сеанса с помощью команды SET. В рамках сеанса их локальное значение может изменить любой пользователь. Изменения в qhb.conf будут влиять на существующие сеансы только в том случае, если в них командой SET не были заданы локальные в рамках сеанса значения.

Более подробную информацию о различных способах изменения этих параметров см. в разделе Настройка параметров.

В это представление нельзя добавить или удалить строки, но можно изменять их. Команда UPDATE, примененная к строке pg_settings, равнозначна выполнению команды SET для этого именованного параметра. Это изменение влияет только на значение, используемое в текущем сеансе. Если команда UPDATE выполняется в рамках транзакции, которая затем прерывается, эффекты UPDATE пропадают при откате транзакции. После фиксации окружающей транзакции эти эффекты сохранятся до конца сеанса, если только не будут переопределены другой командой UPDATE или SET.

В этом представлении специализированные параметры отображаются, только если обслуживающий процесс, выполняющий запрос, загрузил определяющий их модуль расширения (например, посредством указания в shared_preload_libraries, вызовом в этом расширении функции на C/RUST или при выполнении команды LOAD). К примеру, поскольку модули архивирования обычно загружаются процессом архиватора, а не обычными сеансами, в этом представлении не будут отображаться никакие нестандартные параметры, определенные такими модулями, если только специально не загрузить их в обслуживающий процесс, выполняющий запрос.

pg_shadow

В представлении pg_shadow отображаются свойства всех ролей, которые помечены как rolcanlogin в каталоге pg_authid.

Такое название («тень») обусловлено тем фактом, что эта таблица не должна быть доступна для чтения любому пользователю, поскольку она содержит пароли. Доступным для всех представлением pg_shadow является pg_user, в котором прикрыто поле пароля.

Таблица 25. Столбцы pg_shadow

Столбец Тип

Описание

usename name (ссылается на pg_authid.rolname)

Имя пользователя

usesysid oid (ссылается на pg_authid.oid)

Идентификатор этого пользователя

usecreatedb bool

Пользователь может создавать базы данных

usesuper bool

Пользователь является суперпользователем

userepl bool

Пользователь может инициировать потоковую репликацию и включать и выключать в системе режим резервного копирования.

usebypassrls bool

Пользователь обходит любую политику защиты на уровне строк; подробную информацию см. в разделе Политики защиты строк.

passwd text

Пароль (возможно зашифрованный); NULL, если пароля нет. Подробную информацию о том, как хранятся зашифрованные пароли, см. в описании каталога pg_authid.

valuntil timestamptz

Срок действия пароля (используется только для аутентификации по паролю)

useconfig text[]

Сеансовые значения по умолчанию для переменных конфигурации времени выполнения

pg_shmem_allocations

В представлении pg_shmem_allocations отображаются блоки памяти, выделенные в основном сегменте разделяемой памяти сервера. Сюда входит как память, выделенная самой QHB, так и память, выделенная расширениями с помощью механизмов, описанных в подразделе Разделяемая память и легкие блокировки.

Обратите внимание, что в этом представлении не показываются блоки, выделенные с использованием инфраструктуры динамической разделяемой памяти.

Таблица 26. Столбцы pg_shmem_allocations

Столбец Тип

Описание

name text

Имя блока в разделяемой памяти. NULL для неиспользуемых блоков памяти и <anonymous> для анонимных блоков.

off int8

Смещение, с которого начинается выделенный блок. NULL для анонимных блоков, так как дополнительные сведения о них неизвестны.

size int8

Размер блока

allocated_size int8

Размер блока, включая выравнивающее дополнение. Для анонимных блоков информация о дополнении недоступна, поэтому значения в столбцах size и allocated_size всегда будут равны. Для свободной памяти объем дополнения не имеет смысла, так что в этом случае столбцы тоже будут содержать одинаковые значения.

Анонимными считаются блоки, выделенные непосредственно функцией ShmemAlloc(), а не функцией ShmemInitStruct() или ShmemInitHash().

По умолчанию представление pg_shmem_allocations могут читать только суперпользователи или роли с правами роли pg_read_all_stats.

pg_stats

Представление pg_stats обеспечивает доступ к информации, хранящейся в каталоге pg_statistic. Это представление позволяет получить доступ только к тем строкам pg_statistic, что соответствуют таблицам, которые пользователь может читать, а потому к этому представлению можно без опасений разрешить публичный доступ на чтение.

Также pg_stats предназначено для представления информации в более удобочитаемом формате, чем нижележащий каталог, — за счет того, что схему этого представления необходимо расширять всякий раз, когда для каталога pg_statistic определяются новые типы слотов.

Таблица 27. Столбцы pg_stats

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей таблицу

tablename name (ссылается на pg_class.relname)

Имя таблицы

attname name (ссылается на pg_attribute.attname)

Имя столбца, описываемого этой строкой

inherited bool

Если true, то эта строка включает значения из дочерних таблиц иерархии наследования, а не только значения в указанной таблице

null_frac float4

Доля записей столбца, имеющих значение NULL

avg_width int4

Средняя ширина записей столбца в байтах

n_distinct float4

Если больше нуля, то показывает примерное количество уникальных значений в столбце. Если меньше нуля, то модуль числа показывает количество уникальных значений, разделенное на количество строк. (Отрицательная форма используется, когда команда ANALYZE считает, что количество уникальных значений, вероятно, будет увеличиваться по мере роста таблицы; положительная форма используется, когда считается, что столбец имеет фиксированное количество возможных значений.) Например, значение -1 указывает на уникальный столбец, в котором количество уникальных значений совпадает с количеством строк.

most_common_vals anyarray

Список наиболее распространенных значений в столбце. (NULL, если значения, встречающиеся чаще других, не обнаруживаются.)

most_common_freqs float4[]

Список частот наиболее распространенных значений, т. е. количество вхождений каждого значения, деленное на общее количество строк. (NULL, если поле most_common_vals содержит NULL.)

histogram_bounds anyarray

Список значений, разделяющих значения столбца на группы примерно одинаковой популяции. Значения поля most_common_vals, если они присутствуют, исключаются из этого расчета гистограммы. (Этот столбец содержит NULL, если тип данных столбца не имеет оператора < или если список most_common_vals охватывает всю популяцию.)

correlation float4

Статистическая корреляция между физическим порядком строк и логическим порядком значений столбцов. Диапазон допустимых значений: от -1 до +1. Когда значение близко к -1 или +1, сканирование индекса по столбцу будет считаться дешевле, чем когда оно близко к нулю, вследствие уменьшения случайного обращения к диску. (Столбец имеет значение NULL, если тип данных столбца не имеет оператора <.)

most_common_elems anyarray

Список отличных от NULL значений элементов, наиболее часто встречающихся в пределах значений столбца. (NULL для скалярных типов.)

most_common_elem_freqs float4[]

Список частот наиболее распространенных значений элементов, т. е. доля строк, содержащих хотя бы один экземпляр данного значения. За частотами каждого элемента следуют два или три дополнительных значения; это минимум и максимум предыдущих частот каждого элемента и (необязательно) частота элементов NULL. (NULL, если поле most_common_elems содержит NULL.)

elem_count_histogram float4[]

Гистограмма количеств уникальных значений элементов, отличных от NULL, в пределах значений столбца, за которой следует среднее количество различных элементов, отличных от NULL. (NULL для скалярных типов.)

Максимальным количеством записей в полях массива можно управлять на уровне столбцов с помощью команды ALTER TABLE SET STATISTICS или глобально, задавая параметр времени выполнения default_statistics_target.

pg_stats_ext

Представление pg_stats_ext обеспечивает доступ к информации о каждом объекте расширенной статистики в базе данных, сочетая сведения, хранящиеся в каталогах pg_statistic_ext и pg_statistic_ext_data. Это представление дает доступ только к тем строкам этих каталогов, что соответствуют таблицам, которые пользователь может читать, а потому к этому представлению можно без опасений разрешить публичный доступ на чтение.

Также pg_stats_ext предназначено для представления информации в более удобочитаемом формате, чем нижележащие каталоги, — за счет того, что схему этого представления необходимо расширять всякий раз, когда в каталог pg_statistic_ext добавляются новые типы расширенной статистики.

Таблица 28. Столбцы pg_stats_ext

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей таблицу

tablename name (ссылается на pg_class.relname)

Имя таблицы

statistics_schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей объект расширенной статистики

statistics_name name (ссылается на pg_statistic_ext.stxname)

Имя объекта расширенной статистики

statistics_owner oid (ссылается на pg_authid.oid)

Владелец объекта расширенной статистики

attnames name[] (ссылается на pg_attribute.attname)

Имена столбцов, включенных в объект расширенной статистики

exprs text[]

Выражения, включенные в объект расширенной статистики

kinds text[]

Типы объектов расширенной статистики, включенные для этой записи

inherited bool (ссылается на pg_statistic_ext_data.stxdinherit)

Если true, то статистика включает значения из дочерних таблиц иерархии наследования, а не только значения в указанном отношении

n_distinct pg_ndistinct

Количество различных комбинаций значений столбцов. Если больше нуля, то показывает примерное число уникальных значений в комбинации. Если меньше нуля, то модуль числа показывает количество уникальных значений, разделенное на число строк. (Отрицательная форма используется, когда команда ANALYZE считает, что количество уникальных значений, вероятно, будет увеличиваться по мере роста таблицы; положительная форма используется, когда считается, что столбец имеет фиксированное количество возможных значений.) Например, значение -1 указывает на уникальную комбинацию столбцов, в которой количество уникальных комбинаций совпадает с количество строк.

dependencies pg_dependencies

Статистика для функциональной зависимости

most_common_vals text[]

Список наиболее распространенных комбинаций значений в столбцах. (NULL, если комбинации, встречающиеся чаще других, не обнаруживаются.)

most_common_val_nulls bool[]

Список флагов NULL для наиболее распространенных комбинаций значений. (NULL, если поле most_common_vals содержит NULL)

most_common_freqs float8[]

Список частот наиболее распространенных комбинаций, т. е. количество вхождений каждой, деленное на общее количество строк. (NULL, если поле most_common_vals содержит NULL)

most_common_base_freqs float8[]

Список базовых частот наиболее распространенных комбинаций, т. е. произведение частот отдельных значений. (NULL, если поле most_common_vals содержит NULL)

Максимальным количеством записей в полях массива можно управлять на уровне столбцов с помощью команды ALTER TABLE SET STATISTICS или глобально, задав параметр времени выполнения default_statistics_target.

pg_stats_ext_exprs

Представление pg_stats_ext_exprs обеспечивает доступ к информации обо всех выражениях, включенных в объекты расширенной статистики, сочетая сведения, хранящиеся в каталогах pg_statistic_ext и pg_statistic_ext_data. Это представление дает доступ только к тем строкам этих каталогов, что соответствуют таблицам, которые пользователю разрешено читать, а потому к этому представлению можно без опасений разрешить публичный доступ на чтение.

Также pg_stats_ext_exprs предназначено для представления информации в более удобочитаемом формате, чем нижележащие каталоги, — за счет того, что схему этого представления необходимо расширять всякий раз, когда изменяется структура статистики в каталоге pg_statistic_ext.

Таблица 29. Столбцы pg_stats_ext_exprs

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей таблицу

tablename name (ссылается на pg_class.relname)

Имя таблицы, для которой определен объект статистики

statistics_schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей объект расширенной статистики

statistics_name name (ссылается на pg_statistic_ext.stxname)

Имя объекта расширенной статистики

statistics_owner oid (ссылается на pg_authid.oid)

Владелец объекта расширенной статистики

expr text

Выражение, включенное в объект расширенной статистики

inherited bool (ссылается на pg_statistic_ext_data.stxdinherit)

Если true, то статистика включает значения из дочерних таблиц иерархии наследования, а не только значения в указанном отношении

null_frac float4

Доля записей выражения, содержащих NULL

avg_width int4

Средний размер записей выражения в байтах

n_distinct float4

Если больше нуля, то показывает примерное число уникальных значений в выражении. Если меньше нуля, то модуль числа показывает количество уникальных значений, разделенное на число строк. (Отрицательная форма используется, когда команда ANALYZE считает, что количество уникальных значений, вероятно, будет увеличиваться по мере роста таблицы; положительная форма используется, когда считается, что выражение имеет фиксированное количество возможных значений.) Например, значение -1 указывает на уникальное выражение, в котором количество уникальных значений совпадает с количество строк.

most_common_vals anyarray

Список наиболее распространенных значений в выражении. (NULL, если значения, встречающиеся чаще других, не обнаруживаются.)

most_common_freqs float4[]

Список частот наиболее распространенных значений, т. е. число их вхождений, разделенное на общее число строк. (NULL, если поле most_common_vals содержит NULL.)

histogram_bounds anyarray

Список значений, разделяющих значения выражения на группы примерно одинаковой популяции. Значения поля most_common_vals, если они присутствуют, исключаются из этого расчета гистограммы. (Это выражение содержит NULL, если тип данных выражения не имеет оператора < или если список most_common_vals охватывает всю популяцию.)

correlation float4

Статистическая корреляция между физическим порядком строк и логическим порядком значений выражения. Диапазон допустимых значений: от -1 до +1. Когда значение близко к -1 или +1, сканирование индекса по выражению будет считаться дешевле, чем когда это значение близко к нулю, вследствие уменьшения случайного обращения к диску. (Это выражение содержит NULL, если тип данных выражения не имеет оператора <.)

most_common_elems anyarray

Список элементов, отличных от NULL, наиболее часто встречающихся в значениях выражения. (NULL для скалярных типов.)

most_common_elem_freqs float4[]

Список частот наиболее распространенных элементов, т. е. доля строк, содержащих как минимум одно вхождение данного значения. За частотами по элементам следуют два или три дополнительных значения: минимум и максимум предшествующих частот по элементам и, при необходимости, частота элементов NULL. (NULL, если поле most_common_elems содержит NULL.)

elem_count_histogram float4[]

Гистограмма количеств различных элементов, отличных от NULL, в значениях выражения, за которой следует среднее количество элементов, отличных от NULL. (NULL для скалярных типов.)

Максимальным количеством записей в полях-массивах можно управлять на уровне столбцов с помощью команды ALTER TABLE SET STATISTICS или глобально, установив параметр времени выполнения default_statistics_target.

pg_tables

Представление pg_tables обеспечивает доступ к полезной информации о каждой таблице в базе данных.

Таблица 30. Столбцы pg_tables

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей таблицу

tablename name (ссылается на pg_class.relname)

Имя таблицы

tableowner name (ссылается на pg_authid.rolname)

Имя владельца таблицы

tablespace name[] (ссылается на pg_tablespace.spcname)

Имя табличного пространства, содержащего таблицу (NULL, если это табличное пространство по умолчанию для базы данных)

hasindexes bool (ссылается на pg_class.relhasindex)

True, если таблица имеет (или недавно имела) какие-либо индексы

hasrules bool (ссылается на pg_class.relhasrules)

True, если таблица имеет (или когда-то имела) правила

hastriggers bool (ссылается на pg_class.relhastriggers)

True, если таблица имеет (или когда-то имела) триггеры

rowsecurity bool (ссылается на pg_class.relrowsecurity)

True, если в таблице включена защита на уровне строк

pg_timezone_abbrevs

Представление pg_timezone_abbrevs содержит список аббревиатур часовых поясов, которые в настоящее время верно распознаются функциями ввода даты/времени. Содержимое этого представления меняется при изменении параметра времени выполнения timezone_abbreviations.

Таблица 31. Столбцы pg_timezone_abbrevs

Столбец Тип

Описание

abbrev text

Аббревиатура часового пояса

utc_offset interval

Смещение от UTC (положительное значение означает к востоку от Гринвича)

is_dst bool

True, если это аббревиатура летнего времени

Хотя большинство аббревиатур часовых поясов представляют собой фиксированные смещения от UTC, в некоторых поясах значение смещения менялось в ходе истории (подробную информацию см. в разделе Файлы конфигурации даты/времени). В таких случаях это представление отображает их текущее значение.

pg_timezone_names

Представление pg_timezone_names содержит список имен часовых поясов, которые распознаются командой SET TIMEZONE, а также связанные с ними аббревиатуры, смещения UTC и статус летнего времени. (Технически QHB не использует UTC, потому что високосные секунды не обрабатываются). В отличие от сокращений, отображенных в представлении pg_timezone_abbrevs, многие из этих имен подразумевают набор правил перехода на летнее время. Поэтому связанная информация изменяется при пересечении локальных границ перехода на летнее время. Отображаемая информация вычисляется на основе текущего значения функции CURRENT_TIMESTAMP.

Таблица 32. Столбцы pg_timezone_names

Столбец Тип

Описание

name text

Название часового пояса

abbrev text

Аббревиатура часового пояса

utc_offset interval

Смещение от UTC (положительное значение означает к востоку от Гринвича)

is_dst bool

True, если в данный момент установлено летнее время

pg_user

Представление pg_user обеспечивает доступ к информации о пользователях базы данных. Это просто общедоступное представление каталога pg_shadow, в котором прикрыто поле пароля.

Таблица 33. Столбцы pg_user

Столбец Тип

Описание

usename name

Имя пользователя

usesysid oid

Идентификатор этого пользователя

usecreatedb bool

Пользователь может создавать базы данных

usesuper bool

Пользователь является суперпользователем

userepl bool

Пользователь может инициировать потоковую репликацию и включать и выключать режим резервного копирования в системе.

usebypassrls bool

Пользователь обходит каждую политику защиты на уровне строк; подробную информацию см. в разделе Политики защиты строк.

passwd text

Не пароль (всегда читается как ********)

valuntil timestamptz

Срок действия пароля (используется только при аутентификации по паролю)

useconfig text[]

Сеансовые значения по умолчанию для переменных конфигурации времени выполнения

pg_user_mappings

Представление pg_user_mappings обеспечивает доступ к информации о сопоставлениях пользователей. По сути это общедоступное представление каталога pg_user_mapping, которое не показывает поле параметров, если у пользователя нет прав на его использование.

Таблица 34. Столбцы pg_user_mappings

Столбец Тип

Описание

umid oid (ссылается на pg_user_mapping.oid)

OID сопоставления пользователей

srvid oid (ссылается на pg_foreign_server.oid)

OID стороннего сервера, содержащего это сопоставление

srvname name (ссылается на pg_foreign_server.srvname)

Имя стороннего сервера

umuser oid (ссылается на pg_authid.oid)

OID сопоставляемой локальной роли или ноль, если сопоставление пользователей задается для всех

usename name

Имя локального пользователя для сопоставления

umoptions text[]

Специфические параметры сопоставления пользователей в виде строк «ключ=значение»

Чтобы защитить информацию о пароле, сохраненную в качестве параметра сопоставления пользователей, столбец umoptions будет считываться как NULL, если не выполняется одно из следующих условий:

текущий пользователь является сопоставляемым пользователем, который владеет сервером или имеет для него право USAGE

текущий пользователь является владельцем сервера и прочитывается сопоставление для PUBLIC

текущий пользователь является суперпользователем

pg_views

Представление pg_views обеспечивает доступ к полезной информации о каждом представлении в базе данных.

Таблица 35. Столбцы pg_views

Столбец Тип

Описание

schemaname name (ссылается на pg_namespace.nspname)

Имя схемы, содержащей представление

viewname name (ссылается на pg_class.relname)

Имя представления

viewowner name (ссылается на pg_authid.rolname)

Имя владельца представления

definition text

Определение представления (реконструированный запрос SELECT)

Клиент-серверный протокол

Для взаимодействия между внешним и внутренним интерфейсом (клиентами и серверами) в QHB используется протокол, основанный на сообщениях. Этот протокол поддерживается через TCP/IP, а также через сокеты домена Unix. В качестве стандартного номера TCP-порта для серверов, поддерживающих этот протокол, в IANA был зарегистрирован номер 5432, но на практике можно использовать любой порт, не требующий специальных прав.

В этом документе описывается версия 3.0 данного протокола. Один сервер может поддерживать несколько версий протокола. Сообщение о запуске при установке подключения говорит серверу, какую версию протокола пытается использовать клиент. Если основная версия, запрашиваемая клиентом, не поддерживается сервером, подключение будет отклонено (например, это произойдет, если клиент запросит протокол версии 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 или 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

Чтобы начать обмен аутентификационной информацией SASL, сервер передает сообщение AuthenticationSASL. Оно содержит список механизмов аутентификации SASL, которые сервер может принять, в порядке предпочтений сервера.

Клиент выбирает один из поддерживаемых механизмов из списка и передает серверу сообщение SASLInitialResponse. Это сообщение содержит имя выбранного механизма и, возможно, «Начальный ответ клиента», если это использует выбранный механизм.

За этим последует одно или несколько сообщений вызова от сервера и ответа от клиента. Каждый вызов от сервера передается в сообщении AuthenticationSASLContinue, за которым следует ответ от клиента в сообщении SASLResponse. Конкретные детали этих сообщений зависят от механизма.

Наконец, когда обмен аутентификационной информацией успешно завершается, сервер передает сообщение 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-соединения.

Пример

Сервер передает сообщение AuthenticationSASL. Оно содержит список механизмов аутентификации SASL, которые сервер может принять. Если сервер собран с поддержкой SSL, сюда войдут SCRAM-SHA-256-PLUS и SCRAM-SHA-256, иначе — только последний.

Клиент отвечает, передавая сообщение SASLInitialResponse, где указан выбранный механизм, SCRAM-SHA-256 или SCRAM-SHA-256-PLUS. (Клиент волен выбрать любой механизм, но для большей безопасность ему следует выбирать вариант со связыванием каналов, если он это поддерживает.) В поле «Начальный ответ клиента» это сообщение содержит данные client-first-message SCRAM. client-first-message также содержит выбранный клиентом тип связывания каналов.

Сервер передает сообщение AuthenticationSASLContinue, содержащее данные SCRAM server-first-message.

Клиент передает сообщение SASLResponse, содержащее данные SCRAM client-final-message.

Сервер передает сообщение 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 | LOGICAL плагин_вывода } [ ( параметр [, ...] ) ]

Создать слот физической или логической репликации. Подробную информацию о слотах репликации см. в подразделе Слоты репликации.

имя_слота
Имя создаваемого слота. Задаваемое имя должно быть допустимым для слота репликации (см. подраздел Запросы и манипуляции слотов репликации).

плагин_вывода
Имя плагина вывода, используемого для логического декодирования (см. раздел Плагины вывода логического декодирования).

TEMPORARY
Указывает, что этот слот репликации является временным. Временные слоты не сохраняются на диске и автоматически удаляются при ошибке или завершении сеанса.

Поддерживаются следующие параметры:

TWO_PHASE [ boolean ]
Если равен true, этот слот логической репликации поддерживает декодирование двухфазной фиксации. С этим параметром декодируются и передаются серверу команды, связанные с двухфазной фиксацией: PREPARE TRANSACTION, COMMIT PREPARED и ROLLBACK PREPARED. Транзакция будет декодироваться и передаваться во время PREPARE TRANSACTION. Значение по умолчанию — false.

RESERVE_WAL [ boolean ]
Если равен true, этот слот физической репликации резервирует WAL немедленно. В противном случае WAL резервируется только при подключении клиента потоковой репликации. Значение по умолчанию — false.

SNAPSHOT { 'export' | 'use' | 'nothing' }
Решает, что делать со снимком, созданным при инициализации логического слота. С указанием 'export' (по умолчанию) снимок будет экспортироваться для использования в других сеансах. Этот вариант нельзя использовать внутри транзакции. С указанием 'use' снимок будет использоваться для текущей транзакции, выполняющей команду. Этот вариант следует использовать в транзакции, при этом команда CREATE_REPLICATION_SLOT должна идти в этой транзакции первой. Наконец, с указанием 'nothing' снимок будет использоваться только для логического декодирования в обычном режиме, и больше с ним ничего делаться не будет.

В ответ на эту команду сервер передаст набор результатов с одной строкой, содержащей следующие поля:

slot_name (text)
Имя только что созданного слота репликации.

consistent_point (text)
Позиция в WAL, в которой слот стал согласованным. Это самая ранняя позиция, с которой через этот слот репликации может начаться потоковая передача.

snapshot_name (text)
Идентификатор снимка, экспортированного командой. Этот снимок действителен до тех пор, пока через это соединение не будет выполнена новая команда или соединение репликации не будет закрыто. NULL, если созданный слот является физическим.

output_plugin (text)
Имя плагина вывода, используемого только что созданным слотом репликации. NULL, если созданный слот является физическим.

CREATE_REPLICATION_SLOT имя_слота [ TEMPORARY ] { PHYSICAL [ RESERVE_WAL ] | LOGICAL плагин_вывода [ EXPORT_SNAPSHOT | NOEXPORT_SNAPSHOT | USE_SNAPSHOT | TWO_PHASE ] }

Этот вариант синтаксиса команды CREATE_REPLICATION_SLOT по-прежнему поддерживается для совместимости со старыми версиями.

READ_REPLICATION_SLOT имя_слота

Считать некоторую информацию, связанную со слотом репликации. Возвращает кортеж со значениями NULL, если слот репликации не существует. В настоящее время эта команда поддерживается только для слотов физической репликации.

В ответ на эту команду сервер вернет набор результатов с одной строкой, содержащей следующие поля:

slot_type (text)
Тип слота репликации: physical или NULL.

restart_lsn (text)
restart_lsn слота репликации.

restart_tli (int8)
Идентификатор временной шкалы, связанной с restart_lsn, в текущей истории временной шкалы.

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 или с confirmed_flush_lsn слота (см. раздел pg_replication_slots), смотря какое значение больше. Это поведение помогает клиентам избегать необходимости обновлять локальный статус LSN, когда нет данных, требующих обработки. Однако если репликация начинается не с запрошенного LSN, некоторые ошибки на стороне клиента могут остаться незамеченными; поэтому прежде чем передавать START_REPLICATION, клиенту имеет смысл убедиться, что значение confirmed_flush_lsn соответствует его ожиданиям.

Сервер может ответить ошибкой, например, если такой слот не существует. В случае успеха сервер отвечает сообщением CopyBothResponse, а затем начинает передавать клиенту поток WAL.

Сообщения внутри сообщения CopyBothResponse имеют тот же формат, что описан для команды START_REPLICATION ... PHYSICAL, включая два сообщения CommandComplete.

Для обработки выходных данных потоковой передачи используется плагин вывода, связанный с выбранным слотом.

SLOT имя_слота
Имя слота, из которого передаются изменения. Это обязательный параметр, который должен соответствовать существующему слоту логической репликации, созданному командой CREATE_REPLICATION_SLOT в режиме LOGICAL.

XXX/XXX
Позиция в WAL, с которой должна начаться потоковая передача.

имя_параметра
Имя параметра, передаваемого плагину логического декодирования этого слота.

значение_параметра
Необязательное значение в форме строковой константы, связанное с указанным параметром.

DROP_REPLICATION_SLOT имя_слота [ WAIT ]

Удаляет слот репликации, освобождая все зарезервированные ресурсы на стороне сервера. Если это логический слот, созданный не в той базе данных, к которой подключен передатчик WAL, данная команда завершается ошибкой.

имя_слота
Имя удаляемого слота.

WAIT
Этот параметр заставляет команду ждать, если слот активен, пока он не станет неактивным, вместо того чтобы выдать ошибку (поведение по умолчанию).

BASE_BACKUP [ ( параметр [, ...] ) ]

Указывает серверу начать потоковую передачу базовой резервной копии. Система автоматически перейдет в режим резервного копирования до начала передачи и выходит из него, когда копирование завершается. Принимаются следующие параметры:

LABEL 'метка'
Устанавливает метку для резервной копии. Если метка не задана, будет установлена метка base backup. Правила применения кавычек для метки такие же, как для стандартной строки SQL при включенном параметре standard_conforming_strings.

TARGET 'получатель'
Указывает серверу, куда передавать резервную копию. Если получатель client (по умолчанию), данные резервной копии передаются клиенту. Если выбрано значение server, данные резервной копии записываются на сервер по пути, заданному в параметре TARGET_DETAIL. При значении blackhole данные резервной копии никуда не передаются, а просто отбрасываются.
Чтобы задать получателем server, нужны права суперпользователя или роли pg_write_server_files.

TARGET_DETAIL 'подробности'
Предоставляет дополнительную информацию о получателе резервной копии.
В настоящее время этот параметр можно использовать, только когда получателем резервной копии является server. Здесь указывается каталог на сервере, куда следует записать резервную копию.

PROGRESS [ boolean ]
При значении true запросить информацию, необходимую для генерирования отчета о ходе выполнения передачи. В ответ сервер передаст примерный размер в заголовке каждого табличного пространства, из которого можно вычислить, насколько продвинулась передача потока. Вычисление проводится путем подсчета размеров всех файлов еще до начала передачи и поэтому может отрицательно сказаться на производительности. В частности, может возрасти задержка передачи первых данных. Поскольку файлы базы данных в ходе резервного копирования могут меняться, оценка размера будет только приблизительной, и он может как увеличиться, так и уменьшиться за время между вычислением и передачей реальных файлов. Значение по умолчанию — false.

CHECKPOINT { 'fast' | 'spread' }
Устанавливает тип контрольной точки, которая будет выполняться в начале базового резервного копирования. Значение по умолчанию — spread (продолжительная).

WAL [ boolean ]
При значении true включить в резервную копию необходимые сегменты WAL. Сюда войдут все файлы, которые с начала и до конца копирования находились в подкаталоге pg_wal архива базового каталога. Значение по умолчанию — false.

WAIT [ boolean ]
При значении true резервное копирование будет ждать завершения архивации последнего необходимого сегмента WAL или выдавать предупреждение, если архивация журнала не включена. Значение false выключает и ожидание, и предупреждение, поэтому за обеспечение наличия необходимого журнала будет отвечать клиент. Значение по умолчанию — true.

COMPRESSION 'метод'
Указывает серверу сжимать резервную копию заданным методом. В настоящее время поддерживаются методы gzip, lz4 и zstd.

COMPRESSION_DETAIL подробности
Указывает дополнительную информацию о выбранном методе сжатия. Должен использоваться только в сочетании с параметром COMPRESSION. Если значение является целым числом, оно обозначает уровень сжатия. В противном случае это должен быть список элементов, разделенных запятыми, в форме ключевое_слово или ключевое_слово=значение. В настоящее время поддерживаются ключевые слова level и workers.
Ключевое слово level устанавливает уровень сжатия. Для gzip уровень сжатия должен быть целым числом от 1 до 9 (по умолчанию Z_DEFAULT_COMPRESSION или -1), для lz4 — целым числом от 1 до 12 (по умолчанию 0 для режима быстрого сжатия), а для zstd — целым числом от ZSTD_minCLevel() (обычно -131072) до ZSTD_maxCLevel() (обычно 22), (по умолчанию ZSTD_CLEVEL_DEFAULT или 3).
Ключевое слово workers устанавливает количество потоков, которые должны использоваться для параллельного сжатия. Сжатие в несколько параллельных потоков поддерживается только для zstd.

MAX_RATE скорость
Ограничить (дросселировать) максимальный объем данных, передаваемый от сервера клиенту за единицу времени. Ожидаемая единица измерения — килобайты в секунду. Если этот параметр задается, его значение должно быть либо равно нулю, либо находиться в диапазоне от 32 КБ до 1 ГБ (включая границы). Если передается ноль или параметр не задан, скорость передачи не ограничивается.

TABLESPACEMAP [ boolean ]
При значении true включить информацию о символических ссылках, находящихся в каталоге pg_tblspc, в файл tablespace_map. Файл карты табличных пространств содержит имена всех символических ссылок, существующих в каталоге pg_tblspc/ и полный путь для каждой ссылки. Значение по умолчанию — false.

VERIFY_CHECKSUMS [ boolean ]
При значении true контрольные суммы проверяются во время базового резервного копирования, если они включены. Указание false выключает эту проверку. Значение по умолчанию — true.

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. Информационное наполнение каждого сообщения CopyData будет состоять из сообщения в одном из следующих форматов:

new archive (B)

Byte1('n')
Показывает, что это сообщение обозначает начало нового архива. Будет передан один архив для основного каталога данных и по одному для каждого дополнительного табличного пространства; все данные будут в формате tar (исходя из «формата обмена ustar», указанного в стандарте POSIX 1003.1-2008).

String
Имя файла этого архива.

String
Для основного каталога данных — пустая строка. Для других табличных пространств — полный путь к каталогу, из которого был создан этот архив.

manifest (B)

Byte1('m')
Показывает, что это сообщение обозначает начало манифеста резервной копии.

archive or manifest data (B)

Byte1('d')
Показывает, что это сообщение содержит данные архива или манифеста.

Byten
Байты данных.

progress report (B)

Byte1('p')
Показывает, что это сообщение является отчетом о выполнении.

Int64
Количество уже обработанных байтов из текущего табличного пространства.

После передачи 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 и 3.
Версия 2 поддерживается только для серверов версии 1.5.0 и выше и допускает потоковую передачу больших выполняющихся транзакций.
Версия 3 поддерживается только для серверов версии 1.5.2 и выше и допускает потоковую передачу двухфазных фиксаций.

publication_names
Список разделенных запятыми имен публикаций, на которые следует подписаться (получать их изменения). Имена отдельных публикаций воспринимаются как стандартные имена объектов и при необходимости тоже могут заключаться в кавычки.

Сообщения протокола логической репликации

Отдельные сообщения протокола рассматриваются в следующих подразделах. Сами сообщения описаны в разделе Форматы сообщений логической репликации.

Все сообщения верхнего уровня начинаются с байта, указывающего на тип сообщения. Хотя он представлен в коде в виде символа, это знаковый байт, с которым не связана никакая кодировка.

Поскольку протокол потоковой репликации предоставляет длину сообщения, нет необходимости указывать длину в заголовках сообщений верхнего уровня.

Поток сообщений протокола логической репликации

За исключением команды START_REPLICATION и сообщений о ходе воспроизведения, весь информационный поток передается от сервера клиенту.

Протокол логической репликации передает отдельные транзакции одну за другой. Это означает, что все сообщения между парой сообщений Begin и Commit относятся к одной транзакции. Аналогично к одной транзакции относятся все сообщения между парой сообщений Begin Prepare и Prepare. Также протокол передает изменения из больших выполняющихся транзакций между парой сообщений 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 число_строк, где число_строк — это количество измененных строк.
Для команды MERGE тегом является MERGE число_строк, где число_строк — это количеством добавленных, измененных или удаленных строк.
Для команды 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
Флаги; в настоящее время не используются.

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
OID отношения, соответствующий идентификатору в сообщении об отношении.

Byte1('N')
Обозначает следующее сообщение TupleData как новый кортеж.

TupleData
Раздел сообщения TupleData, представляющий содержимое нового кортежа.

Update

Byte1('U')
Показывает, что это сообщение об изменении данных.

Int32
Идентификатор транзакции (присутствует только для потоковых транзакций). Это поле доступно со 2-й версии протокола.

Int32
OID отношения, соответствующий идентификатору в сообщении об отношении.

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 и Stream Abort) доступны со 2-й версии протокола.

Stream Start

Byte1('S')
Показывает, что это сообщение о начале потока.

Int32
Идентификатор транзакции.

Int8
Значение 1 показывает, что это первый сегмент потока с этим идентификатором; 0 для любого другого сегмента потока.

Stream Stop

Byte1('E')
Показывает, что это сообщение об окончании потока.

Stream Commit

Byte1('c')
Показывает, что это сообщение о фиксации транзакции в потоке.

Int32
Идентификатор транзакции.

Int8
Флаги; в настоящее время не используются.

Int64
LSN фиксации.

Int64
Конечный LSN транзакции.

Int64
Метка времени фиксации транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Stream Abort

Byte1('A')
Показывает, что это сообщение о прерывании транзакции в потоке.

Int32
Идентификатор транзакции.

Int32
Идентификатор субтранзакции (для транзакций верхнего уровня будет совпадать с идентификатором транзакций).

Следующие сообщения (Begin Prepare, Prepare, Commit Prepared, Rollback Prepared, Stream Prepare) доступны с 3-й версии протокола.

Begin Prepare

Byte1('b')
Показывает, что это сообщение о начале подготовленной транзакции.

Int64 (XLogRecPtr)
LSN подготовки транзакции.

Int64 (XLogRecPtr)
Конечный LSN подготовленной транзакции.

Int64 (TimestampTz)
Временная метка подготовки транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Int32 (TransactionId)
Идентификатор транзакции.

String
Пользовательский GID подготовленной транзакции.

Prepare

Byte1('P')
Показывает, что это сообщение о подготовленной транзакции.

Int8(0)
Флаги; в настоящее время не используются.

Int64 (XLogRecPtr)
LSN подготовки транзакции.

Int64 (XLogRecPtr)
Конечный LSN подготовленной транзакции.

Int64 (TimestampTz)
Временная метка подготовки транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Int32 (TransactionId)
Идентификатор транзакции.

String
Пользовательский GID подготовленной транзакции.

Commit Prepared

Byte1('K')
Показывает, что это сообщение о фиксации подготовленной транзакции.

Int8(0)
Флаги; в настоящее время не используются.

Int64 (XLogRecPtr)
LSN фиксации подготовленной транзакции.

Int64 (XLogRecPtr)
Конечный LSN фиксации подготовленной транзакции.

Int64 (TimestampTz)
Временная метка подготовки транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Int32 (TransactionId)
Идентификатор транзакции.

String
Пользовательский GID подготовленной транзакции.

Rollback Prepared

Byte1('r')
Показывает, что это сообщение об откате подготовленной транзакции.

Int8(0)
Флаги; в настоящее время не используются.

Int64 (XLogRecPtr)
Конечный LSN подготовленной транзакции.

Int64 (XLogRecPtr)
Конечный LSN отката подготовленной транзакции.

Int64 (TimestampTz)
Временная метка подготовки транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Int64 (TimestampTz)
Временная метка отката транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Int32 (TransactionId)
Идентификатор транзакции.

String
Пользовательский GID подготовленной транзакции.

Stream Prepare

Byte1('p')
Показывает, что это сообщение о потоковой передаче подготовленной транзакции.

Int8(0)
Флаги; в настоящее время не используются.

Int64 (XLogRecPtr)
LSN подготовки транзакции.

Int64 (XLogRecPtr)
Конечный LSN подготовленной транзакции.

Int64 (TimestampTz)
Временная метка подготовки транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Int32 (TransactionId)
Идентификатор транзакции.

String
Пользовательский GID подготовленной транзакции.

Описанные выше сообщения имеют следующие общие разделы.

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') включало пустой строковый параметр; он был удален.

Определение интерфейса для табличных методов доступа

В этой главе описывается интерфейс между ядром системы QHB и табличными методами доступа, управляющими хранилищем для таблиц. Ядро системы мало что знает об этих методах доступа помимо того, что описано в этой главе, поэтому можно разработать совершенно новые типы методов доступа, написав дополнительный код.

Каждый табличный метод доступа описывается строкой в системном каталоге pg_am. В записи в pg_am указывается имя и функция обработки для табличного метода доступа. Эти записи можно создавать и удалять с помощью команд SQL CREATE ACCESS METHOD и DROP ACCESS METHOD.

Функция обработки табличного метода доступа должна быть объявлена как принимающая один аргумент типа internal и возвращающая псевдотип table_am_handler. Аргумент является фиктивным значением, которое требуется только для предотвращения вызова функции непосредственно из команд SQL. Результатом работы функции должен быть указатель на структуру типа TableAmRoutine, содержащую все, что необходимо коду ядра для использования табличного метода доступа. Возвращаемое значение должно существовать столько же, сколько и сам сервер, для чего его обычно определяют как переменную static const в глобальном контексте. Структура TableAmRoutine, также называемая API-структурой метода доступа, определяет поведение метода доступа с помощью функций обратного вызова. Эти функции являются указателями на обычные функции на C/RUST и не видны и не могут вызываться на уровне SQL. Все функции обратного вызова и их поведение определяются в структуре TableAmRoutine (с комментариями внутри структуры, определяющими требования к ним). Для большинства функций обратного вызова имеются оберточные функции, которые документируются с точки зрения пользователя (а не разработчика) табличного метода доступа.

Для реализации метода доступа (МД) разработчику, как правило, требуется реализовать специфичный для этого МД тип слота таблицы кортежей, позволяющий коду снаружи метода доступа хранить ссылки на кортежи МД и обращаться к столбцам кортежа.

В настоящее время способ, которым МД фактически хранит данные, практически ничем не ограничивается. Например, можно, но не обязательно, использовать кэш разделяемых буферов сервера qhb. В случае его использования, вероятно, имеет смысл применить стандартную структуру страницы QHB, как описано в разделе Структура страницы базы данных.

Единственное довольно серьезное ограничение API табличного метода доступа заключается в том, что в настоящее время если МД хочет поддерживать модификации и/или индексы, то необходимо, чтобы у каждого кортежа был идентификатор кортежа (TID), состоящий из номера блока и номера элемента (см. также раздел Структура страницы базы данных). Необязательно, чтобы элементы TID имели то же значение, которое они имеют, например, для метода heap, но если требуется поддержка сканирования по битовой карте (это необязательно), то номер блока должен обеспечивать локальность.

Для восстановления при сбое МД может использовать WAL сервера qhb или нестандартную реализацию журнала. При выборе WAL можно использовать типовые записи WAL или реализовать пользовательский менеджер ресурсов WAL.

Любой разработчик нового табличного метода доступа может обратиться к существующей реализации heap и почерпнуть дополнительную информацию о реализации этого метода.

Определение интерфейса для индексных методов доступа

В этой главе описывается интерфейс между основной системой QHB и индексными методами доступа, управляющими отдельными типами индексов. Основная система мало что знает об индексах помимо того, что описано в этой главе, поэтому можно разработать совершенно новые типы индексов, написав дополнительный код.

Все индексы в QHB с технической точки зрения являются вторичными индексами, то есть индекс физически отделен от файла таблицы, которую описывает. Каждый индекс хранится как отдельное физическое отношение и описывается в записи каталога pg_class. Содержание индекса полностью находится под контролем его индексного метода доступа. На практике все индексные методы доступа разделяют индексы на страницы стандартного размера, чтобы те могли обращаться к содержимому индекса с помощью обычных менеджеров хранилища и менеджеров буферов. (Более того, все существующие индексные методы доступа используют стандартную структуру страницы, описанную в разделе Структура страницы базы данных, и большинство используют тот же формат для заголовков индексов, но эти решения не являются обязательными для метода доступа.)

По сути индекс — это средство сопоставления некоторых значений ключей данных с идентификаторами кортежей (TID) версий строк (кортежей) в родительской таблице индекса. TID состоит из номера блока и номера элемента внутри этого блока (см. раздел Структура страницы базы данных). Этой информации достаточно для извлечения определенной версии строки из таблицы. Сами индексы не знают, что при использовании MVCC может существовать несколько версий одной логической строки; для индекса каждый кортеж является независимым объектом, которому требуется отдельная запись в индексе. Таким образом, при изменении строки в индексах всегда создаются для нее новые записи, даже если значения ключей не изменились. (Кортежи HOT являются исключением из этого утверждения, но индексы не имеют с ними дела.) Индексные записи для неиспользуемых кортежей высвобождаются (при очистке), когда высвобождаются сами неиспользуемые кортежи.

Базовая структура API для индексов

Каждый индексный метод доступа описывается строкой в системном каталоге pg_am. Запись в pg_am содержит имя и функцию обработки индексного метода доступа. Эти записи можно создавать и удалять с помощью команд SQL CREATE ACCESS METHOD и DROP ACCESS METHOD.

Функция обработки индексного метода доступа должна принимать один аргумент типа internal и возвращать псевдотип index_am_handler. Аргумент является фиктивным значением, которое требуется только для предотвращения вызова функций обработки непосредственно из команд SQL. Результатом работы функции должна быть выделенная с помощью palloc структура типа IndexAmRoutine, содержащая все, что должен знать код ядра для использования индексного метода доступа. Структура IndexAmRoutine, также называемая API-структурой метода доступа, включает поля, задающие различные фиксированные свойства метода доступа, например возможность поддержки многостолбцовых индексов. Что еще более важно, она содержит указатели на вспомогательные функции метода доступа, выполняющие всю реальную работу по обращению к индексам. Эти вспомогательные функции являются простыми функциями на C/RUST и не видны и не могут вызываться на уровне SQL. Вспомогательные функции описываются в разделе Функции индексных методов доступа.

Структура IndexAmRoutine определяется таким образом:

typedef struct IndexAmRoutine
{
    NodeTag     type;

    /*
     * Общее количество стратегий (операторов), с которыми возможен просмотр/поиск
     * этого метода доступа (МД). Ноль, если у МД нет фиксированного набора
     * назначенных стратегий.
     */
    uint16      amstrategies;
    /* общее количество вспомогательных функций, используемых этим МД */
    uint16      amsupport;
    /* номер вспомогательной функции options класса операторов или 0 */
    uint16      amoptsprocnum;
    /* поддерживает ли МД упорядочивание (ORDER BY) значений индексированного столбца? */
    bool        amcanorder;
    /* поддерживает ли МД упорядочивание (ORDER BY) результата оператора с индексированным столбцом? */
    bool        amcanorderbyop;
    /* поддерживает ли МД обратное сканирование? */
    bool        amcanbackward;
    /* поддерживает ли МД уникальные индексы (UNIQUE)? */
    bool        amcanunique;
    /* поддерживает ли МД многостолбцовые индексы? */
    bool        amcanmulticol;
    /* требуется ли для сканирование с МД ограничение первого столбца индекса? */
    bool        amoptionalkey;
    /* обрабатывает ли МД условия ScalarArrayOpExpr? */
    bool        amsearcharray;
    /* обрабатывает ли МД условия IS NULL/IS NOT NULL? */
    bool        amsearchnulls;
    /* может ли тип данных индексного хранилища отличаться от типа данных столбца? */
    bool        amstorage;
    /* возможна ли клаcтеризация по индексу этого типа? */
    bool        amclusterable;
    /* обрабатывает ли МД предикатные блокировки? */
    bool        ampredlocks;
    /* обрабатывает ли МД параллельное сканирование? */
    bool        amcanparallel;
    /* поддерживает ли МД столбцы, добавляемые предложением INCLUDE? */
    bool        amcaninclude;
    /* использует ли МД maintenance_work_mem? */
    bool        amusemaintenanceworkmem;
    /* ИЛИ флаги параллельной очистки */
    uint8       amparallelvacuumoptions;
    /* тип данных, хранящихся в индексе, или InvalidOid, если он переменный */
    Oid         amkeytype;

    /* функции интерфейса */
    ambuild_function ambuild;
    ambuildempty_function ambuildempty;
    aminsert_function aminsert;
    ambulkdelete_function ambulkdelete;
    amvacuumcleanup_function amvacuumcleanup;
    amcanreturn_function amcanreturn;   /* может быть NULL */
    amcostestimate_function amcostestimate;
    amoptions_function amoptions;
    amproperty_function amproperty;     /* может быть NULL */
    ambuildphasename_function ambuildphasename;   /* может быть NULL */
    amvalidate_function amvalidate;
    amadjustmembers_function amadjustmembers; /* может быть NULL */
    ambeginscan_function ambeginscan;
    amrescan_function amrescan;
    amgettuple_function amgettuple;     /* может быть NULL */
    amgetbitmap_function amgetbitmap;   /* может быть NULL */
    amendscan_function amendscan;
    ammarkpos_function ammarkpos;       /* может быть NULL */
    amrestrpos_function amrestrpos;     /* может быть NULL */

    /* функции интерфейса для поддержки параллельного сканирования по индексу */
    amestimateparallelscan_function amestimateparallelscan;    /* может быть NULL */
    aminitparallelscan_function aminitparallelscan;    /* может быть NULL */
    amparallelrescan_function amparallelrescan;    /* может быть NULL */
} IndexAmRoutine;

Чтобы индексный метод доступа можно было применять на практике, он также должен иметь одно или несколько семейств операторов и классов операторов, определенных в каталогах pg_opfamily, pg_opclass, pg_amop и pg_amproc. Эти записи позволяют планировщику определить, какие виды запросов можно использовать с индексами этого метода доступа. Семейства операторов и классы описываются в разделе Интерфейсные расширения для индексов, материал которого необходимо изучить, прежде чем приступить к чтению этой главы.

Отдельный индекс определяется записью в каталоге pg_class, описывающей его как физическое отношение, плюс запись в каталоге pg_index, показывающей логическое содержимое индекса, то есть набор столбцов индекса, и семантику этих столбцов, установленную связанными классами операторов. Столбцы индекса (ключевые значения) могут быть либо простыми столбцами нижележащей таблицы, либо выражениями, вычисляемыми по строкам этой таблицы. Обычно для индексного метода доступа не имеет значения, откуда берутся значения ключей индекса (ему всегда передаются заранее вычисленные значения ключей), но очень важна информация о классе оператора в pg_index. Обе эти записи каталога доступны в составе структуры данных Relation, которая передается для всех операций с индексом.

Некоторые из полей флагов структуры IndexAmRoutine имеют нетривиальное назначение. Требования, предъявляемые к amcanunique, рассматриваются в разделе Проверки уникальности индекса. Флаг amcanmulticol означает, что метод доступа поддерживает многостолбцовые индексы, тогда как amoptionalkey — что метод доступа разрешает сканирование, когда для первого столбца индекса не задано индексируемое ограничительное предложение. Когда amcanmulticol равен false, amoptionalkey по сути говорит, поддерживает ли метод доступа полное сканирование по индексу без каких-либо ограничительных условий. Методы доступа, поддерживающие несколько столбцов в индексе, должны поддерживать сканирования, позволяющие пропускать ограничения для любого или всех столбцов после первого, однако им разрешено требовать, чтобы для первого столбца индекса ограничения использовались; об этом сигнализирует установка флага amoptionalkey в false. Одна из причин, по которой метод доступа может установить в amoptionalkey значение false, это, например, если он не индексирует значения NULL. Поскольку большинство индексируемых операторов являются строгими и, следовательно, не могут возвращать true, если операнд равен NULL, на первый взгляд кажется правильным не хранить записи индекса для значений NULL: они никогда не могут быть возвращены при сканировании индекса. Однако этот аргумент не сработает, когда при сканировании индекса отсутствует ограничительное предложение для данного столбца индекса. На практике это означает, что индексы, у которых amoptionalkey равен true, должны индексировать значения NULL, поскольку планировщик может решить использовать такой индекс вообще без ключей сканирования. Связанное с этим ограничение заключается в том, что индексный метод доступа, поддерживающий несколько столбцов индекса, должен поддерживать индексирование значений NULL в столбцах после первого, поскольку планировщик будет предполагать, что индекс можно использовать для запросов, не ограничивающих эти столбцы. Например, рассмотрим индекс по (a,b) и запрос с WHERE a = 4. Система будет считать, что индекс можно использовать для сканирования строк с а = 4, что неверно, если индекс пропускает строки, где b равен NULL. Однако вполне допустимо пропускать строки, в которых первый индексированный столбец равен NULL. Индексный метод доступа, действительно индексирующий значения NULL, может также установить флаг amsearchnulls, показывая, что он поддерживает предложения IS NULL и IS NOT NULL в условиях поиска.

Флаг amcaninclude показывает, поддерживает ли метод доступа «неключевые» столбцы, то есть может ли он сохранить (без обработки) дополнительные столбцы помимо ключевых. Требования в предыдущем абзаце применимы только к ключевым столбцам. В частности, сочетание amcanmulticol=false и amcaninclude=true имеет смысл: оно означает, что в индексе может быть только один ключевой столбец, но при этом могут быть дополнительные неключевые столбцы. Кроме того, неключевые столбцы могут содержать NULL независимо от флага amoptionalkey.

Функции индексных методов доступа

Индексный метод доступа в структуре IndexAmRoutine должен предоставить следующие функции построения и обслуживания индекса:

IndexBuildResult *
ambuild (Relation heapRelation,
         Relation indexRelation,
         IndexInfo *indexInfo);

Построить новый индекс. Отношение индекса было физически создано, но остается пустым. Оно должно быть заполнено всеми фиксированными данными, которые требуются методу доступа, а также записями для всех кортежей, уже существующих в таблице. Обычно для сканирования таблицы на наличие существующих кортежей и вычисления ключей, которые необходимо вставить в индекс, функция ambuild вызывает table_index_build_scan(). Эта функция должна возвращать структуру, выделенную с помощью palloc и содержащую статистические данные о новом индексе.

void
ambuildempty (Relation indexRelation);

Построить пустой индекс и записать его в ветвь инициализации (INIT_FORKNUM) данного отношения. Этот метод вызывается только для нежурналируемых индексов. Пустой индекс, записанный в ветвь инициализации, будет копироваться в основную ветвь отношения при каждом перезапуске сервера.

bool
aminsert (Relation indexRelation,
          Datum *values,
          bool *isnull,
          ItemPointer heap_tid,
          Relation heapRelation,
          IndexUniqueCheck checkUnique,
          IndexInfo *indexInfo);

Вставить новую запись в существующий индекс. Массивы values и isnull задают значения ключей для индексирования, а heap_tid — это TID индексируемого кортежа. Если метод доступа поддерживает уникальные индексы (его флаг amcanunique равен true), тогда аргумент checkUnique указывает тип выполняемой проверки уникальности. Ее тип зависит от того, является ли ограничение уникальности откладываемым; подробную информацию см. в разделе Проверки уникальности индекса. Обычно при выполнении проверки уникальности методу доступа требуется только параметр heapRelation (так ему придется просмотреть кучу, чтобы убедиться в активности кортежа).

Логическое значение indexUnchanged дает подсказку о природе индексируемого кортежа. Когда это значение true, кортеж является дубликатом некоторого существующего кортежа в индексе. Новый кортеж является логически неизмененным преемником MVCC-версии кортежа. Это происходит, когда выполняется команда UPDATE, которая не изменяет столбцы, охватываемые индексом, но все равно требует добавления в индекс новой версии кортежа. Индексный МД может использовать эту подсказку для принятия решения о применении восходящего удаления в тех частях индекса, где скапливается много версий одной логической строки. Обратите внимание, что изменение неключевого столбца не влияет на значение indexUnchanged. Код ядра определяет значение indexUnchanged каждого кортежа, используя подход с низкими издержками, который допускает как ложноположительные, так и ложноотрицательные результаты. Индексные МД не должны рассматривать indexUnchanged как авторитетный источник информации о видимости кортежа или версионировании.

Логическое значение результата функции является значимым, только когда checkUnique имеет значение UNIQUE_CHECK_PARTIAL. В этом случае результат true означает, что новая запись считается уникальной, тогда как false означает, что она может быть неуникальной (и следует запланировать отложенную проверку уникальности). В других случаях рекомендуется всегда возвращать false.

Некоторые индексы могут индексировать не все кортежи. Если кортеж не будет индексироваться, aminsert должна просто вернуться, ничего не делая.

Если индексный МД хочет кэшировать данные между последовательными добавлениями в индекс в одном операторе SQL, он может выделить пространство в indexInfo-> ii_Context и хранить указатель на данные в поле indexInfo->ii_AmCache (которое изначально будет равно NULL).

IndexBulkDeleteResult *
ambulkdelete (IndexVacuumInfo *info,
              IndexBulkDeleteResult *stats,
              IndexBulkDeleteCallback callback,
              void *callback_state);

Удалить кортеж(и) из индекса. Это операция «массового удаления», которая предположительно будет реализована путем сканирования всего индекса и проверки каждой записи на предмет, следует ли ее удалить. Переданную функцию callback следует вызывать в стиле callback( TID, callback_state) returns bool, чтобы определить, нужно ли удалить какую-то конкретную запись индекса, заданную соответствующим TID. Эта функция должна возвращать либо NULL, либо структуру, выделенную palloc и содержащую статистику по результатам операции удаления. NULL можно вернуть, если никакая информация не должна передаваться функции amvacuumcleanup.

Из-за ограничения maintenance_work_mem при необходимости удаления большого количества кортежей может потребоваться вызывать функцию ambulkdelete несколько раз. Аргумент stats является результатом предыдущего вызова для этого индекса (он равен NULL для первого вызова в рамках операции VACUUM). Это позволяет МД накапливать статистику по всей операции. Как правило, ambulkdelete модифицирует и возвращает ту же структуру, если переданный stats не равен NULL.

IndexBulkDeleteResult *
amvacuumcleanup (IndexVacuumInfo *info,
                 IndexBulkDeleteResult *stats);

Выполнить очистку после операции VACUUM (до этого могло быть ноль и более вызовов ambulkdelete). Эта функция не должна делать ничего, кроме возврата статистики по индексу, но может выполнить массовую очистку, например высвобождение пустых страниц индекса. Аргумент stats содержит результат последнего вызова функции ambulkdelete или NULL, если ambulkdelete не вызывалась, поскольку не нужно было удалять никакие кортежи. Если результат отличен от NULL, это должна быть структура, выделенная palloc. Содержащаяся в ней статистика будет использоваться для обновления каталога pg_class и выводиться командой VACUUM, если в той указано VERBOSE. NULL можно вернуть, если индекс вообще не изменялся во время операции VACUUM, однако в противном случае должна возвращаться корректная статистика.

Функция amvacuumcleanup также будет вызваться по завершении операции ANALYZE. В этом случае stats всегда равен NULL, а любое возвращаемое значение будет игнорироваться. Этот случай можно распознать проверкой info->analyze_only. Рекомендуется, чтобы при таком вызове метод доступа выполнял только очистку после добавления данных и только в рабочем процессе автоовакуума.

bool
amcanreturn (Relation indexRelation, int attno);

Проверить, поддерживает ли индекс сканирование только по индексу для данного столбца, возвращая исходное индексированное значение этого столбца. Нумерация атрибутов начинается с 1, т. е. attno для первого столбца должен быть равен 1. Возвращает true, если такое поведение поддерживается, и false в противном случае. Эта функция должна всегда возвращать true для неключевых столбцов (если таковые поддерживаются), поскольку в неключевом столбце, значение которого нельзя извлечь, мало смысла. Если метод доступа вообще не поддерживает сканирование только по индексу, то в поле amcanreturn в его структуре IndexAmRoutine можно установить NULL.

void
amcostestimate (PlannerInfo *root,
                IndexPath *path,
                double loop_count,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation,
                double *indexPages);

Оценить примерную стоимость сканирования индекса. Эта функция полностью описывается в разделе Функции оценки стоимости индекса ниже.

bytea *
amoptions (ArrayType *reloptions,
           bool validate);

Проанализировать и проверить массив параметров для индекса. Эта функция вызывается, только когда для индекса существует массив параметров, отличный от NULL. Аргумент reloptions — это массив типа text, содержащий записи вида имя=значение. Функция должна сформировать значение bytea, которое будет скопировано в поле rd_options записи индекса в кэше отношений. Содержимое bytea определяется самим методом доступа; большинство стандартных методов доступа используют структуру StdRdOptions. Когда аргумент validate равен true, функция должна выдавать соответствующее сообщение об ошибке, если какой-либо из параметров не распознается или имеет недопустимое значение; когда validate равен false, недопустимые значения должны молча игнорироваться. (Аргумент validate имеет значение false при загрузке параметров, уже сохраненных в pg_catalog; недопустимую запись можно найти, только если метод доступа изменил свои правила для параметров, и в этом случае игнорировать устаревшие записи уместно). NULL можно вернуть, если требуется поведение по умолчанию.

bool
amproperty (Oid index_oid, int attno,
            IndexAMProperty prop, const char *propname,
            bool *res, bool *isnull);

Метод amproperty позволяет методу доступа переопределить поведение по умолчанию pg_index_column_has_property и связанных функций. Если у метода доступа нет никакого специального поведения для запросов свойств индекса, то в поле amproperty структуры IndexAmRoutine можно установить NULL. В противном случае метод amproperty будет вызываться с нулевыми аргументами index_oid и attno при вызовах pg_indexam_has_property или с допустимым значением index_oid и attno больше нуля при вызовах pg_index_column_has_property. Аргумент prop является перечислимым значением, идентифицирующим проверяемое свойство, тогда как аргумент propname является исходной строкой имени свойства. Если код ядра не распознает имя свойства, то в prop передается AMPROP_UNKNOWN. Методы доступа могут определять нестандартные имена свойств, проверяя propname на совпадение (для согласованности с кодом ядра используйте для этого pg_strcasecmp); для имен, известных коду ядра, лучше проверить аргумент prop. Если метод amproperty возвращает true, значит он определил результат проверки свойства: он должен установить в *res возвращаемое логическое значение или установить в *isnull true, чтобы вернуть NULL. (Обе указанные переменные инициализируются перед вызовом в false.) Если метод amproperty возвращает false, то код ядра продолжает свою обычную логику для определения результата проверки свойства.

Методы доступа, поддерживающие операторы сортировки, должны реализовывать проверку свойства AMPROP_DISTANCE_ORDERABLE, так как код ядра не знает, как это сделать, и вернет NULL. Также стоит реализовать проверку AMPROP_RETURNABLE, если ее можно сделать гораздо дешевле, чем открывая индекс и вызывая amcanreturn, что является поведением кода ядра. Поведение по умолчанию должно быть удовлетворительным для всех остальных стандартных свойств.

char *
ambuildphasename (int64 phasenum);

Вернуть текстовое название фазы построения индекса с заданным номером. Номера фаз выводятся во время построения индекса через интерфейс функции pgstat_progress_update_param. Затем названия фаз отображаются в представлении pg_stat_progress_create_index.

bool
amvalidate (Oid opclassoid);

Проверить записи каталога для заданного класса операторов, насколько адекватно это может сделать метод доступа. Например, сюда может входить проверка на предмет того, предоставляются ли все необходимые вспомогательные функции. Функция amvalidate должна вернуть false, если класс операторов является недопустимым. О проблемах следует сообщать посредством сообщений ereport, обычно уровня INFO.

void
amadjustmembers (Oid opfamilyoid,
                 Oid opclassoid,
                 List *operators,
                 List *functions);

Проверить предложенные новые члены семейства операторов (операторы и функции), насколько адекватно это может сделать метод доступа, и установить типы их зависимостей, если значения по умолчанию неудовлетворительны. Эта функция вызывается во время выполнения команд CREATE OPERATOR CLASS и ALTER OPERATOR FAMILY ADD; в последнем случае аргумент opclassoid имеет значение InvalidOid. Аргументы List являются списками структур OpFamilyMember, как описано в amapi.h. Проверки, проводимые этой функцией, обычно являются подмножеством проверок, осуществляемых amvalidate, поскольку amadjustmembers не может предположить, что видит полный набор членов. Например, будет иметь смысл проверить сигнатуры вспомогательной функции, но не проверять, предоставляются ли все необходимые вспомогательные функции. О любых проблемах можно сообщить, выдав ошибку. Связанные с зависимостями поля структур OpFamilyMember инициализируются кодом ядра, создавая жесткие зависимости от класса операторов, если это CREATE OPERATOR CLASS, или мягкие зависимости от семейства оператором, если это ALTER OPERATOR FAMILY ADD. Функция amadjustmembers может скорректировать эти поля, если более уместно некое другое поведение. К примеру, GIN, GiST и SP-GiST всегда устанавливают для членов- операторов мягкую зависимость от семейства операторов, поскольку в этих типах индексов связь между оператором и классом операторов относительно слаба; поэтому имеет смысл разрешить свободное добавление и удаление членов-операторов. Необязательным вспомогательным функциям обычно тоже выдают мягкие зависимости, чтобы при необходимости их можно было удалить.

Цель индекса, конечно же, заключается в поддержке сканирования для кортежей, соответствующих индексируемому условию WHERE, часто называемому квалификатором или ключом сканирования. Более подробно семантика индексного сканирования описывается в разделе Сканирование индекса ниже. Индексный метод доступа может поддерживать «простые» индексные сканирования, индексные сканирования «по битовой карте» или оба этих вида. Индексный метод доступа должен или может предоставить следующие относящиеся к сканированию функции:

IndexScanDesc
ambeginscan (Relation indexRelation,
             int nkeys,
             int norderbys);

Подготовить метод к сканированию индекса. Параметры nkeys и norderbys указывают количество условий и операторов сортировки, которые будут использоваться в этом сканировании; это может быть полезно для выделения памяти. Обратите внимание, что фактические значения ключей сканирования еще не предоставляются. Результатом функции должна быть структура, выделенная с помощью palloc. В связи с особенностями реализации, индексный метод доступа должен создать эту структуру путем вызова функции RelationGetIndexScan(). В большинстве случаев все действия ambeginscan сводятся к данному вызову и, возможно, получению блокировок; интересные этапы запуска сканирования индекса происходят в функции amrescan.

void
amrescan (IndexScanDesc scan,
          ScanKey keys,
          int nkeys,
          ScanKey orderbys,
          int norderbys);

Запустить или перезапустить сканирование индекса, возможно, с новыми ключами сканирования. (Для перезапуска с использованием ранее переданных ключей в аргументах keys и/или orderbys передается NULL.) Обратите внимание, что количество ключей или операторов сортировки не может быть больше значений, переданных в функцию ambeginscan. На практике возможность перезапуска используется, когда в соединении с вложенным циклом выбирается новый внешний кортеж, и поэтому требуется новое значение сравнения с ключом, но структура ключа сканирования остается прежней.

boolean
amgettuple (IndexScanDesc scan,
            ScanDirection direction);

Извлечь следующую запись в заданном сканировании, двигаясь по индексу в указанном направлении (вперед или назад). Возвращает true, если кортеж получен, false, если подходящих кортежей не осталось. В случае успеха (true) TID этого кортежа сохраняется в структуре scan. Обратите внимание, что «успех» означает только то, что индекс содержит запись, соответствующую ключам сканирования, а не то, что кортеж обязательно все еще существует в таблице или пройдет проверку на видимость в снимке вызывающего кода. При успехе функция amgettuple должна также установить в scan->xs_recheck значение true или false. False означает, что запись индекса точно соответствует ключам сканирования. True означает, что это не точно, и условия, представленные ключами сканирования, следует перепроверить для кортежа кучи после его извлечения. Эта мера предосторожности поддерживает операторы индексов «c потерями». Обратите внимание, что перепроверка будет распространяться только на условия сканирования; предикат частичных индексов (если таковой имеется) никогда не перепроверяется кодом, вызывающим amgettuple.

Если индекс поддерживает сканирование только по индексу (т. е. amcanreturn возвращает true для любого из его столбцов), то в случае успешного сканирования МД также должен проверить значение scan->xs_want_itup, и если оно равно true, то МД обязан вернуть исходные индексированные данные для этой записи индекса. В столбцах, для которых amcanreturn возвращает false, можно вернуть NULL. Данные могут быть возвращены в форме указателя IndexTuple, сохраненного в scan->xs_itup, с дескриптором кортежа scan->xs_itupdesc, или в форме указателя HeapTuple, сохраненного в scan->xs_hitup, с дескриптором кортежа scan->xs_hitupdesc. (Последний формат следует использовать при восстановлении данных, которые могут не уместиться в IndexTuple). В любом случае управление данными, на которые ссылается указатель, является ответственностью метода доступа. Данные должны оставаться пригодными по крайней мере до следующего вызова amgettuple, amrescan, или amendscan для сканирования.

Функцию amgettuple нужно предоставлять, только если метод доступа поддерживает «простые» индексные сканирования. Если это не так, то в поле amgettuple его структуры IndexAmRoutine необходимо установить NULL.

int64
amgetbitmap (IndexScanDesc scan,
             TIDBitmap *tbm);

Извлечь все кортежи в данном сканировании и добавить их в переданную вызывающим кодом структуру TIDBitmap (то есть объединяет операцией OR множество идентификаторов кортежей со множеством, уже находящимся в битовой карте). Возвращается количество извлеченных кортежей (это может быть только примерное число; например, некоторые МД не распознают дубликаты). При добавлении идентификаторов кортежей в битовую карту amgettbitmap может пометить, что для некоторых кортежей нужно перепроверить условия сканирования. Это аналогично выходному параметру xs_recheck функции amgettuple. Примечание: в текущей реализации поддержка этой возможности объединена с поддержкой хранения с потерями самой битовой карты, поэтому вызывающая функция перепроверяет и условия сканирования, и предикат частичного индекса (если таковой имеется) для кортежей, помеченных для повторной проверки. Однако так может быть не всегда. Функции amgettbitmap и amgettuple нельзя использовать в одном сканировании индекса; при использовании amgettbitmap есть и другие ограничения, описанные в разделе Сканирование индекса.

Функцию amgettbitmap нужно предоставлять, только если метод доступа поддерживает сканирование индекса «по битовой карте». Если это не так, то в поле amgettbitmap его структуры IndexAmRoutine необходимо установить NULL.

void
amendscan (IndexScanDesc scan);

Завершить сканирование и освободить ресурсы. Саму структуру scan освобождать не следует, но любые блокировки или закрепления, полученные внутри метода доступа, должны быть освобождены, равно как и любая другая память, выделенная ambeginscan и другими функциями, относящимися к сканированию.

void
ammarkpos (IndexScanDesc scan);

Пометить текущую позицию сканирования. Метод доступа должен поддерживать запоминание только одной позиции на сканирование.

Функцию ammarkpos нужно предоставлять, только если метод доступа поддерживает упорядоченное сканирование. Если это не так, то в поле ammarkpos его структуры IndexAmRoutine можно установить NULL.

void
amrestrpos (IndexScanDesc scan);

Восстановить сканирование до самой последней отмеченной позиции.

Функцию amrestrpos нужно предоставлять, только если метод доступа поддерживает упорядоченное сканирование. Если это не так, то в поле amrestrpos его структуры IndexAmRoutine можно установить NULL.

В дополнение к поддержке обычных индексных сканирований, некоторые типы индексов могут поддерживать параллельное сканирование индекса, позволяющее нескольким обслуживающим процессам совместно выполнять сканирования индекса. Индексный метод доступа должен организовать все так, чтобы каждый участвующий процесс возвращал подмножество кортежей, полученных обычным, непараллельным сканированием индекса, но таким образом, чтобы объединение этих подмножеств равнялось множеству кортежей, которые были бы возвращены при обычном, непараллельном сканировании индекса. Кроме того, хотя никакого глобального упорядочения кортежей, возвращаемых параллельным сканированием, не требуется, порядок внутри этого подмножества кортежей, возвращаемого в каждом участвующем процессе, должен соответствовать запрошенному порядку. Для поддержки параллельного сканирования индекса можно реализовать следующие функции:

Size
amestimateparallelscan (void);

Предварительно рассчитать и вернуть количество байтов динамической разделяемой памяти, которое потребуется методу доступа для выполнения параллельного сканирования. (Это количество дополняет, а не заменяет объем памяти, требуемый для независимых от МД данных в структуре ParallelIndexScanDescData.)

Нет необходимости реализовывать эту функцию для методов доступа, которые не поддерживают параллельное сканирование или для которых не требуются дополнительные байты памяти.

void
aminitparallelscan (void *target);

Эта функция будет вызываться для инициализации динамической разделяемой памяти в начале параллельного сканирования. Аргумент target будет указывать на количество байтов как минимум не меньше возвращенного ранее функцией amestimateparallelscan, и эта функция может использовать этот объем памяти для хранения любых данных, какие пожелает.

Нет необходимости реализовывать эту функцию для методов доступа, не поддерживающих параллельное сканирование, или в тех случаях, когда требуемая разделяемая память не требует инициализации.

void
amparallelrescan (IndexScanDesc scan);

Эта функция, если она реализована, будет вызываться при необходимости перезапуска параллельного сканирования индекса. Она должна сбросить все разделяемое состояние, установленное с помощью aminitparallelscan, чтобы это сканирование перезапустилось с начала.

Сканирование индекса

В индексном сканировании индексный метод доступа отвечает за возврат TID всех кортежей, которые по его сведениям соответствуют ключам сканирования. Метод доступа не участвует в фактической выборке этих кортежей из родительской таблицы индекса, равно как и в определении того, успешно ли они проходят проверку видимости сканирования или другие условия.

Ключ сканирования — это внутреннее представление предложения WHERE в форме ключ_индекса оператор константа, где ключ индекса является одним из столбцов индекса, а оператор — одним из членов семейства операторов, связанных с этим столбцом. Сканирование индекса имеет ноль или более ключей сканирования, неявно объединенных операцией AND — ожидается, что возвращенные кортежи будут удовлетворять всем указанным условиям.

Метод доступа для конкретного запроса может сообщить, что это индекс с потерями, т. е. требует перепроверок. Это подразумевает, что сканирование индекса вернет все записи, удовлетворяющие ключу сканирования, и, возможно, дополнительные записи, которые ему не удовлетворяют. Затем механизм основной системы, отвечающий за сканирование индекса, снова применит условия индекса к кортежу кучи, чтобы проверить, действительно ли его нужно выбрать. Если параметр перепроверки не задан, сканирование индекса должно возвращать множество записей, точно соответствующих ключу.

Обратите внимание, что именно метод доступа должен гарантировать, что он правильно находит все и только те записи, которые удовлетворяют всем заданным ключам сканирования. Кроме того, основная система просто передаст все предложения WHERE, соответствующие ключам индекса и семействам операторов, без какого-либо семантического анализа на избыточность или противоречивость. Например, при заданном условии WHERE x > 4 AND x > 14, где x является индексированным столбцом B-дерева, именно самой функции amrescan B-дерева нужно будет понять, что первый ключ сканирования избыточен, и его можно отбросить. Объем предварительной обработки, требуемой во время выполнения amrescan, будет зависеть от степени, до которой индексному методу доступа нужно сводить ключи сканирования к «нормализованной» форме.

Некоторые методы доступа возвращают записи индекса в строго определенном порядке, другие — нет. На самом деле существует два различных способа, которыми метод доступа может поддерживать упорядоченный вывод:

Методы доступа, всегда возвращающие записи в естественном порядке их данных как, например, в B-дереве), должны установить в amcanorder значение true. В настоящее время такие методы доступа должны использовать для своих операторов равенства и сортировки номера стратегий, совместимых с B-деревьями.

Методы доступа, поддерживающие операторы сортировки, должны установить в amcanorderbyop значение true. Это указывает на то, что индекс способен возвращать записи в порядке, удовлетворяющем предложению ORDER BY ключ_индекса оператор константа. Модификаторы сканирования этой формы могут передаваться в функцию amrescan, как описывалось ранее.

У функции amgettuple имеется аргумент direction, который может принимать значение ForwardScanDirection (обычный вариант) или BackwardScanDirection. Если в первом вызове после amrescan задается BackwardScanDirection, то множество подходящих записей индекса будет сканироваться от конца к началу, а не в обычном направлении от начала к концу, поэтому функция amgettuple должна вернуть последний соответствующий кортеж в индексе, а не первый, как это обычно бывает. (Это будет происходить только для методов доступа, установивших флаг amcanorder в true.) После первого вызова amgettuple должна быть готова продолжить сканирование в любом направлении от последней возвращенной записи. (Но если флаг amcanbackward равен false, все последующие вызовы будут иметь то же направление, что и первый.)

Методы доступа, поддерживающие упорядоченное сканирование, должны поддерживать «маркировку» позиции сканирования и последующий возврат к помеченной позиции. В одну позицию можно вернуться несколько раз. Однако запоминать нужно только одну позицию на сканирование; новый вызов функции ammarkpos переопределяет позицию, помеченную ранее. Методам доступа, не поддерживающим упорядоченное сканирование, необязательно реализовывать функции ammarkpos и amrestrpos в IndexAmRoutine; вместо этого можно установить в этих указателях NULL.

Как позиция сканирования, так и помеченная позиция (если таковая имеется) должны оставаться в согласованном состоянии, несмотря на параллельные добавления или удаления из индекса. Сканирование может не возвращать только что добавленную запись, которое оно бы обнаружило, если бы та существовала на момент начала сканирования, или вернуть такую запись при повторном сканировании или возврате, даже если она не была возвращена в первый раз. Аналогично параллельное удаление может отражаться или не отражаться в результатах сканирования. Важно, чтобы добавления или удаления не приводили к пропуску или к дублированию возвращаемых записей, которые не участвовали в этих операциях.

Если индекс хранит исходные значения индексированных данных (а не некоторое их представление с потерями), полезно поддерживать сканирование только по индексу, в котором индекс возвращает фактические данные, а не только TID кортежа кучи. Это позволит избежать нагрузки ввода/вывода только в том случае, если карта видимости показывает, что TID находится на полностью видимой странице; иначе кортеж кучи все равно придется прочитать, чтобы проверить видимость для MVCC. Но метода доступа это уже не касается.

Вместо amgettuple сканирование индекса можно выполнить с помощью функции amgettbitmap, чтобы извлечь все кортежи за один вызов. Это может быть гораздо эффективнее amgettuple, потому что позволяет избежать циклов блокировки/ разблокировки в методе доступа. В принципе, вызов amgettbitmap должен приводить к тому же результату, что и несколько вызовов amgettuple, но чтобы упростить дело, мы накладываем несколько ограничений. Во-первых, amgettbitmap возвращает все кортежи сразу, и маркировка или восстановление позиции сканирования не поддерживается. Во-вторых, кортежи возвращаются в виде битовой карты, в которой нет конкретного упорядочивания, поэтому amgettbitmap не принимает аргумент direction. (Операторы упорядочивания также никогда не будут предоставлены для такого сканирования.) Кроме того, amgettbitmap не может сканировать только по индексу, поскольку нет никакого способа вернуть содержимое индексных кортежей. В-третьих, amgettbitmap не гарантирует никакой блокировки для возвращаемых кортежей, и последствия этого описываются в разделе Особенности блокировки индексов.

Обратите внимание, что методу доступа разрешается реализовывать только amgettbitmap, но не amgettuple, и наоборот, если его внутренняя реализация не совместима с какой-то из этих API.

Особенности блокировки индексов

Индексные методы доступа должны обрабатывать параллельные изменения индекса несколькими процессами. Ядро системы QHB получает блокировку AccessShareLock на индекс во время его сканирования и RowExclusiveLock при изменении индекса (включая обычную операцию VACUUM). Поскольку эти типы блокировок не конфликтуют, метод доступа отвечает за обработку любых мелкомодульных блокировок, которые могут ему понадобиться. Блокировка целого индекса в режиме ACCESS EXCLUSIVE будет устанавливаться только во время его создания, уничтожения или операции REINDEX (вместо этого устанавливается SHARE UPDATE EXCLUSIVE вместе с CONCURRENTLY).

Построения типа индекса, поддерживающего параллельные изменения, обычно требует тщательного и подробного анализа требуемого поведения.

Помимо собственных внутренних требований к согласованности индекса, параллельные изменения создают проблемы согласованности между родительской таблицей (кучей) и индексом. Поскольку QHB отделяет чтение и изменения кучи от чтения и изменения индекса, существуют временные промежутки, в которых индекс может быть несогласованным с таблицей. Эта проблема решается с помощью следующих правил:

Новая запись в куче создается перед созданием для нее записи в индексе. (Поэтому параллельное сканирование индекса, скорее всего, не сможет увидеть запись кучи. Это нормально, поскольку читатель индекса в любом случае не заинтересован в получении незафиксированной строки. Но см. раздел Проверки уникальности индекса.)

Когда запись кучи удаляется (командой VACUUM), сначала должны быть удалены все ее записи в индексах.

Сканирование индекса должно закрепить страницу индекса, содержащую элемент, возвращенный последним вызовом функции amgettuple, а функция ambulkdelete не должна удалять записи со страниц, закрепленных другими обслуживающими процессами. Необходимость этого правила объясняется ниже.

Без третьего правила читатель индекса может увидеть запись индекса непосредственно перед ее удалением VACUUM, а затем перейти к соответствующей записи кучи после того, как ее удалит VACUUM. Это не создает никаких серьезных проблем, если этот номер элемента еще не используется, когда читатель к нему переходит, поскольку пустой слот элемента будет игнорироваться функцией heap_fetch(). Но что делать, если третий обслуживающий процесс уже повторно использовал этот слот элемента для чего-то другого? При использовании снимка, совместимого с MVCC, это не проблема, потому что новое содержимое слота наверняка будет слишком новым, чтобы пройти тест видимости для этого снимка. Однако со снимком, не совместимым с MVCC (например SnapshotAny), возможна ситуация, когда будет принята и возвращена строка, в действительности не соответствующая ключам сканирования. От подобного сценария можно защититься, всегда требуя перепроверки ключей сканирования для строки кучи, но это слишком затратно. Вместо этого используется закрепление страницы индекса в качестве посредника, указывающего, что читатель все еще может быть «в пути» от записи индекса к соответствующей записи кучи. Блокировка функции ambulkdelete на таких закрепленных страницах гарантирует, что VACUUM не сможет удалить запись кучи раньше, чем читатель закончит работать с ней. Это решение не требует больших затрат времени выполнения и добавляет издержки на блокировку только в редких случаях, когда на самом деле существует конфликт.

Такое решение требует, чтобы сканирования индексов были «синхронными»: нам нужно извлекать каждый кортеж кучи сразу после сканирования соответствующей записи индекса. Это дорого по ряду причин. «Асинхронное» сканирование, при котором мы собираем из индекса много TID, a к кортежам кучи обращаемся только через некоторое время после этого, требует гораздо меньше издержек на блокировки и позволяет реализовать более эффективный сценарий обращения к куче. В соответствии с приведенным выше анализом, синхронный подход необходимо использовать для снимков, несовместимых с MVCC, а асинхронное сканирование применимо для запросов, использующих снимок MVCC.

При сканировании индекса функцией amgetbitmap метод доступа не закрепляет страницы индекса ни для каких возвращаемых кортежей. Поэтому такие сканирования можно безопасно использовать только со снимками, совместимыми с MVCC.

Когда флаг ampredlocks не установлен, любое сканирование, использующее этот индексный метод доступа в сериализуемой транзакции, получит неблокирующую предикатную блокировку для всего индекса. Это приведет к возникновению конфликта чтения-записи при добавлении любого кортежа в этот индекс в параллельной сериализуемой транзакции. Если среди набора параллельных сериализуемых транзакций обнаруживаются определенные типы конфликтов чтения-записи, одна из этих транзакций может быть отменена для защиты целостности данных. Когда флаг установлен, это указывает, что индексный метод доступа реализует более мелкомодульную предикатную блокировку, которая будет стремиться снизить частоту подобных отмен транзакций.

Проверки уникальности индекса

QHB реализует ограничения уникальности SQL, используя уникальные индексы, то есть индексы, не допускающие нескольких записей с одинаковыми ключами. Метод доступа, поддерживающий эту особенность, устанавливает флаг amcanunique в true. (В настоящее время его поддерживает только B-дерево.) Столбцы, перечисленные в предложении INCLUDE, при обеспечении уникальности не учитываются.

Из-за MVCC необходимо всегда разрешать физическое существование в индексе повторяющихся записей: эти записи могут ссылаться на последовательные версии одной логической строки. Поведение, которого мы хотим добиться на самом деле, состоит в том, что ни один снимок MVCC не может включать две строки с одинаковыми ключами индекса. Это разбивается на следующие ситуации, которые необходимо проверить при добавлении в уникальный индекс новой строки:

Если конфликтующая допустимая строка была удалена текущей транзакцией, это нормально. (В частности, поскольку UPDATE всегда удаляет старую версию строки перед добавлением новой, это позволит выполнить для строки операцию UPDATE без изменения ключа.)

Если конфликтующая строка была добавлена пока еще не зафиксированной транзакцией, необходимо дождаться фиксации этой транзакции. Если она откатывается назад, то конфликта не будет. Если же она фиксируется без повторного удаления конфликтующей строки, возникает нарушение уникальности. (На практике мы просто дожидаемся завершения другой транзакции, а затем повторяем полную проверку видимости.)

Аналогичным образом, если конфликтующая допустимая строка была удалена пока еще не зафиксированной транзакцией, необходимо дождаться фиксации или прерывания этой транзакции, а затем повторить проверку.

Более того, непосредственно перед сообщением о нарушении уникальности согласно вышеуказанным правилам метод доступа должен повторно проверить работоспособность добавляемой строки. Если она признана неиспользуемой, то ни о каком нарушении сообщать не нужно. (Эта ситуация не может возникнуть во время обычного сценария добавления строки, только что созданной текущей транзакцией. Однако это может произойти при выполнении CREATE UNIQUE INDEX CONCURRENTLY.)

Индексный метод доступа обязан выполнять данные проверки сам, а это означает, что он должен обратиться к куче и проверить состояние фиксации всех строк, которые согласно содержимому индекса имеет повторяющийся ключ. Это, без сомнения, некрасиво и не модульно, зато позволяет избежать избыточной работы: если бы мы делали отдельную пробу, то поиск конфликтующей строки в индексе фактически повторялся в процессе поиска места для добавления новой записи индекса для новой строки. Более того, нет очевидного способа избежать условий гонки, если проверка конфликта не является неотъемлемой частью добавления новой записи индекса.

Если ограничение уникальности является откладываемым, возникает дополнительная сложность: нужно иметь возможность добавлять запись индекса для новой строки, но откладывать любую ошибку нарушения уникальности до конца оператора или даже позже. Чтобы избежать ненужных повторных поисков индекса, индексный метод доступа должен выполнить предварительную проверку уникальности во время изначального добавления строки. Если проверка показывает, что конфликтующего активного кортежа нет, дальнейшие проверки не проводятся. В противном случае повторная проверка планируется на время, когда придет время применить ограничение. Если во время повторной проверки и добавленный кортеж, и какой-то другой кортеж с тем же ключом являются активными, то необходимо сообщить об ошибке. (Обратите внимание, что в данном случае «активный» фактически означает «все кортежи в цепочке HOT записи индекса активны».) Чтобы реализовать это, в функцию aminsert передается параметр checkUnique, имеющий одно из следующих значений:

UNIQUE_CHECK_NO указывает, что проверка уникальности не должна выполняться (это не уникальный индекс).

UNIQUE_CHECK_YES указывает, что это неоткладываемый уникальный индекс, и проверка уникальности должна быть выполнена немедленно, как описано выше.

UNIQUE_CHECK_PARTIAL указывает, что ограничение уникальности является откладываемым. QHB будет использовать этот режим для добавления каждой строки в индекс. Метод доступа должен допускать повторяющиеся записи в индексе и сообщать о любых потенциальных дубликатах, возвращая false из aminsert. Для каждой строки, для которой возвращается false, будет запланирована отложенная повторная проверка.
Метод доступа должен выявить все строки, которые могут нарушить ограничение уникальности, но ложноположительные результаты не являются ошибкой. Это позволяет выполнить проверку, не дожидаясь завершения других транзакций; конфликты, выявленные на данном этапе, не рассматриваются как ошибки и будут перепроверены позже, когда они могут уже исчезнуть.

UNIQUE_CHECK_EXISTING указывает, что это отложенная повторная проверка строки, отмеченной как потенциально нарушающая ограничение уникальности. Хотя это реализуется путем вызова aminsert, в этом случае метод доступа не должен добавлять новую запись индекса, поскольку она уже имеется. Вместо этого метод доступа должен проверить, есть ли другая активная запись индекса. Если это так и если целевая строка тоже еще активна, сообщить об ошибке.
Рекомендуется, чтобы при вызове с UNIQUE_CHECK_EXISTING метод доступа дополнительно убеждался, что для целевой строки действительно имеется существующая запись в индексе, и выдавал ошибку, если ее нет. Это хорошая идея, потому что значения кортежа индекса, переданные в aminsert, будут пересчитаны. Если определение индекса включает в себя функции, которые на самом деле не являются постоянными, мы можем проверять неправильную область индекса. Проверка того, что целевая строка обнаруживается при повторной проверке, подтверждает, что сканируются те же значения кортежа, что использовались при изначальном добавлении строки.

Функции оценки стоимости индекса

Функция amcostestimate предоставляет информацию, описывающую возможное сканирование индекса, включая предложения WHERE и ORDER BY, которые были определены как применимые с индексом. Она должна возвращать оценку стоимости обращения к индексу и избирательность предложений WHERE (то есть доли строк родительской таблицы, которые будут получены во время сканирования индекса). Для простых случаев почти всю работа оценщика стоимости можно выполнить путем вызова стандартных подпрограмм оптимизатора; смысл наличия функции amcostestimate заключается в том, чтобы позволить индексному методу доступа предоставлять знания, специфичные для типа индекса, если это может улучшить стандартные оценки.

Каждая функция amcostestimate должна иметь следующую сигнатуру:

void
amcostestimate (PlannerInfo *root,
                IndexPath *path,
                double loop_count,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation,
                double *indexPages);

Первые три параметра являются входными:

root
Информация планировщика об обрабатываемом запросе.

path
Рассматриваемый путь доступа к индексу. Все поля, кроме значений стоимости и избирательности, являются действительными.

loop_count
Количество повторений сканирования индекса, которое должно быть учтено в оценке стоимости. Как правило, это значение бывает больше единицы при рассмотрении параметризованного сканирования, планируемого к использованию при соединении с вложенным циклом. Обратите внимание, что оценка стоимости все равно должна рассчитываться только для одного сканирования; большое значение loop_count означает, что при многократных сканированиях может быть целесообразно применить кэширование.

Последние пять параметров являются указателями на выходные данные:

*indexStartupCost
Стоимость обработки запуска индекса

*indexTotalCost
Общая стоимость обработки индекса

*indexSelectivity
Индекс избирательности

*indexCorrelation
Коэффициент корреляции между порядком сканирования индекса и порядком записей в нижележащей таблицы

*indexPages
Количество листовых страниц индекса

Обратите внимание, что функции оценки затрат необходимо писать на C, а не на SQL или любом другом доступном процедурном языке, поскольку они должны обращаться к внутренним структурам данных планировщика/оптимизатора.

Стоимость обращения к индексу следует рассчитывать с помощью следующих параметров:

последовательная выборка дисковых блоков имеет стоимость seq_page_cost, произвольная выборка — random_page_cost, а стоимость обработки одной строки индекса обычно принимается как cpu_index_tuple_cost. Кроме того, для всех операторов сравнения, вызываемых во время обработки индекса (особенно за вычисление самих условий индекса), следует взимать цену в виде соответствующего количества значений cpu_operator_cost.

Стоимость обращения должна включать все дисковые и процессорные затраты, связанные со сканированием самого индекса, но не затраты на получение или обработку строк родительской таблицы, идентифицированной индексом.

«Стоимость запуска» является частью общей стоимости сканирования, которая должна быть израсходована до начала извлечения первой строки. Для большинства индексов она может считаться нулевой, но для типа индекса с высокой стоимостью запуска это значение может быть ненулевым.

В indexSelectivity следует установить значение предполагаемой доли строк родительской таблицы, которая будет получена при сканировании индекса. В случае запроса с потерями это значение, как правило, бывает выше, чем доля строк, которые фактически удовлетворяют заданным условиям соответствия.

В indexCorrelation следует установить значение корреляции (в диапазоне от -1.0 до 1.0) между порядком записей в индексе и таблице. Это значение используется для корректировки оценки стоимости извлечения строк из родительской таблицы.

В indexPages следует установить количество листовых страниц. Это значение используется для оценки количества рабочих процессов для параллельного сканирования индекса.

Когда loop_count больше единицы, возвращаемые числа должны быть средними значениями, ожидаемыми для любого одного сканирования индекса.

Оценка стоимости

Типичный оценщик стоимости будет действовать следующим образом:

Оценить и вернуть долю строк родительской таблицы, которые будут посещены на основе заданных условий соответствия. При отсутствии каких-либо специфических знаний для данного типа индекса следует использовать стандартную функцию оптимизатора clauselist_selectivity():

*indexSelectivity = clauselist_selectivity(root, path->indexquals,
                                           path->indexinfo->rel->relid,
                                           JOIN_INNER, NULL);

Оценить количество строк индекса, которые будут посещены во время сканирования. Для многих типов индексов это то же самое, что значение indexSelectivity, умноженное на количество строк в индексе, но оно может быть и больше. (Обратите внимание, что размер индекса в страницах и строках можно посмотреть в структуре path->indexinfo.)

Оценить количество страниц индекса, которые будут получены во время сканирования. Это может быть просто значение indexSelectivity, умноженное на размер индекса в страницах.

Вычислить стоимость обращения к индексу. Универсальный оценщик может сделать это так:

/*
 * Обычно предполагается, что страницы индекса будут считываться последовательно,
 * поэтому их стоимость будет seq_page_cost, а не random_page_cost за страницу.
 * Также мы назначаем цену за вычисление условий индекса для каждой его строки.
 * Предполагается, что все стоимости пропорционально увеличиваются при сканировании.
 */
cost_qual_eval(&index_qual_cost, path->indexquals, root);
*indexStartupCost = index_qual_cost.startup;
*indexTotalCost = seq_page_cost * numIndexPages +
    (cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;

Однако все вышеизложенное не учитывает амортизацию чтений индекса при повторном его сканировании.

Оценить коэффициент корреляции. Для простого упорядоченного индекса по одному полю это значение можно получить из pg_statistic. Если корреляция неизвестна, то консервативная оценка равна нулю (корреляция отсутствует).

Типовые записи WAL

Хотя все встроенные модули, протоколирующие в WAL, поддерживают собственные типы записей WAL, существует также типовой вид записи WAL, описывающий изменения страниц типовым образом. Это полезно для расширений, предоставляющих нестандартные методы доступа, поскольку они не могут зарегистрировать свои подпрограммы воспроизведения WAL.

По сравнению с пользовательскими менеджерами ресурсов WAL типовые записи WAL проще реализовать в расширении, и для применения этих записей не требуется загружать библиотеку расширения.

Примечание
Типовые записи WAL игнорируются во время логического декодирования. Если для вашего расширения требуется логическое декодирование, рассмотрите возможность использования пользовательского менеджера ресурсов WAL.

API для построения типовых записей WAL определяется в access/generic_xlog.h и реализуется в access/transam/generic_xlog.c.

Чтобы выполнить изменение данных, протоколируемых в WAL с помощью типового средства записи WAL, выполните следующие действия:

state = GenericXLogStart(relation) — начать построение типовой записи WAL для данного отношения.

page = GenericXLogRegisterBuffer(state, buffer, flags) — зарегистрировать буфер, который будет изменен в текущей типовой записи WAL. Эта функция возвращает указатель на временную копию страницы буфера, где должны быть сделаны изменения. (Не изменяйте содержимое буфера напрямую). Третий аргумент — это битовая маска флагов, применимая к данной операции. В настоящее время единственным таким флагом является GENERIC_XLOG_FULL_IMAGE, означающий, что в запись WAL нужно включить не конкретные изменения, а образ полной страницы. Обычно этот флаг устанавливается, если страница новая или была полностью переписана. GenericXLogRegisterBuffer можно повторить, если при помощи протоколируемого в WAL действия необходимо изменить несколько страниц.

Применить изменения к образам страниц, полученным на предыдущем этапе.

GenericXLogFinish(state) — применить изменения к буферам и выдать типовую запись WAL.

Построение записи WAL можно отменить на любом из вышеперечисленных этапов путем вызова GenericXLogAbort(state). Это приведет к отмене всех изменений в копиях образа страницы.

Пожалуйста, при использовании типового средства записи WAL обратите внимание на следующие моменты:

Никакие прямые модификации буферов не допускаются! Все изменения следует делать в копиях, полученных из GenericXLogRegisterBuffer(). Другими словами, код, создающий типовые записи WAL, никогда не должен сам вызывать BufferGetPage(). Тем не менее вызывающая функция отвечает за закрепление/открепление и блокировку/ разблокировку буферов в нужный момент. Эксклюзивная блокировка должна удерживаться на каждом целевом буфере от вызова GenericXLogRegisterBuffer() до завершения вызова GenericXLogFinish().

Регистрацию буферов (этап 2) и модификацию образов страниц (этап 3) можно свободно сочетать, т. е. оба этапа можно повторять в любой последовательности. Имейте в виду, что буферы должны регистрироваться в том же порядке, в каком для них должны быть получены блокировки во время воспроизведения.

Максимальное количество буферов, которые можно зарегистрировать для типовой записи WAL, равно значению MAX_GENERIC_XLOG_PAGES. При превышении этого предела будет выдана ошибка.

Типовой WAL предполагает, что изменяемые страницы имеют стандартную компоновку и, в частности, что между pd_lower и pd_upper нет полезных данных.

Так как изменяются копии буферных страниц, GenericXLogStart() не запускает критическую секцию, поэтому можно безопасно выделять память, выдавать ошибки и т. д. между GenericXLogStart() и GenericXLogFinish(). Единственная критическая секция присутствует внутри GenericXLogFinish(). Также во время выхода из-за ошибки не нужно беспокоиться о вызове GenericXLogAbort().

GenericXLogFinish() заботится о маркировке грязных буферов и установке их LSN. Вам не нужно делать это явно.

Для нежурналируемых отношений все работает так же, за исключением того, что фактическая запись WAL не выдается. Поэтому обычно нет необходимости выполнять явные проверки нежурналируемых отношений.

Функция воспроизведения типового WAL будет получать эксклюзивные блокировки для буферов в том же порядке, в котором они были зарегистрированы. После воспроизведения всех изменений блокировки будут освобождаться в том же порядке.

Если для зарегистрированного буфера не задается GENERIC_XLOG_FULL_IMAGE, типовая запись WAL содержит разницу между старым и новым образами страниц. Эта разница основывается на побайтовом сравнении. Результат будет не очень компактным в случае перемещения данных в пределах страницы, однако это может быть улучшено в будущем.

Пользовательские менеджеры ресурсов WAL

В этой граве рассматривается интерфейс между ядром системы QHB и пользовательскими менеджерами ресурсов WAL, позволяющий расширениям напрямую интегрировать свои объекты в WAL.

Расширению, особенно реализующему табличные методы доступа или индексные методы доступа, может понадобиться использовать WAL для восстановления, репликации и/или логического декодирования. Пользовательские менеджеры ресурсов представляют собой более гибкой альтернативу типовым записям WAL (которые не поддерживают логическое декодирование), но реализовать их в расширении сложнее.

Чтобы создать новый пользовательский менеджер ресурсов WAL, сначала определите структуру RmgrData с реализациями методов менеджера ресурсов.

/*
 * Таблица методов для менеджеров ресурсов.
 *
 * Эта структура должна быть постоянно синхронизирована с определением PG_RMGR в
 * файле rmgr.c.
 *
 * Метод rm_identify должен вернуть имя записи, исходя из xl_info (без ссылки на
 * rmid). Например, XLOG_BTREE_VACUUM мог бы быть назван "VACUUM". Затем может быть
 * вызван метод rm_desc для получения дополнительной информации о записи, если
 * таковая имеется (например, последний блок).
 *
 * Метод rm_mask принимает на вход страницу, модифицированную менеджером ресурсов, и
 * маскирует биты, которые не должны помечаться при проверке wal_consistency_checking.
 *
 * RmgrTable[] индексируется значениями RmgrId (см. rmgrlist.h). Если rm_name имеет
 * значение NULL, соответствующая запись RmgrTable считается недопустимой.
 */
typedef struct RmgrData
{
    const char *rm_name;
    void        (*rm_redo) (XLogReaderState *record);
    void        (*rm_desc) (StringInfo buf, XLogReaderState *record);
    const char *(*rm_identify) (uint8 info);
    void        (*rm_startup) (void);
    void        (*rm_cleanup) (void);
    void        (*rm_mask) (char *pagedata, BlockNumber blkno);
    void        (*rm_decode) (struct LogicalDecodingContext *ctx,
                              struct XLogRecordBuffer *buf);
} RmgrData;

Затем зарегистрируйте свой новый менеджер ресурсов.

/*
 * Регистрация нового пользовательского менеджера ресурсов WAL.
 *
 * Идентификаторы менеджеров ресурсов должны быть глобально уникальными среди всех
 * расширений. Зарезервируйте уникальный RmgrId для вашего расширения на странице
 * https://wiki.postgresql.org/wiki/CustomWALResourceManagers, чтобы избежать
 * конфликтов с другими разработчиками расширений. Во время разработки пользуйтесь
 * RM_EXPERIMENTAL_ID, чтобы не резервировать новые идентификаторы без необходимости.
 */
extern void RegisterCustomRmgr(RmgrId rmid, RmgrData *rmgr);

Функцию RegisterCustomRmgr следует вызывать из функции _PG_init модуля расширения. Во время разработки нового расширения в качестве rmid используйте RM_EXPERIMENTAL_ID. Когда будете готовы представить расширение пользователям, зарезервируйте новый идентификатор менеджера ресурсов на странице Custom WAL Resource Manager (Пользовательский менеджер ресурсов WAL).

Добавьте модуль расширения, реализующий пользовательский менеджер ресурсов, в список параметра shared_preload_libraries чтобы он загружался сразу при запуске QHB.

Примечание
Расширение должно оставаться в shared_preload_libraries до тех пор, пока в системе могут существовать какие-либо пользовательские записи WAL. В противном случае QHB не сможет применять или декодировать пользовательские записи WAL, из-за чего сервер может не запуститься.

Физическое хранение базы данных

В этой главе представлен обзор формата физического хранения, используемого базами данных QHB.

Структура файлов базы данных

В этом разделе описывается формат хранения на уровне файлов и каталогов.

Как правило, файлы конфигурации и данных, используемые кластером баз данных, хранятся вместе в каталоге данных кластера, обычно называемом PGDATA (по имени переменной среды, которую можно использовать для его определения). Обычно PGDATA располагается в /var/lib/qhb/data. На одной машине могут существовать несколько кластеров, управляемых различными экземплярами сервера.

Каталог PGDATA содержит несколько подкаталогов и управляющих файлов, как показано в Таблице 1. В дополнение к этим необходимым элементам в PGDATA традиционно хранятся файлы конфигурации кластера qhb.conf, qhb_hba.conf, и qhb_ident.conf, хотя их можно разместить и в другом месте.

Таблица 1. Содержимое PGDATA

Элемент
Описание

QHB_VERSION
Файл, содержащий номер основной версии QHB

base
Подкаталог, содержащий подкаталоги для каждой базы данных

current_logfiles
Файл, содержащий файлы журнала, в которые в настоящее время делает записи сборщик протоколируемых событий

global
Подкаталог, содержащий общие таблицы кластера, например pg_database

pg_commit_ts
Подкаталог, содержащий данные о времени фиксации транзакций

pg_dynshmem
Подкаталог, содержащий файлы, используемые подсистемой динамической разделяемой памяти

pg_logical
Подкаталог, содержащий данные состояния для логического декодирования

pg_multixact
Подкаталог, содержащий данные о состоянии мультитранзакций (используется для совместных блокировок строк)

pg_notify
Подкаталог, содержащий данные о состоянии прослушивания/уведомлений (LISTEN/NOTIFY)

pg_replslot
Подкаталог, содержащий данные слота репликации

pg_serial
Подкаталог, содержащий информацию о зафиксированных сериализуемых транзакциях

pg_snapshots
Подкаталог, содержащий экспортированные снимки

pg_stat
Подкаталог, содержащий постоянные файлы для подсистемы статистики

pg_stat_tmp
Подкаталог, содержащий временные файлы для подсистемы статистики

pg_subtrans
Подкаталог, содержащий данные о состоянии субтранзакций

pg_tblspc
Подкаталог, содержащий символьные ссылки на табличные пространства

pg_twophase
Подкаталог, содержащий файлы состояния для подготовленных транзакций

pg_wal
Подкаталог, содержащий файлы WAL (Write Ahead Log, журнал упреждающей записи)

pg_xact
Подкаталог, содержащий данные о состоянии фиксации транзакций

qhb.auto.conf
Файл, используемый для хранения параметров конфигурации, задаваемых командой ALTER SYSTEM

qhbmaster.opts
Файл, записывающий параметры командной строки, с которыми сервер был запущен в последний раз

qhbmaster.pid
Файл блокировки, записывающий идентификатор текущего процесса qhbmaster (PID), путь к каталогу данных кластера, время запуска qhbmaster, номер порта, путь к каталогу сокета домена Unix (может быть пустым), первый допустимый адрес прослушивания (listen_address) (IP-адрес или * либо пустое значение, если нет прослушивания по TCP) и идентификатор сегмента разделяемой памяти (этот файл отсутствует после завершения работы сервера)

Для каждой базы данных в кластере существует подкаталог внутри PGDATA/base, названный по OID базы данных в pg_database. По умолчанию в этом подкаталоге находятся файлы базы данных; в частности, там хранятся ее системные каталоги.

Обратите внимание, что в следующих разделах описано поведение встроенного табличного метода доступа heap и встроенных индексных методов доступа. Благодаря расширяемой природе QHB другие методы доступа могут работать по-другому.

Каждая таблица и индекс хранятся в отдельном файле. Для обычных отношений эти файлы называются по номеру файлового узла таблицы или индекса, который можно найти в pg_class.relfilenode. Но для временных отношений имя файла имеет вид tBBB_FFF, где BBB — это идентификатор обслуживающего процесса, создавшего этот файл, а FFF — это номер файлового узла. В любом случае помимо основного файла (также известного как основная ветвь) у каждой таблицы и индекса имеется карта свободного пространства (см. раздел Карта свободного пространства), где хранится информация о свободном пространстве, доступном в отношении. Карта свободного пространства хранится в файле, имя которого состоит из номера файлового узла и суффикса _fsm. Таблицы также имеют карту видимости, хранящуюся в ветви с суффиксом _vm, для отслеживания страниц, не содержащих неиспользуемые кортежи. Карта видимости описывается далее в разделе Карта видимости. У нежурналируемых таблиц и индексов имеется третий файл, называемый ветвью инициализации, который хранится в ветви с суффиксом _init (см. раздел Ветвь инициализации).

ВНИМАНИЕ!
Обратите внимание, что хотя файловый узел таблицы часто совпадает с ее OID, так бывает не всегда. Некоторые операции, например TRUNCATE, REINDEX, CLUSTER и некоторые формы ALTER TABLE, могут изменить файловый узел, сохранив при этом OID. Старайтесь не рассчитывать, что файловый узел и OID таблицы — это одно и то же. Кроме того, для некоторых системных каталогов, включая сам pg_class, pg_class.relfilenode содержит ноль. Фактические номера файловых узлов этих каталогов хранятся в структуре данных более низкого уровня и могут быть получены с помощью функции pg_relation_filenode().

Когда объем таблицы или индекса превышает 1 ГБ, они делятся на сегменты размером в гигабайт. Имя файла первого сегмента совпадает с номером файлового узла (filenode); последующие сегменты получают имена filenode.1, filenode.2 и т. д. Такая компоновка позволяет избежать проблем на платформах, имеющих ограничения по размеру файлов. (На самом деле, 1 ГБ — всего лишь размер сегмента по умолчанию. Этот размер можно регулировать с помощью параметра конфигурации --with-segsize при сборке QHB.) Теоретически для карт свободного пространства и карт видимости тоже может потребоваться несколько сегментов, хотя на практике это вряд ли произойдет.

Таблица, содержащая столбцы с потенциально большими записями, будет иметь связанную с ней таблицу TOAST, предназначенную для отдельного хранения полей, которые слишком велики, чтобы хранить их непосредственно в строках таблицы. Поле pg_class.reltoastrelid связывает таблицу с ее таблицей TOAST, если таковая имеется. Более подробную информацию см. в разделе TOAST.

Содержание таблиц и индексов рассматривается ниже в разделе Структура страницы базы данных.

Табличные пространства делают сценарий более сложным. Каждое пользовательское табличное пространство имеет символьную ссылку в каталоге PGDATA/pg_tblspc, указывающую на физический каталог табличного пространства (т. е. расположение, указанное в команде CREATE TABLESPACE данного табличного пространства). Эта символьная ссылка получает имя по OID табличного пространства. Внутри физического каталога табличного пространства есть подкаталог с именем, зависящим от версии сервера QHB, например: QHB_1.4_202208051. (Этот подкаталог необходим для того, чтобы последующие версии базы данных могли бесконфликтно использовать одно и тоже расположение, заданное в CREATE TABLESPACE.) В подкаталоге конкретной версии для каждой базы данных, имеющей элементы в табличном пространстве, существует подкаталог, названный по OID этой базы. Таблицы и индексы хранятся в этом каталоге, используя схему именования файловых узлов. Табличное пространство pg_default недоступно через pg_tblspc, но соответствует PGDATA/base. Аналогично табличное пространство pg_global недоступно через pg_tblspc, но соответствует PGDATA/global.

Функция pg_relation_filepath() показывает весь путь (относительно PGDATA) любого отношения. Это зачастую удобнее, чем запоминать множество вышеперечисленных правил. Но имейте в виду, что эта функция просто выдает имя первого сегмента основной ветви отношения — чтобы найти все файлы, связанные с этим отношением, вам может понадобиться добавить номер сегмента и/или _fsm, _vm или _init .

Временные файлы (для операций вроде сортировки данных большего объема, чем может поместиться в памяти) создаются внутри PGDATA/base/qhb_tmp, или в подкаталоге qhb_tmp каталога табличного пространства, если для них указано табличное пространство, отличное от pg_default. Имя временного файла имеет вид qhb_tmpPPP.NNN, где PPP — это PID обслуживающего процесса-владельца, а NNN служит для разделения различных временных файлов этого обслуживающего процесса.

TOAST

В этом разделе представлен обзор TOAST (The Oversized-Attribute Storage Technique, методика хранения сверхбольших атрибутов).

QHB использует фиксированный размер страницы (обычно 8 Кб) и не позволяет кортежам занимать несколько страниц. Поэтому нельзя напрямую хранить очень большие значения полей. Чтобы преодолеть это ограничение, большие значения полей сжимаются и/или разбиваются на несколько физических строк. Это происходит прозрачно для пользователя и на большую часть внутреннего кода влияет мало. Эту методику называют TOAST (шутл. омоним тоста, или «лучшего, что изобрели после хлеба в нарезке»). Инфраструктура TOAST также используется для улучшения обработки больших значений данных в памяти.

Только некоторые типы данных поддерживают TOAST — нет необходимости тратить ресурсы на типы данных, которые не могут создавать большие значения полей. Для поддержки TOAST тип данных должен иметь представление переменной длины (varlena), в котором, как правило, первое четырехбайтовое слово любого хранящегося значения содержит общую длину значения в байтах (включая само это слово). TOAST не ограничивает остальную часть представления типа данных. Специальные представления, совокупно именуемые значениями в формате TOAST, работают путем модификации или интерпретирования по-своему этого начального слова длины. Поэтому функции уровня C/RUST, поддерживающие типы данных, подходящие для TOAST, должны аккуратно обрабатывать входные значения, которые потенциально могут быть в формате TOAST: входные данные могут на самом деле и не состоять из четырехбайтового слова длины и содержимого, пока не будут распакованы. (Обычно это делают, вызывая макрос PG_DETOAST_DATUM прежде, чем что-то делать с входным значением, но в некоторых случаях возможны более эффективные подходы. Более подробную информацию см. в подразделе Особенности TOAST.)

TOAST забирает два бита из слова длины varlena (старшие биты на машинах с обратным порядком байтов и младшие биты на машинах с прямым порядком байтов), тем самым ограничивая логический размер любого значения типа данных, подходящих для TOAST, до 1 ГБ (2³⁰ - 1 байт). Когда оба бита равны нулю, значение является обычным, не в формате TOAST значением соответствующего типа данных, а оставшиеся биты слова длины задают общий размер данных (включая слово длины) в байтах. Когда установлен самый старший или самый младший бит, значение имеет только однобайтовый заголовок вместо обычного четырехбайтового, а оставшиеся биты этого байта задают общий размер данных (включая байт длины) в байтах. Этот альтернативный подход обеспечивает экономичное хранение значений размером менее 127 байт, при этом позволяя при необходимости увеличить значение типа данных до 1 ГБ. Значения с однобайтовыми заголовками не выравниваются по какой-либо конкретной границе, тогда как значения с четырехбайтовыми заголовками выравниваются как минимум по четырехбайтовой границе; это отсутствие необходимости в заполнении для выравнивания обеспечивает дополнительную экономию места, что ощутимо для коротких значений. В особом случае, если все оставшиеся биты однобайтового заголовка равны нулю (что потенциально невозможно с учетом включения размера длины), значение является указателем на вынесенные данные с несколькими возможными альтернативами, как описывается ниже. Тип и размер такого указателя TOAST определяется кодом, хранящимся во втором байте данных. Наконец, когда самый старший или самый младший бит очищен, но соседний бит установлен, значит, содержимое данных было сжато и должно быть распаковано перед использованием. В этом случае оставшиеся биты четырехбайтового слова длины задают общий размер сжатых, а не исходных данных. Обратите внимание, что сжатие также возможно для вынесенных данных, но заголовок varlena не говорит, произошло ли оно, — на это указывает содержимое указателя TOAST.

Методику, которая будет использоваться для сжатия внутренних или вынесенных данных, можно выбрать для каждого столбца по отдельности, установив параметр столбца COMPRESSION в команде CREATE TABLE или ALTER TABLE. По умолчанию для столбцов, у которых методика не задана явно, во время добавления данных используется метод из параметра default_toast_compression.

Как уже упоминалось, существует несколько типов указателя данных TOAST. Старейший и самый распространенный тип — это указатель на вынесенные данные, хранящиеся в таблице TOAST, которая отделена от таблицы, содержащей указатель TOAST, но связана с ней. Такие указатели на диске создаются с помощью кода управления TOAST (в access/common/toast_internals.c), когда кортеж, сохраняемый на диск, слишком велик, чтобы храниться как есть. Более подробная информация приводится в подразделе Отдельное хранение TOAST на диске. Как вариант, указатель TOAST может содержать указатель на вынесенные данные, находящиеся в другом месте в памяти. Такие данные обязательно недолговременные и никогда не оказываются на диске, но они очень полезны для предотвращения копирования и избыточной обработки больших значений данных. Более подробная информация приводится в подразделе Отдельное хранение TOAST в памяти.

Отдельное хранение TOAST на диске

Если любой из столбцов таблицы подходит для TOAST, таблица будет иметь связанную таблицу TOAST, OID которой хранится в записи таблицы pg_class.reltoastrelid. На диске значения в формате TOAST содержатся в таблице TOAST, что подробнее описывается ниже.

Отделенные значения делятся (после сжатия, если оно применяется) на фрагменты размером не более TOAST_MAX_CHUNK_SIZE байт (по умолчанию это значение выбирается таким образом, чтобы на странице помещались четыре строки фрагмента, то есть размер одного фрагмента составляет около 2000 байт). Каждый фрагмент хранится в виде отдельной строки в таблице TOAST, принадлежащей таблице-владельцу. У каждой таблицы TOAST есть столбцы chunk_id (OID, определяющий конкретное значение в формате TOAST), chunk_seq (порядковый номер для фрагмента в пределах его значения) и chunk_data (фактические данные фрагмента). Уникальный индекс по chunk_id и chunk_seq обеспечивает быстрое получение значений. Таким образом, в указателе на данные, представляющем вынесенное значение TOAST на диске, должен храниться OID таблицы TOAST, в которой нужно искать, и OID конкретного значения (его chunk_id). Для удобства указатели на данные также хранят логический размер данных (исходную несжатую длину), физический сохраненный размер (отличается, если применялось сжатие) и использованный метод сжатия, если он задан. Учитывая байты заголовка varlena, общий размер указателя данных TOAST на диске составляет 18 байт независимо от фактического размера представляемого значения.

Код обработки TOAST запускается, только когда значение строки, подлежащее хранению в таблице, больше TOAST_TUPLE_THRESHOLD байт (обычно 2 КБ). Код TOAST будет сжимать и/или перемещать значения полей за пределы таблицы, пока значение строки не станет короче TOAST_TUPLE_TARGET байт (по умолчанию тоже 2 КБ) или пока дальнейшее уменьшение станет невозможным. Во время операции UPDATE значения неизмененных полей обычно сохраняются как есть; поэтому изменение строки с вынесенными значениями не несет затрат, связанных с TOAST, если ни одно из таких значений не изменилось.

Код обработки TOAST распознает четыре различные стратегии для хранения столбцов с поддержкой TOAST на диске:

PLAIN не допускает сжатие или хранение вне таблицы. Это единственно возможная стратегия для столбцов типов данных, которые не поддерживают TOAST.

EXTENDED допускает как сжатие, так и хранение вне таблицы. Это стратегия по умолчанию для большинства типов данных с поддержкой TOAST. Сначала будет предпринята попытка сжатия, затем, если строка все еще слишком велика, сохранение вне таблицы.

EXTERNAL допускает хранение вне таблицы, но не сжатие. Использование EXTERNAL ускорит операции с подстрокой в больших столбцах text и bytea (ценой увеличения объема памяти для хранения), потому что такие операции оптимизированы для выборки только необходимых частей вынесенного значения, когда оно не сжато.

MAIN допускает сжатие, но не хранение вне таблицы. (На самом деле для таких столбцов по-прежнему будет выполняться хранение вне таблицы, но только в крайнем случае, когда нет другого способа сделать строку достаточно маленькой, чтобы уместить на странице.)

Каждый тип данных с поддержкой TOAST задает стратегию по умолчанию для столбцов этого типа данных. Кроме того, стратегию для указанного столбца таблицы можно изменить с помощью ALTER TABLE ... SET STORAGE.

TOAST_TUPLE_TARGET можно настроить для каждой таблицы с помощью ALTER TABLE ... SET (toast_tuple_target = N)

По сравнению с более простым подходом, который позволяет значениям строк занимать несколько страниц, эта схема имеет ряд преимуществ. Предполагая, что обычно запросы уточняются путем сравнения с относительно маленькими значениями ключей, большая часть работы будет выполнена с использованием основной записи строки. Большие значения атрибутов в формате TOAST будут извлекаться (если вообще будут выбраны) только во время отправки результата клиенту. Таким образом, основная таблица оказывается намного меньше, а значит, в кэш разделяемого буфера помещается больше ее строк, чем их было бы без отдельного хранения. Наборы данных для сортировки тоже уменьшаются, и сортировка чаще будет выполняться полностью в памяти. Небольшой тест показал, что таблица, содержащая типичные HTML-страницы и их URL, после сохранения занимала половину объема исходных данных, включая таблицу TOAST, и что основная таблица содержала только около 10% всех данных (URL и некоторые небольшие HTML-страницы). При этом не было никакой разницы по времени выполнения по сравнению с таблицей без использования TOAST, в которой все HTML-страницы были уменьшены до 7 КБ, чтобы поместиться в строках.

Отдельное хранение TOAST в памяти

Указатели TOAST могут указывать на данные, находящиеся не на диске, а в другом месте в памяти текущего серверного процесса. Очевидно, что такие указатели не могут быть долговечными, но тем не менее они полезны. В настоящее время существует два подвида: указатели на косвенные данные и указатели на развернутые данные.

Косвенные указатели TOAST просто указывают на непосредственное значение varlena, хранящееся где-то в памяти. Изначально этот подвид был создан просто как доказательство концепции, но в настоящее время он используется при логическом декодировании во избежание потенциальной необходимости создания физических кортежей больше 1 ГБ (что может произойти при сведении всех вынесенных значений полей в один кортеж). У такого способа весьма ограниченное применение, поскольку владелец такого указателя должен в полной мере осознавать, что ссылочные данные существуют, только пока существует указатель, и никакая инфраструктура этого не изменит.

Развернутые указатели TOAST полезны для сложных типов данных, представление которых на диске не особенно подходит для вычислительных целей. Например, стандартное представление varlena массива QHB включает информацию о размерности, битовую карту элементов NULL, если таковые имеются, а затем значения всех элементов по порядку. Когда сам тип элемента имеет переменную длину, единственный способ найти N-й элемент — это просканировать все предыдущие элементы. Такое представление подходит для хранения на диске из-за его компактности, но для вычислений с массивом гораздо удобнее иметь «развернутое» или «деконструированное» представление, в котором были определены начальные местоположения всех элементов. Механизм указателя TOAST поддерживает эту потребность, позволяя переданному по ссылке элементу Datum указывать либо на стандартное значение varlena (представление на диске), либо на указатель TOAST, указывающий на развернутое представление где-то в памяти. Детали этого развернутого представления зависят от типа данных, но он должен иметь стандартный заголовок и соответствовать также другим требованиям API.

Функции уровня C/RUST, работающие с таким типом данных, могут выбрать для обработки любое представление. Функции, которые не знают о развернутом представлении, а просто применяют к своим входным данным макрос PG_DETOAST_DATUM, будут автоматически получать традиционное представление varlena, поэтому поддержку развернутого представления можно вводить постепенно, по одной функции за раз.

Указатели TOAST на развернутые значения далее делятся на указатели для чтения-записи и только для чтения. Представление, на которое они указывают, в любом случае будет одинаковым, но функция, получающая указатель для чтения-записи, может изменять ссылочное значение сразу на месте, тогда как функция, получающая указатель только для чтения, не должна этого делать; если она хочет получить измененную версию значения, сначала она должна создать копию. Это различие и некоторые связанные с ним соглашения позволяют избежать ненужного копирования развернутых значений при выполнении запроса.

Для всех типов указателей TOAST в памяти код обработки TOAST гарантирует, что подобные данные указателей не могут случайно сохраниться на диске. Указатели TOAST в памяти автоматически разворачиваются в обычные внутренние значения varlena перед сохранением, а затем могут преобразоваться в указатели TOAST на диске, если иначе содержащий их кортеж был бы слишком большим.

Карта свободного пространства

Каждое отношение кучи и индекса, за исключением хеш-индексов, имеет собственную расширяемую карту свободного пространства (Free Space Map, FSM) Целью такой карты является быстрое обнаружение страницы, на которой достаточно свободного пространства, чтобы сохранить кортеж, либо определить, что такой страницы не существует и что отношение следует увеличить на одну страницу. Она хранится вместе с основными данными отношения в отдельной ветви отношений, называемой по номеру файлового узла отношения с суффиксом _fsm. Например, если номер файлового узла отношения равен 12345, то FSM хранится в файле с именем 12345_fsm, в том же каталоге, что и основной файл отношения.

Важно, чтобы эта карта оставалась небольшой, чтобы ее можно было быстро просмотреть. Поэтому мы не пытаемся записать точный объем свободного пространства на странице. Мы выделяем по одному байту карты на каждую страницу, что позволяет нам записать свободное пространства со степенью детализации в 1/256 страницы. Можно также сказать, что хранимое значение — это свободное пространство, разделенное на BLCKSZ/256 (с округлением в меньшую сторону). Мы предполагаем, что свободное пространство всегда должно быть меньше BLCKSZ, поскольку у всех страниц есть некоторые служебные данные; так что максимальное значение карты составляет 255.

В целях ускорения поиска карта свободного пространства является не просто массивом записей для каждой страницы. Она организована в виде дерева страниц FSM, и внутри каждой такой страницы находится двоичное дерево, как описано ниже

Для изучения информации, хранящейся на картах свободного пространства, можно воспользоваться модулем pg_freespacemap.

Структура страницы FSM

Внутри каждой страницы FSM используется структура в виде двоичного дерева, где в листовых узлах хранятся сведения об объеме свободного пространства на страницах кучи (или на страницах FSM более низкого уровня, см. подраздел Структура более высокого уровня ниже), по одному листовому узлу на каждую страницу кучи. В нелистовых узлах хранятся сведения о максимальном объеме свободного пространства среди их дочерних страниц.

Например:

    4
 4     2
3 4   0 2    <- Этот уровень представляет страницы кучи

Для работы со страницами требуются две основные операции: поиск и обновление.

Для поиска страницы с объемом в X свободного пространства следует передвигаться по дереву по пути, где n >= X, пока не дойдете до самого низа. Если обе дочерних страницы данного узла отвечают этому условию, можно произвольно выбрать любую из них.

Для обновления объема свободного пространства на странице до X сначала нужно обновить листовой узел, соответствующий странице кучи, затем передать это изменение узлам выше, для чего следует переходить к каждому родителю и заново вычислять его значение как максимальное из значений двух его дочерних страниц. Эту операцию нужно повторять, пока не достигнете корня или родителя, значение которого не изменится.

У такой структуры данных есть несколько полезных свойств:

чтобы выявить, что страницы с X байтами свободного пространства не существует, достаточно всего лишь просмотреть корневой узел

меняя выбор того, к какой дочерней странице перейти в алгоритме поиска (когда выбор есть), можно реализовать различные стратегии, например, отдавая предпочтение страницам поближе к заданной или распределяя нагрузку равномерно по всей таблице.

Подпрограммы более высокого уровня, использующие страницы FSM, обращаются к ним через функции fsm_set_avail() и fsm_search_avail(). Интерфейс этих функций скрывает внутреннюю древесную структуру страницы, воспринимая страницу FSM как черный ящик с определенным количеством «слотов» для хранения информации о свободном пространстве. (Тем не менее подпрограммы более высокого уровня должны иметь представление о древесной структуре всей карты.)

Двоичное дерево хранится на каждой странице FSM в виде массива. Поскольку заголовок занимает на странице некоторое место, двоичное дерево несовершенно. То есть у него отсутствуют несколько листовых узлов с правого края и имеется несколько бесполезных нелистовых узлов справа же. Поэтому такое дерево выглядит примерно так:

        0
    1       2
  3   4   5   6
 7 8 9 A B

где цифры обозначают позицию каждого узла внутри массива. Обратите внимание, что дерево гарантированно укомплектовано выше листового уровня; отсутствуют только некоторые листовые узлы. Это отражается в том, что число доступных «слотов» на страницу не вполне кратно 2.

Также у страницы FSM имеется указатель на следующий слот, fp_next_slot, определяющий, откуда начинать следующий поиск свободного пространства на этой странице. Это сделано для того, чтобы рассредоточить страницы, возвращаемые поисками по FSM. Когда несколько обслуживающих процессов одновременно добавляют данные в отношение, конкуренции между ними можно избежать, заставив их добавлять данные на разные страницы. Но также желательно заполнять страницы в последовательном порядке, чтобы получить преимущество упреждающей выборки и пакетных записей в ОС. За обеспечение этого отвечает FSM, и указатель на следующий слот помогает добиться желаемого поведения.

Структура более высокого уровня

В целях вертикального масштабирования вышеописанной структуры данных за пределы одной страницы подобная древесная структура поддерживается по всем страницам. Листовые узлы на страницах более высокого уровня соответствуют страницам FSM более низкого уровня. Корневой узел каждой страницы имеет то же значение, что и соответствующий листовой узел его родительской страницы.

Корневая страница всегда хранится в физическом блоке 0.

Например, предположим, что на каждой странице FSM может храниться информация о 4 страницах (в реальности на ней хранится (BLCKSZ - заголовки) / 2, или ~4000, при условии, что BLCKSZ имеет значение по умолчанию), тогда мы получим такую структуру диска:

  0     <-- страница 0 на уровне 2 (корневая страница)
   0     <-- страница 0 на уровне 1
    0     <-- страница 0 на уровне 0
    1     <-- страница 1 на уровне 0
    2     <-- ...
    3
   1     <-- страница 1 на уровне 1
    4
    5
    6
    7
   2
    8
    9
    10
    11
   3
    12
    13
    14
    15

где цифры — это номера страниц на этом уровне, начиная с 0.

Чтобы найти физический блок #, соответствующий листовой странице n, нужно посчитать количество листовых страниц и страниц более высокого уровня, предшествующих странице n. Формула получается такой:

y = n + (n / F + 1) + (n / F^2 + 1) + ... + 1

где F — это разветвление (4 в примере выше). Первая n — это количество предшествующих листовых страниц, вторая — количество страниц на уровне 1 и так далее.

Чтобы не усложнять процесс, дерево всегда имеет постоянную высоту. Чтобы охватить максимальный размер отношения, равный 2³² - 1 блоков, достаточно трех уровней при стандартном значенииBLCKSZ (4000³ > 2³²).

Способ адресации

Подпрограммы более высокого уровня оперируют «логическими» адресами, состоящими из:

уровня,

логического номера страницы

слота (если применимо)

Самые нижние страницы FSM имеют уровень номер 0, уровень выше — номер 1, а корень — номер 2. Как показано на диаграмме выше, логический номер страницы — это номер страницы на этом уровне, начиная с 0.

Блокировки

При перемещении вниз в поисках свободного пространства блокируется только по одной странице за раз: родительская страница освобождается прежде, чем блокировка переходит на дочернюю. Если дочерняя страница параллельно модифицируется, и когда вы к ней переходите, на ней больше не осталось свободного пространства, придется начать все сначала (после корректировки родительской страницы, чтобы не попасть в бесконечную петлю).

Во время поиска используются совместные блокировки буферов, но при обновлении страниц используются эксклюзивную блокировки. Однако указатель на следующий слот обновляется и во время поисков, несмотря на то, что удерживается всего лишь совместная блокировка. fp_next_slot — просто подсказка, и при повреждении ее можно с легкостью сбросить в исходное состояние, поэтому лучше принять некоторый риск подобного рода, чем платить издержками, возникающими при эксклюзивных блокировках.

Восстановление

FSM не протоколируются в WAL явно. Вместо этого исправление возможных повреждений возложено на ряд мер по самокоррекции. В итоге при записи в FSM это воспринимается как подсказка, поэтому следует пользоваться функцией MarkBufferDirtyHint(), а не MarkBufferDirty().

Прежде всего, когда на странице FSM устанавливается значение, после завершения распространения изменения корневой узел этой страницы сравнивается с новым значением. Он должен быть больше или равен только что установленному значению, или мы получим поврежденную страницу с родителем, у которого слишком маленькое значение. Во-вторых, поврежденные страницы выявляются во время поиска, при перемещении вниз по дереву. Такая проверка заметит, если в родительском узле установлено слишком высокое значение. В обоих случаях узлы более верхнего уровня на странице немедленно перестраиваются, исправляя повреждение в пределах этой страницы.

По мере обработки кучи команда VACUUM обновляет все страницы FSM самого нижнего уровня с корректным объемом свободного пространства на соответствующих страницах кучи. Это производится через функцию fsm_set_avail(), так что верхние узлы на этих страницах обновляются немедленное. Периодически VACUUM вызывает функцию FreeSpaceMapVacuum[диапазон], чтобы распространить новую информацию о свободном пространстве по верхним страницам дерева FSM.

Карта видимости

Каждое отношение кучи имеет карту видимости (Visibility Map, VM) для отслеживания того, какие страницы содержат только те кортежи, которые считаются видимыми для всех активных транзакций; также она отслеживает, какие страницы содержат только замороженные кортежи. Она хранится вместе с основными данными отношения в отдельной ветви отношения, называемой по номеру файлового узла отношения с суффиксом _vm. Например, если номер файлового узла отношения равен 12345, то VM хранится в файле с именем 12345_vm, в том же каталоге, что и основной файл отношения. Обратите внимание, что индексы не имеют карту видимости.

Карта видимости хранит по два бита на каждую страницу кучи. Первый бит, если он установлен, указывает, что страница является полностью видимой, или, другими словами, что страница не содержит никаких кортежей, которые необходимо очистить. Эта информация также может использоваться сканированиями только по индексу для ответа на запросы, использующие только кортеж индекса. Второй бит, если он установлен, означает, что все кортежи на странице были заморожены. Это означает, что даже очистке с защитой от зацикливания счетчиков транзакций не нужно повторно просматривать эту страницу.

Карта является консервативной в том смысле, что мы уверены, что когда бит установлен, нам известно, что условие истинно, но когда бит не установлен, оно может быть как истинным, так и ложным. Биты карты видимости устанавливаются только очисткой, но зато сбрасываются любыми операциями, изменяющими данные на странице.

Для изучения информации, хранящейся в карте видимости, можно воспользоваться модулем pg_visibility.

Ветвь инициализации

Каждая нежурналируемая таблица и каждый индекс нежурналируемой таблицы имеют ветвь инициализации. Ветвь инициализации — это пустая таблица или индекс соответствующего типа. Когда нежурналируемую таблицу нужно сбросить до пустого состояния из-за сбоя, ветвь инициализации копируется поверх основной ветви, а все остальные ветви стираются (они будут автоматически воссоздаваться по мере необходимости).

Структура страницы базы данных

В этом разделе представлен обзор формата страницы, используемого в таблицах и индексах QHB.¹ Последовательности и таблицы TOAST форматируются так же, как обычная таблица.

В следующем объяснении предполагается, что байт содержит 8 бит. Кроме того, термин элемент относится к индивидуальному значению данных, хранящемуся на странице. В таблице элемент — строка; в индексе — запись индекса.

Каждая таблица и индекс хранятся в виде массива страниц фиксированного размера (обычно 8 КБ, хотя при компиляции сервера можно выбрать другой размер страницы). В таблице все страницы логически равнозначны, поэтому определенный элемент (строка) может храниться на любой странице. В индексах первая страница обычно резервируется как метастраница, содержащая управляющую информацию, и в индексе могут быть разные типы страниц, в зависимости от индексного метода доступа.

В Таблице 2 показана общая структура страницы. Каждая страница состоит из пяти частей.

Таблица 2. Общая структура страницы

Элемент | Описание
PageHeaderData | Длина 24 байта. Содержит общую информацию о странице, включая указатели на свободное пространство.
ItemIdData | Массив идентификаторов элементов, указывающих на фактические элементы. Каждая запись является парой (смещение, длина). По 4 байта на элемент.
Free space | Свободное пространство. Новые идентификаторы элементов выделяются с начала этой области, новые элементы — с конца.
Items | Собственно сами элементы.
Special space | Данные, специфичные для индексного метода доступа. Различные методы хранят различные данные. Для обычных таблиц это пустое поле.

Первые 24 байта каждой страницы состоят из заголовка страницы (PageHeaderData). Его формат подробно описан в Таблице 3. В первом поле отслеживается самая последняя запись WAL, связанная с этой страницей. Второе поле содержит контрольную сумму страницы, если включен расчет контрольных сумм. Далее идет 2-байтовое поле, содержащее биты флагов. За ним следуют три 2-байтовых целочисленных поля (pd_lower, pd_upper, и pd_special). Они содержат смещения в байтах от начала страницы до начала свободного пространства, до конца свободного пространства и до начала специального пространства. Следующие 2 байта заголовка страницы, pd_pagesize_version, содержат размер страницы и индикатор версии (на настоящий момент это версия 4). Размер страницы в основном присутствует только для перекрестной проверки; в инсталляции нет поддержки страниц разных размеров. Последнее поле служит подсказкой, показывающей, будет ли выгодно очистить страницу: она отслеживает самый старый неочищенный XMAX на странице.

Таблица 3. Структура PageHeaderData

Поле | Тип | Длина | Описание
pd_lsn | PageXLogRecPtr | 8 байт | LSN: следующий байт после последнего байта WAL-записи для последнего изменения на этой странице
pd_checksum | uint16 | 2 байта | Контрольная сумма страницы
pd_flags | uint16 | 2 байта | Биты флагов
pd_lower | LocationIndex | 2 байта | Смещение до начала свободного места
pd_upper | LocationIndex | 2 байта | Смещение до конца свободного пространства
pd_special | LocationIndex | 2 байта | Смещение до начала специального пространства
pd_pagesize_version | uint16 | 2 байта | Информация о размере страницы и номере версии структуры
pd_prune_xid | TransactionId | 4 байта | Самый старый неочищенный XMAX на странице, или ноль, если отсутствует

После заголовка страницы идут идентификаторы элементов (ItemIdData), каждый из которых занимает четыре байта. Идентификатор элемента содержит смещение в байтах до начала элемента, его длину в байтах и несколько битов атрибутов, влияющих на его интерпретацию. Новые идентификаторы элементов выделяются по мере надобности с начала свободного пространства. Количество наличествующих идентификаторов элементов можно определить, просмотрев поле pd_lower, значение которого увеличивается при выделении нового идентификатора. Поскольку до своего освобождения идентификатор элемента никогда не перемещается, его индекс можно использовать для ссылки на элемент в течение длительного времени, даже если сам элемент перемещается по странице для уплотнения свободного пространства. На самом деле, каждый указатель на элемент (ItemPointer, также известный как CTID), созданный QHB, состоит из номера страницы и индекса идентификатора элемента.

Сами элементы хранятся в пространстве, выделенном с конца свободного пространства. Точная структура меняется в зависимости от того, что будет содержать таблица. В таблицах и последовательностях используется структура с именем HeapTupleHeaderData, описанная ниже.

Последний раздел — это «специальный раздел», который может содержать все, что метод доступа желает сохранить. Например, индексы B-деревья хранят ссылки на левый и правый родственные элементы страницы, а также некоторые другие данные, относящиеся к структуре индекса. Обычные таблицы вообще не используют специальный раздел (что указывается установкой в pd_special значения, равного размеру страницы).

На Рисунке 1 показано, как эти части располагаются на странице.

Рисунок 1. Структура страницы

Структура строк таблицы

Все строки таблицы обладают схожей структурой. Они состоят из заголовка фиксированного размера (занимающего 23 байта на большинстве машин), за которым следует необязательная битовая карта пустых значений, необязательное поле идентификатора объекта и пользовательские данные. Заголовок подробно описывается в Таблице 4. Собственно пользовательские данные (столбцы строки) начинаются со смещения, указанного в t_hoff, которое всегда должно быть кратно расстоянию MAXALIGN для платформы. Битовая карта пустых значений присутствует только в том случае, если бит HEAP_HASNULL установлен в t_infomask. Если она присутствует, то начинается сразу после фиксированного заголовка и занимает столько байт, чтобы на каждый столбец данных приходилось по одному биту (т. е. количество битов равно числу атрибутов, указанному в t_infomask2). В этом списке битов бит 1 обозначает элемент, отличный от NULL, бит 0 — NULL. Когда битовой карты нет, все столбцы считаются отличными от NULL. Идентификатор объекта присутствует только в том случае, если бит HEAP_HASOID_OLD установлен в t_infomask. Если он присутствует, то находится сразу перед границей t_hoff. Любое заполнение, требуемое для того, чтобы сделать t_hoff кратным MAXALIGN, будет находиться между битовой картой пустых значений и идентификатором объекта. (Это в свою очередь гарантирует, что идентификатор объекта тоже выровнен соответствующим образом.)

Таблица 4. Структура Heaptupleheaderdata

Поле | Тип | Длина | Описание
t_xmin | TransactionId | 4 байта | метка XID добавления
t_xmax | TransactionId | 4 байта | метка XID удаления
t_cid | CommandId | 4 байта | метка CID добавления и/или удаление (пересекается с t_xvac)
t_xvac | TransactionId | 4 байта | XID для операции VACUUM при перемещении версии строки
t_ctid | ItemPointerData | 6 байт | текущий TID этой или более новой версии строки
t_infomask2 | uint16 | 2 байта | количество атрибутов плюс различные биты флагов
t_infomask | uint16 | 2 байта | различные биты флагов
t_hoff | uint8 | 1 байт | смещение до пользовательских данных

Интерпретацию фактических данных можно выполнить только с помощью информации, полученной из других таблиц, в основном из pg_attribute. Ключевые значения, необходимые для определения местоположения полей: attlen и attalign. Не существует способа напрямую получить конкретный атрибут, за исключением случая, когда все поля имеют фиксированную длину и нет значений NULL. Все эти особенности охвачены в функциях heap_getattr, fastgetattr и heap_getsysattr.

Чтобы прочитать данные, требуется просмотреть каждый атрибут по очереди. Первым делом нужно проверить, содержит ли поле NULL согласно битовой карте пустых значений. Если это так, следует перейти к следующему полю. После этого необходимо убедиться в правильности выравнивания. Если поле имеет фиксированную ширину, то все байты просто отдаются. Если же это поле переменной длины (attlen = -1), то все становится немного сложнее. Все типы данных переменной длины имеют общую структуру заголовка struct varlena, включающую в себя общую длину хранимого значения и некоторые биты флагов. В зависимости от этих флагов, данные могут храниться либо внутри, либо в таблице TOAST; кроме того, они могут быть сжатыми (см. раздел TOAST).

¹

На самом деле использование этого формата страницы не требуется ни для табличных, ни для индексных методов доступа. Табличный метод доступа heap всегда использует этот формат. Все существующие индексные методы тоже используют базовый формат, но данные, хранящиеся на метастраницах индекса, обычно не следуют правилам компоновки элементов.

Кортежи только в куче (Heap-Only Tuples, HOT)

Для обеспечения параллелизма высокой степени QHB использует для сохранения строк многоверсионное управление параллельным доступом (MVCC). Однако у MVCC имеется несколько побочных эффектов, когда дело касается запросов на изменение данных. В частности, при изменении требуется, чтобы в таблицы добавлялись новые версии строк. Кроме того, для каждой измененной строки в индекс должны добавляться новые записи, а удаление старых версий строк и их записей в индексе может быть затратным.

Для снижения издержек на изменения данных в QHB имеется оптимизация, называемая «кортежи только в куче» (HOT). Эта оптимизация возможна в следующих случаях:

Изменение не модифицирует столбцы, на которые ссылаются индексы таблицы, включая индексы по выражениям и частичные индексы.

На странице, содержащей старую строку, имеется достаточно свободного места для измененной строки.

В таких случаях принцип «кортежи только в куче» предоставляет две оптимизации:

Для представления измененных строк не нужны новые записи индекса.

Старые версии измененных строк могут быть полностью удалены во время обычной операции, включая SELECT, и не требуют периодического выполнение операций очистки. (Это возможно, поскольку индексы не ссылаются на свои идентификаторы элементов страниц.)

Подводя итог вышесказанному, изменения кортежей только в куче могут выполняться, только если не изменяются столбцы, задействованные в индексах. Можно поспособствовать появлению достаточного места для изменений HOT, уменьшив фактор заполнения таблицы. Даже если этого не делать, изменения HOT все равно произойдут, поскольку новые строки будут естественным образом мигрировать на новые страницы и существующие страницы, где достаточно свободного места для новых версий строк. Системное представление pg_stat_all_tables позволяет мониторить изменения в режиме HOT и в обычном режиме.

Объявления и начальное содержимое системных каталогов

QHB использует множество различных системных каталогов для ведения мониторинга за существованием и свойствами объектов базы данных, например, таблиц и функций. Физически системный каталог ничем не отличается от простой пользовательской таблицы, но серверный код на C знает структуру и свойства каждого каталога и может напрямую манипулировать ими на низком уровне. Поэтому, например, не рекомендуется пытаться изменять структуру каталога на лету; это нарушит встроенные в код на C предположения о том, как располагаются строки в каталоге. Тем не менее структура каталогов может меняться от одной основной версии к другой.

Структуры этих каталогов объявляются в специально отформатированных заголовочных файлах C в каталоге /usr/local/qhb/include/catalog/ дерева исходного кода. Для каждого каталога имеется заголовочный файл, названный по имени каталога (например, pg_class.h для pg_class), который определяет набор столбцов в этом каталоге, а также другие основные свойства, например, его OID.

У многих каталогов имеются исходные данные, которые должны быть загружены в них в фазу «начальной загрузки» утилитой qhb_bootstrap (или initdb), чтобы привести систему в состояние, когда она сможет выполнять команды SQL. (Например, файл pg_class.h должен содержать запись для одноименного каталога, а также для всех остальных системных каталогов и индексов.) Эти исходные данные хранятся в редактируемой форме в файлах данных, которых также находятся в каталоге /usr/local/qhb/include/catalog/. Например, в файле pg_proc.dat описываются все исходные строки, которые должны быть добавлены в каталог pg_proc.

Чтобы создать файлы каталогов и загрузить в них эти исходные данные, серверный процесс, работающий в режиме начальной загрузки, прочитывает файл BKI (Backend Interface, серверный интерфейс), содержащий команды и исходные данные. Файл qhb.bki, используемый в этом режиме, создается из вышеупомянутых заголовочных файлов и файлов данных при сборке дистрибутива QHB скриптом на Perl genbki.pl. Хотя файл qhb.bki и привязан к конкретному выпуску QHB, но является платформонезависимым и устанавливается в подкаталог share дерева инсталляции.

Кроме того, скрипт genbki.pl создает для каждого каталога производный заголовочный файл, например, pg_class_d.h для каталога pg_class. Этот файл содержит автоматически генерируемые макроопределения и может содержать другие макросы, объявления перечислений и т. д., которые могут быть полезны для клиентского кода на C, читающего определенный каталог.

Большинству разработчиков QHB нет необходимости заниматься файлом BKI напрямую, но для добавления в код сервера практически любой нетривиальной функциональности потребуется модификация заголовочных файлов и/или файлов исходных данных каталога. Далее в этой главе изложена некоторая информация об этом, а также для полноты описывается формат файла BKI.

Правила объявления системных каталогов

Важнейшей составляющей заголовочного файла каталога является определение структуры на C, описывающее макет каждой строки каталога. Оно начинается с макроса CATALOG, который, когда дело касается компилятора C, является просто сокращенной записью typedef struct FormData_имя_каталога. Каждое поле в этой структуре порождает столбец каталога. Поля можно аннотировать с помощью макросов свойств BKI, описанных в файле genbki.h, например, чтобы определить для поля значение по умолчанию или пометить его как допускающее или не допускающее NULL. Строку CATALOG тоже можно аннотировать некоторыми другими макросами свойств BKI, описанными в genbki.h, чтобы определить другие свойства каталога в целом, например, является ли он разделяемым отношением.

Код кэша системного каталога (и вообще весь код, изменяющий каталог) предполагает, что части всех кортежей системных каталогов, имеющие фиксированную длину, действительно присутствуют, поскольку он отображает на них объявление этой структуры на C. Таким образом, все поля переменной длины и поля, принимающие NULL, должны помещаться в конец, и обращаться к ним как к полям структуры нельзя. К примеру, если вы попытаетесь установить в поле pg_type.typrelid значение NULL, при попытке какого-нибудь куска кода обратиться к полю typetup->typrelid (или, что еще хуже, к полю typetup->typelem, потому что оно следует за typrelid) возникнет сбой. Это приведет к случайным ошибкам или даже нарушениям сегментации.

В качестве частичной защиты от ошибок такого типа поля, имеющие переменную длину или принимающие NULL, нельзя делать видимыми напрямую для компилятора C. Это достигается посредством оборачивания их в #ifdef CATALOG_VARLEN ... #endif (где CATALOG_VARLEN — это символ, который никогда не будет определен). Это не дает коду на C беспрепятственно пытаться обращаться к полям, которые могут отсутствовать или находиться по некоторому другому смещению. В качестве независимой защиты, препятствующей созданию некорректных строк, мы требуем, чтобы все столбцы, которые не должны принимать NULL, помечались соответствующим образом в каталоге pg_attribute. Код начальной загрузки автоматически пометит столбцы каталога как NOT NULL, если они имеют фиксированную ширину и перед ними нет никаких столбцов, принимающих NULL или имеющих переменную ширину. Там, где это правило неприемлемо, вы можете скорректировать пометку, в зависимости от ситуации применив аннотации BKI_FORCE_NOT_NULL и BKI_FORCE_NULL.

Клиентский код не должен включать никакие заголовочные файлы каталогов pg_xxx.h, поскольку эти файлы могут содержать код на C, который не будет компилироваться вне кода сервера. (Обычно это происходит из-за того, что эти файлы также содержат объявления для функций в файлах /usr/local/qhb/ backend/catalog/.) Вместо этого клиентский код может включать соответствующий сгенерированный заголовок pg_xxx_d.h, который будет содержать OID #defines и все остальные данные, которые могут быть полезны на стороне клиента. Если вы желаете, чтобы макрос или другой код в заголовке каталога был видимым для клиентского кода, заключите эту секцию в #ifdef EXPOSE_TO_CLIENT_CODE ... #endif, чтобы указать скрипту genbki.pl скопировать эту секцию в заголовок pg_xxx_d.h.

Некоторые из каталогов настолько основополагающие, что их нельзя даже создать командой BKI create, используемой для большинства каталогов, поскольку этой команде нужно записать в эти каталоги информацию, описывающую новый каталог. Они называются каталогами начальной загрузки, и для их определения требуется много дополнительной работы: вы должны вручную подготовить для них соответствующие записи в предварительно загружаемом содержимом каталогов pg_class и pg_type, и эти записи нужно будет обновлять при последующих изменениях в структуре каталога. (Кроме того, каталогам начальной загрузки необходимы предварительно загруженные записи в каталоге pg_attribute, но, к счастью, сейчас с этой задачей справляется скрипт genbki.pl.) По возможности не делайте из новых каталогов каталоги начальной загрузки.

Исходные данные системных каталогов

У каждого каталога, обладающего какими-либо вручную создаваемыми исходными данными (у некоторых их нет), имеется соответствующий файл .dat, содержащий эти данные в редактируемом формате.

Формат файла данных

Каждый файл .dat содержит литералы структуры данных Perl, которые просто вычисляются функцией eval для создания в памяти структуры данных, состоящей из массива хеш-ссылок, по одной на каждую строку каталога. Слегка модифицированная выдержка из записи pg_database.dat продемонстрирует характерные особенности:

[

# Здесь мог быть комментарий.
{ oid => '1', oid_symbol => 'Template1DbOid',
  descr => 'database\'s default template',
  datname => 'template1', encoding => 'ENCODING',
  datlocprovider => 'LOCALE_PROVIDER', datistemplate => 't',
  datallowconn => 't', datconnlimit => '-1', datfrozenxid => '0',
  datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
  datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' },

]

Важные замечания:

Общая схема файла: открывающая квадратная скобка, один или более наборов фигурных скобок, каждый из которых представляет строку каталога, закрывающая квадратная скобка. После каждой закрывающей фигурной скобки ставится запятая.

В каждой строке каталога записываются через запятую пары ключ => значение. Допустимыми ключами являются имена столбцов каталога, а также ключи метаданных oid, oid_symbol, array_type_oid и descr. (Использование oid и oid_symbol описывается ниже в подразделе Присвоение OID, а array_type_oid — в подразделе Автоматическое создание типов массивов. descr предоставляет для объекта строку описания, которое будет добавлено в каталог pg_description или pg_shdescription соответственно.) Хотя ключи метаданных не являются обязательными, все определенные столбцы каталога должны присутствовать, за исключением случаев, когда в файле .h данного каталога для столбца задано значение по умолчанию. (В примере выше поле datdba было опущено, поскольку файл pg_database.h предоставляет для него подходящее значение по умолчанию.)

Все значения должны быть заключены в апострофы. Внутри значения апострофы экранируются обратным слэшем. Обратные слэши в данных можно, но необязательно удваивать; это соответствует правилам Perl по заключению в кавычки простых литералов. Обратите внимание, что обратные слэши, фигурирующие в данных, будут обрабатываться сканером начальной загрузки как управляющие символы, в соответствии с теми же правилами, что действуют для управляющих строковых констант (см. подраздел Строковые константы с управляющими последовательностями в стиле C); например, \t преобразуется в символ табуляции. Если вы действительно хотите получить обратный слэш в итоговом значении, вам понадобится написать четыре этих символа: Perl отбрасывает два, оставляя \\ сканеру начальной загрузки.

Значения NULL представляются как _null_. (Обратите внимание, что создать значение именно с такой строкой невозможно.)

Комментарии предваряются знаком # и должны располагаться в отдельных строках.

Значения полей, являющиеся OID других записей каталога, должны представляться символьными именами, а не фактическими числовыми OID. (В примере выше такую ссылку содержит поле dattablespace.) Это описывается ниже в подразделе Поиск OID по ссылке.

Поскольку хеши являются неупорядоченными структурами данных, порядок полей и схема строк не имеют семантической значимости. Однако для поддержания согласованного представления установлено несколько правил, которые применяет скрипт форматирования reformat_dat_file.pl:

В рамках каждой пары фигурных скобок сначала идут поля метаданных (именно в таком порядке) oid, oid_symbol, array_type_oid и descr (если присутствуют), а затем собственные поля каталога в определенном для них порядке.

При необходимости между полями вставляются переводы строк для ограничения длины строки 80 символами, если это возможно. Также перевод строки вставляется между полями метаданных и обычными полями.

Если в файле .h каталога задается значение по умолчанию для столбца и в записи данных имеется такое же значение, скрипт reformat_dat_file.pl уберет его из файла данных. Это поддерживает компактное представление данных.

Скрипт reformat_dat_file.pl сохраняет пустые строки и строки комментариев в неизменном виде.

Рекомендуется запускать скрипт reformat_dat_file.pl перед вставкой патчей в данные каталога. Для удобства можно просто переключиться на /usr/local/qhb/include/catalog/ и выполнить make reformat-dat-files.

Если вы хотите добавить новый метод уменьшения представления данных, вы должны реализовать его в скрипте reformat_dat_file.pl, а также научить Catalog::ParseData() разворачивать данные обратно в полное представление.

Присвоение OID

Строке каталога, находящейся в исходных данных, можно вручную присвоить OID, введя поле метаданных oid => nnnn. Более того, когда присваивается OID, для него можно создать макрос C, введя поле метаданных oid_symbol => имя.

Предварительно загружаемые строки каталога должны иметь заранее назначенные OID, если в других предварительно загружаемых строках на них есть ссылки по OID. Также заранее назначенный OID требуется, если на OID строки нужно ссылаться из кода на C. В отсутствие же этих обстоятельств поле метаданных oid можно опустить, и в этом случае код начальной загрузки присваивает OID автоматически. На практике мы обычно заранее присваиваем OID всем предварительно загружаемым строкам в заданном каталоге (даже если на самом деле только для некоторых из них есть перекрестные ссылки) либо не присваиваем их вообще.

Ввод фактического числового значения любого OID в коде на C считается крайне нежелательным; вместо этого всегда нужно использовать макрос. Прямые обращения к OID в каталоге pg_proc — вполне обычное явление, поэтому существует специальный скрипт Perl Gen_fmgrtab.pl, автоматически создающий необходимые макросы. Имеется также схожий — но по историческим причинам реализованный по-другому — автоматический метод создания макросов для OID в каталоге pg_type. Поэтому в этих двух каталогах записи oid_symbol не нужны. Аналогичным образом устанавливаются макросы для OID системных каталогов и индексов в каталоге pg_class. Для всех остальных каталогов потребуется вручную указать все нужные макросы с помощью записей oid_symbol.

Чтобы найти для новой предварительно загружаемой строки доступный OID, запустите скрипт unused_oids. Он выводит диапазоны неиспользуемых OID, включающие граничные значения (например, выходная строка 45-900 означает, что OID с 45 по 900 включительно еще не были назначены). В настоящее время для присвоения вручную зарезервированы значения OID 1–9999; скрипт unused_oids просто просматривает заголовки каталогов и файлы .dat и проверяет, какие значения в них отсутствуют. Также для поиска ошибок можно использовать скрипт duplicate_oids. (Скрипт genbki.pl присвоит OID всем строкам, которым он не был присвоен вручную, а также выявит повторяющиеся OID во время компиляции.)

При выборе OID для патча, который не предполагается принять сразу, лучше всего использовать группу более-менее последовательных OID, начиная с некоторого случайного числа в диапазоне 8000—9999. Это сводит к минимуму риск конфликтов OID с другими параллельно разрабатываемыми патчами. Чтобы сохранить диапазон 8000—9999 незанятым для целей разработки после внесения патча в главный репозиторий git, его OID следует перенумеровать, заменив доступными номерами ниже этого диапазона. Обычно это будет происходить ближе к концу каждого цикла разработки, так что все OID, занятые патчами, принятыми в этом цикле, перенумеруются одновременно. Для этой цели можно применять скрипт renumber_oids.pl. Если обнаружится, что у еще не принятого патча возникли конфликты OID с некоторыми недавно внесенными патчами, скрипт renumber_oids.pl может помочь и с исправлением этой ситуации.

Вследствие этого соглашения о возможной перенумерации OID, присваиваемых патчами, такие OID не должны считаться стабильными, пока патч не будет включен в официальный релиз. Однако мы не меняем после релиза вручную присвоенные OID объектов, поскольку это может вызвать различные проблемы совместимости.

Если genbki.pl нужно присвоить OID записи каталога, которой он не присвоен вручную, этот скрипт выберет значение в диапазоне 10000—11999. Счетчик OID сервера получает значение 12000 на старте процесса начальной загрузки. Таким образом, объекты, созданные обычными командами SQL на поздних фазах начальной загрузки, например, объекты, созданные при выполнении скрипта information_schema.sql, получают OID от 12000 и выше.

Во время обычной работы базы данных присваиваются OID только от 16384 и выше. Тем самым гарантируется, что диапазон 10000—16383 свободен для OID, присваиваемых автоматически скриптом genbki.pl или при начальной загрузке. Эти автоматически присваиваемые OID не считаются стабильными и могут меняться от одной инсталляции к другой.

Поиск OID по ссылке

В принципе, перекрестные ссылки из одной строки исходного каталога на другую можно написать, просто указав заранее присвоенный OID ссылочной строки в ссылающемся поле. Однако это противоречит политике проекта, поскольку провоцирует ошибки, а ссылки сложны для восприятия и могут побиться при перенумерации недавно присвоенных OID. Поэтому вместо этого скрипт genbki.pl предоставляет механизмы для записи символических ссылок. Они работают по следующим правилам:

Использование символических ссылок в некотором столбце каталога включается добавлением указания BKI_LOOKUP(правило_поиска) в определение этого столбца, где правило_поиска — это имя ссылочного каталога, например pg_proc. BKI_LOOKUP можно добавить к столбцам типа Oid, regproc, oidvector или Oid[]; в последних двух случаях поиск будет выполняться для каждого элемента массива.

Также допускается добавление указания BKI_LOOKUP(encoding) к целочисленным столбцам, чтобы обращаться к кодировкам наборов символов; в настоящее время эти кодировки не представлены в виде OID каталога, но имеют набор значений, известный скрипту genbki.pl.

Для некоторых столбцов каталога в записях допускаются нули вместо корректных ссылок. Если это допустимо, напишите BKI_LOOKUP_OPT вместо BKI_LOOKUP. Затем можно указать 0 в качестве записи столбца (Если столбец объявлен как regproc, можно написать - вместо 0.) За исключением этого особого случая, все записи в столбце BKI_LOOKUP должны быть символическими ссылками. Скрипт genbki.pl предупредит о нераспознанных именах.

На большинство видов объектов каталога ссылаются просто по их именам. Обратите внимание, то имена типов должны в точности соответствовать полям typname в записях каталога pg_type; псевдонимы типов использовать нельзя, например, указать integer вместо int4.

Функция может быть представлена своим значением proname (имя_функции), если оно уникально среди записей pg_proc.dat (это работает как ввод значения типа regproc). В противном случае укажите ее как имя_функции(имя_типа_аргумента,имя_типа_аргумента,...), как в regprocedure. Имена типов аргументов должны быть записаны в точности так, как они фигурируют в поле proargtypes в записи pg_proc.dat. Пробелы в этой строке не допускаются.

Операторы представляются в виде имя_оператора(левый_тип,правый_тип), при этом имена типов записываются в точности так, как они фигурируют в полях oprleft и oprright в записи pg_operator.dat. (Вместо опущенного операнда унарного оператора записывается 0.)

Имена классов операторов и семейств операторов уникальны только в рамках метода доступа, по этому они представляются в виде имя_метода_доступа/имя_объекта.

Ни в одном из этих случаев не поддерживается дополнение имени схемой; ожидается, что все объекты, созданные во время начальной загрузки, будут находиться в схеме pg_catalog.

Во время выполнения скрипт genbki.pl разрешает все символические ссылки и помещает в генерируемый файл BKI простые числовые OID. Как следствие, серверному процессу начальной загрузки нет необходимости заниматься символическими ссылками.

Желательно пометить ссылочные столбцы с OID макросами BKI_LOOKUP или BKI_LOOKUP_OPT, даже если в каталоге нет исходных данных, требующих поиска. Это позволит скрипту genbki.pl записать отношения по внешнему ключу, существующие в системных каталогах. Эта информация используется в регрессионных тестах для проверки на предмет некорректных записей. Также обратите внимание на макросы DECLARE_FOREIGN_KEY, DECLARE_FOREIGN_KEY_OPT, DECLARE_ARRAY_FOREIGN_KEY и DECLARE_ARRAY_FOREIGN_KEY_OPT, применяемые для объявления отношений по внешнему ключу, которые слишком сложны для BKI_LOOKUP (обычно это многостолбцовые внешние ключи).

Автоматическое создание типов массивов

У большинства скалярных типов данных должны иметься соответствующие типы массивов (то есть стандартный тип массива переменной длины с элементами скалярного типа, на который ссылается поле typarray записи скалярного типа в каталоге pg_type). В большинстве случаев скрипт genbki.pl может автоматически сгенерировать запись в pg_type для типа массива.

Чтобы воспользоваться этим механизмом, просто напишите поле метаданных array_type_oid => nnnn в записи скалярного типа в каталоге pg_type, указав OID, который будет использоваться для типа массива. После этого можно будет опустить поле typarray, поскольку оно будет автоматически заполняться этим OID.

Имя сгенерированного типа массива представляет собой имя скалярного типа с добавленным в начале символом подчеркивания. Другие поля записи массива заполняются из аннотаций BKI_ARRAY_DEFAULT(значение) в файле pg_type.h или, если какой-либо аннотации нет, копируются из скалярного типа. (Исключение составляет поле typalign.) Затем в полях typelem и typarray этих двух записей устанавливаются перекрестные ссылки друг на друга.

Рецепты по редактированию файлов данных

Ниже приведены некоторые предложения, касающиеся наиболее легких способов выполнения распространенных задач при изменении файлов данных каталога.

Добавление в каталог нового столбца со значением по умолчанию: Добавьте столбец в заголовочный файл с аннотацией BKI_DEFAULT(значение). Файл данных потребуется будет редактировать только при добавлении в существующие строки поля, где нужно не значение по умолчанию.

Добавление значения по умолчанию в существующий столбец, который его не имеет: Добавьте в заголовочный файл аннотацию BKI_DEFAULT, а затем выполните make reformat-dat-files, чтобы удалить ставшие избыточными записи поля.

Удаление столбца, со значением по умолчанию или без: Удалите столбец из заголовочного файла, а затем выполните make reformat-dat-files, чтобы удалить ставшие бесполезными записи поля.

Изменение или удаление существующего значения по умолчанию: Нельзя просто изменить заголовочный файл, поскольку из-за этого текущие данные будут некорректно интерпретироваться. Сначала выполните make expand-dat-files, чтобы перезаписать файлы данных со всеми явно добавленными значениями по умолчанию, потом измените или удалите аннотацию BKI_DEFAULT, а затем выполните make reformat-dat-files, чтобы снова удалить лишние поля.

Ситуативное массовое редактирование: Скрипт reformat_dat_file.pl можно адаптировать для выполнения многих типов массовых изменений. Просмотрите его блочные комментарии, показывающие, куда можно вставить одноразовый код. В следующем примере мы намерены объединить два логических поля в каталоге pg_proc в символьное поле:

Добавьте новый столбец со значением по умолчанию в файл pg_proc.h:

+    /* См. ниже PROKIND_ categories */
+    char        prokind BKI_DEFAULT(f);

Создайте на основе reformat_dat_file.pl новый скрипт для добавления соответствующих значений на лету:

-           # На данный момент мы имеем в качестве хеша целую строку в памяти
-           # и можем выполнять любые операции, какие пожелаем. Как уже указывалось
-           # этот скрипт удаляет только значения по умолчанию, но его можно
-           # адаптировать для выполнения ситуационного массового редактирования.
+           # Ситуационное изменение для перемещения в prokind
+           # К этому моменту значение по умолчанию уже заполнено, поэтому
+           # при необходимости перейдите к другим значениям
+           if ($values{proisagg} eq 't')
+           {
+               $values{prokind} = 'a';
+           }
+           elsif ($values{proiswindow} eq 't')
+           {
+               $values{prokind} = 'w';
+           }

Выполните новый скрипт:

$ cd /usr/local/qhb/include/catalog
$ perl  rewrite_dat_with_prokind.pl  pg_proc.dat

На этом этапе файл pg_proc.dat имеет все три столбца: prokind, proisagg и proiswindow, хотя они будут фигурировать только в тех строках, где им присваиваются значения, отличные от значений по умолчанию.

Удалите старые столбцы из файла pg_proc.h:

-    /* это агрегат? */
-    bool        proisagg BKI_DEFAULT(f);
-
-    /* это оконная функция? */
-    bool        proiswindow BKI_DEFAULT(f);

Наконец, выполните make reformat-dat-files, чтобы удалить бесполезные старые записи из файла pg_proc.dat.

Формат файла BKI

В этом разделе описывается, как сервер QHB интерпретирует файлы BKI. Это описание будет легче понять, если под рукой в качестве примера будет файл qhb.bki.

Вводимая в BKI информация состоит из последовательности команд. Команды формируются из нескольких синтаксических единиц, в зависимости от синтаксиса конкретной команды. Синтаксические единицы обычно разделяются пробельными символами, но это необязательно, если нет какой-то неоднозначности. Специальный разделитель команд отсутствует; следующая синтаксическая единица, которая синтаксически не может принадлежать предыдущей команде, начинает новую команду. (Обычно для ясности новую команду размещают в новой строке.) Синтаксическими единицами могут быть определенные ключевые слова, специальные символы (скобки, запятые и т. п.), идентификаторы, числа или строки в апострофах. Все они чувствительны к регистру.

Строки, начинающиеся с #, игнорируются.

Команды BKI

create имя_таблицы oid_таблицы [bootstrap] [shared_relation] [rowtype_oid oid] (имя1 = тип1 [FORCE NOT NULL | FORCE NULL ] [, имя2 = тип2 [FORCE NOT NULL | FORCE NULL ], ...])

Создать таблицу имя_таблицы с заданным oid_таблицы и столбцами, указанными в скобках.
Напрямую bootstrap.c поддерживает следующие типы столбцов: bool, bytea, char (1 байт), name, int2, int4, regproc, regclass, regtype, text, oid, tid, xid, cid, int2vector, oidvector, _int4 (массив), _text (массив), _oid (массив), _char (массив), _aclitem (массив). Хотя возможно создавать таблицы, содержащие столбцы других типов, это нельзя сделать, пока не будет создан и заполнен соответствующими записями каталог pg_type. (По сути это означает, что в каталогах начальной загрузки могут использоваться только эти типы столбцов, но другие каталоги могут содержать любые встроенные типы.)
С указанием bootstrap таблица будет создана только на диске; никакие записи о ней не будут введены в каталоги pg_class, pg_attribute и т. д. Таким образом, таблица не будет доступна для обычных операций SQL, пока такие записи не будут созданы специально (командами insert). Этот параметр используется для создания самих каталогов pg_class и им подобных.
Если указать shared_relation, таблица создается как разделяемая. Дополнительно посредством предложения rowtype_oid можно задать OID типа строки таблицы (OID записи в pg_type); если оно не указано, OID генерируется автоматически. (Предложение rowtype_oid бесполезно, если указано bootstrap, но его все равно можно добавить для документирования.)

open имя_таблицы

Открыть таблицу имя_таблицы для добавления данных. Любая другая таблица, открытая в данный момент, закрывается.

close имя_таблицы

Закрыть открытую таблицу. Имя таблицы должно задаваться для перепроверки.

insert ( [значение_oid] значение1 значение2 ... )

Добавить новую строку в открытую таблицу, используя в качестве значений столбцов значение1, значение2, и т. д.
Значения NULL можно задать с помощью специального ключевого слова _null_. Значения, не похожие на идентификаторы или цифровые строки, должны заключаться в апострофы. (Чтобы включить апостроф в значение, продублируйте его. Также в строке разрешено экранирование в стиле управляющей строки обратного слэша.)

declare [unique] index имя_индекса oid_индекса on имя_таблицы using имя_метода_доступа ( класс_оп1 имя1 [, ...] )

Создать индекс имя_индекса с заданным oid_индекса в таблице имя_таблицы с методом доступа имя_метода_доступа. Индекс строится по полям имя1, имя2 и т. д., и для них используются классы операторов класс_оп1, класс_оп2 и т. д. соответственно. Эта команда создает файл индекса и добавляет для него соответствующие записи в каталог, но не инициализирует содержимое индекса.

declare toast oid_таблицы_toast oid_индекса_toast on имя_таблицы

Создать таблицу TOAST для таблицы имя_таблицы. Таблице TOAST присваивается заданный oid_таблицы_toast, а ее индексу — oid_индекса_toast. Как и с declare index, заполнение индекса откладывается.

build indices

Заполнить индексы, объявленные ранее.

Структура файла BKI начальной загрузки

Команду open нельзя применять, если используемые ей таблицы не существуют и в них нет записей для открываемой таблицы. (Минимальный набор этих таблиц: pg_class, pg_attribute, pg_proc и pg_type.) Чтобы можно было заполнить сами эти таблицы, команда create с параметром bootstrap неявно открывает создаваемую таблицу для добавления данных.

Кроме того, команды declare index и declare toast нельзя применять, пока не будут созданы и заполнены нужные им системные каталоги.

Таким образом, структура файла qhb.bki должна быть следующей:

create bootstrap (создать) одну из критичных таблиц.

insert (добавить) данные, описывающие как минимум критичные таблицы.

close (закрыть).

Повторить для других критичных таблиц.

create (создать) (без bootstrap) некритичную таблицу.

open (открыть).

insert (добавить) желаемые данные.

close (закрыть).

Повторить для других некритичных таблиц.

Определить индексы и таблицы TOAST.

build indices (заполнить индексы).

Несомненно, существуют и другие, недокументированные зависимости, определяющие конкретный порядок.

Пример BKI

Следующая последовательность команд создаст таблицу test_table с OID 420, имеющую три столбца oid, cola и colb типов oid, int4 и text соответственно, и добавит две строки в эту таблицу:

create test_table 420 (oid = oid, cola = int4, colb = text)
open test_table
insert ( 421 1 'value 1' )
insert ( 422 2 _null_ )
close test_table

Как планировщик использует статистику

Эта глава основывается на материалах, изложенных в разделах Использование EXPLAIN и Статистика, используемая планировщиком и более подробно описывает то, как планировщик использует системную статистику для оценки количества строк, которые может вернуть каждая часть запроса. Это существенная часть процесса планирования, обеспечивающая большую часть исходного материала для расчета стоимости запроса.

Цель этой главы состоит не в том, чтобы подробно документировать код, а в том, чтобы представить обзор того, как он работает. Возможно, это ускорит обучение для тех, кто впоследствии захочет прочитать код.

Примеры оценки количества строк

В приведенных ниже примерах используются таблицы в базе данных регрессионного теста QHB. Обратите также внимание, что поскольку ANALYZE при создании статистики использует случайную выборку, после каждого выполнения ANALYZE результаты будут немного меняться.

Начнем с очень простого запроса:

EXPLAIN SELECT * FROM tenk1;

                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..458.00 rows=10000 width=244)

Как планировщик определяет мощность таблицы tenk1, рассматривается в разделе Статистика, используемая планировщиком, но повторяется здесь для полноты картины. Количество страниц и строк можно узнать в pg_class:

SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';

 relpages | reltuples
----------+-----------
      358 |     10000

Это текущее количество по состоянию на последние выполнение VACUUM или ANALYZE в этой таблице. Затем планировщик извлекает фактическое текущее количество страниц в таблице (это дешевая операция, не требующая сканирования таблицы). Если это значение отличается от relpages, то reltuples пропорционально изменяется, чтобы приблизиться к текущей оценке количества строк. В приведенном выше примере значение relpages является актуальным, поэтому количество строк равно reltuples.

Давайте перейдем к примеру с диапазонным условием в предложении WHERE:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000;

                                   QUERY PLAN
--------------------------------------------------------------------------------
 Bitmap Heap Scan on tenk1  (cost=24.06..394.64 rows=1007 width=244)
   Recheck Cond: (unique1 < 1000)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..23.80 rows=1007 width=0)
         Index Cond: (unique1 < 1000)

Планировщик проверяет условие предложения WHERE и ищет функцию избирательности для оператора < в pg_operator. Она хранится в столбце oprrest, и в данном случае там записано scalarltsel. Функция scalarltsel получает гистограмму для unique1 из pg_statistic. Для запросов, вводимых вручную, удобнее искать информацию в более простом представлении pg_stats:

SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='unique1';

                   histogram_bounds
------------------------------------------------------
 {0,993,1997,3050,4040,5036,5957,7057,8029,9016,9995}

Далее обрабатывается отрезок гистограммы, занимаемый числами «<1000». Это и есть избирательность. Гистограмма делит диапазон на равные частотные сегменты, поэтому остается только найти сегмент, в котором находится нужное значение, и подсчитать его долю и долю всех предшествующих ему сегментов. Значение 1000 явно находится во втором сегменте (993-1997). Предполагая линейное распределение значений внутри каждого сегмента, можно вычислить избирательность как:

selectivity = (1 + (1000 - bucket[2].min)/(bucket[2].max - bucket[2].min))/num_buckets
            = (1 + (1000 - 993)/(1997 - 993))/10
            = 0.100697

то есть сумма одного целого сегмента и пропорциональной доли второго, разделенная на количество сегментов. Теперь ожидаемое количество строк можно рассчитать как произведение избирательности и мощности tenk1:

rows = rel_cardinality * selectivity
     = 10000 * 0.100697
     = 1007  (rounding off)

Далее рассмотрим пример с условием равенства в предложении WHERE:

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'CRAAAA';

                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=30 width=244)
   Filter: (stringu1 = 'CRAAAA'::name)

Опять же, планировщик проверяет условие в WHERE и ищет функцию избирательности для =, и это функция eqsel. Для оценки равенства гистограмма бесполезна; вместо нее для определения избирательности используется список наиболее распространенных значений (most common value, MCV). Давайте рассмотрим MCV с некоторыми дополнительными столбцами, которые пригодятся позже:

SELECT null_frac, n_distinct, most_common_vals, most_common_freqs FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';

null_frac         | 0
n_distinct        | 676
most_common_vals  | {EJAAAA,BBAAAA,CRAAAA,FCAAAA,FEAAAA,GSAAAA,JOAAAA,MCAAAA,NAAAAA,WGAAAA}
most_common_freqs | {0.00333333,0.003,0.003,0.003,0.003,0.003,0.003,0.003,0.003,0.003}

Поскольку CRAAAA находится в списке MCV, избирательность определяется просто соответствующей записью в списке наиболее распространенных частот (most common frequency, MCF):

selectivity = mcf[3]
            = 0.003

Как и прежде, расчетное количество строк будет просто произведением избирательности и кардинальности tenk1:

rows = 10000 * 0.003
     = 30

Теперь рассмотрим тот же запрос, но с константой, отсутствующей в списке MCV:

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'xxx';

                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=15 width=244)
   Filter: (stringu1 = 'xxx'::name)

Здесь проблема в другом: как оценить избирательность, когда значение отсутствует в списке MCV. Для решения можно использовать тот факт, что искомое значение не находится в списке, в сочетании с частотой всех значений в списке MCV:

selectivity = (1 - sum(mvf))/(num_distinct - num_mcv)
            = (1 - (0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003 +
                    0.003 + 0.003 + 0.003 + 0.003))/(676 - 10)
            = 0.0014559

Нужно сложить все частоты из списка MCV и вычесть эту сумму из единицы, а затем разделить результат на количество остальных уникальных значений. Это число вычисляется, исходя из предположения, что часть столбца, которая не входит в MCV, равномерно распределяется среди всех остальных уникальных значений. Обратите внимание, что здесь нет значений NULL, так что нет нужды беспокоиться о них (в противном случае мы бы вычитали из числителя и долю NULL). Затем ожидаемое количество строк вычисляется как обычно:

rows = 10000 * 0.0014559
     = 15  (rounding off)

Предыдущий пример с условием unique1 < 1000 был упрощенной версией того, что действительно делает scalarltsel. Теперь, когда мы увидели пример использования MCV, можно описать процесс более детально. В данном случае пример был верен, поскольку unique1 является уникальным столбцом, а значит, у него нет MCV (очевидно, что никакое значение не встречается чаще, чем любое другое). Для неуникального столбца обычно существуют и гистограмма, и список MCV, и гистограмма не включает значения столбца, представленные в списке MCV. Это сделано для более точной оценки. В этой ситуации scalarltsel напрямую применяет условие (например «<1000») к каждому значению списка MCV и суммирует частоты MCV, для которых это условие является истинным. Это дает точную оценку избирательности в той части таблицы, которая содержит значения MCVs. Затем, так же, как описано выше, гистограмма используется для оценки избирательности в части таблицы, не содержащей MCV, а потом эти два числа складываются для оценки общей избирательности. Например, рассмотрим

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 < 'IAAAAA';

                         QUERY PLAN
------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=3077 width=244)
   Filter: (stringu1 < 'IAAAAA'::name)

Мы уже видели информацию MCV для stringu1, а вот его гистограмма:

SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';

                                histogram_bounds
--------------------------------------------------------------------------------
 {AAAAAA,CQAAAA,FRAAAA,IBAAAA,KRAAAA,NFAAAA,PSAAAA,SGAAAA,VAAAAA,XLAAAA,ZZAAAA}

Проверяя список MCV, мы обнаруживаем, что условие stringu1 < 'IAAAAA' удовлетворяется первыми шестью записями, но не последними четырьмя, поэтому избирательность для значений из множества MCV определяется как

selectivity = sum(relevant mvfs)
            = 0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003
            = 0.01833333

Суммирование всех MCF также говорит нам, что общая доля значений, представленных в списке MCV, составляет 0,03033333, и поэтому доля значений, представленная гистограммой, составляет 0,96966667 (опять же, нет значений NULL, иначе здесь их пришлось бы исключить). Видно, что значение IAAAAA расположено почти в конце третьего сегмента гистограммы. Используя довольно тривиальные предположения о частоте различных символов, планировщик получает оценку 0,298387 для части значений из гистограммы, которые меньше IAAAAA. Затем мы объединяем оценки для значений MCV и не MCV:

selectivity = mcv_selectivity + histogram_selectivity * histogram_fraction
            = 0.01833333 + 0.298387 * 0.96966667
            = 0.307669

rows        = 10000 * 0.307669
            = 3077  (rounding off)

В этом конкретном примере коррекция от списка MCV весьма невелика, поскольку распределение значений столбца на самом деле довольно плоское (статистика, показывающая эти конкретные значения как более распространенные, чем другие, как правило, является следствием ошибки выборки). В более типичном случае, когда некоторые значения являются значительно более распространенными, чем другие, этот сложный процесс эффективно повышает точность благодаря верному определению избирательности для наиболее распространенных значений.

Теперь рассмотрим случай с несколькими условиями в предложении WHERE:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000 AND stringu1 = 'xxx';

                                   QUERY PLAN
--------------------------------------------------------------------------------
 Bitmap Heap Scan on tenk1  (cost=23.80..396.91 rows=1 width=244)
   Recheck Cond: (unique1 < 1000)
   Filter: (stringu1 = 'xxx'::name)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..23.80 rows=1007 width=0)
         Index Cond: (unique1 < 1000)

Планировщик предполагает, что эти два условия независимы, так что их избирательности можно перемножить между собой:

selectivity = selectivity(unique1 < 1000) * selectivity(stringu1 = 'xxx')
            = 0.100697 * 0.0014559
            = 0.0001466

rows        = 10000 * 0.0001466
            = 1  (rounding off)

Обратите внимание, что количество строк, которые предположительно вернутся при сканирования битового индекса, отражает только условие, используемое для индекса; это важно, поскольку влияет на оценку стоимости последующих выборок из кучи.

Наконец мы рассмотрим запрос, включающий в себя соединение:

EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 50 AND t1.unique2 = t2.unique2;

                                      QUERY PLAN
--------------------------------------------------------------------------------------
 Nested Loop  (cost=4.64..456.23 rows=50 width=488)
   ->  Bitmap Heap Scan on tenk1 t1  (cost=4.64..142.17 rows=50 width=244)
         Recheck Cond: (unique1 < 50)
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..4.63 rows=50 width=0)
               Index Cond: (unique1 < 50)
   ->  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.00..6.27 rows=1 width=244)
         Index Cond: (unique2 = t1.unique2)

Ограничение на tenk1, unique1 < 50 вычисляется перед соединением с вложенным циклом. Все обрабатывается аналогично предыдущему примеру с диапазоном. На этот раз значение 50 попадает в первый сегмент гистограммы unique1:

selectivity = (0 + (50 - bucket[1].min)/(bucket[1].max - bucket[1].min))/num_buckets
            = (0 + (50 - 0)/(993 - 0))/10
            = 0.005035

rows        = 10000 * 0.005035
            = 50  (rounding off)

Ограничением для соединения является t2.unique2 = t1.unique2. Оператор — уже известный =, однако функцию избирательности (eqjoinsel) мы получаем из столбца oprjoin таблицы pg_operator. Функция eqjoinsel ищет статистическую информацию как для tenk2, так и для tenk1:

SELECT tablename, null_frac,n_distinct, most_common_vals FROM pg_stats
WHERE tablename IN ('tenk1', 'tenk2') AND attname='unique2';

tablename  | null_frac | n_distinct | most_common_vals
-----------+-----------+------------+------------------
 tenk1     |         0 |         -1 |
 tenk2     |         0 |         -1 |

В этом случае отсутствует информация MCV для unique2, поскольку все значения оказываются уникальными, а значит, используется алгоритм, который полагается только на число уникальных значений для обоих отношений с учетом значений NULL:

selectivity = (1 - null_frac1) * (1 - null_frac2) * min(1/num_distinct1, 1/num_distinct2)
            = (1 - 0) * (1 - 0) / max(10000, 10000)
            = 0.0001

То есть из единицы для каждого отношения вычитается доля значений NULL, а результат делится на максимальное число уникальных значений. Количество строк, которое, скорее всего, будет получено при соединении, вычисляется как мощность декартова произведения двух входных значений, умноженная на избирательность:

rows = (outer_cardinality * inner_cardinality) * selectivity
     = (50 * 10000) * 0.0001
     = 50

Если бы имелись списки MCV для двух столбцов, eqjoinsel использовала бы прямое сравнение со списками MCV для определения избирательности соединения части значений столбца, представленной в списке MCV. Оценка для остальных значений выполнялась бы способом, показанным выше.

Обратите внимание, что здесь inner_cardinality имеет значение 10000, то есть неизмененный размер tenk2. При проверке выходных данных EXPLAIN может показаться, что оценка строк соединения вычисляется как 50 * 1, то есть количество внешних строк умножается на предполагаемое количество строк, получаемых при каждым внутреннем индексном сканировании tenk2. Но это не так: размер отношения соединения оценивается до того, как выбирается какой-либо конкретный план соединения. Если все работает нормально, то два способа оценки размера соединения дадут примерно одинаковый результат, но из-за ошибки округления и других факторов иногда они заметно отличаются.

Примеры многомерной статистики

Функциональные зависимости

Многомерную корреляцию можно продемонстрировать с помощью очень простого набора данных — таблицы с двумя столбцами, содержащими одинаковые значения:

CREATE TABLE t (a INT, b INT);
INSERT INTO t SELECT i % 100, i % 100 FROM generate_series(1, 10000) s(i);
ANALYZE t;

Как объясняется в разделе Статистика, используемая планировщиком, планировщик может определить мощность таблицы t, используя количество страниц и строк, полученных из pg_class:

SELECT relpages, reltuples FROM pg_class WHERE relname = 't';

 relpages | reltuples
----------+-----------
       45 |     10000

Распределение данных очень простое: в каждом столбце имеется всего 100 уникальных значений, распределенных равномерно.

В следующем примере показан результат оценки условия WHERE для столбца a:

EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1;
                                 QUERY PLAN
-------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..170.00 rows=100 width=8) (actual rows=100 loops=1)
   Filter: (a = 1)
   Rows Removed by Filter: 9900

Планировщик проверяет условие и определяет, что избирательность этого предложения равна 1%. Сравнивая эту оценку и фактическое количество строк, мы видим, что эта оценка очень точна (в действительности совершенно точна, поскольку таблица очень маленькая). При изменении условия WHERE, чтобы использовался столбец b, генерируется идентичный план. Но посмотрите, что произойдет, если мы применим одно и то же условие к обоим столбцам, объединив их с помощью AND:

EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 1;
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=1 width=8) (actual rows=100 loops=1)
   Filter: ((a = 1) AND (b = 1))
   Rows Removed by Filter: 9900

Планировщик оценивает избирательность каждого условия индивидуально, приходя к тем же оценкам в 1%, что и выше. Затем он предполагает, что условия независимы, и поэтому перемножает их избирательности, выводя окончательную оценку избирательности, равную всего 0,01%. Это значительное преуменьшение, поскольку фактическое количество строк, соответствующих условиям (100), на два порядка больше.

Эту проблему можно решить путем создания объекта статистики, который предпишет команде ANALYZE вычислить многомерную статистику функциональной зависимости по двум столбцам:

CREATE STATISTICS stts (dependencies) ON a, b FROM t;
ANALYZE t;
EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 1;
                                  QUERY PLAN
-------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=100 width=8) (actual rows=100 loops=1)
   Filter: ((a = 1) AND (b = 1))
   Rows Removed by Filter: 9900

Многомерное количество уникальных значений

Аналогичная проблема возникает при оценке мощности наборов из нескольких столбцов, например числа групп, которые будут генерироваться предложением GROUP BY. Когда в GROUP BY задается один столбец, оценка уникальных значений (которые можно увидеть как расчетное количество строк, возвращаемых узлом HashAggregate) очень точна:

EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a;
                                       QUERY PLAN
-----------------------------------------------------------------------------------------
 HashAggregate  (cost=195.00..196.00 rows=100 width=12) (actual rows=100 loops=1)
   Group Key: a
   ->  Seq Scan on t  (cost=0.00..145.00 rows=10000 width=4) (actual rows=10000 loops=1)

Но без многомерной статистики оценка количества групп в запросе с двумя столбцами в GROUP BY, в частности, в следующем примере, отклоняется на порядок:

EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a, b;
                                       QUERY PLAN
--------------------------------------------------------------------------------------------
 HashAggregate  (cost=220.00..230.00 rows=1000 width=16) (actual rows=100 loops=1)
   Group Key: a, b
   ->  Seq Scan on t  (cost=0.00..145.00 rows=10000 width=8) (actual rows=10000 loops=1)

При переопределении объекта статистики, чтобы тот включал подсчет количества уникальных значений для двух столбцов, оценка значительно улучшается:

DROP STATISTICS stts;
CREATE STATISTICS stts (dependencies, ndistinct) ON a, b FROM t;
ANALYZE t;
EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a, b;
                                       QUERY PLAN
--------------------------------------------------------------------------------------------
 HashAggregate  (cost=220.00..221.00 rows=100 width=16) (actual rows=100 loops=1)
   Group Key: a, b
   ->  Seq Scan on t  (cost=0.00..145.00 rows=10000 width=8) (actual rows=10000 loops=1)

Списки MCV

Как упоминалось в подразделе Функциональные зависимости, функциональные зависимости являются очень дешевым и эффективным типом статистики, но их основное ограничение заключается в их глобальном характере (зависимости отслеживаются только на уровне столбцов, но не между отдельными значениями столбцов).

В этом подразделе рассматривается многомерный вариант списков MCV, очевидное расширение статистики по столбцам, описанной в разделе Примеры оценки количества строк. Такой вариант решает проблему с вышеупомянутым ограничением, храня отдельные значения, но это, естественно, выходит дороже с точки зрения построения статистики в ходе ANALYZE, затрат на хранение и времени, уходящего на планирование.

Взглянем еще раз на запрос из подраздела Функциональные зависимости, но теперь со списком MCV, созданным на том же наборе столбцов (не забудьте удалить функциональные зависимости, чтобы планировщик точно использовал только новую статистику).

DROP STATISTICS stts;
CREATE STATISTICS stts2 (mcv) ON a, b FROM t;
ANALYZE t;
EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 1;
                                   QUERY PLAN
-------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=100 width=8) (actual rows=100 loops=1)
   Filter: ((a = 1) AND (b = 1))
   Rows Removed by Filter: 9900

Оценка так же точна, как и с функциональными зависимостями, в основном благодаря тому, что таблица довольно мала и имеет простое распределение с небольшим количеством уникальных значений. Прежде чем рассматривать второй запрос, с которым функциональные зависимости справились не очень успешно, давайте просмотрим список MCV.

Просмотреть список MCV можно с помощью возвращающей множество функции pg_mcv_list_items.

SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
                pg_mcv_list_items(stxdmcv) m WHERE stxname = 'stts2';
 index |  values  | nulls | frequency | base_frequency
-------+----------+-------+-----------+----------------
     0 | {0, 0}   | {f,f} |      0.01 |         0.0001
     1 | {1, 1}   | {f,f} |      0.01 |         0.0001
   ...
    49 | {49, 49} | {f,f} |      0.01 |         0.0001
    50 | {50, 50} | {f,f} |      0.01 |         0.0001
   ...
    97 | {97, 97} | {f,f} |      0.01 |         0.0001
    98 | {98, 98} | {f,f} |      0.01 |         0.0001
    99 | {99, 99} | {f,f} |      0.01 |         0.0001
(100 rows)

Эта проверка подтверждает, что в двух столбцах есть 100 различных комбинаций, и все они примерно равновероятны (частота 1% для каждого из них). Базовая частота — это частота, вычисляемая из статистики по каждому столбцу независимо от статистики по нескольким столбцам. Если бы в любом из столбцов были значения NULL, это было бы указано в столбце nulls.

При оценке избирательности планировщик применяет все условия для элементов в списке MCV, а затем суммирует частоты подходящих элементов.

По сравнению с функциональными зависимостями списки MCV имеют два ключевых преимущества. Во-первых, в списке хранятся фактические значения, что позволяет решить, какие комбинации совместимы.

EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 10;
                                 QUERY PLAN
---------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=1 width=8) (actual rows=0 loops=1)
   Filter: ((a = 1) AND (b = 10))
   Rows Removed by Filter: 10000

Во-вторых, списки MCV обрабатывают более широкий диапазон типов предложений, а не только предложения с равенством, как функциональные зависимости. Например, взгляните на следующий запрос с диапазоном значений для той же таблицы:

EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a <= 49 AND b > 49;
                                QUERY PLAN
---------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=1 width=8) (actual rows=0 loops=1)
   Filter: ((a <= 49) AND (b > 49))
   Rows Removed by Filter: 10000

Статистика планировщика и безопасность

Доступ к таблице pg_statistic имеют только суперпользователи, поэтому обычные пользователи не могут получить из нее информацию о содержании таблиц других пользователей. Некоторые функции оценки избирательности будут использовать предоставленный пользователем оператор (оператор, находящийся в запросе, или связанный оператор) для анализа сохраненной статистики. Например, чтобы определить, применимо ли сохраненное наиболее распространенное значение, оценщик избирательности должен будет выполнить соответствующий оператор =, чтобы сравнить константу в запросе с сохраненным значением. Таким образом, данные в pg_statistic потенциально могут передаваться пользовательским операторам. Соответствующим образом изготовленный оператор может «сливать» передаваемые ему операнды намеренно (например, протоколируя их в журнал или записывая в другую таблицу) или случайно, (показывая их значения в сообщениях об ошибках), в обоих случаях потенциально демонстрируя данные из pg_statistic пользователю, который не должен их видеть.

Чтобы предотвратить это, ко всем встроенным функциям оценки избирательности применяется следующая мера безопасности. Чтобы иметь возможность использовать сохраненную статистику при планировании запроса, текущий пользователь должен либо иметь право SELECT для таблицы или вовлеченных столбцов, либо использовать оператор с характеристикой LEAKPROOF (точнее, она должна быть у функции, на которой основан оператор). Иначе оценщик избирательности будет вести себя так, словно статистика недоступна, и планировщик будет продолжать использовать стандартные или резервные предположения.

Если пользователь не имеет необходимых прав для таблицы или столбцов, то во многих случаях запрос в конечном итоге получит ошибку «отказано в доступе», и тогда этот механизм не будет виден на практике. Но если пользователь читает данные из представления с барьером безопасности, то планировщик может захотеть проверить статистику нижележащей таблицы, которая в противном случае недоступна для этого пользователя. В этом случае оператор должен быть герметичным, иначе статистика не будет использоваться. Это не будет проявляться напрямую, за исключением, возможно, получения неоптимального плана запроса. При подозрении на этот вариант можно попробовать запустить запрос от имени более привилегированного пользователя, чтобы посмотреть, не выбирается ли другой план.

Это ограничение применяется только в тех случаях, когда планировщику необходимо выполнить пользовательский оператор для одного или нескольких значений из pg_statistic. Соответственно, планировщику разрешается использовать общую статистическую информацию, например долю значений NULL или количество уникальных значений в столбце, независимо от прав доступа.

Содержащиеся в сторонних расширениях функции оценки избирательности, потенциально работающие со статистикой, вызывая пользовательские операторы, должны следовать тем же правилам безопасности. Практические руководства см. в исходном коде QHB.

Формат манифеста резервной копии

Манифест резервной копии, генерируемый утилитой qhb_basebackup, предназначен в первую очередь для того, чтобы эту копию можно было проверить с помощью утилиты qhb_verifybackup. Однако другие программные средства тоже могут прочитать файл манифеста резервной копии и использовать содержащуюся там информацию в своих целях. В связи с этим в данной главе описывается формат файла манифеста.

Манифест резервной копии представляет собой документ JSON в кодировке UTF-8. (Хотя в целом документы JSON должны быть представлены в Unicode, QHB допускает использование типов данных json и jsonb в любой поддерживаемой сервером кодировке. Для манифестов резервных копий подобное исключение не делается.) Этот документ JSON всегда содержит объект; ключи, находящиеся в этом объекте, описываются в разделе ниже.

Объект верхнего уровня в манифесте копии

Документ JSON, представляющий манифест копии, содержит следующие ключи.

PostgreSQL-Backup-Manifest-Version
Связанное с этим ключом значение всегда равно целому числу 1.

Files
Связанное с этим ключом значение всегда является списком объектов, каждый из которых описывает один файл, находящийся в резервной копии. В этом списке отсутствуют записи о файлах WAL, необходимых для использования этой копии, а также о самом манифесте. Структура каждого объекта в этом списке описана в разделе Объект файла в манифесте копии.

WAL-Ranges
Связанное с этим ключом значение всегда является списком объектов, каждый из которых описывает диапазон записей WAL, которые должны быть доступны для чтения из определенной временной шкалы, чтобы можно было воспользоваться этой резервной копией. Подробнее структура этих объектов описана в разделе Объект диапазона WAL в манифесте копии.

Manifest-Checksum
Этот ключ всегда находится в последней строке файла манифеста резервной копии. Связанное с ним значение является контрольной суммой всех предыдущих строк, вычисленной по алгоритму SHA256. Здесь используется фиксированный метод расчета контрольных сумм, чтобы клиенты могли провести инкрементальный синтаксический анализ манифеста. Хотя алгоритм SHA256 значительно дороже алгоритма CRC32C, обычно манифест достаточно мал, чтобы эти дополнительные вычисления ни на что особо не влияли.

Объект файла в манифесте копии

Это объект, описывающий один файл и содержащий ключ Path или Encoded-Path. Обычно он содержит ключ Path. Связанное строковое значение представляет собой путь к файлу относительно корневого каталога резервной копии. У файлов, расположенных в пользовательском табличном пространстве, первыми компонентами пути будут pg_tblspc и OID этого табличного пространства. Если путь является строкой не в кодировке UTF-8 или пользователь хочет, чтобы кодировались пути для всех файлов, то вместо ключа Path будет присутствовать ключ Encoded-Path. В нем содержатся те же данные, но закодированные в виде строки шестнадцатеричных цифр, каждая пара которых представляет один байт.

Всегда присутствуют следующие два ключа:

Size
Ожидаемый размер этого файла, в виде целого числа.

Last-Modified
Время последнего изменения файла, выданное сервером в процессе создания резервной копии. В отличие от других полей, хранящихся в копии, это поле не используется утилитой qhb_verifybackup. Оно добавлено исключительно для информации.

Если резервная копия была сделана с включенным вычислением контрольных сумм файлов, будут присутствовать следующие ключи:

Checksum-Algorithm
Алгоритм, который применялся для расчета контрольной суммы этого файла. В настоящее время этот алгоритм будет одинаковым для всех файлов в манифесте резервной копии, но в будущих выпусках это может измениться. На данный момент поддерживаются алгоритмы CRC32C, SHA224, SHA256, SHA384 и SHA512.

Checksum
Контрольная сумма, вычисленная для этого файла и хранящаяся в виде последовательности шестнадцатеричных цифр, по две на каждый байт контрольной суммы.

Объект диапазона WAL в манифесте копии

Объект, описывающий диапазон WAL, всегда содержит три ключа:

Timeline
Временная шкала для этого диапазона записей WAL, в виде целого числа.

Start-LSN
Позиция LSN, с которой должно начаться воспроизведение на указанной временной шкале, чтобы эту резервную копию можно было применить для восстановления. LSN хранится в формате, принятом в QHB, т. е. в виде строки, состоящей из двух разделенных слэшем строк длиной от 1 до 8 шестнадцатеричных символов каждая.

End-LSN
Самый первый LSN, на котором может закончиться воспроизведение на указанной временной шкале при восстановлении из резервной копии. Он хранится в том же формате, что и Start-LSN.

Обычно в манифесте описывается всего один диапазон WAL. Однако если копия была сделана с резервного сервера, из-за повышения переключившего временные шкалы во время ее создания, в манифесте могут присутствовать несколько диапазонов, относящихся к разным временным шкалам. К одной временной шкале всегда будет относиться только один диапазон WAL.

Модуль безопасного хранения QSS

QSS (Quantum Secure Storage), модуль безопасного хранения СУБД «Квант-Гибрид», позволяет создавать таблицы, которые шифруются с помощью криптоалгоритма «Кузнечик» (ГОСТ 34.1-2015) в режиме MGM (Р 1323565.1.026–2019) при записи на диск.

Описание

Шифрование осуществляет отдельное программное обеспечение — средство криптографической защиты (СКЗИ) Quantum Secure Storage (QSS).

Примечание
Данный продукт приобретается и лицензируется отдельно.

Сервер QSS можно устанавливать как одновременно с QHB, так и после, т. е. QSS можно установить и начать использовать, когда QHB уже находится в эксплуатации.

Сервер QSS следует запускать на том же хосте, что и сервер QHB, в противном случае может наблюдаться существенное снижение производительности. Клиентские библиотеки, необходимые для взаимодействия QHB с QSS, устанавливаются в том же пакете, что и сам сервер QSS. Пользователя, от имени которого запускается QHB, нужно добавить в специальную группу qss-client (группа создается автоматически при установке QSS). Также следует создать в QSS рабочий ключ, который будет использоваться для шифрования.

Подробную информацию о том, как устанавливать, настраивать QSS и создавать в нем ключи, см. в документации к QSS.

Использование

Возможности

Шифрование происходит на уровне отдельных таблиц. Включение шифрования таблицы включает также шифрование индексов этой таблицы, связанной таблицы TOAST и сообщений WAL о действиях в этой таблице. Тем не менее столбцы определенных типов, хранящиеся не в основном файле таблицы и не в TOAST (например, большие объекты (LOB), тип rbytea), будут незашифрованными.

Информацию о том, как включить шифрование RBytea, см. в описании расширения Rbytea.

Шифрование отдельных столбцов не реализовано и нецелесообразно, поскольку накладные расходы на шифрование практически не зависят от длины шифруемых блоков.

Также реализовано шифрование материализованного представления. Рекомендуется использовать шифрование материализованного представления, если в составе есть шифрованные данные. Однако обратите внимание, что автоматически шифрование данных при создании шифрованного материального представления не включается.

Шифрование таблиц каталога включить нельзя (например, шифровать тексты хранимых процедур на SQL).

ВАЖНО!
Если у зашифрованной таблицы есть DEFAULT CONSTRAINT, то значение по умолчанию хранится в каталоге в незашифрованном виде. То же самое и для текста триггеров, заполняющих шифрованную таблицу.

Также нельзя включить шифрование партиционированных таблиц.

Данные на диске и в кеше операционной системы зашифрованы (и это является преимуществом по сравнению с шифрованием на уровне диска), данные в памяти QHB не зашифрованы, так как к ним нужен оперативный доступ.

Не поддерживается копирование шифрованной таблицы из базы-шаблона в новую базу во время CREATE DATABASE; в частности, нельзя создавать шифрованные таблицы в базе template1.

Конфигурационные параметры

Настройки QSS находятся в отдельном конфигурационном файле qss2_config.toml; пример файла генерируется при инициализации кластера (при помощи расширения initdb).

  key = "key_name" # идентификатор ключа, существующего в QSS, которым будет производиться шифрование
  shmem = true # будет ли шифрование проводиться через разделяемую память
  provider = "compressed-append" # "compressed-append" или "two-phase" - способ хранения атрибутов nonce, tag шифрованных страниц в файлах QHB
  socket_address = "/run/qss/client.socket" # для связи с сервером qss; при установке QSS по умолчанию менять не будет необходимости

Параметры shmem и provider не рекомендуется менять без консультации с техподдержкой, поскольку значения по умолчанию обеспечивают наилучшую производительность. Параметр provider нельзя менять, когда уже имеются зашифрованные данные.

В основном конфигурационном файле использование QSS включается параметром

qss_mode = 2 # По историческим причинам это целочисленный параметр, допустимые значения 2 и 0

При запуске с qss_mode = 2 проверяется корректность настройки QSS, и если обнаруживаются какие-либо проблемы, то QHB не запускается.

Запуск с qss_mode = 0, когда есть зашифрованные таблицы, разрешен, но обратиться к зашифрованным таблицам будет невозможно.

Создание таблицы

При создании таблицы или материализованного представления нужно указать предложение using qss.

create table tab_qss(c1 int, c2 text) using qss;

create materialized view mat_view_qss using qss as
    select c1, string_agg(c2, ','), count(1) from tab_qss group by c1;

Превратить уже созданную таблицу в зашифрованную или наоборот (с помощью ALTER TABLE) нельзя.

RBytea

Модуль RBytea позволяет создавать столбцы типа RBytea.

Шифрование всех столбцов типа RBytea регулируется одним параметром, а не отдельно для каждой таблицы. Однако допускается иметь одновременно шифрованные и не шифрованные значения RBytea. Каждое отдельное значение шифруется (или не шифруется) в зависимости от значения параметра в конкретный момент. Таким образом, вы можете включать и выключать шифрование RBytea.

Параметр

rbytea.filesystem_qss_mode = 2

При qss_mode = 0 значение этого параметра игнорируется.

Подробную информацию о параметрах конфигурирования RBytea см. в разделе Параметры конфигурации расширения Rbytea.

Примечание
При шифровании RBytea невозможен такой режим эксплуатации значений этого типа, когда данные типа RBytea хранятся в единственном экземпляре для нескольких узлов кластера на сетевом хранилище, т. к. разные узлы будут использовать разные ключи QSS

Шифрование статистик

Статистики по данным таблицы могут содержать чувствительную информацию, такую как наиболее частотные значения в каждой колонке. Статистики по шифрованным таблицам желательно тоже шифровать. Для этого надо дополнительно включить параметр qss_encrypt_statistic.

После этого статистики помещаются вместо таблиц каталога pg_statistic, pg_statistic_ext_data в аналогичные шифрованные таблицы qss_statistic, qss_statistic_ext_data. Статистики нешифрованных таблиц тоже будут храниться там.

После переключения статистики будут недоступны в моменте, что может приводить к плохим планам выполнения запросов. У вас есть следующие варианты, как их заполнить:

запустить VACUUM ANALYZE;

подождать, пока тоже самое сделает автовакуум (т.е. если ничего не делать, то статистики соберутся "сами");

скопировать имеющиеся статистики запросом, чтобы не ждать VACUUM ANALYZE:

delete from qss_statistic;
insert into qss_statistic select * from pg_statistic;
delete from qss_statistic_ext_data;
insert into qss_statistic_ext_data select * from pg_statistic_ext_data;

(При выключении qss_encrypt_statistic копирование в обратную сторону.)

После включения шифрования статистик рекомендуется удалить нешифрованные

delete from pg_statistic;
delete from pg_statistic_ext_data;

ВНИМАНИЕ
Таблицу pg_statistic_ext трогать не надо ни в каких сценариях!

Обслуживание

В этом разделе под терминами «расшифровать таблицу»/«зашифровать таблицу» подразумевается выгрузка содержимого таблицы и последующее ее пересоздание. Это можно сделать разными способами, например, перенести данные в другую таблицу в той же базе. При этом вам может понадобиться удалить и заново воссоздать ограничения, индексы и т. д.

Примечание
К данным операциям применимы общие рекомендации по ETL (Extract, Transform, Load — извлечение, преобразование, загрузка).

Нельзя загружать данные в зашифрованную таблицу с помощью QDL. С незашифрованной таблицей этот модуль использовать можно, но в сценариях, описанных ниже, такая возможность не пригодится.

Смена ключа

ВНИМАНИЕ
Не меняйте ключ, если у вас есть зашифрованные данные. Смена ключа возможна только при наличии текущего активного ключа. Сменить ключ после того, как у ключа, на котором зашифрованы данные, истек срок действия, будет невозможно.

Расшифруйте все данные.

Убедитесь в отсутствии зашифрованных таблиц.

Если использовались зашифрованные статистики, то эти данные надо удалить: выключить qss_encrypt_statistic, потом выполнить:

truncate table qss_statistic;
truncate table qss_statistic_ext_data;

(truncate table можно делать шифрованным таблицам и при выключенном QSS).

Создайте другой ключ в QSS, запишите этот ключ в файл qss2_config.toml и перезапустите QHB.

Снова зашифруйте таблицы.

Опционально включить шифрование статистик.

Не допускайте истечения срока действия ключа, иначе прочесть данные будет невозможно. Узнать срок действия ключа можно в административном приложении QSS.

Миграция с QSS 1.0

Миграция с QSS версии 1.0 будет производиться одновременно с миграцией на другую версию QHB. Нужно расшифровать все таблицы, провести миграцию, затем снова зашифровать таблицы.

Создание резервной копии

Резервную копию можно будет восстановить только на тот же хост, поскольку ключи шифрования привязаны к хосту и не могут быть перенесены на другую машину (это основное правило использование QSS, связанное с требованиями безопасности). Если хост, на котором ранее располагались QSS и QHB, более не может использоваться по каким либо причинам (например, техническая авария), то возможно восстановление QHB на другой машине при условии восстановления из резервной копии ключей QSS (информацию о резервном копировании ключей см. в документации, поставляемой вместе с продуктом qss)

Резервную копию рекомендуется создавать с помощью утилиты qbackup. При этом резервная копия создается обычным способом, однако не рекомендуется использовать потоковое резервное копирование с указанием wal-method=stream. Для формирования зашифрованной потоковой резервной копии требуется наличие непрерывной архивации WAL и потокового резервного копирования в режиме wal-method=fetch или wal-method=none.

Также нельзя использовать автоматическое переключение узла кластера, с которого проводится резервное копирование. Файлы резервной копии, взятые с разных хостов, не должны попадать в один каталог резервных копий, поэтому автоматическое переключение мастера не должно приводить к переключению резервного копирования на другой узел.

Подробную информацию см. в документацию по QSS.

Репликация

На каждом узле необходим свой сервер QSS. У каждого узла будет свой ключ, при этом имена ключей могут совпадать, поскольку сами они все равно будут разными. Это позволит иметь на всех узлах идентичные файлы qss2_config.toml.

Допустимы следующие варианты (см. раздел Сравнение различных решений) репликации:

Потоковая репликация (синхронная и асинхронная)

Логическая репликация

Репликация основной-резервный на основе триггеров

Репликация на основе SQL в ПО промежуточного слоя (через репитер запросов и т. п.)

Варианты репликации, которые не будут работать совместно с QSS:

На разделяемых дисках (сетевой диск или репликация на уровне FS)

Доставка журналов упреждающей записи

На каждом узле должно быть задано одинаковое значение параметра qss_mode. При включении и настройке шифрования на мастере обязательно требуется включение и настройка шифрования на реплике до начала заполнения шифрованной таблицы данными. В противном случае репликационный узел прекратит функционирование

ВАЖНО!
Подключить узел к потоковой репликации можно, только пока нет зашифрованных таблиц. В том числе не должно быть включено шифрование статистик, таблицы qss_statistic и qss_statistic_ext_data пустые.

Особенности и ограничения шифрования в QHB

Отсутствие возможности зашифровать и расшифровать (с помощью команды ALTER TABLE) уже созданную таблицу.

При шифровании RBytea нельзя хранить данные этого типа в единственном экземпляре для нескольких узлов кластера на сетевом хранилище.

Перешифрование возможно только путем расшифрования и повторного шифрования.

Отсутствие возможности загружать данные в зашифрованные таблицы с помощью модуля QDL.

Восстановление резервной копии возможно только на тот же хост, с которого она была сделана.

Все узлы кластера должны быть созданы до начала шифрования. Подключить новый узел потоковой репликации после начала шифрования нельзя.

Отсутствие возможности зашифровать партиционированные таблицы.

Отсутствие возможности использовать для шифрованных таблиц параметр APPEND_ONLY.

Отсутствие возможности шифрования таблиц каталога (например, хранимые процедуры).

Отсутствие возможности шифрования отдельных столбцов.

База, в которой есть шифрованные таблицы, не может быть использована как шаблон в CREATE DATABASE.

Асинхронный пул соединений QCP

Асинхронный пул соединений (Quantum Connection Pool, QCP) обеспечивает подключение и поддержку связи с удаленными клиентами.

Общий принцип работы

QCP принимает входящие подключения от удаленных клиентов по адресу, указанному в listening_address (по умолчанию — 0.0.0.0:8080), и и перенаправляет их трафик серверам баз данных, перечисленных в разделе servers. Соединения с серверами устанавливаются (и завершаются) автоматически, по мере необходимости.

В зависимости от режима работы, задаваемого параметром relay_mode, перенаправление трафика данных от клиентов к серверам осуществляется одним из следующих способов:

relay_mode: Session: при первом обращении к серверу клиенту выделяется уникальное соединение с базой данных, которое возвращается в пул только при отключении клиента.

relay_mode: Smart: при обращении к серверу клиенту выделяется уникальное соединение с базой данных, которое возвращается в пул тогда и только тогда, когда в ответе от сервера будет стоять флаг Idle (см. раздел Форматы сообщений, сообщение ReadyForQuery).

С полным описанием настроек можно ознакомиться в примере конфигурационного файла qcp/config-example.yaml.

Запуск и работа

Запуск осуществляется с помощью утилиты qcp (qcp --help для списка параметров), остановка — утилитой qcp-ctrl (qcp-ctrl --help для списка параметров). Например, чтобы остановить запущенный экземпляр QCP, необходимо выполнить команду qcp-ctrl quit.

Вывод записей журнала в процессе работы QCP контролируется параметром log_output (см. qcp/config-example.yaml), при этом уровень протоколирования задается параметром log_level (подробнее про уровни протоколирования см. на справочной странице LevelFilter на соответствующем сайте). Например, чтобы выводить записи журнала уровня Info и выше в файл /tmp/qcp.log, необходимо задать следующие параметры в файле конфигурации:

log_level: Info
log_output:
    file: /tmp/qcp.log

Потребление памяти

При запуске программы единовременно выделяется количество памяти, указанное в разделе arena файла конфигурации:

# Конфигурация памяти (необязательно)
arena:
    chunk_size: 65 KB # Размер одного блока памяти; поддерживаются суффиксы B, KB, MB, GB

    # Общее количество потребляемой памяти можно указать, либо используя параметр
    # «количество блоков памяти»:
    chunks_count: 3150 # Количество таких блоков
    # либо указав общее количество напрямую:
    total_size: 3.1 GB # Поддерживаются суффиксы B, KB, MB, GB

Подключение к СУБД

QCP может подключаться либо к одиночному серверу СУБД, либо к кластеру СУБД, управляемому Patroni.

Для подключения к серверу СУБД в разделе server: необходимо указать его адрес, например:

server:
    # Одиночный сервер
    address: # Тип и адрес подключения (TCP или сокет домена Unix)
      Tcp: "127.0.0.1:1234"
      # Unix: "/home/mexus/devel/optim/qhb-with-rust/build/dbsockets/.s.PGSQL.5432"

Для подключения к кластеру СУБД, управляемому Patroni:

server:
    # Кластер Patroni
    patroni:
      # Сервера (узлы) из кластера
      nodes:
        - dbms_address: # Адрес СУБД
            Tcp: "127.0.0.1:1234"
          rest_url: "http://127.0.0.1:8090" # URL REST API patroni
        - dbms_address: # Адрес СУБД
            Unix: "/путь/к/сокету/unix"
          rest_url: "http://127.0.0.2:8090" # URL REST API patroni
      cluster_name: my_cluster  # Имя кластера (используется как префикс для метрик).
      single_try_timeout: 5 sec # Необязательный параметр: максимальное ограничение времени на
                                # одну попытку узнать, какой из серверов кластера мастер.
      master_check_interval: 1500 ms # Необязательный параметр: как часто
                                     # опрашивать кластер. По умолчанию — 1500
                                     # миллисекунд.

Необходимо перечислить список всех узлов кластера, указывая адрес СУБД и адрес REST API Patroni для каждого узла. Так же необходимо задать имя кластера с помощью параметра cluster_name.

При таком подключении QCP будет регулярно опрашивать Patroni на всех узлах с целью определения текущего мастера. Если мастер изменяется, то QCP прерывает все текущие запросы от подключенных клиентов и подключается к новому мастеру.

Также можно указать дополнительные параметры:

single_try_timeout: ограничение по времени на каждый REST запрос,

master_check_interval: как часто опрашивать кластер с целью определения текущего мастера.

Независимо от того, осуществляется ли подключение к кластеру или к одной СУБД, для описания подключений используются следующие необязательные параметры:

inactive_timeout: временной интервал, после которого неактивное соединение с сервером будет закрыто при условии наличия минимального количества подключений.

lifetime: примерное максимальное время жизни соединения к серверу, по истечении которого оно будет закрыто.

heart_beat_interval: интервал проверки допустимости соединения с сервером в период, когда подключение не ассоциировано ни с одним клиентом.

Многопользовательский режим

QCP поддерживает возможность подключения к кластеру СУБД, используя различные пары пользователь-БД.

Для каждой из таких пар создается отдельный пул подключений. Когда клиент подключается к QCP, он указывает логин пользователя и имя БД, и, в соответствии с этими параметрами, QCP помещает его в нужный пул.

При попытке подключения к QCP с парой пользователь-БД, которая отсутствует в конфигурации, клиент получит ошибку:

FATAL:  Unknown user name, database name, or invalid combination of user and database
-- КРИТИЧНО: Неизвестное имя пользователя, имя базы данных или недопустимое
-- сочетание пользователя и базы данных

При этом в логах QCP появится примерно следующее сообщение:

[qcp::relay_helper] [WARN] Client ... startup sequence failed: Unknown user-database pair: user "user", db "db"
-- (сбой в последовательности запуска: неизвестная пара пользователь-БД:
-- пользователь "user", БД "db")

Пары пользователь-БД перечисляются в секции connections: раздела server:, например:

      # Первый пул подключений.
      - username: user # Имя пользователя
        password: something # Необязательный параметр: пароль для аутентификации
        database: utilities # Необязательный параметр: имя базы данных.
                            # Если не указана, в качестве имени базы будет
                            # использоваться имя пользователя.
        backends: # Количество подключений с данным именем пользователя и базой
            initial: 1 # .. при старте пула
            min: 1     # .. минимальное
            max: 3     # .. максимальное
        # Необязательная секция. Описывает то, как клиент должен быть
        # аутентифицирован на QCP. Если секция не задана, то для авторизации
        # пользователя с данным именем пользователя для данной базы на QCP будет
        # использоваться тот же пароль, какой задан для сервера, если он задан.
        client_auth:
            # Метод аутентификации: trust или md5.
            method: md5
            # Пароль для авторизации. Если не задан, будет использоваться тот
            # же, что и для подключения QCP к базе.
            password: lol2
        options: # Необязательный параметр: дополнительные параметры подключения,
                 # передаваемые базе данных
            option_name: option_value
      # Второй пул подключений.
      - username: qhb # Имя пользователя
        backends: # Количество подключений с данным именем пользователя и базой
            initial: 2 # .. при старте пула
            min: 2     # .. минимальное
            max: 5     # .. максимальное

Для описания каждого пула подключений для пары пользователь-БД требуется указать имя пользователя (username) и имя БД (database). Поле database можно пропустить, тогда оно примет то же значение, что и username.

Помимо этого, необходимо указать ограничения на количество подключений к СУБД в данном пуле, в секции backends::

initial указывает, сколько будет открыто подключений к СУБД с данной парой пользователь-БД при старте QCP,

min — ограничение на минимальное количество подключений к СУБД, может быть меньше, чем initial.

max — максимальное количество подключений к СУБД. Должно быть не меньше, чем initial и min.

Если для подключения к СУБД с данными пользователем и БД требуется пароль, то его можно указать в поле password.

Так же доступна секция options:, позволяющая передать произвольные аргументы командной строки для сервера в виде имя: значение.

Авторизация клиентов на QCP

По умолчанию для каждой пары пользователь-БД для авторизации пользователей QCP использует такие же настройки, как и для авторизации QCP на СУБД: если СУБД требует пароль, то и QCP будет требовать от пользователя его же, если СУБД не требует пароль, то и QCP не будет.

Данное поведение можно переопределить, используя секцию client_auth::

поле method позволяет переопределить метод авторизации,

поле password позволяет задать пароль, отличный от пароля, используемого для подключения к СУБД.

Применение параметров без перезапуска QCP

Некоторые параметры конфигурации можно изменить, не перезапуская QCP. Такой способ называется «перезагрузка файла конфигурации».

Для этого необходимо изменить требуемые параметры в конфигурационном файле, после чего выполнить

sudo systemctl reload qcp

Если QCP работает не под управлением systemd, то необходимо определить идентификатор процесса QCP (обратитесь к системному администратору, чтобы узнать, как это сделать на вашей ОС), после чего выполнить следующую команду, подставляя найденный идентификатор вместо ***

***:

sudo kill -USR1 <pid>

Для того чтобы обновленная конфигурация применилась, необходимо, чтобы конфигурационный файл был допустимым. В противном случае в логах QCP отобразится невозможность применения обновленной конфигурации, и QCP продолжит работать в прежнем режиме. Частичное применение конфигурации в таком случае исключено.

Если применение возможно, в логах появится соответствующая запись:

[INFO] Reloaded configuration file /путь/к/config.yaml
-- [ИНФОРМАЦИЯ] перезагружен файл конфигурации /путь/к/config.yaml

Обратите внимание, что для применения обновленной конфигурации требуется некоторое время, которое может достигать нескольких секунд при высокой нагрузке.

Применение параметров подключения для каждой пары пользователь-БД сопровождается записями в логе:

[INFO] Updating connection configuration for user "qhb", db "qhb"
[INFO] Applied new connection parameters for user "qhb", db "qhb"
-- [ИНФОРМАЦИЯ] Обновление конфигурации подключения для пользователя "qhb", БД "qhb"
-- [ИНФОРМАЦИЯ] Добавлены новые параметры подключения для пользователя "qhb", БД "qhb"

Применение параметров входящих подключений сопровождается записью в журнале:

[INFO] Incoming parameters updated
-- [ИНФОРМАЦИЯ] Обновлены параметры входящих подключений

Изменение следующих параметров не может быть осуществлено без перезапуска QCP, поэтому они будут проигнорированы QCP при перезагрузке файла конфигурации:

arena

log_level

log_output

log_colouring

log_timestamps

worker_threads

Следующие параметры будут обновлены во время следующего их использования:

accept_clients_no_backends: параметр будет обновлен при подключении следующего клиента, либо же когда появится доступное подключение к СУБД, если прием новых подключений от клиентов был приостановлен.

в секции servers_retry_back_off параметры min_wait, max_wait и max_wait_at будут обновлены при следующей попытке установить подключение к СУБД.

в секции server параметры inactive_timeout, lifetime, check_interval и heart_beat_interval будут применены к подключениям к СУБД, установленным после перезагрузки файла конфигурации.

Изменение типа подключения к СУБД (одиночный сервер, кластер под управлением Patroni) приведет к отключению всех текущих клиентов и разрыву всех текущих подключений к СУБД.

Удаление подключения из секции connections: раздела server: приведет к отключению всех клиентов, использующих такие подключения, а также к разрыву всех таких подключений к СУБД.

Изменение имени пользователя (username), имени БД (database), пароля (password) или параметров командной строки сервера (options) у подключения приведет к отключению всех клиентов, использующих такие подключения, а также к разрыву всех таких подключений к СУБД и последующему переподключению к СУБД с обновленными параметрами.

Изменение поля client_auth у подключения будет применено для всех последующих подключений к QCP.

Модуль прямой загрузки больших объемов данных QDL

Модуль прямой загрузки данных (Quantum Data Loader, QDL) — утилита, позволяющая осуществить загрузку из файла формата CSV в таблицу формата QHB согласно конфигурации. Скорость работы при этом существенно выше, чем при выполнении команд INSERT и COPY, за счет использования оптимизированного многопоточного кода, отсутствия блокировок таблицы и обхода транзакционного ядра.

Поддерживаемые типы полей

Типы | Названия
Целочисленные | smallint, int2, integer, int4, int, bigint, int8
Числовые с плавающей запятой | float4, real, float8, double precision
Последовательности | smallserial, serial, bigserial
Текстовые | character, char, varchar, character varying, text
Числа с указанной точностью | decimal, numeric, dec
Логические | bool, boolean
Идентификаторы | uuid, oid
Временные | timestamp, date
Двоичные | bytea

Примечание
В текущей версии QDL допускается использование значений NaN (Not a Number) в таких типах данных, как float (числовые с плавающей запятой) и numeric (числа с указанной точностью). В дополнение к обычным числовым значениям и NaN float также имеют специальные значения: infinity и -infinity. Обратите внимание, что все значения, находящиеся в файле формата CSV, не заключаются в кавычки.

Идентификаторы UUID записываются в виде последовательности шестнадцатеричных цифр в нижнем регистре, разделенных знаками минуса на несколько групп, в таком порядке: группа из 8 цифр, за ней три группы из 4 цифр и наконец группа из 12 цифр, что в сумме составляет 32 цифры. Пример UUID в этом стандартном виде: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11. Временные типы поддерживают такие специальные значения как infinity, -infinity и epoch.

Синтаксис

qdl [флаги] [параметры] [субкоманды]

Флаги:

-h
--help
Выводит справочную информацию об аргументах командной строки qdl.

-v
--verbose
Выводит отладочную информацию qdl.

-V
--version
Выводит информацию о версии qdl.

Параметры:

-c
--config <файл_конфигурации>
Путь к файлу конфигурации qdl.

-d
--data <файл_данных>
Путь к исходным данным. Используйте '-', чтобы читать из stdin.

Субкоманды:

create_table
Генерирует SQL-скрипт для создания таблицы.

help
Выводит список доступных субкоманд или справку по заданным субкомандам.

insert_values
Заполняет файл с таблицей значениями.

validate
Проверяет правильность файла CSV с данными.

Команда create_table

Генерирует SQL-скрипт, создающий таблицу СУБД согласно конфигурации, а также заполняет ее первыми тремя кортежами CSV-таблицы. Это требуется для создания структуры таблицы, которая будет заполнена данными. Также это позволяет получить пути расположения файлов и их названия (OID, требуемый для генерирования командой insert_values).

qdl --config <файл_конфигурации> --data <файл_данных> create_table [флаги]

Флаги:

-h
--help
Выводит справочную информацию об аргументах командной строки create_table.

-V
--version
Выводит информацию о версии create_table.

Примечание
В формате CSV все символы являются значимыми. Значение в таблице, дополненное пробелами или любыми другими символами, кроме разделителя, будет включать эти символы. Это может приводить к ошибкам при заполнении таблицы данными из системы, дополняющей строки CSV пробельными символами до некоторой фиксированной ширины. В случае возникновения такой проблемы необходимо обработать файл CSV и удалить из него замыкающие пробельные символы, прежде чем загружать данные из него в QHB.

Пример использования:

qdl --config config.yml
    --data data.csv
    create_table

Команда insert_values

Производит загрузку из файла в формате CSV в таблицу формата, используемого QHB. Данная команда выполняется многопоточно; количество ядер, отводимое под параллельную обработку, указывается в файле конфигурации. Пользователь может установить пороговый процент срабатывания механизма очистки избыточно выделенной памяти.

Параметр --out-dir <выходной_каталог> является обязательным для ввода из командной строки.

Также рекомендуется предварительно удостовериться, что версия компоновки страницы QHB равна 4. Сделать это можно, например, при помощи расширения pageinspect::page_header.

qdl --config <файл_конфигурации> --data <файл_данных> insert_values [параметры] --out-dir <выходной_каталог> [флаги]

Флаги:

-h
--help
Выводит справочную информацию об аргументах командной строки insert_values.

-V
--version
Выводит информацию о версии insert_values.

Параметры:

-o
--out-dir <выходной_каталог>
Выходной каталог.

-r
--ram-usage-percent <%_оперативной_памяти>
Процент используемой оперативной памяти, после которой запускается сборщик мусора для внутренних буферов (от 1 до 99; значение по умолчанию — 80).

Пример использования:

qdl --config config.yml \
    --data data.csv \
    insert_values \
    --out-dir output/

Команда validate

Проверяет логическую целостность CSV-файла и согласованность структуры данных с файлом конфигурации, сигнализируя об ошибках соответствующим кодом завершения.

    qdl --config <файл_конфигурации> --data <файл_данных> validate [флаги]

Флаги:

-h
--help
Выводит справочную информацию об аргументах командной строки validate.

-V
--version
Выводит информацию о версии validate.

Пример использования:

qdl --config config.yml
    --data data.csv
    validate

Файл конфигурации

Файл конфигурации имеет следующие разделы:

general — базовая конфигурация приложения. Содержит поля:

threads (целочисленное) — количество потоков-обработчиков сырых данных.

chunk_size (целочисленное, необязательное) — размер внутреннего буфера передачи данных; данный параметр может меняться с целью оптимизации в нестандартных аппаратных конфигурациях.

checksum (логическое, необязательное, по умолчанию true) — задает расчет контрольных сумм.

data_source — выбор источника данных. В данной версии единственным поддерживаемым форматом является csv. Содержит поля:

delimiter — разделитель столбцов для CSV.

double_quote — если true, то две кавычки подряд воспринимаются как одна экранированная кавычка.

escape — символ, используемый для экранирования.

comment — символ, отмечающий начало комментария.

output — конфигурация выходных данных. Содержит поля:

segment_size (целочисленное, необязательное) — максимальное количество страниц в сегменте базы данных.

oid (целочисленное) — идентификатор основной таблицы.

toast_oid (целочисленное, необязательное) — идентификатор таблицы TOAST.

table — конфигурация таблицы. Содержит поля:

name (строковое) — имя таблицы.

fields (повторяющиеся field) — описание каждого из полей (например, field_1 : varchar и необязательная пометка [nullable]).

Пример файла конфигурации

# Конфигурация системы:
general:
    # Количество потоков, выделяемых для анализа и сериализации строк.
    # Обратите внимание, что помимо этих потоков обязательно выделяются еще два:
    # поток чтения и поток записи в файл.
    threads: 2
    checksum: true

# Конфигурация исходных данных:
data_source:
    csv:
        # Разделитель столбцов для CSV:
        delimiter: ";"

# Конфигурация получаемых данных:
output:
    # Идентификатор объекта. Определяет целочисленный идентификатор для группы
    # файлов целевой таблицы.
    oid: 16385
    # Идентификатор таблицы для хранения больших данных
    toast_oid: 16386

# Конфигурация целевой таблицы:
table:
    # Имя таблицы. Требуется для задания имени создаваемой таблицы.
    name: "t_qdl"
    # Столбцы в формате "имя" : "тип"
    fields:
        id : integer
        social_id : integer [nullable]
        first_name : varchar
        last_name : varchar
        city : varchar (30)
        profession : varchar (30) [nullable]
        salary : double precision
        description : varchar [nullable]

Сценарий использования

ВНИМАНИЕ!
Во избежание проблем с правами доступа все последующие операции требуется проводить от имени пользователя, который запускает экземпляр базы данных.

ШАГ 0 (необязательный):

Проверить CSV файл на согласованность с конфигурацией. Это может сэкономить значительное количество времени в случаях, когда конвертируется CSV-файл большого объема, так как если в ходе работы команды insert_values будет обнаружена ошибка, то ее выполнение придется повторить после исправления причин ее возникновения.

qdl --config config.yml --data data.csv validate

ШАГ 1:

Сгенерировать SQL-скрипт для создания таблицы, в которую планируется загрузить данные, а впоследствии использовать ее в СУБД. Также этот скрипт получает OID (идентификатор объекта; потребуется для файла конфигурации insert_values) и пути файлов базы данных. Эти операции могут быть произведены вручную, но во избежание возможных ошибок рекомендуется использовать скрипт.

qdl --config config.yml --data data.csv create_table

ШАГ 2:

Исполнить SQL-скрипт. OID, полученный в ходе выполнения, требуется разместить в файле конфигурации config.yml.

ШАГ 3:

Загрузить таблицу. Этот процесс может занять значительное время, в зависимости от объема загружаемых данных. Потоки, выполняющие чтение/конвертацию/запись, имеют определенные названия; это позволит системному администратору оценить загруженность каждого отдельно взятого потока ядра и определить их количеством таким образом, чтобы добиться оптимальной производительности. Также нужно заметить, что внутренние буфера qdl не имеют ограничения размера, а значит, при очень медленной записи на диск возможна ситуация, когда эти буферы займут всю оперативную память.

qdl --config config.yml --data data.csv insert_values --out-dir ./output_dir/

ШАГ 4:

Заменить файлы СУБД сгенерированными при помощи qdl — копируем файлы при помощи команд операционной системы. После этого нужно выполнить следующий запрос:

SELECT qhb_attach_qdl_data('имя_таблицы');

При этом сбрасывается кэш данных таблицы и соответствующей ей таблицы TOAST (если она имеется), происходит переподключение файлов данных таблицы, переиндексация таблицы TOAST, регистрация данных для функциональности CDC (Change Data Capture).

Примечание
Для чтения из stdin нужно использовать вертикальную черту (|). Примеры использования команд:

Команда create_table:

cat data.csv | qdl --config config.yml --data - create_table

Команда validate:

cat data.csv | qdl --verbose --config config.yml --data - validate

Команда insert_values:

cat data.csv | qdl --config config.yml --data - insert_values --out-dir output/

Вызов справочной информации:

qdl -h

См. также

Пример использования QDL

Пример использования QDL

Общие замечания

QDL (Quantum Data Loader) — дополнительный модуль, который не входит в основную поставку QHB и устанавливается отдельно. Эта утилита позволяет на основе заданного описания структуры таблицы и соответствующих данных в CSV-файле сформировать в многопоточном режиме файл таблицы, минуя обычные механизмы базы данных. Полученный файл можно скопировать в качестве файла таблицы в каталог базы данных.

Подробное описание модуля см. в главе Модуль прямой загрузки данных QDL.

Вариант использования QDL в случае загрузки данных в новую партицию существующей таблицы

Допустим, имеется таблица, партиционированная по диапазонам дат. Пусть каждый диапазон охватывает один год. В качестве упрощенного примера возьмем таблицу, у которой имеется три поля c типами timestamp, int и text. Пусть на данный момент имеется две партиции с данными за 2019 и 2020 годы, и необходимо максимально быстро загрузить данные за 2021 год. Сделаем это с использованием QDL.

Шаг 1. Формирование данных тестовой таблицы

-- Партиционированная таблица qdl_test
DROP TABLE IF EXISTS qdl_test;
CREATE TABLE qdl_test (
    timestamp_value timestamp NOT NULL,
    int_value       int NOT NULL,
    text_value      text
) PARTITION BY RANGE (timestamp_value);

-- Тестовые данные за 2019 год
DROP TABLE IF EXISTS qdl_test_y2019;
CREATE TABLE qdl_test_y2019 PARTITION OF qdl_test
   FOR VALUES FROM ('2019-01-01') TO ('2020-01-01');

INSERT INTO qdl_test_y2019
SELECT to_timestamp(extract('epoch' FROM to_timestamp('2019-01-01','yyyy-mm-dd')) + random()*60*60*24*365)::timestamp AS timestamp_value,
       round(random()*1000000)::int AS int_value,
       substr(md5(random()::text), 0, 30) AS text_value
  FROM generate_series(1,1000);

-- Тестовые данные за 2020 год
DROP TABLE IF EXISTS qdl_test_y2020;
CREATE TABLE qdl_test_y2020 PARTITION OF qdl_test
   FOR VALUES FROM ('2020-01-01') TO ('2021-01-01');

INSERT INTO qdl_test_y2020
SELECT to_timestamp(extract('epoch' FROM to_timestamp('2020-01-01','yyyy-mm-dd')) + random()*60*60*24*365)::timestamp AS timestamp_value,
       round(random()*1000000)::int AS int_value,
       substr(md5(random()::text), 0, 30) AS text_value
  FROM generate_series(1,1000);

Шаг 2. Генерация данных для загрузки в виде CSV-файла

В целях тестирования сгенерируем данные за 2021 год в файле /tmp/qdl_data.csv, используя возможности команды COPY. В качестве символа-разделителя выберем точку с запятой (;). Этот же символ необходимо будет использовать в файле конфигурации QDL.

Примечание
На этапе загрузки данных в QDL подразумевается, что первая строка файла данных содержит заголовок с названиями полей, поэтому при выгрузке в CSV команде COPY необходимо добавить параметр header true. Иначе при загрузке вместо заголовка будет пропущена первая строка с данными, что недопустимо.

COPY (SELECT to_timestamp(extract('epoch' FROM to_timestamp('2021-01-01','yyyy-mm-dd')) + random()*60*60*24*365)::timestamp AS timestamp_value
           , round(random()*1000000) AS int_value
           , substr(md5(random()::text), 0, 30) AS text_value
        FROM generate_series(1,1000))
  TO '/tmp/qdl_data.csv'
  WITH (format csv, delimiter ';', header true);

Шаг 3. Создание конфигурационного файла /tmp/config.yml, содержащего описание загружаемой таблицы для QDL

Следует отметить, что на первом этапе параметр oid в разделе output не играет роли, поэтому его значение можно оставить нулевым. Важным здесь является символ-разделитель (delimiter), описание таблицы и ее столбцов (параметры партиции table). Если значение столбца может быть равным NULL, после наименования типа нужно добавить ключевое слово [nullable].

Список поддерживаемых при загрузке типов данных приводится в подразделе Поддерживаемые типы полей.

В данном примере файл конфигурации /tmp/config.yml выглядит следующим образом:

# Конфигурация системы:
general:
    # Количество потоков, выделяемых для разбора и сериализации строк.
    # Следует обратить внимание, что помимо этих потоков обязательно выделяются
    # еще два: поток чтения и поток записи в файл.
    threads: 2
    # Задание расчета контрольных сумм
    checksum: true

# Конфигурация исходных данных:
data_source:
    csv:
        # Разделитель столбцов для CSV:
        delimiter: ";"

# Конфигурация получаемых данных:
output:
    # Идентификатор объекта. Определяет целочисленный идентификатор для группы
    # файлов целевой таблицы.
    # Не важен до выполнения этапа создания таблицы
    oid: 0
    # Идентификатор таблицы для хранения больших данных
    toast_oid: 0

# Конфигурация целевой таблицы:
table:
    # Имя таблицы. Требуется для generate_sql, чтобы задать имя создаваемой таблицы.
    name: "qdl_test_y2021"
    # Столбцы в формате "имя" : "тип"
    fields:
        timestamp_value : timestamp
        int_value : integer
        text_value : text [nullable]

Шаг 4. Проверка целостности описания таблицы и данных в CSV-файле

Это необязательный шаг, но его желательно выполнить, чтобы не терять результаты работы по формированию файла таблицы, если формат данных в какой-то строке окажется неверным. В этом случае загрузку данных придется запустить с самого начала.

Следующая команда проверяет логическую целостность CSV-файла и согласованность структуры данных с файлом конфигурации, сигнализируя об ошибках соответствующим кодом завершения.

/usr/bin/qdl --config /tmp/config.yml --data /tmp/qdl_data.csv validate

Шаг 5. Получение и применение скрипта создания таблицы и вывода значения OID

/usr/bin/qdl --config /tmp/config.yml --data /tmp/qdl_data.csv create_table > /tmp/table_script.sql

Вывод результата работы команды выглядит следующим образом:

CREATE TABLE qdl_test_y2021("timestamp_value" timestamp NOT NULL, "int_value" integer NOT NULL, "text_value" text);
CHECKPOINT;
-- Расположение основной таблицы
SELECT pg_relation_filepath('qdl_test_y2021');
-- Расположение таблицы TOAST
SELECT pg_relation_filepath('pg_toast.pg_toast_' || (SELECT oid FROM pg_class WHERE relname = 'qdl_test_y2021'));

Скрипт нужно выполнить в базе данных.

Запросы SELECT отображают путь относительно каталога кластера баз данных. Первый выведет путь к основной таблице. Второй выведет путь к соответствующей таблице TOAST, если она имеется.

Путь к каталогу отображается с помощью команды:

SHOW data_directory;

Шаг 6. Формирование файла таблицы

Это основная и самая ресурсоемкая операция. Важно выбрать эффективное значение для количества параллельных потоков (параметр threads в разделе general), участвующих в формировании файла таблицы. Это значение будет зависеть от количества процессоров на используемой машине, а также от возможностей дисковой системы. Относительно большое значение потоков может усилить конкуренцию за диски, и возможный выигрыш может быть нивелирован.

Значение OID таблицы нужно прописать в файле /tmp/config.yml в качестве значения параметра oid в разделе output. Допустим, в приведенном скрипте в результате первого запроса SELECT функция pg_relation_filepath вывела значение base/13676/16463, тогда для OID таблицы пропишем значение 16463. Имя сформированного в результате файла таблицы будет соответствовать этому параметру. Аналогично прописываем toast_oid (OID таблицы TOAST), если он был получен.

В результате работы утилиты в каталоге, указанном в параметре --out-dir, появятся файлы с заданными OID в качестве имени, в приведенном примере это будет файл 16463. В случае, если размер данных окажется более 1 Гб, создадутся дополнительные файлы с соответствующими расширениями (16463.1, 16463.2 и т. д.).

/usr/bin/qdl --config /tmp/config.yml --data /tmp/qdl_data.csv insert_values --out-dir /tmp

Шаг 7. Копирование полученного файла средствами ОС в каталог базы данных

Если обработка данных происходит непосредственно на сервере баз данных, команда копирования может выглядеть примерно так:

cp /tmp/16463 <путь_к_каталогу_данных>/base/13676/16463

Шаг 8. Сброс кэша данных таблицы и некоторые вспомогательные действия

Необходимо выполнить следующую команду:

SELECT qhb_attach_qdl_data('qdl_test_y2021');

При этом происходит сброс кэша данных таблицы, переподключение файлов данных таблицы, переиндексация соответствующей таблицы TOAST (при ее наличии), регистрация данных для функциональности CDC (Change Data Capture, захват изменения данных).

Шаг 9. Проверка данных таблицы

Можно убедиться, что данные доступны как на основном сервере, так и на резервном.

SELECT * FROM qdl_test_y2021 LIMIT 10;
SELECT count(*) FROM qdl_test_y2021; -- при желании можно убедиться в соответствии количеству строк в файле

Теперь при необходимости можно построить требуемые индексы.

ВНИМАНИЕ!
Следует отметить, что загрузка данных в обход стандартных механизмов работы базы данных приведет к тому, что при восстановлении базы из резервной копии и при применении архивных журналов данные в таблицах, загруженных через QDL, не восстановятся. При выполнении массовой загрузки данных через QDL желательно сразу после этого выполнить полное резервное копирование базы данных. Альтернативным вариантом может стать использование инкрементального резервного копирования, изменения для которого по загружаемым данным фиксируются автоматически.

Шаг 10. Включение данных в партиционированную таблицу

Сделаем таблицу qdl_test_y2021 частью таблицы qdl_test, превратив ее в партицию и добавив ограничения по диапазону дат.

ALTER TABLE qdl_test ATTACH PARTITION qdl_test_y2021
  FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');

При этом на резервном сервере уже будет иметься заранее подготовленная таблица qdl_test_y2021, и команда успешно выполнится.

Теперь при запросе данных 2021 года в таблице qdl_test используется обращение к новой партиции.

qhb=# explain select count(*) from qdl_test where timestamp_value >= to_timestamp('2021-01-01','YYYY-MM_DD');
                                           QUERY PLAN
-------------------------------------------------------------------------------------------------
 Aggregate  (cost=79.79..79.80 rows=1 width=8)
   ->  Append  (cost=0.00..78.84 rows=379 width=0)
         Subplans Removed: 2
         ->  Seq Scan on qdl_test_y2021  (cost=0.00..26.95 rows=377 width=0)
               Filter: (timestamp_value >= to_timestamp('2021-01-01'::text, 'YYYY-MM_DD'::text))
(5 rows)

В плане запроса отражено обращение к партиции qdl_test_y2021.

Замечания, касающиеся физической репликации

При использовании физической репликации необходимо иметь в виду, что данные таблицы на главном сервере формируются без генерации журналов упреждающей записи. Данные, скопированные в виде готового файла, не попадут автоматически через репликацию на резервные серверы. Поэтому необходимо также скопировать файл таблицы в соответствующую директорию на резервных серверах. Если этого не сделать, на резервные серверы попадет только пустая таблица, созданная на шаге 5.

Необходимо также учитывать, что репликация может осуществляться с задержкой, связанной либо с переключением файлов журналов упреждающей записи (если не используется потоковая репликация), либо со специально настроенным отставанием резервного сервера по времени, которое применяется в целях защиты от случайной потери данных на основном сервере.

Особенности выполнения изменений на физических резервных серверах нужно учитывать на следующих шагах:

Шаг 5. Команды скрипта выполнятся на резервных серверах автоматически, возможно, с некоторой задержкой.
Шаг 7. Необходимо скопировать подготовленный файл таблицы также в соответствующий каталог данных на резервных серверах. Этот шаг на резервных серверах нужно выполнить после того, как на них будут созданы таблицы.
Шаг 8. Необходимо выполнить приведенный запрос по сбросу данных также и на резервных серверах.
Шаг 9. Репликация индексов выполнится автоматически. Однако необходимо, чтобы к моменту завершения репликации индексов на резервных серверах были выполнены все предыдущие шаги (5, 7 и 8) и таблица была готова к чтению данных через доступ по индексам.
Шаг 10. Скрипт выполнится на резервных серверах автоматически, возможно, с некоторой задержкой. Желательно убедиться в успешном выполнении операции на резервных серверах.

Пример использования QDL в случае логической репликации таблицы

Нередко возникает необходимость загрузить объемные данные в новую таблицу, изменения в которой должны передаваться с помощью логической репликации в некое отдельное хранилище данных. QDL можно успешно использовать и в этой ситуации. В данном случае приведен простой пример обычной таблицы, который тем не менее демонстрирует все характерные нюансы.

Предварительные замечания

В случае логической репликации термины «основной сервер» и «резервный сервер» используются относительно реплицируемой таблицы. Логическая репликация подразумевает наличие на стороне резервного сервера копии реплицируемой таблицы. При инициализации логической репликации все данные таблицы на стороне основного сервера копируются целиком стандартными встроенными средствами. Чтобы избежать копирования всего объема загружаемых данных с основного сервера на резервный, можно инициализировать логическую репликацию с пустыми таблицами, заменив после этого содержимое таблиц как на основном сервере, так и на резервном. На стороне резервного сервера таблица будет иметь в общем случае свой OID, поэтому файл с данными при копировании нужно будет переименовать соответствующим образом. Затем необходимо сбросить закэшированные данные таблицы как на основном сервере, так и на резервном. Далее репликация изменений будет выполняться в обычном порядке. Разумеется, до окончания выполнения работ по настройке репликации и копированию данных таблица на основном сервере не должна обновляться.

Ниже по шагам приводится пример загрузки и настройки логической репликации таблицы с использованием QDL.

Шаг 1. Создание CSV-файла с данными для тестирования

В целях тестирования сгенерируем данные в файле /tmp/qdl_log_repl.csv, используя возможности команды COPY. В качестве символа-разделителя выберем точку с запятой (;). Этот же символ необходимо будет использовать в файле конфигурации QDL.

Примечание
На этапе загрузки данных в QDL подразумевается, что первая строка файла данных содержит заголовок с названиями полей, поэтому при выгрузке в CSV команде COPY необходимо добавить параметр header true. Иначе при загрузке вместо заголовка будет пропущена первая строка с данными, что недопустимо.

Воспользуемся тем же скриптом, что и в прошлом примере.

COPY (SELECT to_timestamp(extract('epoch' FROM to_timestamp('2021-01-01','yyyy-mm-dd')) + random()*60*60*24*365)::timestamp AS timestamp_value
           , round(random()*1000000) AS int_value
           , substr(md5(random()::text), 0, 30) AS text_value
        FROM generate_series(1,1000))
  TO '/tmp/qdl_log_repl.csv'
  WITH (format csv, delimiter ';', header true);

Шаг 2. Создание конфигурационного файла /tmp/config_qdl_log_repl.yml, содержащего описание загружаемой таблицы для QDL

Следует отметить, что на первом этапе параметр oid в разделе output не играет роли, поэтому его значение можно оставить нулевым. Важным здесь является символ-разделитель (delimiter), описание таблицы и ее столбцов (параметры партиции table). Если значение столбца может быть равным NULL, после наименования типа нужно добавить ключевое слово [nullable].

Список поддерживаемых при загрузке типов данных приводится в подразделе Поддерживаемые типы полей.

В данном примере файл конфигурации /tmp/config_qdl_log_repl.yml выглядит следующим образом:

# Конфигурация системы:
general:
    # Количество потоков, выделяемых для разбора и сериализации строк.
    # Следует обратить внимание, что помимо этих потоков обязательно выделяются
    # еще два: поток чтения и поток записи в файл.
    threads: 2
    # Задание расчета контрольных сумм
    checksum: true

# Конфигурация исходных данных:
data_source:
    csv:
        # Разделитель столбцов для CSV:
        delimiter: ";"

# Конфигурация получаемых данных:
output:
    # Идентификатор объекта. Определяет целочисленный идентификатор для группы
    # файлов целевой таблицы.
    # Не важен до выполнения этапа создания таблицы
    oid: 0
    # Идентификатор таблицы для хранения больших данных
    toast_oid: 0

# Конфигурация целевой таблицы:
table:
    # Имя таблицы. Требуется для generate_sql, чтобы задать имя создаваемой таблицы.
    name: "qdl_log_repl"
    # Столбцы в формате "имя" : "тип"
    fields:
        timestamp_value : timestamp
        int_value : integer
        text_value : text [nullable]

Шаг 3. Проверка целостности описания таблицы и данных в CSV-файле

Это необязательный шаг, но его желательно выполнить для того, чтобы не терять результаты работы по формированию файла таблицы, если формат данных в какой-то строке окажется неверным. В этом случае загрузку данных придется запустить с самого начала.

Следующая команда проверяет логическую целостность CSV-файла и согласованность структуры данных с файлом конфигурации, сигнализируя об ошибках соответствующим кодом завершения.

/usr/bin/qdl --config /tmp/config_qdl_log_repl.yml --data /tmp/qdl_data.csv validate

Шаг 4. Получение и применение скрипта создания таблицы и вывода значений OID

/usr/bin/qdl --config /tmp/config_qdl_log_repl.yml --data /tmp/qdl_log_repl.csv create_table > /tmp/table_script.sql

Вывод результата работы команды выглядит следующим образом:

CREATE TABLE qdl_log_repl("timestamp_value" timestamp NOT NULL, "int_value" integer NOT NULL, "text_value" text);
CHECKPOINT;
-- Расположение основной таблицы
SELECT pg_relation_filepath('qdl_test_y2021');
-- Расположение таблицы TOAST
SELECT pg_relation_filepath('pg_toast.pg_toast_' || (SELECT oid FROM pg_class WHERE relname = 'qdl_log_repl'));

Скрипт нужно выполнить как на основном сервере, так и на резервном.

Первый запрос SELECT скрипта выведет путь к таблице относительно каталога кластера баз данных. Обратите внимание, что на каждой базе данных выведутся свои значения функции pg_relation_filepath с номером OID таблицы в конце. Этот номер нужно будет использовать в параметре oid раздела output конфигурационного файла QDL.

На этом же шаге следует удалить все строки из таблицы qdl_log_repl на основном и на резервном серверах:

DELETE FROM qdl_log_repl;

При инициализации репликации строки таблицы копируются с основного сервера на резервный; в данном случае этот шаг фактически будет пропущен.

Шаг 5. Настройка логической репликации

Следует отметить, что для успешной работы логической репликации параметр wal_level на основном сервере должен принимать значение logical.

На основном сервере выполните следующий скрипт:

SELECT pg_create_logical_replication_slot('test_slot','pgoutput');
CREATE PUBLICATION test_pub FOR TABLE qdl_log_repl;

На резервном сервере замените в тексте следующей команды <имя_пользователя> на действительное имя пользователя в базе основного сервера и выполните ее.

CREATE SUBSCRIPTION test_sub CONNECTION 'port=5432 user=<имя_пользователя> dbname=qhb' PUBLICATION test_pub WITH (create_slot = false, slot_name = test_slot);

Шаг 6. Формирование файла таблицы

Это основная и самая ресурсоемкая операция. Важно выбрать эффективное значение для количества параллельных потоков (параметр threads раздела general), участвующих в формировании файла таблицы. Это значение будет зависеть от количества процессоров на используемой машине, а также от возможностей дисковой системы. Относительно большое значение потоков может усилить конкуренцию за диски, и возможный выигрыш может быть нивелирован.

Номер OID таблицы нужно прописать в файле /tmp/config_qdl_log_repl.yml в качестве значения параметра oid в разделе output. Допустим, в приведенном скрипте в результате последнего запроса функция pg_relation_filepath вывела на основном сервере значение base/13676/17101, а на резервном — base/13676/18203. Тогда для основного сервера пропишем в качестве параметра oid значение 17101. Имя сформированного в результате файла таблицы будет соответствовать этому параметру.

Значение на резервном сервере будет отличаться. При копировании необходимо будет переименовать файл так, чтобы имя соответствовало номеру OID на резервном сервере. Если не указать правильное значение, в лучшем случае файл будет просто лишним, а в худшем можно случайно перезаписать уже существующую таблицу с номером OID, совпадающим с номером OID таблицы на основном сервере.

Далее необходимо выполнить следующий скрипт:

/usr/bin/qdl --config /tmp/config.yml --data /tmp/qdl_data.csv insert_values --out-dir /tmp

В результате работы утилиты в каталоге, указанном в параметре --out-dir, появится файл с заданным OID в качестве имени; в приведенном примере это будет файл 17101. В случае, если размер данных окажется более 1 Гб, создадутся дополнительные файлы с соответствующими расширениями (17101.1, 17101.2 и т. д.).

Шаг 7. Копирование полученного файла средствами ОС в каталог базы данных на основном и резервном серверах

На основном сервере команда копирования может выглядеть приблизительно следующим образом:

cp /tmp/16463 <путь к каталогу данных на основном сервере>/base/13676/17101

На резервном сервере имя файла отличается (OID базы данных также может быть другим, но в этом примере не изменен):

cp /tmp/16463 <путь к каталогу данных на резервном сервере>/base/13676/18203

Шаг 8. Сброс кэша данных таблицы и некоторые вспомогательные действия

Необходимо выполнить следующую команду:

SELECT qhb_attach_qdl_data('qdl_test_y2021');

При этом происходит сброс кэша данных таблицы, переподключение файлов данных таблицы, переиндексация соответствующей таблицы TOAST (при ее наличии), регистрация данных для функциональности CDC.

Шаг 9. Проверка данных таблицы

Можно убедиться, что данные доступны как на основном сервере, так и на резервном.

SELECT * FROM qdl_log_repl limit 10;
SELECT count(*) FROM qdl_log_repl; -- при желании можно убедиться в соответствии количеству строк в файле

При необходимости теперь можно построить требуемые индексы.

ВНИМАНИЕ!
Следует отметить, что загрузка данных в обход стандартных механизмов работы базы данных приводит к тому, что при восстановлении базы из резервной копии и при применении архивных журналов данные в таблицах, загруженных через QDL, не восстанавливаются. При выполнении массовой загрузки данных через QDL желательно сразу после этого выполнить полное резервное копирование базы. Чтобы это копирование не было дополнительным, желательно согласовать политику выполнения массовой загрузки данных с политикой выполнения резервного копирования. Альтернативным вариантом может стать использование инкрементального резервного копирования, изменения для которого по загружаемым данным фиксируются автоматически.

Заключение

Используя QDL, можно эффективно загружать в базу объемные данные, в том числе в случае использования физической или логической репликации. Тестирование показывает, что общее время загрузки данных с помощью QDL и время копирования полученного файла в 3,5 раза меньше по сравнению с продолжительностью загрузки тех же данных с помощью команды COPY.

Приведенный подход можно использовать в хранилищах и витринах данных, где время загрузки новых данных является критичным показателем, а данные, как правило, поступают из других источников.

Модуль загрузки данных метрик в базу данных QDLM

Для загрузки данных метрик в базу данных используется модуль QDLM (Quantum Data Loader for Metrics), который, в свою очередь, основан на функциональности прямой загрузки данных (предварительная установка QDL для работы QDLM не требуется). Функциональность прямой загрузки данных работает в обход стандартного механизма и позволяет в разы сократить время, необходимое для загрузки данных. Однако следует помнить о том, что при такой загрузке данные не попадают в стандартные файлы WAL, и данные не появятся при восстановлении базы из резервной копии и последующего воспроизведения WAL. Но при использовании инкрементального резервного копирования эти данные будут скопированы автоматически.

Настройка загрузки данных метрик в таблицу базы данных

В настоящее время можно организовать как локальную обработку CSV-файлов метрик на том же экземпляре QHB, так и централизованную обработку CSV-файлов из нескольких источников. Во втором случае подразумевается либо запись файлов в каталог CSV-файлов от нескольких серверов метрик, расположенных на одном хосте с несколькими экземплярами QHB, либо копирование CSV-файлов с удаленных хостов любыми средствами операционной системы. Возможна также комбинация этих вариантов.

Установка QDLM

Установка QDLM осуществляется аналогично другим модулям QHB. Более подробную информацию см. в пошаговой инструкции по начальной загрузке, установке и запуску.

Настройка параметров в файле параметров QDLM

В файле /etc/qdlm/config.yml нужно прописать значения для следующих параметров:

# максимальный период переключения csv-файлов (в минутах), поступающих от различных
# серверов метрик задается для проверки сформированных ранее csv-файлов, которые
# могли быть пропущены при обработке, и пропуска тех, которые могут быть записаны
# еще не до конца
max_rotation_age: 60

# каталог CSV-файлов
csv_directory: "/var/lib/qhb/csv_files"

# параметры соединения с базой данных
qhb_connection: "host=localhost port=5432 user=qhb dbname=qhb"

# файл параметров прямой загрузки данных
qdl_config: "/etc/qdlm/qdl.yml"

# каталог данных
qhb_data: "/var/lib/qhb/data"

Параметры соединения описаны в разделе Строки подключения. Особенностью QDLM является возможность подключения к базе данных только на локальном хосте. В текущем релизе это обусловлено копированием сформированных файлов таблиц напрямую в каталог базы данных в локальной файловой системе. В случае необходимости использования отдельной базы данных (например, metrics) и отдельного пользователя для базы данных метрик (например, metrics_user) можно выполнить от имени суперпользователя следующий набор команд из psql:

CREATE DATABASE metrics;
CREATE USER metrics_user WITH login;
GRANT all ON database metrics to metrics_user;
\c metrics;
GRANT usage ON schema pg_toast to metrics_user;
GRANT all privileges ON all tables IN schema pg_toast to metrics_user;

После этого в строке соединения можно прописать соответствующие имена пользователя и базы данных:

# параметры соединения с базой данных
qhb_connection: "host=localhost port=5432 user=metrics_user dbname=metrics"

Файл параметров прямой загрузки данных метрик

Предварительно настроенный файл параметров прямой загрузки данных метрик копируется при установке QDLM и не требует изменений. Путь к нему по умолчанию: /etc/qdlm/qdl.yml.

Запуск утилиты QDLM

Запуск прямой загрузки данных метрик производится следующей командой:

qdlm load -c /etc/qdlm/config.yml

Запуск прямой загрузки данных метрик с параметром -D /<PATH>/<QHB-DATA> переназначает каталог данных.

При запуске утилита проверяет каталог, указанный в параметре csv_directory и начинает обработку csv-файлов, которые были обновлены не позднее, чем max_rotation_age минут назад. Если в каталог записываются файлы нескольких серверов метрик, в качестве max_rotation_age администратором устанавливается максимальное значение в минутах из всех значений параметра rotation_age соответствующих серверов метрик. Благодаря такому ограничению все свежие файлы, в которые может происходить запись, не будут обрабатываться. После обработки накопившихся файлов утилита переходит в режим ожидания события окончания записи очередного файла в каталоге, после чего новый файл обрабатывается, и его данные заносятся в новую партицию таблицы metric_archive. В текущем релизе схема таблицы предзадана и не меняется. После этого происходит проверка, нет ли файлов, события по которым могли быть случайно пропущены (например, такое возможно при слишком большой загрузке системы). Если такие файлы обнаруживаются, они обрабатываются, после чего утилита снова переходит в режим ожидания получения события об окончании записи очередного csv-файла. Обработанные csv-файлы перемещаются в подкаталог qdlm_swap каталога, указанного в csv_directory. В дальнейшем обработанные csv-файлы можно удалить или переместить в некий архив.

Результаты обработки

После запуска обработки CSV-файлов первым шагом проверяется, созданы ли партиционированная таблица metric_archive и последовательность seq_metric_chunk в схеме public базы данных, указанной в параметрах. Если эти объекты еще не созданы, они создаются автоматически.

Таблица 1. Таблица metric_archive

Столбец Тип

Описание

instance_id text

Значение, указанное в параметре qhb_instance сервера метрик. В общем случае данные могут поступать из различных источников и относиться к разным базам данных.

metric_dt timestamp

Дата и время формирования метрики

metric_type_id smallint

Тип метрики: 0 — Counter (счетчик), 1 — Gauge (уровень), 2 — Timer (время)

metric_name text

Название метрики

metric_name_ext text

Наименования агрегатов (std, max, min, sum, count, median и перцентили) для метрик типа Timer

metric_value double precision

Значение метрики

Данные каждого CSV-файла обрабатываются аналогично тому, как обрабатываются данные при загрузке таблицы через QDL. После обработки очередного файла в базе данных появляется таблица metric_archive_N, где N — номер из последовательности seq_metric_chunk. По окончании обработки таблица становится партицией партиционированной таблицы metric_archive. Партиционирование идет по двум столбцам: instance_id и metric_dt. При присоединении таблицы в качестве партиции задаются минимальная и максимальная границы значений столбца metric_dt. В дальнейшем при обращении к таблице metric_archive при указании условий по instance_id и metric_dt будет автоматически происходить выбор соответствующих партиций.

Менеджер кеша дисковых блоков TARQ

Менеджер кеша дисковых блоков — самобалансирующийся менеджер кеша дисковых блоков с автоматической компенсацией нагрузки на дисковую систему.

Алгоритм вытеснения из кеша нужен для работы в среде, где в целях ускорения операций с диском часть быстродействующей памяти используется для размещения наиболее часто востребованных блоков. Данный алгоритм служит для определения блока, который можно выбросить с наименьшими потерями для общей производительности (т. е. такого блока, который будет использоваться не так часто и/или интенсивно как остальные).

QHB может запускаться с усовершенствованным алгоритмом вытеснения, называемым TARQ.

Алгоритм предполагает поддержку двух списков страниц, L1 и L2. Максимальная длина обоих списков составляет 2c, где c — размер кеша в страницах. Оба списка формируются в стиле LRU. При перемещении в кеш страницы, номер которой отсутствует в обоих списках, этот номер заносится в начало списка L1. При обращении к странице, номер которой фигурирует в одном из списков, этот номер переносится в начало списка L2. Важной особенностью алгоритма является то, что только в начале каждого из списков (в подсписках T1 и T2) располагаются номера страниц, находящихся в кеше; таким образом поддерживается история страниц, недавно вытесненных из кеша. Страница для замещения выбирается из конца подсписка T1 или T2 в зависимости от значения параметра p, определяющего текущую допустимую длину подсписка T1, а тем самым и длину T2. Адаптивность алгоритма состоит в том, что значение p изменяется в зависимости от вида рабочей нагрузки.

Для включения и управления новым алгоритмом вытеснения используются параметры в конфигурационном файле (подробную информацию см. в подразделе Параметры менеджера кеша дисковых блоков TARQ).

Дамп и загрузка содержимого менеджера кэша дисковых блоков UMCA

Экземпляр QHB может работать достаточно долгое время. В процессе он накапливает данные о загруженных дисковых блоках. Если по каким-то причинам появится необходимость остановить его работу, то эти данные будут потеряны. В этом случае после запуска новому экземпляру придется заново собирать эти данные.

Модуль UMCA (Universal Memory Cache Analyzer, универсальный анализатор кэш-памяти) позволяет сохранять содержимое менеджера кэша дисковых блоков в файловой системе, а затем загружать его обратно, тем самым восстанавливая прежнее состояние. За счет этого время на прогрев экземпляра не затрачивается. (Подробное описание менеджера кэша дисковых блоков см. в разделе Менеджер кэша дисковых блоков TARQ)

ВНИМАНИЕ!
Функции UMCA работают, только если в конфигурационном файле qhb.conf параметр use_qhb_cache установлен в значение true, иначе вызовы функций будут завершаться ошибкой.

Создание дампа

Для создания дампов используется встроенная SQL-функция qhb_dump_disk_cache. Она копирует содержимое менеджера кэша дисковых блоков и затем сохраняет его в файле. В качестве результата возвращается количество сохраненных блоков. При вызове необходимо указать тип дампа (регистр символов не имеет значения). Ниже представлена соотносительная таблица всех типов дампов и затрагиваемых списков страниц.

Тип дампа | Затрагиваемые списки
FULL | B1, B2, L1, L2
HOT | L2
COLD | L1
SHADOW | B1, B2

Ниже приведен пример вызова функции qhb_dump_disk_cache, в результате которого создается полный дамп содержимого менеджера кэша дисковых блоков. Если ошибок нет, значит, функция выполнилась успешно.

SELECT qhb_dump_disk_cache('FULL');

Все дампы сохраняются в подкаталоге qhb-disk-cache-dump каталога базы данных. Файл имеет имя, состоящее из типа дампа (см. таблицу выше) и текущего UTC-времени.

Сохранение дампа в файл с произвольным именем

Функция qhb_dump_disk_cache имеет форму со вторым параметром, позволяющим дополнительно передать имя файла, под которым будет сохранен создаваемый дамп. Ниже приведен пример вызова функции qhb_dump_disk_cache, в результате которого будет создан дамп горячих блоков с именем test.dump.

SELECT qhb_dump_disk_cache('HOT','old.dump');

Загрузка дампа

Для загрузки из дампов используется встроенная SQL-функция qhb_load_disk_cache. Она загружает данные из предварительно созданного дампа. В качестве результата возвращается количество загруженных блоков. При вызове необходимо указать название или абсолютный путь к дампу. Ниже приведен пример вызова функции qhb_load_disk_cache, в результате выполнения которого будут загружены блоки. Если ошибок нет, значит, функция выполнилась успешно.

SELECT qhb_load_disk_cache('2020-12-03_12:50:21.dump');

В примере выше указано название дампа, поэтому функция будет искать его в стандартном подкаталоге хранения дампов менеджера кэша дисковых блоков. Также можно указать полный путь к дампу, как это сделано в примере ниже.

SELECT qhb_load_disk_cache('/opt/qhb-data/qhb-disk-cache-dumps/2020-12-03_12:50:21.dump');

Загрузка последнего дампа

Функция qhb_load_disk_cache имеет форму без параметров; в этом случае функция будет искать последний измененный файл, доступный для чтения. Ниже приведен пример вызова функции qhb_load_disk_cache без параметров.

SELECT qhb_load_disk_cache();

Если в каталоге есть несколько дампов, с помощью команды touch можно обновить время изменения дампа, тогда он будет считаться последним.

touch new-last.dump

Просмотр файлов дампа

Дистрибутив QHB содержит утилиту qhb_disk_cache_dump_viewer, которая позволяет посмотреть содержимое дампов менеджера кэша дисковых блоков. Ниже приведен пример использования этой утилиты.

qhb_disk_cache_dump_viewer 2020-12-03_15:34:53.dump

Сортировка вывода

Утилита qhb_disk_cache_dump_viewer имеет параметр сортировки вывода --sort-by (сокращенно -s). По умолчанию включена сортировка по номеру блока. С помощью значений, описанных в таблице ниже, можно указать, по какому столбцу сортировать вывод.

Значение параметра --sort-by | Описание
ForkNum | сортирует вывод по столбцу fork_num
BlockNum | сортирует вывод по столбцу block_num
SpcNode | сортирует вывод по столбцу spc_node
DbNode | сортирует вывод по столбцу db_node
RelNode | сортирует вывод по столбцу rel_node

Ниже приведен пример вывода утилиты qhb_disk_cache_dump_viewer.

GEN      ID: [ fork_num | block_num | spc_node | db_node | rel_node ]
L1        1: [        0 |         0 |     1663 |   12674 |     2615 ]
L1        2: [        0 |         5 |     1663 |   12674 |     2617 ]
L1        3: [        0 |         1 |     1663 |   12674 |     2654 ]
L1      129: [        2 |         0 |     1663 |   12674 |     3456 ]
L1      130: [        0 |        42 |     1663 |   12674 |     2838 ]
L1      257: [        0 |         1 |     1663 |   12674 |     1259 ]
L1      258: [        0 |         2 |     1663 |   12674 |     2610 ]
L1      259: [        0 |         3 |     1663 |   12674 |     2696 ]

Получение размера дампа в блоках

Утилита qhb_disk_cache_dump_viewer имеет параметр отображения размера дампа в блоках --size (сокращенно -S). Этот параметр несовместим с параметром --sort-by.

Ниже приведен пример вывода qhb_disk_cache_dump_viewer с параметром --size.

B1: 0
B2: 0
L1: 159
L2: 0
Total: 159

Параметр HOLDMEM и дополнительные кэши дисковых блоков

ВНИМАНИЕ!
В релизе QHB 1.5.2 эта функциональность является экспериментальной, ее использование в установке на производственной среде не рекомендовано.

В QHB для ускорения работы с таблицами имеется возможность создавать их особым параметром HOLDMEM, который указывает, где они будут размещаться. Этот параметр может иметь три значения: OFF, POSSIBLY и ONLY.

Синтаксис:

CREATE TABLE cats (id int) WITH (HOLDMEM = OFF);
-- то же самое, что и CREATE TABLE cats (id int);

CREATE TABLE dogs (id int) WITH (HOLDMEM = POSSIBLY);

CREATE UNLOGGED TABLE cows (id int) WITH (HOLDMEM = ONLY);

Примечание
Обратите внимание, что таблицы с HOLDMEM = ONLY должны быть созданы с параметром UNLOGGED (нежурналируемые).

Значения HOLDMEM

OFF — это обычные таблицы. При создании таблицы этот параметр можно опустить. Обычно он нужен, когда требуется изменить свойство таблицы.

ALTER TABLE dogs SET (HOLDMEM = OFF);

POSSIBLY — это таблицы, которые по возможности находятся в памяти.

ALTER TABLE dogs SET (HOLDMEM = POSSIBLY);

ONLY — это таблицы, которые всегда находятся в памяти.

ALTER TABLE dogs SET (HOLDMEM = ONLY);

Примечание
Все таблицы с параметром HOLDMEM = ONLY должны быть нежурналируемыми.
Отдельным запросом сделайте таблицу UNLOGGED:

ALTER TABLE dogs set UNLOGGED;

а затем

ALTER TABLE dogs SET (HOLDMEM = ONLY);

HOLDMEM = POSSIBLY

Таблицы, созданные с параметром HOLDMEM = POSSIBLY, предназначены для хранения данных, к которым требуется быстрый доступ. Для них используется отдельный кэш LRU, за счет простоты которого достигается быстродействие при обращении к нему во время поиска страницы. Однако алгоритм LRU имеет меньше возможностей настройки, чем TARQ, поэтому его использование нужно в особых случаях, например, при работе с данными, к которым требуется частое обращение.

HOLDMEM = ONLY

Таблицы, созданные с параметром HOLDMEM = ONLY, предназначены для хранения данных, к которым требуется максимально быстрый доступ.

Однако нужно знать некоторые особенности при работе с ним:

Так как все данные хранятся в памяти, переполнение этого буфера приведет к ошибке. Также при сбое или экстренном завершении данные не могут быть и не будут восстановлены.

Не поддерживается механизм TOAST.

При создании таблицы всем столбцам будет принудительно установлен режим хранения PLAIN.

У существующей таблицы нельзя изменить атрибут хранения на HOLDMEM = ONLY, если есть соответствующая таблица TOAST. Предварительно необходимо выключить TOAST с помощью команды ALTER TABLE имя_таблицы ALTER COLUMN имя_столбца SET STORAGE PLAIN. Обратите внимание, что при изменении режима хранения существующая таблица TOAST не будет удалена. На таблицу TOAST параметр HOLDMEM = ONLY не действует.

При установке атрибута хранения HOLDMEM = POSSIBLY или HOLDMEM = OFF после HOLDMEM = ONLY автоматическое включение поддержки TOAST не производится. При возникновении потребности в такой поддержке ее необходимо включить вручную с помощью команды ALTER TABLE имя_таблицы ALTER COLUMN имя_столбца SET STORAGE.

Для включения и управления алгоритмом вытеснения для таблиц, по возможности хранящихся в памяти, используются параметры в файле конфигурации (подробную информацию см. в подразделе Включение и управление алгоритмом вытеснения параметра HOLDMEM).

Совместимость

Параметр HOLDMEM, так же как и значения OFF, POSSIBLY и ONLY, реализованные в QHB, является расширением стандарта SQL.

См. также

ALTER TABLE

Таблицы APPEND_ONLY

В QHB для ускорения работы с таблицами имеется возможность создавать их с особым параметром APPEND_ONLY. Этот параметр предназначен для таблиц, с которыми не производятся модификации, например журналы, данные с датчиков и т. п. В такие таблицы можно только добавлять записи, но делается это с максимальной скоростью, так как не выполняется полноценного MVCC анализа.

Для этого варианта хранилища характерны также следующие свойства:

Отсутствует необходимость в автоочистке.

Поддерживаются все типы индексов.

Для удаления старых данных можно использовать партиционирование таблицы и удалять данные партициями, либо использовать команду TRUNCATE.

Не поддерживается механизм TOAST.

Создание таблицы с APPEND_ONLY выполняется запросом:

CREATE TABLE имя_таблицы (...) USING APPEND_ONLY;

Также для ускорения работы с таблицей возможно создание таблицы с параметром HOLDMEM. Для этого необходимо при создании таблицы указать параметр UNLOGGED (нежурналируемая) и добавить WITH (HOLDMEM = ONLY). Запрос на создание такой таблицы выглядит следующим образом:

CREATE UNLOGGED TABLE имя_таблицы (...) USING APPEND_ONLY WITH (HOLDMEM = ONLY)

Сервер метрик

Сервер метрик используется в инфраструктуре QHB для сбора, агрегации и пересылки метрик в различные системы:

систему мониторинга Graphite;

базу данных временных рядов Prometheus;

агент по сбору метрик и журналов Vector;

для сохранения данных метрик в CSV-файлах.

Список и особенности формирования метрик QHB содержатся в разделе Метрики QHB.

Сервер метрик должен быть установлен и настроен на каждой машине, где работают компоненты QHB (сам сервер баз данных или QCP). Подробную информацию см. в разделе Установка.

При необходимости агрегатор сервера метрик можно разместить на отдельном хосте. Подробную информацию см. в разделе Работа сервера метрик в режиме разделенного коллектора и агрегатора.

Настройка сервера метрик

Пример файла конфигурации устанавливается по пути /etc/metricsd/config-example.yaml.

Для работы сервера необходимо скопировать его в /etc/metricsd/config.yaml и скорректировать необходимые параметры. Особого внимания требует раздел aggregation → backends. Настройка сервера метрик под разных потребителей представлена в соответствующих разделах:

Конфигурация сервера метрик для Graphite;

Конфигурация сервера метрик для формирования CSV-файлов;

Конфигурация сервера метрик для Vector;

Конфигурация сервера метрик для Prometheus.

Для автоматического запуска сервера при старте системы активируйте соответствующий сервис systemd:

$ sudo systemctl enable --now metricsd.service

В случае потери связи между сервером метрик и QHB (происходит в случае перезагрузки сервиса сервера метрик) в результате чего снижается количество поступающих метрик и в журнале появляются уведомления «Failed to open a metrics sender» (не удалось открыть передатчик метрик), для повторного подключения сервера метрик к базе администратор должен выполнить либо перезапуск QHB, либо вызов следующей функции в QHB:

SELECT metrics_reset();

После выполнения команды необходимо убедиться, что количество поступающих метрик вернулось к изначальному объему (восстановление должно произойти в течении 1 минуты).

В конфигурации базы нужно изменить строчку:

#metrics_collector_path = 'path/to/metrics`

на

metrics_collector_path = '@metrics-collector'

А затем перезапустить базу данных в следующем порядке:

запустить metricsd;

запустить базу данных;

запустить систему для приема метрик (Graphite, Prometheus и т. д.).

Конфигурация сервера метрик для Graphite

Тема установки и настройки Graphite выходит за рамки данной документации. Пожалуйста, обратитесь к документации Graphite.

  # Конфигурация серверов. Должен быть сконфигурирован хотя бы один сервер.
  backends:
    # Конфигурация сервера graphite
    - graphite:
      # Адрес терминала TCP Graphite для текстового протокола. Порт по умолчанию: 2003.
      # Доступен только протокол TCP, поэтому если Graphite не принимает подключение
      # к этому порту, это вызовет ошибку!
      address: "graphite:2003"
      # Префикс, добавляемый к имени каждой метрики. Необязательный параметр; по
      # умолчанию — пустая строка.
      prefix: ""
      # Время ожидания подключения. Необязательный параметр; по умолчанию — 30
      # секунд.
      connection_timeout: "30 sec"
      # Время ожидания отправки данных. Необязательный параметр; по умолчанию — 5
      # секунд.
      send_timeout: "5 sec"

Исправьте параметр address на реальный адрес сервера Graphite в вашей сети. Также рекомендуется изменить значение параметра prefix на, например, имя машины, на которой запущен сервер. Этот префикс будет добавляться ко всем генерируемым метрикам.

Конфигурация сервера метрик для формирования CSV-файлов

  # Конфигурация серверов. Должен быть сконфигурирован хотя бы один сервер.
  backends:
    # Конфигурация серверного процесса CSV (в случае необходимости копирования
    # данных о метриках в CSV-файлы)
    - csv:
      # Каталог, содержащий CSV-файлы.
      directory: "/var/lib/qhb/csv_create"
      # Системный идентификатор экземпляра QHB.
      qhb_instance: "instance_name"
      # Интервал переключения CSV-файлов (необязательно).
      rotation_age: "1 h"
      # Настройка времени следующего переключения после запуска (необязательно).
      rotation_offset: 2022-03-01T18:00:00

Параметры конфигурации серверного процесса CSV необходимы в случае, если нужно сохранять данные метрик в CSV-файлы. Для этого используются следующие параметры:

directory указывает место расположения CSV-файлов.

qhb_instance — системный идентификатор экземпляра; не может быть пустым и содержать кавычки, в чем-то аналогичен параметру prefix, но его значение при загрузке данных метрик заполняет отдельный столбец в таблице метрик, не связанный с именем метрик. Может быть полезен при наличии нескольких экземпляров QHB на одном сервере.

rotation_age — период переключения CSV-файлов. Единицы измерения можно задавать, например, в виде секунд (s), минут (m), часов (h), дней (d).

rotation_offset — этот параметр задает время переключения на очередной CSV-файл после запуска сервера метрик. Время указывается для часового пояса UTC. Параметр удобно использовать для выравнивания начала очередного периода, например, по началу следующего часа.

Конфигурация сервера метрик для Vector

Тема установки и настройки сборщика журналов Vector выходит за рамки данной документации. Пожалуйста, обратитесь к документации Vector.

Пример настройки конфигурации источника данных Vector для сервера метрик QHB:

sources:
  qhb_metricsd:
    type: "http_server"
    address: "metricsd:80"
    acknowledgements: false
    encoding: "ndjson"

Настройка параметров сервера метрик:

  # Конфигурация серверов. Должен быть сконфигурирован хотя бы один сервер.
  backends:
    # Конфигурация серверного процесса Vector
    - vector.dev:
        # URL-адрес слушателя vector типа "http_server", настроенного на получение
        # данных в формате "ndjson".
        url: "metricsd:80"

Измените в параметрах address и url значение metricsd на реальный адрес сервера метрик в вашей сети.

Конфигурация сервера метрик для Prometheus

Тема установки и настройки Prometheus выходит за рамки данной документации. Пожалуйста, обратитесь к документации Prometheus.

Пример изменений в конфигурации Prometheus для сервера метрик QHB:

scrape_configs:
  # Имя операции (job_name) добавляется в виде метки `job=<job_name>` во все
  # временные ряды, извлекаемые из этой конфигурации.

  - job_name: 'metricsd'
     # metrics_path по умолчанию принимает значение '/metrics'
     # scheme по умолчанию принимает значение 'http'.

  static_configs:
    - targets: ['metricsd:8080']

Настройка параметров сервера метрик:

  # Конфигурация серверов. Должен быть сконфигурирован хотя бы один сервер.
  backends:
    # Конфигурация серверного процесса Prometheus:
    - prometheus:
        # Адрес, который будет выставлен для сканирования процессом Prometheus.
        listening_address: "metricsd:8080"

Измените в параметрах targets и listening_address значение metricsd на реальный адрес сервера метрик в вашей сети.

ВНИМАНИЕ!
При отправке метрик в Prometheus все точки, присутствующие в названии метрики, сервером метрик заменяются на «_».

Настройка метрик queryid

Каталог операторов SQL, зарегистрированных для метрик queryid

Группа метрик queryid позволяет вести мониторинг работы отдельных операторов SQL. Для этого имена метрик формируются с использование идентификатора того или иного оператора. Для хранения текстов операторов используется системный каталог qhb_queryid. При добавлении нового оператора в эту таблицу ему присваивается идентификатор, а также может быть присвоено имя. При анализе метрик можно быстро отыскать полный текст оператора SQL, к которому относятся эти метрики.

Список метрик группы queryid содержится в подразделе queryid.

Для управления этим каталогом (особенно важно предотвратить его неконтролируемое разрастание) предусмотрены функции, доступные пользователю, и параметры конфигурации.

Включить группу метрик queryid (она по умолчанию отключена) можно так же, как и остальные группы метрик:

SELECT metrics_config_enable_group('queryid', false);

Описание этой функции (и парной ей для выключения) содержится в Таблице 63.

Но, в отличие от остальных групп метрик, вызова функции metrics_config_enable_group недостаточно для того чтобы метрики начали генерироваться при выполнении операторов SQL. Необходимо либо разрешить авторегистрацию операторов и потом активизировать метрики хотя бы для одного из них, либо явно зарегистрировать хотя бы один оператор SQL.

Параметры конфигурации для настройки работы метрик queryid

Имя параметра | Тип | Описание | Значение по умолчанию
queryid_auto_register | bool | Добавлять в таблицу только явно зарегистрированные запросы | false
queryid_max_saved | integer | Предельное количество операторов и их идентификаторов, сохраняемых в системной таблице qhb_queryid | 1000
queryid_update_threshold | interval | Дискретность обновления временной метки доступа к записи | 1min

Параметр queryid_auto_register разрешает автоматическую регистрацию всех поступающих на выполнение операторов. Поскольку их может быть много, флаги active при этом устанавливаются в false. Если запретить автоматическую регистрацию, то при явной регистрации оператора (см. далее) его флаг active устанавливается в true. Для работы с метриками флаг active хотя бы одного оператора нужно будет выставить в true.

Параметр queryid_max_saved нужен для управления автоочисткой таблицы qhb_queryid. По достижении количества записей значения, указанного в queryid_max_saved, будет вызвана процедура очистки, которая удалит записи с самыми старыми значениями временной метки доступа last_used. При этом именованные записи не будут удаляться, пока остается хотя бы одна неименованная запись.

Параметр queryid_update_threshold призван уменьшить нагрузку на базу данных в случае потока одних и тех же операторов. В файле конфигурации он может быть задан, как и другие временные параметры, с использованием суффиксов ms, s и min.

«Виртуальный» параметр queryid_normalize

Существует еще один, «виртуальный», параметр queryid_normalize = true, который не присутствует в файле конфигурации, но может быть временно, только в контексте одного сеанса подключения к базе данных, изменен вызовом функции metrics_queryid_normalize(false). После этого тексты обрабатываемых операторов не будут подвергаться процедуре нормализации, которая, в частности, абстрагирует значения параметров запросов. Таким образом, станет возможно проследить за работой оператора с конкретными значениями параметров.

Функции для работы с метриками queryid

Имя | Параметры | Описание
metrics_queryid_normalize | bool | Разрешить/запретить нормализацию текста операторов
metrics_queryid_register | string | Зарегистрировать оператор, присвоив ему идентификатор
metrics_queryid_enable | string | Разрешить генерацию метрик для оператора (или нескольких)
metrics_queryid_disable | string | Запретить генерацию метрик для оператора (или нескольких)
metrics_queryid_disable_all |  | Запретить генерацию метрик для всех операторов
metrics_queryid_rename | string, string | Связать с оператором новое имя
metrics_queryid_clear_name | string | Очистить имя оператора
metrics_queryid_show |  | Показать текущие значения параметров

metrics_queryid_normalize(true или false)

Функция metrics_queryid_normalize разрешает или запрещает нормализацию текстов запросов (только в контексте текущего процесса бэкенда). По умолчанию — true, то есть нормализация выполняется.

metrics_queryid_register('текст запроса')

Функция metrics_queryid_register регистрирует запрос (любой оператор SQL или даже произвольную строку): вставляет запись в таблицу qhb_queryid сразу в активизированном состоянии, но не исполняет сам запрос. Пустая строка в качестве аргумента не допускается. В случае успешной регистрации запроса регистрируется полный набор метрик с соответствующим QueryId. Если запрос построен некорректно и не сможет быть исполнен позже, эти метрики никогда не сгенерируются. Если установлен параметр конфигурации queryid_auto_register=true, использование этой функции становится необязательным, но будут регистрироваться все подряд исполняемые операторы SQL, что часто нежелательно.

metrics_queryid_enable('id или имя или список')

Функция metrics_queryid_enable разрешает запросу, связанному с указанным QueryId или именем, генерировать метрики. Пустая строка в качестве аргумента не допускается. Разрешается передавать несколько идентификаторов или имен в виде списка через запятую. Разрешается передавать идентификатор не полностью, а только первые символы при условии, что они однозначно определяют единственный существующий QueryId.

metrics_queryid_disable('id или имя или список')

Функция metrics_queryid_disable запрещает запросу, связанному с указанным QueryId или именем, генерировать метрики. Пустая строка в качестве аргумента не допускается. Разрешается передавать несколько идентификаторов или имен в виде списка через запятую. Разрешается передавать идентификатор не полностью, а только первые символы при условии, что они однозначно определяют единственный существующий QueryId.

metrics_queryid_disable_all()

Функция metrics_queryid_disable_all запрещает всем зарегистрированным запросам генерировать метрики.

metrics_queryid_rename('id или имя', 'новое имя')

Функция metrics_queryid_rename связывает запрос с известным QueryId или ранее установленным именем с новым именем. Пустые строки в качестве аргументов не допускаются. Списки в качестве аргументов не допускаются. Существующие имена или QueryId в качестве нового имени не допускаются. В случае успешного связывания регистрируется полный набор метрик с новым именем.

metrics_queryid_clear_name('id или имя или список')

Функция metrics_queryid_clear_name удаляет связь существующих запросов с именем. Пустая строка в качестве аргумента не допускается. Разрешается передавать несколько идентификаторов или имен в виде списка через запятую. Разрешается передавать идентификатор не полностью, а только первые символы при условии, что они однозначно определяют единственный существующий QueryId. В случае, если удаление действительно произошло, заново регистрируется полный набор метрик с соответствующим QueryId.

metrics_queryid_show();

Функция metrics_queryid_show показывает текущие значения параметров.

Разделяемый кэш планов

ВНИМАНИЕ!
В релизе QHB 1.5.2 эта функциональность является экспериментальной, ее использование в установке на производственной среде не рекомендовано.

Разделяемый кэш планов позволяет экономить процессорное время и оперативную память не пересоздавая планы выполнения SQL-запросов, сохраняя их в разделяемой памяти. Это является отличительной особенностью по сравнению с кэшем планов в обычном PostgreSQL, который выделяет пустой буфер каждому новому backend-соединению.

Параметры разделяемого кэша в qhb.conf

qhb_plan_cache_size – размер буфера разделяемой памяти. При нулевом значении кэш выключен.

Пример настройки

qhb_plan_cache_size = 1GB # включает кэш планов с буфером в один ГБ

Встроенная очередь сообщений

Описание

Очереди сообщений представляет собой подсистему СУБД для отправки типизированных сообщений и их получения заинтересованными (подписанными) клиентами. Существует два вида очередей: персистентные и in-memory. Если последние хранят сообщения до обработки всеми подписанными клиентами исключительно в памяти, то персистентные могут сохранять данные на диске, благодаря чему данные будут доступны после перезапуска базы.

На данный момент реализована только in-memory очередь. Она хранит элементы в буфере фиксированного размера, и если тот заполнится, функция qhb_enqueue будет перезаписывать наиболее старые элементы.

Необходимые системные настройки

Так как Брокер Очередей используется выделенный системный процесс СУБД, то необходимо учитывать разрешённое максимальное количество данных процессов задаваемое системной настройкой max_worker_processes.
Следует установить значение, с учётом количества брокеров, которые будут обсуживать очереди на данном кластере QHB.

В случае, если невозможно запустить дополнительные серверные процессы, функция start_mq_broker() вернёт ошибку.

Запуск брокера очереди сообщений при старте СУБД

Для запуска брокера очереди сообщений при старте СУБД в файл qhb.conf необходимо добавить параметр with_mq_broker с указанием для каких баз данных и от имени каких пользователей производить запуск. Важно, чтобы как база данных, так и пользователь существовали, а так же чтобы у пользователя были необходимые права для работы с базой данных, в противном случае старт СУБД будет прерван.

# Добавление автоматического запуска брокера очереди сообщений для баз данных dbname1 и db_name2
echo with_mq_broker = 'db_owner1:db_name1, db_owner2:db_name2' >> $PGDATA/qhb.conf;

# Добавление автоматического запуска брокера очереди сообщений только для базы данных dbname1
echo with_mq_broker = 'db_owner1:db_name1' >> $PGDATA/qhb.conf;

Функции для работы с очередью сообщений

Имя | Описание
status_mq_broker() | Запрос статуса брокера
start_mq_broker() | Запуск брокера очереди сообщений во время работы СУБД
stop_mq_broker() | Остановка брокера очереди сообщений
qhb_create_queue(name,typeoid) | Создание очереди указанного типа
qhb_delete_queue(name) | Удаление очереди
qhb_register(queue,subscriber,callback) | Регистрация подписчика для обработки новых сообщений
qhb_unregister(queue,subscriber) | Удаление подписчика
qhb_enqueue(queue,data) | Добавление элемента в очередь
qhb_dequeue() | Получение элемента из очереди (доступно только из подписанного обработчика)

Брокер очереди сообщений

Брокер очереди сообщений — это отдельный процесс, который предоставляет внутренний интерфейс для работы с очередью. Для того чтобы с ней работать, требуется явно запустить данный процесс с помощью функции start_mq_broker(). Помимо брокера также запускается процесс worker, который фактически вызывает переданные ему обработчики и в случае возникновения каких-либо ошибок способен перезапустится. Оба процесса реализованы как встроенные BackgroundWorker, а следовательно, их pid можно обнаружить в таблице pg_stat_activity.

Процедура обработки сообщений

Сообщения доставляются подписчикам асинхронно с помощью вызова указанной пользователем процедуры. Для того чтобы обрабатывать новые сообщения, необходимо зарегистрировать процедуру обработки с помощью функции qhb_register. Сама процедура может быть создана с помощью CREATE PROCEDURE и должна иметь пустое множество аргументов, к примеру:

CREATE PROCEDURE callback()
LANGUAGE plpgsql AS $$
BEGIN
    CREATE TABLE IF NOT EXISTS my_table (x TEXT);
    INSERT INTO my_table
    SELECT x FROM qhb_dequeue() as (x TEXT);
END; $$;

Элемент безусловно удаляется из очереди до вызова обработчика, даже если обработчик не вызывает qhb_dequeue. Каждый вызов qhb_dequeue в пределах одного вызова обработчика будет возвращать один и тот же результат вне зависимости от того, сколько раз она была вызвана.

Любые ошибки, возникшие в процедуре, логируются и далее никак не обрабатываются (в том числе процедура обработки не удаляется из списка подписчиков). Возникновение ошибки в одном из обработчиков не влияет на получение сообщений другими обработчиками.

ВАЖНО Не существует никаких ограничений для использования функций для работы с очередью в процедуре обработки сообщений. То есть в обработчике сообщений кроме получения сообщения из очереди при помощи qhb_dequeue можно также добавить сообщение в очередь, создать и / или удалить очередь, зарегистрировать или удалить подписчика и т.п. Использовать отличные от qhb_dequeue функции для работы с очередью в обработчике нужно очень внимательно и осторожно - добавление сообщения в очередь в обработчике сообщения может привести к неуправляемому поведению системы, неконтролируемым побочным эффектам и потере сообщений в очереди из-за их перезаписи. При реализации процедуры обработки сообщения лучшей практикой будет избегать использования каких-либо функций работы с очередью кроме qhb_dequeue.

Типизация

Очередь является строго типизированной и может содержать элементы только одного типа; данный тип явно указывается при создании очереди. К примеру, очередь с именем my_queue1 и типом int4 можно создать следующим образом:

SELECT qhb_create_queue(name := 'my_queue1', typeoid := oid)
FROM pg_type
WHERE typname = 'int4';

Список поддерживаемых типов ограничен следующим перечнем:

Класс типа | Название конкретного типа
Числовой тип | int2, int4, int8
Тип с плавающей запятой | float4, float8
Логический тип | bool
Строковый тип | text, cstring, bpchar, varchar, mvarchar
Временной тип | timestamp, timestamptz, time, timetz, date, interval

Тип char напрямую не поддерживается. Вместо него следует использовать аналогичный тип bpchar.

При использовании enqueue или dequeue с некорректным типом будет выдана соответствующая ошибка.

Поддержка транзакционности

Очередь поддерживает транзакционность СУБД: отправка сообщения в очередь через qhb_enqueue внутри транзакции выполнит саму отправку только после успешного завершения текущей транзакции. При выполнении команды ROLLBACK или необработанном исключении внутри транзакции данные в очередь доставлены не будут и далее будут игнорироваться (обработчик при этом также остается зарегистрированным).

Внутреннее состояние очереди сообщений

Состояние очереди хранится для каждой базы данных отдельно в системных таблицах qhb_queue и qhb_queue_consumer. Первая содержит список очередей и их типы, вторая содержит список подписанных обработчиков и их соответствие с очередью. Данные таблицы рекомендуется использовать только в информационных целях, поскольку их изменение непосредственно ведет к неконсистентному состоянию.

Планировщик заданий QSched

QSched — встроенный планировщик заданий для QHB.

Описание

Пользовательские задания выполняются набором фоновых процессов, которые уникальны для каждой базы данных. Основой для работы планировщика является набор системных таблиц с префиксом pg_catalog.qsched_..., который доступен для просмотра администратору базы данных и пользователю-владельцу базы данных.

Для создания и планирования заданий предусмотрено право SCHED, которое присваивается администратором базы данных пользователям, имеющим право работать с этой базой.

Использование

Присвоение права работы с QSched

Администратор/владелец базы данных вправе наделить любого пользователя правом работы с планировщиком:

-- GRANT CONNECT ON DATABASE ... TO ...;
GRANT SCHED ON DATABASE ... TO ...;

Запуск планировщика во время работы СУБД

SELECT start_qsched_master();
-- Из процедуры будет возвращен PID планировщика для данной базы данных.

Запуск планировщика при старте СУБД

Для запуска планировщика при старте СУБД в файл qhb.conf необходимо добавить параметр with_qsched с указанием, для каких баз данных и от имени каких пользователей производить запуск. Важно, чтобы как база данных, так и пользователь существовали, а так же чтобы у пользователя были необходимые права для работы с базой данных, в противном случае старт СУБД будет прерван.

# Добавление автоматического запуска Qsched для баз данных dbname1 и db_name2
echo with_qsched = 'db_owner1:db_name1, db_owner2:db_name2' >> $PGDATA/qhb.conf;

# Добавление автоматического запуска Qsched только для базы данных dbname1
echo with_qsched = 'db_owner1:db_name1' >> $PGDATA/qhb.conf;

Проверка статуса планировщика

SELECT status_qsched_master();
-- Из процедуры будет возвращено значение типа boolean планировщика, которое
-- указывает на то, работает ли "мастер" (true) или нет (false) для данной базы данных.

Остановка планировщика

SELECT stop_qsched_master();
-- Из процедуры будет возвращен идентификатор завершенного процесса PID планировщика
-- для данной базы данных.

Создание задачи

Задачи создаются и планируются посредством SQL-интерфейса, предусмотренного в планировщике QSched:

-- Общая форма для примера:
SELECT qsched_job_create_{proc,block,query}('job name', 'job payload');
-- Из процедуры будет возвращен JOB_ID:Oid.

-- Пример немедленного выполнения (для отладки):
SELECT qsched_job_exec(/* id задачи */ 1);

-- Конкретная функция для создания задачи с содержимым в виде запроса:
SELECT qsched_job_create_query('job name', 'SELECT 123;');
-- Из процедуры будет возвращен JOB_ID:Oid.

-- Конкретная функция для создания задачи с содержимым в виде plpgsql-блока (в
-- данном примере — бесконечный цикл):
SELECT qsched_job_create_block('job name', 'BEGIN LOOP PERFORM pg_sleep(1); END LOOP; END;')
-- из процедуры будет возвращен JOB_ID:Oid.

-- Конкретная функция для создания задачи с содержимым в виде хранимой процедуры
-- (существование процедуры 'proc_name' проверяется во время выполнения):
SELECT qsched_job_create_proc('job name', 'proc_name');
-- Из процедуры будет возвращен JOB_ID:Oid.

Примечания по содержимому выполняемых заданий

При создании задачи можно указать несуществующую в данный момент процедуру, имя которой будет разрешаться при выполнении (относительно текущего значения параметра search_path), либо можно указать процедуру сразу с именем схемы:

SELECT qsched_job_create_proc('job name','my_schema.my_proc');

Для создания задачи с переменным набором аргументов, но общей целевой функцией полезно использовать синтаксис с plpgsql-блоками:

-- Пример использования анонимного блока с целью фиксирования значений аргументов
-- (аргументы также могут рассчитываться внутри анонимного блока):
SELECT qsched_job_create_block('job name', $$BEGIN PERFORM my_proc('my_arg1', 'my_arg2'); END;$$);

Создание расписания

Задача планируется одним или несколькими периодическими расписаниями с cron-подобным синтаксисом:

-- Запланирует для выполнения каждые 30 секунд:
SELECT qsched_schedule_create(/* id задачи */ 1, '1/30 * * * * *');

-- Включает все расписания данной задачи в набор выполняемых фоновых задач QSched:
SELECT qsched_job_enable(/* id задачи */ 1);

Выключение расписаний/задач

-- Выключает все расписания для данной задачи из набора выполняемых фоновых задач
-- QSched:
SELECT qsched_job_disable(/* id задачи */ 1);

Включение и выключение конкретных расписаний

Наряду с функциями включения и отключения целостных задач qsched_job_{enable,disable} существуют функции, которые работают с гранулярностью расписания. Так, передав идентификатор расписания в функции qsched_schedule_{enable,disable}, можно активировать или отключить конкретное расписание конкретной задачи.

Узнать идентификаторы расписаний можно в системном представлении qsched_my_jobs.

Просмотр логов планировщика

Для просмотра доступны логи по задачам, которые либо были созданы текущим пользователем, либо планировались для выполнения от его имени:

-- Покажет представление, основанное на системной таблице pg_catalog.qsched_job_run_status:
SELECT * FROM qsched_my_log;

Просмотр общей таблицы задач и расписаний для пользователя

Обратившись к представлению qsched_my_jobs, пользователь может увидеть задачи и расписания, которые создавал сам, либо те, которые запланированы исполняться от его имени.

-- Выведет полную сводку по "вашим" задачам и расписаниям:
SELECT * FROM qsched_my_jobs;

Динамический расчет времени следующего выполнения задачи

Допустим, мы создали задачу и установили ей расписание в формате crontab:

SELECT qsched_job_create_query('job name', 'SELECT 123');
SELECT qsched_schedule_create(1, '* * * * * *');

SELECT qsched_job_enable(1);
COMMIT;

В данном случае мы активировали задачу, и, согласно расписанию, она будет выполняться каждую секунду. Но, например, мы хотим, чтобы следующее время выполнения задачи рассчитывалось динамически, после очередного успешного выполнения. Для этого SQL-интерфейс QSched предоставляет функцию qsched_schedule_dyn_override, которая для заданного расписания позволяет определить команду SQL для расчета времени следующего выполнения:

-- Общий синтаксис:
SELECT qsched_schedule_dyn_override(/* Oid расписания */, /*динамический SQL*/);

-- Пример:
SELECT qsched_schedule_dyn_override(1, $$CURRENT_TIMESTAMP + '1 day'$$);

Результат выполнения данной команды SQL всегда будет интерпретирован как время, а точнее TIMESTAMP WITHOUT TIME ZONE.

Теперь задача будет выполнятся не каждую секунду, а только через день после последнего успешного выполнения.

Отмена динамического вычисления времени следующего старта задачи

Если вы хотите, чтобы задача не вычисляла свое время запуска динамически, согласно выражению в столбце next_fire_statement, а просто действовала по расписанию, то следует выполнить запрос с использованием функции qsched_schedule_cancel_override, передав ей идентификатор расписания:

SELECT qsched_schedule_cancel_override(/* id расписания */ 1);

Здесь для примера указан идентификатор 1, но это может быть любое значение типа OID. Тогда ваша задача будет следовать расписанию для определения времени запуска.

Убедиться в правильности изменений можно обратившись к представлению qsched_my_jobs, задав желаемый идентификатор задачи:

SELECT next_fire_statement FROM qsched_my_jobs WHERE job_id = /* id задачи */ 1;

Выполнение произвольного SQL для задачи, завершившейся ошибкой

Допустим, мы создали задачу и ее идентификатор 123. Задача может завершиться неудачей при любом исполнении, и мы хотим настроить выражение SQL, которое бы выполнялось процессом QSched при таком неудачном завершении:

-- Вторым аргументом указано выражение SQL Fallback:
SELECT qsched_job_set_fallback_sql(123, 'UPDATE err_log_table SET failure = 1;');

Теперь каждый раз, когда данная задача будет завершаться неудачно, исполнитель процесса QSched будет пытаться обновить записи в таблице err_log_table с выставлением поля failure в 1. (Данная операция дана в качестве примера, можно использовать произвольное выражение SQL.)

Если и задача завершилась неудачно, и Fallback-выражение выполнилось с ошибкой, в системных таблицах QSched данный факт отмечается и доступен для просмотра в представлении qsched_my_log:

-- Столбец double_fault означает двойную ошибку — и содержимого задачи, и
-- Fallback-выражения.
SELECT double_fault FROM qsched_my_log;

Отмена SQL-выражения Fallback для конкретной задачи

Если вы хотите, чтобы Fallback-выражение больше не выполнялось при неудачном завершении какой-либо задачи, необходимо выполнить запрос с использованием функции qsched_job_unset_fallback_sql:

SELECT qsched_job_unset_fallback_sql(/* id задачи */ 1);

Удаление и отключение ненужных задач

В данной версии поддерживается полное удаление задачи (исключая историю исполнения) с помощью функции qsched_job_delete:

-- Удаление Задачи, ее расписания, логов и историю исполнения:
SELECT qsched_job_delete(1 /* oid задачи */);

Поскольку полное удаление может быть нежелательным, также предусмотрено простое выключение всех расписаний для конкретной задачи:

-- Сделает все расписания для данной задачи неактивными, включая запланированные
-- с помощю qsched_job_exec немедленные запуски:
SELECT qsched_job_disable(1 /* oid задачи */);

Активировать все расписания для данной задачи обратно можно с помощью функции qsched_job_enable(/* oid задачи */).

Конфигурация автоматической очистки логов планировщика

Автоматическая очистка логов планировщика конфигурируется при помощи следующих параметров: | Имя | Описание | Обязателен | :-- | :-- | :--
| qsched_log_rotation_max_lifetime | Максимальное время жизни записи | Да, при отсутствии qsched_log_rotation_max_entries | qsched_log_rotation_max_entries | Максимальное количество сохраняемых записей | Да, при отсутствии qsched_log_rotation_max_lifetime | qsched_log_rotation_period | Период запуска очистки записей | Да | qsched_log_rotation_strategy | Стратегия очистки | Нет | qsched_log_rotation_adjustment_period | Период подсчета записей для автоматической стратегии | Нет

Параметры очистки автоматически перезагружаются при подаче сигнала SIGHUP или при вызове pg_reload_conf(), таким образом возможно изменить параметры очистки логов или включить/выключить ее во время работы СУБД.

Очистка может производиться по следующим стратегиям:

Delete (значение 0), удаляет устаревшие записи при помощи DELETE (по-умолчанию).

Truncate (значение 1), сохраняет актуальные записи, очищает таблицу при помощи TRUNCATE, загружает сохраненные записи обратно. Потенциально быстрее, чем Delete, когда удаляется больше записей, чем сохраняется.

Автоматически (значение 2), периодически считает количество сохраняемых/удаляемых записей, и если нужно сохранить больше половины записей, то используется Delete, в противном случае - Truncate. Delete/Truncate используются до следующего подсчета записей. Периодичность подсчета записей определяется параметром qsched_log_rotation_adjustment_period. Например, если указать этот параметр равным двум, то пересчет записей (а так же потенциальная смена стратегии очистки) будет производиться каждые два запуска очистки. Если же указать ноль, то пересчет будет производиться при каждом запуске.

Примечание
Поскольку TRUNCATE системных таблиц требует разрешения на модификацию системных таблиц, для работ стратегий 1 и 2 требуется дополнительный параметр конфигурации allow_system_table_mods = true.

Для запуска автоматической очистки логов планировщика в файл qhb.conf необходимо добавить параметры qsched_log_rotation_max_lifetime и/или qsched_log_rotation_max_entries, а так же qsched_log_rotation_period:

# Максимальное время жизни записи - 30 минут
echo qsched_log_rotation_max_lifetime = '30m' >> $PGDATA/qhb.conf;
# Максимальное количество сохраняемых записей - 500
echo qsched_log_rotation_max_entries = '500' >> $PGDATA/qhb.conf;
# Запускать очистку раз в 5 минут
echo qsched_log_rotation_period = '5m' >> $PGDATA/qhb.conf;

Проверка статуса автоматической очистки

Узнать, включена ли автоматическая очистка логов:

SELECT qsched_log_rotation_status();

Выводит true при включенной очистке и false при выключенной.

Узнать текущую стратегию очистки логов:

SELECT qsched_log_rotation_static_strategy();

Выводит текущую стратегию, 0 для Delete, 1 для Truncate, 2 для Delete. -1 выводится при отключенной очистке.

Узнать текущую стратегию очистки логов при использовании автоматической стратегии:

SELECT qsched_log_rotation_auto_strategy();

Выводит текущую стратегию, 0 для Delete, 1 для Truncate. -1 выводится в случаях, когда очистка отключена, когда автоматическая стратегия не используется, а так же до первого запуска очистки, поскольку на тот момент стратегия еще ни разу не была выработана.

Конфигурация автоматической очистки логов планировщика

Автоматическая очистка логов планировщика конфигурируется при помощи следующих параметров: | Имя | Описание | Обязателен | :-- | :-- | :--
| qsched_log_rotation_max_lifetime | Максимальное время жизни записи | Да, при отсутствии qsched_log_rotation_max_entries | qsched_log_rotation_max_entries | Максимальное количество сохраняемых записей | Да, при отсутствии qsched_log_rotation_max_lifetime | qsched_log_rotation_period | Период запуска очистки записей | Да | qsched_log_rotation_strategy | Стратегия очистки | Нет | qsched_log_rotation_adjustment_period | Период подсчета записей для автоматической стратегии | Нет

Параметры очистки автоматически перезагружаются при подаче сигнала SIGHUP или при вызове pg_reload_conf(), таким образом возможно изменить параметры очистки логов или включить/выключить ее во время работы СУБД.

Очистка может производиться по следующим стратегиям:

Delete (значение 0), удаляет устаревшие записи при помощи DELETE (по-умолчанию).

Truncate (значение 1), сохраняет актуальные записи, очищает таблицу при помощи TRUNCATE, загружает сохраненные записи обратно. Потенциально быстрее, чем Delete, когда удаляется больше записей, чем сохраняется.

Автоматически (значение 2), периодически считает количество сохраняемых/удаляемых записей, и если нужно сохранить больше половины записей, то используется Delete, в противном случае - Truncate. Delete/Truncate используются до следующего подсчета записей. Периодичность подсчета записей определяется параметром qsched_log_rotation_adjustment_period. Например, если указать этот параметр равным двум, то пересчет записей (а так же потенциальная смена стратегии очистки) будет производиться каждые два запуска очистки. Если же указать ноль, то пересчет будет производиться при каждом запуске.

Примечание
Поскольку TRUNCATE системных таблиц требует разрешения на модификацию системных таблиц, для работ стратегий 1 и 2 требуется дополнительный параметр конфигурации allow_system_table_mods = true.

Для запуска автоматической очистки логов планировщика в файл qhb.conf необходимо добавить параметры qsched_log_rotation_max_lifetime и/или qsched_log_rotation_max_entries, а так же qsched_log_rotation_period:

# Максимальное время жизни записи - 30 минут
echo qsched_log_rotation_max_lifetime = '30m' >> $PGDATA/qhb.conf;
# Максимальное количество сохраняемых записей - 500
echo qsched_log_rotation_max_entries = '500' >> $PGDATA/qhb.conf;
# Запускать очистку раз в 5 минут
echo qsched_log_rotation_period = '5m' >> $PGDATA/qhb.conf;

Проверка статуса автоматической очистки

Узнать, включена ли автоматическая очистка логов:

SELECT qsched_log_rotation_status();

Выводит true при включенной очистке и false при выключенной.

Узнать текущую стратегию очистки логов:

SELECT qsched_log_rotation_static_strategy();

Выводит текущую стратегию, 0 для Delete, 1 для Truncate, 2 для Delete. -1 выводится при отключенной очистке.

Узнать текущую стратегию очистки логов при использовании автоматической стратегии:

SELECT qsched_log_rotation_auto_strategy();

Выводит текущую стратегию, 0 для Delete, 1 для Truncate. -1 выводится в случаях, когда очистка отключена, когда автоматическая стратегия не используется, а так же до первого запуска очистки, поскольку на тот момент стратегия еще ни разу не была выработана.

Число допустимых параллельных процессов

С помощью параметра конфигурации qsched_parallel_slaves администратор данной базы данных может установить ограничение на число параллельных процессов QSched, выделенных под исполнение пользовательских задач.

-- Увеличить допустимое число параллельных процессов до 4:
ALTER DATABASE SET qsched_parallel_slaves TO 4;

-- После этого следует перезапустить координатор процессов:
SELECT start_qsched_master();

-- Но перед этим следует завершить текущий координатор.
-- Узнать PID можно, обратившись к дереву процессов либо к представлению pg_stat_activity.
\! kill -TERM <pid_of_qsched_master>

SHOW qsched_parallel_slaves;

Данное ограничение действует по отношению к конкретной базе данных и не распространяется на процессы-координаторы QSched Master.

Ограничения QSched

В QSched введено стандартное для многих Enterprise-планировщиков ограничение: хотя многие задачи могут исполняться параллельно, данная конкретная задача (имеющая OID и имя) не будет исполняться параллельно с экземпляром той же самой задачи. Это сделано для защиты от ненамеренной порчи данных, если, например, содержимое задачи модифицирует таблицы СУБД или делает какие-то другие модифицирующие действия.

Переменные уровня сеанса qhb-variables

ВНИМАНИЕ!
В релизе QHB 1.5.2 эта функциональность является экспериментальной, ее использование в установке на производственной среде не рекомендовано.

Описание

qhb-variables позволяет создавать переменные, которые будут удалены по завершении сеанса. Этот механизм является альтернативой временным таблицам в местах, где их производительности недостаточно. Переменные хранятся в пакетах. Существует два типа переменных: скалярные и табличные. В скалярных переменных за раз можно хранить только одно значение любого типа, в табличных же можно хранить множество строк. Также переменные подразделяются на транзакционные и нетранзакционные. Состояние первых не зависит от результата транзакции, а вторые меняют свое значение, учитывая COMMIT и ROLLBACK. Тип переменной нельзя менять после создания до полного удаления (в случае транзакционной переменной до выполнения COMMIT).

Функции qhb-variables

Функции для работы со скалярными переменными

Функция

Описание

var_set ( пакет text, переменная text, значение (non)anynonarray, is_transactional bool default false ) → void

Установка значения переменной, в том числе обновление.

var_get ( пакет text, переменная text, тип_переменной (non)anynonarray, strict bool default true ) → anynonarray

Получение значения переменной.

Примеры использования

SELECT var_set('pack', 'var', 0);
 var_set
---------

(1 row)

SELECT var_get('pack', 'var', NULL::int);
 var_get
---------
       0
(1 row)

BEGIN;
SELECT var_set('pack', 'var', 'before savepoint'::text, true);
 var_set
---------

(1 row)

SAVEPOINT zero;
SELECT var_set('pack', 'var', 'after savepoint'::text, true);
 var_set
---------

(1 row)

SELECT var_get('pack', 'var', NULL::text);
     var_get
-----------------
 after savepoint
(1 row)

ROLLBACK TO zero;
SELECT var_get('pack', 'var', NULL::text);
     var_get
------------------
 before savepoint
(1 row)

Функции для работы с табличными переменными

Функция

Описание

var_insert ( пакет text, переменная text, запись record, is_transactional bool default false ) → void

Вставляет новую запись в табличную переменную. Первый столбец записи является первичным ключом. Возвращает ошибку, если запись с таким ключом уже есть или запись имеет другую сигнатуру.

var_update ( пакет text, переменная text, запись record ) → boolean

Обновляет запись в табличной переменной, опираясь на поле ключа. Возвращает true, если запись была обновлена. Если сигнатура переменной отличается от предоставленной строки, то возникнет ошибка.

var_delete ( пакет text, переменная text, значение anynonarray ) → boolean

Удаляет запись по ключу. Возвращает true, если запись была удалена.

var_select ( пакет text, переменная text ) → set of record

Возвращает текущее значение переменной.

var_select ( пакет text, переменная text, значение anynonarray ) → record

Возвращает текущее значение переменной по ключу.

var_select ( пакет text, переменная text, значение anyarray ) → set of record

Возвращает текущее значение переменной по ключам.

Примеры использования

SELECT var_insert('pac', 'var', ROW(NULL::int, 0::int));
 var_insert
------------

(1 row)

SELECT var_insert('pac', 'var', ROW(0::int, 1::int));
 var_insert
------------

(1 row)

SELECT var_insert('pac', 'var1', ROW(0::int, 2::int));
 var_insert
------------

(1 row)

SELECT var_insert('pac', 'var1', ROW(2::int, 2::int));
 var_insert
------------

(1 row)

SELECT var_select('pac', 'var');
 var_select
------------
 (,0)
 (0,1)
(2 rows)

SELECT var_select('pac', 'var1', 0);
 var_select
------------
 (0,2)
(1 row)

SELECT var_select('pac', 'var1', ARRAY[0, 1]);
 var_select
------------
 (0,2)
(1 row)

Общие функции переменных

Функция

Описание

var_exists ( пакет text, переменная text, is_record bool ) → bool

Возвращает true, если переменная существует в данном пакете.

var_exists ( пакет text ) → bool

Возвращает true, если пакет существует.

var_remove ( пакет text, переменная text, is_record bool) → void

Удаляет переменную по имени. Если переменной или пакета не существует, то будет возвращена ошибка.

var_remove ( пакет text ) → void

Удаляет пакет со всеми переменными внутри него. Если пакета не существует, то будет возвращена ошибка.

var_free () → void

Удаляет все пакеты со всеми переменными внутри.

var_list () → table ( пакет text, переменная text, тип_переменной text, is_transactional bool )

Возвращает список всех пакетов и переменных с указанием их транзакционности и типа.

var_stats () → table ( выделенная_память bigint )

Возвращает объем памяти, занимаемый переменными. Обратите внимание, что эта функция возвращает выделенное место, которое может быть использовано не полностью. Для получения более подробной информацией используйте функцию pg_backend_memory_contexts().

В одном пакете могут быть две переменные с одинаковым именем, но только при условии, что одна из них табличная, а другая скалярная. По умолчанию функции var_exists и var_remove удаляют обе переменные, поэтому если вам нужно выбрать только табличную переменную, передайте последним параметром true, а для удаления только скалярной переменной передайте false. Этот параметр является необязательным.

Пример:

BEGIN;
SELECT var_set('pack', 'var', 0, true);
 var_set
---------

(1 row)

SELECT var_insert('pack', 'var', ROW(0::int, 1::int), true);
 var_insert
------------

(1 row)

SELECT * FROM var_list();
 package | variable |         variable_type         | is_transactional
---------+----------+-------------------------------+------------------
 pack    | var      | Scalar(integer)               | t
 pack    | var      | Record(row(integer, integer)) | t
(2 rows)

SAVEPOINT first;
SELECT var_remove('pack', 'var', true);
 var_remove
------------

(1 row)

SELECT var_exists('pack', 'var', true);
 var_exists
------------
 f
(1 row)

SELECT var_exists('pack', 'var', false);
 var_exists
------------
 t
(1 row)

SELECT var_exists('pack', 'var');
 var_exists
------------
 t
(1 row)

SELECT var_remove('pack', 'var');
 var_remove
------------

(1 row)

SELECT var_exists('pack', 'var', false);
 var_exists
------------
 f
(1 row)

ROLLBACK TO first;
SELECT var_exists('pack', 'var');
 var_exists
------------
 t
(1 row)

Примеры использования

BEGIN;
SELECT var_set('pack', 'var', 'non-transactional'::text);
 var_set
---------

(1 row)

SELECT var_set('pack', 'var1', 'transactional'::text, true);
 var_set
---------

(1 row)

SELECT * FROM var_list();
 package | variable | variable_type | is_transactional
---------+----------+---------------+------------------
 pack    | var      | Scalar(text)  | f
 pack    | var1     | Scalar(text)  | t
(2 rows)

SAVEPOINT first;
SELECT var_free();
 var_free
----------

(1 row)

SELECT var_stats();
 var_stats
-----------
      8192
(1 row)

SELECT * FROM var_list();
 package | variable | variable_type | is_transactional
---------+----------+---------------+------------------
(0 rows)

ROLLBACK TO first;
SELECT * FROM var_list();
 package | variable | variable_type | is_transactional
---------+----------+---------------+------------------
 pack    | var1     | Scalar(text)  | t
(1 row)

Другие примеры использования

Вставка из таблицы и деструктуризация SELECT:

CREATE TABLE test_table (id int, t text);
INSERT INTO test_table VALUES (0, 'first'), (1, 'second');
SELECT var_insert('pack', 'var', test_table) FROM test_table;
 var_insert
------------

(2 rows)

SELECT var_select('pack', 'var');
 var_select
------------
 (1,second)
 (0,first)
(2 rows)

SELECT * FROM var_select('pack', 'var') AS (id int, t text);
 id |   t
----+--------
  1 | second
  0 | first
(2 rows)

Изменение типа при полном удалении переменной:

SELECT var_set('pack', 'var', 1::int);
 var_set
---------

(1 row)

SELECT var_set('pack', 'var_transactional', 1::int, true);
 var_set
---------

(1 row)

SELECT * FROM var_list();
 package |     variable      |  variable_type  | is_transactional
---------+-------------------+-----------------+------------------
 pack    | var_transactional | Scalar(integer) | t
 pack    | var               | Scalar(integer) | f
(2 rows)

BEGIN;
SELECT var_free();
 var_free
----------

(1 row)

SELECT var_set('pack', 'var', 1::text); -- успешно
 var_set
---------

(1 row)

-- SELECT var_set('pack', 'var_transactional', 1::text, true); -- Ошибка
COMMIT;
SELECT var_set('pack', 'var_transactional', 1::text, true); -- Успешно
 var_set
---------

(1 row)

SELECT * FROM var_list();
 package |     variable      | variable_type | is_transactional
---------+-------------------+---------------+------------------
 pack    | var_transactional | Scalar(text)  | t
 pack    | var               | Scalar(text)  | f
(2 rows)

Этот пример работает, потому что переменные были безвозвратно удалены при вызове var_free.

Сохранение элементов таблицы. При вставке в переменную создается независимая копия данных.

CREATE TABLE test_table (id int, t text);
INSERT INTO test_table VALUES (0, 'first'), (1, 'second'), (2, 'third');
SELECT var_insert('pack', 'var', test_table) FROM test_table WHERE id < 2;
 var_insert
------------

(2 rows)

DROP TABLE test_table;
CREATE TABLE test_table AS SELECT * FROM var_select('pack', 'var') AS (id int, t text);
SELECT * FROM test_table;
 id |   t
----+--------
  1 | second
  0 | first
(2 rows)

SELECT + CURSOR

При вызове var_select создается мгновенная копия данных, поэтому на результат SELECT var_update не повлияет. Однако если переменная была удалена, то получение оставшихся данных станет невозможным.

CREATE TABLE test_table (id int, t text);
INSERT INTO test_table VALUES (0, 'first'), (1, 'second'), (2, 'third');
SELECT var_insert('pack', 'var', test_table, true) FROM test_table;
 var_insert
------------

(3 rows)

BEGIN;
DECLARE r1_cur CURSOR FOR SELECT var_select('pack', 'var');
FETCH 1 in r1_cur;
 var_select
------------
 (2,third)
(1 row)

SELECT var_remove('pack');
 var_remove
------------

(1 row)

FETCH 1 in r1_cur;
ERROR:  var_select: Existence: Variable with name `var` does not exist in package `pack`
-- ОШИБКА:  var_select: Существование: В пакете `pack` не существует переменной с именем `var`
ROLLBACK;
SELECT var_select('pack', 'var');
 var_select
------------
 (2,third)
 (1,second)
 (0,first)
(3 rows)

Профилирование долгих запросов

Данная функциональность позволяет отслеживать состояние выполнения запроса, не дожидаясь его окончания. Стоит учитывать, что включение профилирования привносит некоторое количество затрат для всех запросов серверного процесса.

Подготовка

Для включения профилирования необходимо установить параметр query_profiling в настройках (либо в конфигурационном файле, либо динамически с помощью команды SET).

Для профилирования также необходима функционирующая очередь сообщений, в частности, вызов select start_mq_broker();. Далее все взаимодействие происходит с системными очередями, которые уже созданы.

Анализ информации очень чувствителен к статической информации поддерживаемой СУБД, поэтому рекомендуется использовать для исследуемых таблиц команду ANALYZE.

Анализ информации

Для получения информации о выполнении запроса модуль предоставляет 2 основные функции:

qhb_running_queries

qhb_dump_qep

Функция qhb_running_queries возвращает временную таблицу, которая содержит следующую информацию:

Название столбца | Тип | Описание | Комментарий
query_id | int8 | Уникальный идентификатор запроса |
backend_pid | int8 | PID postgres-бэкенда, на котором выполняется запрос | Равнозначен значению из pg_stat_activity.pid
query_string | text | Текстовая строка запроса |
query_started | timestamptz | Время начала работы запроса |
ETC | timestamptz | Ориентировочное время окончания работы | Может быть NULL, при сложных запросах оценка может быть далека от реальности
elapsed_counter | float8 | Внутренний счетчик для отслеживания выполненной работы запроса |
estimated_total_counter | float8 | Внутреннее ожидаемое количество работы для запроса |

Функция qhb_dump_qep принимает идентификатор запроса из qhb_running_queries и отображает дерево плана запроса. Второй аргумент принимает строку со способом форматирования:

'full' — отображает полные значения счетчиков;

'scaled' — отображает значения счетчиков приведенные к одному порядку (отображается в поле units);

'scientific' — отображает значения счетчиков;

'percent' — отображает значения счетчиков в процентах;

'mixed_percent' — отображает значения счетчиков в процентах и абсолютное ожидаемое значение;

Функция возвращает дерево плана, равнозначное тому, что возвращает EXPLAIN, а также статус выполнения для каждого узла. Стоит иметь в виду, что узлы могут иметь прогресс в 100%, но тем не менее вызываться повторно, увеличивая счетчик циклов. Пример анализа запроса:

# select start_mq_broker();
 start_mq_broker
-----------------

(1 row)

# create table tb (x int);
CREATE TABLE
# insert into tb select generate_series(1, 50000);
INSERT 0 50000
# select a.x, count(*) from tb a, tb b group by a.x;

В другом клиенте:

# select * from qhb_running_queries();
 query_id | backend_pid |                    query_string                    |         query_started         |              ETC              |  elapsed_counter   | estimated_total_counter
----------+-------------+----------------------------------------------------+-------------------------------+-------------------------------+--------------------+-------------------------
        7 |        1064 | select a.x, count(*) from tb a, tb b group by a.x; | 2022-02-10 16:34:01.719477+03 | 2022-02-10 16:46:49.189314+03 | 2337060.9605833497 |             97739647.85
(1 row)

# select qhb_dump_qep(query_id, 'percent') from qhb_running_queries() where query_string like 'select a.x%';
                  qhb_dump_qep
-------------------------------------------------
                                                +
 ~  Agg (progress: 0.00%, loops:  0)            +
   ~  Nest Loop (progress: 5.69%, loops:  0)    +
     -> Seq Scan (progress: 6.45%, loops:  0)   +
     ~  Material (progress: 88.30%, loops: 3648)+
       ~  Seq Scan (progress: 88.32%, loops:  0)+

(1 row)

Ограничения

Реализация в текущем виде содержит ограничения:

Память для внутренних структур профилирования фиксирована и ограничена 8 МБ, при превышении данного значения профилирование будет игнорироваться.

Значение ETC предсказывается интерполяцией всего по двум точкам во времени, что означает, что это значение может достаточно сильно колебаться с течением времени.

Для анализа работы запроса используются оценки затрат узлов дерева плана, полученные от планировщика. Данные значения могут быть неточными, и они никак не корректируются в процессе выполнения запроса.

Часть II. Установка и настройка

В этой части рассматриваются темы, представляющие интерес для администратора базы данных QHB. Это включает в себя установку программного обеспечения, настройку сервера, управление пользователями и базами данных, а также задачи по обслуживанию. Любой, кто запускает сервер QHB, даже для личного использования, особенно в производственной среде, должен быть знаком с темами, описанными в этой части.

Информация в этой части расположена приблизительно в том порядке, в котором ее должен прочитать новый пользователь. Однако главы являются автономными, и при желании их можно читать независимо друг от друга в любом порядке. Информация в этой части представлена в повествовательной форме. Читателям, ищущим полное описание конкретной команды, следует обратиться к главе Команды SQL.

Первые несколько глав написаны так, чтобы их можно было понять без предварительных знаний, чтобы новые пользователи, которым необходимо настроить собственный сервер, спокойно могли начать свое исследование. Остальные же главы этой части — о настройке и управлении — предполагают, что читатель уже знаком с общими принципами использования базы данных QHB. Для получения дополнительной информации рекомендуется ознакомиться с частями Внутреннее устройство и Язык SQL.

Установка

Поддерживаемые платформы

Состав поставки

Установка из репозитория пакетов

Установка с обновлением

Начало работы

Создание базы данных

Доступ к базе данных

Настройка и эксплуатация сервера

Учетная запись пользователя QHB

Создание кластера баз данных

Запуск сервера баз данных

Управление ресурсами ядра

Завершение работы сервера

Обновление кластера QHB

Предотвращение имитации сервера

Варианты шифрования

Защита TCP/IP-соединений посредством SSL

Защита TCP/IP-соединений посредством шифрования GSSAPI

Защита TCP/IP-соединений посредством туннелей SSH

Улучшенная безопасность. Очистка памяти

Конфигурация сервера

Настройка параметров

Расположение файлов

Подключения и аутентификация

Потребление ресурсов

Журнал упреждающей записи

Репликация

Планирование запросов

Регистрация ошибок и протоколирование

Статистика времени выполнения

Автоматическая очистка

Параметры клиентских соединений по умолчанию

Управление блокировками

Совместимость с разными версиями и платформами

Обработка ошибок

Предустановленные параметры

Специализированные параметры

Параметры для разработчиков

Краткие аргументы

Параметры модулей и расширений, специфичных для QHB

Советы по производительности

Создание демонстрационных таблиц

Использование EXPLAIN

Статистика, используемая планировщиком

Управление планировщиком с помощью явных предложений JOIN

Заполнение базы данных

Настройки, снижающие прочность

Аутентификация клиентского приложения

Файл qhb_hba.conf

Файл сопоставлений имен пользователей

Методы аутентификации

Аутентификация trust

Аутентификация по паролю

Аутентификация GSSAPI

Аутентификация ident

Аутентификация peer

Аутентификация LDAP

Аутентификация RADIUS

Аутентификация по сертификату

Аутентификация PAM

Аутентификация BSD

Проблемы аутентификации

Роли в базе данных

Роли базы данных

Атрибуты ролей

Членство в роли

Удаление ролей

Предопределенные роли

Безопасность функций

Роли ИБ

Управление базами данных

Обзор

Создание базы данных

Шаблоны баз данных

Конфигурация базы данных

Удаление базы данных

Табличные пространства

Локализация

Поддержка языковых стандартов

Поддержка правил сортировки

Поддержка кодировок

Регулярные задачи обслуживания базы данных

Регулярная очистка

Регулярная переиндексация

Обслуживание файлов журнала

Резервное копирование и восстановление

SQL-дамп

Резервное копирование на уровне файловой системы

Непрерывное архивирование и восстановление на момент времени (PITR)

Инструмент резервного копирования qbackup

Синтаксис

Обзор

Ограничения применимости

Термины

Установка и подготовка

Использование

Справка по командной строке

Параметры

Версионирование

Высокая доступность, балансировка нагрузки и репликация

Сравнение различных решений

Доставка журналов на резервные серверы

Переключение после отказа

Горячий резерв

Мониторинг активности базы данных

Стандартные инструменты Unix

Система накопительной статистики

Просмотр информации о блокировках

Отчеты о ходе выполнения команд

Динамическая трассировка

Метрики QHB

Информационные панели метрик QHB для Grafana

Мониторинг использования диска

Определение использования диска

Ошибка переполнения диска

Надежность и журнал упреждающей записи

Надежность

Контрольные суммы данных

Журнал упреждающей записи (WAL)

Асинхронная фиксация

Конфигурация WAL

Внутреннее устройство WAL

Логическая репликация

Публикация

Подписка

Фильтры строк

Списки столбцов

Конфликты

Ограничения

Архитектура

Мониторинг

Безопасность

Параметры конфигурации

Быстрая настройка

JIT-компиляция

Что такое JIT-компиляция?

Когда применять JIT-компиляцию?

Конфигурация

Расширяемость

Регрессионные тесты

Выполнение тестов

Оценка тестирования

Вариантные сравнительные файлы

Проверка тестового покрытия

Процедурные языки

Установка процедурных языков

Написание обработчика процедурного языка

Пример написания обработчика

Клиентские приложения QHB

clusterdb — кластеризовать базу данных QHB

createdb — создать новую базу данных QHB

createuser — определить новую учетную запись пользователя QHB

dropdb — удалить базу данных QHB

dropuser — удалить учетную запись пользователя QHB

qhb_amcheck — проверить одну или несколько баз данных QHB на предмет повреждения

qhb_basebackup — создать базовую резервную копию кластера QHB

pgbench — запустить тест оценки производительности QHB

pg_config — получить информацию об установленной версии QHB

qhb_dump — выгрузить базу данных QHB в файл скрипта или другой архивный файл

qhb_dumpall — выгрузить кластер баз данных QHB в файл скрипта

qhb_isready — проверить состояние соединения с сервером QHB

qhb_receivewal — принимать потоковую передачу журналов упреждающей записи с сервера QHB

qhb_recvlogical — управлять потоками логического декодирования QHB

qhb_restore — восстановить базу данных QHB из архивного файла, созданного qhb_dump

qhb_verifybackup — проверить целостность базовой резервной копии кластера QHB

psql — интерактивный терминал QHB

reindexdb — переиндексировать базу данных QHB

vacuumdb — провести очистку памяти и анализ базы данных QHB

Серверные приложения QHB

initdb — создать новый кластер баз данных QHB

qhb_archivecleanup — очистить архивные файлы WAL QHB

qhb_bootstrap — создать новый кластер базы данных QHB (функциональный аналог initdb, полностью переделан с учетом особенностей QHB)

qhb_checksums — включить, выключить или проверить контрольные суммы данных в кластере баз данных QHB

qhb_controldata — отобразить управляющую информацию кластера баз данных QHB

qhb_ctl — инициализировать, запустить, остановить или управлять сервером QHB

qhb_resetwal — сбросить журнал упреждающей записи и другую управляющую информацию кластера баз данных QHB

qhb_rewind — синхронизировать каталог данных QHB с другим каталогом данных, который был разветвлен из него

qhb_test_fsync — определить самый быстрый вариант wal_sync_method для QHB

qhb_test_timing — оценить издержки измерения времени

qhb_upgrade — обновить экземпляр сервера QHB

qhb_waldump — отобразить журнал упреждающей записи кластера баз данных QHB в удобочитаемом виде

Написание обертки сторонних данных

Функции оберток сторонних данных

Подпрограммы обратного вызова обертки сторонних данных

Вспомогательные функции обертки сторонних данных

Планирование запросов с оберткой сторонних данных

Блокировка строк в обертках сторонних данных

Написание метода отбора выборки из таблицы

Вспомогательные функции метода отбора выборки

Написание провайдера индивидуализированного сканирования

Создание путей индивидуализированного сканирования

Создание планов индивидуализированного сканирования

Выполнение индивидуализированного сканирования

Генетический оптимизатор запросов

Обработка запроса как сложная задача оптимизации

Генетические алгоритмы

Генетическая оптимизация запросов (GEQO) в QHB

Дополнительные материалы

Установка

Поддерживаемые платформы

Платформа, то есть комбинация архитектуры процессора и операционной системы, считается поддерживаемой сообществом разработчиков QHB, если в коде предусмотрены возможности для работы на этой платформе и на текущий момент он успешно собирается и проходит регрессионные тесты на ней.

Для QHB версии 1.5.2 поддерживаются:

Centos Linux 7, x86_64

Centos Linux 8, x86_64

РЕД ОС 7, x86_64

Альт Сервер 8 СП

Альт Сервер 9, x86_64

Fedora 36, x86_64

Fedora 37, x86_64

Fedora 38, x86_64

Fedora 39, x86_64

Linux Debian 10, x86_64

Astra Linux Special Edition, «Смоленск», 1.7, x86_64

Ubuntu 18.04, x86_64

Ubuntu 22.04, x86_64

openSUSE Leap 15.4, x86_64

в docker-контейнере

Состав поставки

QHB поставляется в виде следующих пакетов:

qhb-core — ядро QHB;

qhb-contrib — расширения QHB;

qcp — пулер соединений;

qdl — средство быстрой загрузки;

qdlm — средство загрузки данных метрик в БД;

qbackup — инструмент резервного копирования;

metricsd — сервер метрик.

Дополнительно предоставляются пакеты внешних расширений:

PostGIS — Пространственные и географические объекты для QHB;

Patroni — Управление высокодоступными кластерами;

mysql_fdw — обертка внешних данных MySQL для QHB;

Orafce — функции совместимости и пакеты Oracle;

pg_hint_plan — модуль настройки планов выполнения с помощью «подсказок»;

pg_repack — модуль реорганизации таблиц с минимальными блокировками;

pg_store_plans — модуль для отслеживания статистики плана выполнения операторов SQL;

pgtt - модуль управления глобальными временными таблицами;

plpgsql_check — модуль расширенной проверки plpgsql;

sr_plan — модуль управления хранением планов запросов в QHB.

Установка из репозитория пакетов

ВНИМАНИЕ!
Если вы обновляете версию QHB или мигрируете базы данных с других СУБД, обратитесь к разделу Установка с обновлением.

В этой главе описывается установка QHB из предварительно упакованного дистрибутива. Двоичные пакеты QHB для поддерживаемых платформ можно найти на странице загрузки.

Стандартная процедура установки QHB выглядит так:

Подключение репозитория и установка пакетов

Подключите репозиторий пакетов и установите пакеты для выбранной платформы со страницы загрузки.

Инициализация кластера баз данных

Инициализируйте кластер баз данных при помощи утилиты initdb или qhb_bootstrap. В данном примере /opt/qhb/data — расположение каталога базы данных.

/usr/local/qhb/bin/initdb -D /opt/qhb/data -U qhb

или

/usr/local/qhb/bin/qhb_bootstrap -D /opt/qhb/data -U qhb

ВНИМАНИЕ!
Запуск утилит необходимо производить из-под пользователя qhb, владельца экземпляра, или предварять запуск командой sudo -u qhb.

Примечание
Рекомендуется пользоваться утилитой qhb_bootstrap. Утилита initdb планируется к удалению в будущих версиях QHB.

Настройка сервиса

Если необходимо, настройте сервис базы данных; см. Настройка сервиса базы данных.

Создание базы данных и конфигурирование

Следуйте инструкции по созданию и настройке базы данных; см. Начало работы.

Установка с обновлением

Обновление версии QHB и миграция баз данных с других СУБД.

При переходе с одной версии QHB к другой иногда происходят такие изменения в структурах данных каталога СУБД, которые делают невозможным функционирование новой версии без преобразования данных старой базы.

Для такого преобразования предусмотрена специальная утилита обновления экземпляра qhb_upgrade.

Некоторые обновления не требуют запуска qhb_upgrade:

Обновление пакетов из репозитория, если база данных еще не была создана, или планируется создание новой, а старая не важна

Установка QHB впервые

Миграция базы с помощью утилиты qhb_dump или qhb_dumpall.

Если же планируется перенос или миграция базы данных, перед обновлением нужно воспользоваться утилитой qhb_upgrade. Утилита может обновлять данные не только при обновлении версии QHB, но даже при миграции с PostgreSQL некоторых версий.

Некоторые промежуточные обновления QHB не меняют структуру данных, и их установка не требует каких-то специальных действий. В этом случае утилита qhb_upgrade сообщит об этом.

Начало работы

Создание базы данных

Первый способ проверить, есть ли у вас доступ к серверу баз данных, — попытаться создать базу данных. Работающий сервер QHB способен управлять многими базами данных. Как правило, для каждого проекта или пользователя используется отдельная база.

Возможно, ваш администратор уже создал для вас базу данных. В таком случае этот этап можно пропустить и перейти к следующему разделу.

Чтобы создать новую базу данных, в этом примере названную mydb, используется следующая команда:

$ createdb mydb -h localhost

Если в ответ на это не появляется никаких сообщений, значит, этот этап был успешным и можно пропустить оставшуюся часть этого раздела.

Если вы видите сообщение вида:

createdb: command not found
-- команда не найдена

значит, QHB не была установлена должным образом. Либо она вообще не была установлена, либо не была включена в путь поиска вашей оболочки. Тогда попробуйте вызвать команду по абсолютному пути:

$ /var/lib/qhb/data/bin/createdb mydb

Ваш путь может быть другим. Обратитесь к вашему администратору или ознакомьтесь с инструкциями по установке, чтобы исправить ситуацию.

Возможен и другой ответ:

createdb: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: No such file or directory
        Is the server running locally and accepting connections on that socket?
-- ошибка: не удалось подключиться к серверу через сокет "/tmp/.s.PGSQL.5432": Данный файл или каталог не существует
     -- Этот сервер действительно работает локально и принимает соединения через этот сокет?

Это означает, что сервер не был запущен или был запущен не там, где утилита createdb собиралась к нему подключиться. Опять же, обратитесь к инструкциям по установке или проконсультируйтесь с администратором.

Возможен и другой ответ:

createdb: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: FATAL:  role "joe" does not exist
-- ошибка: не удалось подключиться к серверу через сокет "/tmp/.s.PGSQL.5432": КРИТИЧНО:  роль "joe" не существует

где упоминается ваше имя пользователя. Это произойдет, если администратор не создал для вас учетную запись пользователя QHB. (Учетные записи пользователей QHB отличаются от учетных записей пользователей операционной системы). Если вы администратор, обратитесь к главе Роли в базе данных за помощью в создании учетных записей. Вам нужно будет стать пользователем операционной системы, под которым была установлена QHB (обычно qhb), чтобы создать первую учетную запись пользователя. Возможно также, что вам было присвоено имя пользователя QHB, отличающееся от имени пользователя вашей операционной системы; в этом случае следует использовать ключ -U или установить переменную среды PGUSER, чтобы указать свое имя пользователя в QHB.

Если у вас есть учетная запись пользователя, но у нее нет прав, необходимых для создания базы данных, вы увидите следующее:

createdb: database creation failed: ERROR:  permission denied to create database
-- ошибка: создать базу данных не удалось: ОШИБКА:  нет прав на создание базы данных

Не каждый пользователь имеет право создавать новые базы данных. Если QHB отказывается создавать для вас базы данных, ваш администратор должен предоставить вам разрешение на их создание. В таком случае обратитесь к нему. Если вы установили QHB самостоятельно, то для целей данного руководства следует войти в систему под учетной записью пользователя, под которой вы запустили сервер.¹

Кроме того, можно создавать базы данных с другими именами. QHB позволяет создавать любое количество баз данных. Имена баз данных должны начинаться с буквенного символа и иметь длину не более 63 байт. Удобный выбор — создать базу данных с вашим текущим именем пользователя. Многие программы предполагают такое имя по умолчанию, что может помочь вам сэкономить время на ввод текста. Чтобы создать эту базу данных, просто напишите:

$ createdb

Если вы больше не хотите использовать свою базу данных, ее можно удалить. Например, если вы являетесь владельцем (создателем) базы данных mydb, ее можно уничтожить с помощью следующей команды:

$ dropdb mydb

(Для этой команды имя базы данных не совпадает по умолчанию с именем учетной записи пользователя. Вы всегда должны указывать его.) Это действие физически удаляет все файлы, связанные с базой данных, и не может быть отменено, так что эту операцию следует выполнять крайне продуманно.

Больше информации по командам createdb и dropdb можно найти в соответствующих разделах.

¹

В качестве объяснения того, почему это работает: имена пользователей QHB отделены от учетных записей пользователей операционной системы. Подключаясь к базе данных, вы можете выбрать, под каким именем пользователя QHB подключаться; если вы этого не сделаете, по умолчанию будет использоваться имя вашей текущей учетной записи в операционной системе. Таким образом, в QHB всегда будет учетная запись пользователя, имя которого совпадает с именем пользователя операционной системы, запустившего сервер, причем этот пользователь всегда имеет разрешение на создание баз данных. Вместо входа в систему под этим пользователем можно везде указать параметр -U, чтобы передать имя пользователя QHB для подключения.

Доступ к базе данных

Создав базу данных, вы можете получить к ней доступ одним из следующих способов:

С помощью интерактивного терминала QHB, называемого psql, который позволяет в интерактивном режиме вводить, редактировать и выполнять команды SQL.

С использованием существующего графического инструмента внешнего интерфейса, например pgAdmin, DBeaver или офисного пакета с поддержкой ODBC или JDBC. Эти возможности не рассматриваются в этом руководстве.

Написав собственное приложение с использованием одной из нескольких доступных языковых привязок.

Испытать примеры из этого руководства можно, запустив psql. Это можно сделать для базы данных mydb, набрав команду:

$ psql -h localhost -d mydb

Если вы не укажете имя базы данных, по умолчанию будет использоваться имя вашей учетной записи. Мы уже сталкивались с этой схемой в предыдущем разделе при работе с createdb.

Внутри psql вы увидите следующее сообщение:

psql - Interactive terminal qhb (1.5.2)
Enter "\help" in order to get help

mydb(username)=#

Это будет означать, что вы являетесь суперпользователем базы данных, что наиболее вероятно, если вы сами установили экземпляр QHB. Суперпользователь имеет неограниченный доступ к базе данных; впрочем, для целей данного руководства это не имеет значения.

Если вы столкнулись с проблемами при запуске psql, вернитесь к предыдущему разделу. Диагностики createdb и psql схожи, и если первая сработала, то вторая тоже должна сработать.

Последняя строка, выведенная psql, является приглашением и показывает, что psql ожидает ввод, то есть вы можете вводить в рабочее пространство запросы SQL. Попробуйте эти команды:

mydb(username)=# select version();
version
----------
QHB 1.5.2
(1 row)

mydb(username)=# SELECT current_date;
    date
------------
 2023-10-07

mydb(username)=# SELECT 2 + 2;
 ?column?
----------
        4
(1 row)

Программа psql имеет ряд внутренних команд, которые не являются командами SQL. Они начинаются с символа обратного слэша «\». Например, можно получить справку по синтаксису различных команд SQL в QHB, написав:

mydb(username)=# \h

Чтобы выйти из psql, напишите:

mydb(username)=# \q

и psql завершит свою работу и вернет вас в командную оболочку. (Чтобы узнать больше о внутренних командах, введите \? в приглашении psql.) Все возможности psql освещаются в соответствующем разделе. В этом руководстве мы не будем использовать эти функциональные возможности явно, но вы можете самостоятельно применять их по мере необходимости.

Настройка и эксплуатация сервера

В этой главе рассказывается, как настроить и запустить сервер баз данных и как он взаимодействует с операционной системой.

Учетная запись пользователя QHB

Как и любой серверный процесс, доступный для внешнего мира, QHB рекомендуется запускать под отдельной учетной записью пользователя. Эта учетная запись должна владеть только данными, которыми управляет сервер, и не должна использоваться совместно с другими служебными процессами. (Например, было бы неудачной идеей выбрать пользователя nobody.) В частности, не рекомендуется назначать этого пользователя владельцем исполняемых файлов QHB, потому что скомпрометированный серверный процесс может их изменить.

Для добавления в вашу систему учетной записи пользователя Unix выполните команду useradd или adduser. Часто используется имя пользователя qhb, которое предполагается в этой документации, но при необходимости можно использовать другое имя.

Создание кластера баз данных

Прежде чем что-либо делать, вы должны инициализировать область хранения баз данных на диске. Она называется кластером баз данных. (В стандарте SQL применяется термин кластер каталогов.) Кластер баз данных — это набор баз данных, который управляется одним экземпляром работающего сервера баз данных. После инициализации кластер баз данных будет содержать базу данных с именем qhb, являющуюся базой данных по умолчанию для использования утилитами, пользователями и сторонними приложениями. Сам сервер баз данных не требует существования базы данных qhb, но многие внешние утилиты предполагают, что она существует. Также в каждом кластере во время инициализации создаются еще две базы данных с именами template1 и template0. Как следует из названий, они будут использоваться в качестве шаблонов для баз данных, создаваемых впоследствии, и не должны использоваться для фактической работы. (Информацию о создании новых баз данных в кластере см. в главе Управление базами данных.)

С точки зрения файловой системы, кластер баз данных — это один каталог, в котором будут храниться все данные. Он называется каталогом данных или областью данных. Место хранения данных полностью зависит от пользователя. Какого-то стандартного расположения не существует, хотя популярны такие места, как /var/lib/qhb/data или /u01/qhb/data. Чтобы использовать каталог данных, его нужно инициализировать с помощью программы qhb_bootstrap (или initdb), которая устанавливается с QHB. Расположение файловой системы вашего кластера баз данных указывается с помощью параметра -D, например:

qhb_bootstrap -D /var/lib/qhb/data

или

$ initdb -D /var/lib/qhb/data

Обратите внимание, что вы должны выполнить эту команду при входе в учетную запись пользователя QHB, которая описана в предыдущем разделе.

Совет
В качестве альтернативы параметру -D можно установить переменную среды PGDATA.

Как вариант, команду initdb можно запустить через программу qhb_ctl следующим образом:

$ qhb_ctl -D /var/lib/qhb/data initdb

Этот вариант может быть удобнее, если вы используете qhb_ctl для запуска и остановки сервера (см. раздел Запуск сервера баз данных); таким образом, qhb_ctl будет единственной командой, с помощью которой вы будете управлять экземпляром сервера баз данных.

Команда qhb_bootstrap попытается создать указанный вами каталог, если он еще не существует. Конечно, это не удастся, если у qhb_bootstrap нет прав на запись в родительский каталог. Обычно рекомендуется, чтобы пользователь QHB владел не только каталогом данных, но и его родительским каталогом, поэтому это не должно стать проблемой. Если желаемый родительский каталог тоже не существует, сначала нужно его создать, используя права суперпользователя, если находящийся выше каталог недоступен для записи. Так что процесс может выглядеть так:

root# mkdir /usr/local/qhb
root# chown qhb /usr/local/qhb
root# su qhb
qhb$ qhb_bootstrap -D /var/lib/qhb/data

При работе от root выполняем:

root# mkdir /var/lib/qhb/data
root# chown qhb /var/lib/qhb/data
root# sudo -u qhb /usr/local/qhb/bin/qhb_bootstrap -D /var/lib/qhb/data

Команда qhb_bootstrap не станет запускаться, если каталог данных существует и уже содержит файлы; это предотвращает случайную перезапись существующей установки.

Поскольку каталог данных содержит все данные, хранящиеся в базе данных, важно, чтобы он был защищен от несанкционированного доступа. Поэтому qhb_bootstrap отзывает права доступа у всех, кроме пользователя QHB и, при необходимости, группы. Групповой доступ, если он включен, доступен только для чтения. Это позволяет непривилегированному пользователю из одной группы с владельцем кластера создавать резервную копию данных кластера или выполнять другие операции, для которых требуется только чтение.

Обратите внимание, что включение или выключение группового доступа в существующем кластере требует отключения этого кластера и установки соответствующего режима для всех каталогов и файлов перед перезапуском QHB. В противном случае в каталоге данных может существовать комбинация режимов. Для кластеров, разрешающих доступ только владельцу, подходят режимы 0700 для каталогов и 0600 для файлов. Для кластеров, разрешающих чтение и для группы, подходят режимы 0750 для каталогов и 0640 для файлов.

Однако, хотя содержимое каталога защищено, настройка аутентификации клиента по умолчанию позволяет любому локальному пользователю подключаться к базе данных и даже стать ее суперпользователем. Если вы не доверяете другим локальным пользователям, рекомендуется использовать один из параметров qhb_bootstrap: -W, --pwprompt или --pwfile, чтобы назначить пароль суперпользователю базы данных. Кроме того, укажите -A scram-sha-256, чтобы не использовался trust — режим аутентификации по умолчанию, или измените созданный файл qhb_hba.conf после запуска qhb_bootstrap, но перед первым запуском сервера. (Другие разумные подходы включают использование аутентификации peer или разрешений файловой системы для ограничения соединений. Подробную информацию см. в главе Аутентификация клиентского приложения.)

Также команда qhb_bootstrap инициализирует для кластера баз данных локаль по умолчанию. Обычно она просто берет параметры локали из среды и применяет их к инициализированной базе данных. Можно указать для базы данных и другую локаль; более подробную информацию об этом можно найти в разделе Поддержка языковых стандартов. Порядок сортировки по умолчанию, используемый в конкретном кластере баз данных, устанавливается qhb_bootstrap, и хотя вы можете создавать новые базы данных, используя другой порядок сортировки, но порядок, используемый в базах-шаблонах, которые создает утилита qhb_bootstrap, нельзя изменить без их удаления и пересоздания. Кроме того, использование локалей, отличных от C или POSIX, влияет на производительность. Поэтому важно правильно сделать этот выбор с первого раза.

Команда qhb_bootstrap также устанавливает для кластера баз данных кодировку набора символов по умолчанию. Обычно ее следует выбирать в соответствии с настройками локали. Подробную информацию см. в разделе Поддержка кодировок.

Локали, отличные от C или POSIX, при упорядочении набора символов полагаются на библиотеку сопоставления операционной системы. Это определяет порядок ключей, хранящийся в индексах. По этой причине кластер не может переключиться на несовместимую версию библиотеки, ни посредством восстановления снимка состояния, ни через двоичную потоковую репликацию, другую операционную систему или обновление операционной системы.

Использование добавочных файловых систем

Многие установки создают свои кластеры баз данных не в «корневой» файловой системе (томе) компьютера, а в других томах. Если вы решите сделать это, не рекомендуется пытаться использовать в качестве каталога для хранения данных самый верхний каталог добавочного тома (точку монтирования). Лучше всего будет создать в каталоге точки монтирования каталог, который принадлежит пользователю QHB, а затем уже в нем создать каталог данных. Это позволит избежать проблем с разрешениями, особенно для таких операций, как qhb_upgrade, а также гарантирует чистые ошибки, если добавочный том окажется отключен.

Файловые системы

Как правило, для QHB можно использовать любую файловую систему с семантикой POSIX. Пользователи предпочитают другие файловые системы по разным причинам, включая поддержку поставщиков, производительность и изученность. Опыт показывает, что при прочих равных не следует ожидать значительных изменений в производительности или в поведении просто от переключения файловых систем или незначительных изменений конфигурации файловой системы.

NFS

Для хранения каталога данных QHB можно использовать файловую систему NFS. QHB не подстраивается специально под файловые системы NFS, то есть предполагает, что NFS ведет себя точно так же, как локально подключенные накопители. QHB не использует функциональности, демонстрирующие нестандартное поведение в NFS, например блокировку файлов.

Единственное жесткое требование для использования NFS с QHB — это монтирование файловой системы с использованием параметра hard. С параметром hard процессы могут «зависать» на неопределенное время при возникновении проблем с сетью, поэтому такая конфигурация потребует тщательной настройки мониторинга. Параметр soft будет прерывать системные вызовы в случае проблем с сетью, но QHB не будет повторять системные вызовы, прерванные таким образом, поэтому любое такое прерывание приведет к сообщению об ошибке ввода/вывода.

Нет необходимости использовать параметр монтирования sync. Поведения параметра async вполне достаточно, поскольку QHB вызывает fsync в нужное время для очистки кэшей записи (аналогично тому, как это происходит в локальной файловой системе). Однако настоятельно рекомендуется использовать параметр экспорта sync на сервере NFS в системах, где он существует (в основном, это Linux). В противном случае fsync или ее эквивалент на клиенте NFS не гарантируют того, что данные достигнут постоянного хранилища на сервере, что может привести к повреждению данных, как и выключение параметра fsync. Значения по умолчанию этих параметров монтирования и экспорта различаются у разных поставщиков и версий, поэтому рекомендуется в любом случае проверить и, возможно, указать их явно, чтобы избежать двусмысленности.

В некоторых случаях к внешнему хранилищу можно получить доступ через NFS или протокол более низкого уровня, например iSCSI. В последнем случае хранилище представляется как блочное устройство, и на нем можно создать любую поддерживаемую файловую систему. Такой подход может избавить администратора баз данных от необходимости иметь дело с некоторыми особенностями NFS, но, конечно, тогда сложность управления удаленным хранилищем возникает на других уровнях.

Запуск сервера баз данных

Прежде чем кто-либо сможет получить доступ к базе данных, вы должны запустить сервер баз данных. Программа сервера баз данных называется qhb.

Программа qhb должна знать, где найти файлы базы данных, которые она должна использовать. Это делается с помощью параметра -D*, например:

qhb# qhb -D /var/lib/qhb/data

При запуске от root:

root# sudo -u qhb ./qhb -D /var/lib/qhb/data

При этом сервер продолжит работу на переднем плане. Эта программа должна выполнятся из-под учетной записи пользователя QHB. Без параметра -D сервер попытается использовать каталог данных, заданный в переменной среды PGDATA. Если эта переменная тоже не указана, произойдет сбой.

Обычно лучше запускать qhb в фоновом режиме. Для этого используется обычный синтаксис оболочки Unix:

$ qhb -D /var/lib/qhb/data >logfile 2>&1 &

Важно где-то хранить выходные данные сервера из каналов stdout и stderr, как показано выше. Это поможет при аудите и диагностики проблем. (Более подробно обработка файлов журнала рассматривается в разделе Обслуживание файлов журнала.)

Также программа qhb принимает ряд других параметров командной строки. Подробную информацию см. на справочной странице qhb и в главе Конфигурация сервера.

Этот синтаксис оболочки может быстро стать громоздким. Поэтому для упрощения некоторых задач предоставляется программа-обертка qhb_ctl. Например:

qhb_ctl start -l logfile

запустит сервер в фоновом режиме и поместит вывод в именованный файл журнала. Параметр -D имеет то же значение, что и для qhb. Кроме того, qhb_ctl способна останавливать сервер.

Обычно возникает желание запускать сервер баз данных при загрузке компьютера. Скрипты автозапуска зависят от операционной системы. Некоторые из них распространяются с пакетами QHB. Для установки такого скрипта в систему понадобятся права root.

Различные системы имеют разные соглашения для запуска служебных процессов во время загрузки. Во многих системах имеется файл /etc/rc.local или /etc/rc.d/ rc.local. Другие используют каталоги init.d или rc.d Что бы вы ни делали, сервер должен работать под учетной записью пользователя QHB, а не под root или любым другим пользователем. Поэтому стоит формировать свои команды, используя su qhb -c '...'. Например:

su qhb -c 'qhb_ctl start -D /var/lib/qhb/data -l serverlog'

Вот несколько более конкретных предложений для ряда операционных систем. (В каждом случае вместо указанных нами общих значений обязательно используйте надлежащие каталог установки и имя пользователя.)

Для запуска во FreeBSD используйте файл contrib/start-scripts/freebsd в исходном дистрибутиве QHB.

В OpenBSD добавьте в файл /etc/rc.local следующие строки:

if [ -x /usr/local/qhb/bin/qhb_ctl -a -x /usr/local/qhb/bin/qhb ]; then
    su -l qhb -c '/usr/local/qhb/bin/qhb_ctl start -s -l /var/qhb/log -D /var/lib/qhb/data'
 echo -n ' qhb'
fi

В системах Linux либо добавьте

/usr/local/qhb/bin/qhb_ctl start -l logfile -D /var/lib/qhb/data

в /etc/rc.d/rc.local или /etc/rc.local, либо используйте файл contrib/start-scripts/linux в исходном дистрибутиве QHB.

При использовании systemd можно использовать следующий файл сервисного блока (например в /etc/systemd/system/qhb.service):

[Unit]
Description=QHB database server
Documentation=man:qhb(1)
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
User=qhb
ExecStart=/usr/local/qhb/bin/qhb -D /var/lib/qhb/data
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=infinity

[Install]
WantedBy=multi-user.target

Применение Type=notify требует, чтобы сервер был скомпилирован с указанием configure --with-systemd.

Тщательно продумайте настройку тайм-аута. На данный момент в systemd тайм-аут по умолчанию составляет 90 секунд, и процесс, который не уведомит о готовности в течение этого времени, будет уничтожен. Но у сервера QHB, которому может потребоваться при запуске выполнить восстановление после сбоя, на подготовку может уйти гораздо больше времени. Предлагаемое значение infinity выключает логику тайм-аута.

В NetBSD воспользуйтесь скриптами запуска для FreeBSD или для Linux, в зависимости от предпочтений.

В Solaris создайте файл с именем /etc/init.d/qhb, содержащий следующую строку:

su - qhb -c "/usr/local/qhb/bin/qhb_ctl start -l logfile -D /var/lib/qhb/data"

Затем создайте символическую ссылку на него в каталоге /etc/rc3.d с именем S99qhb.

Во время работы сервера его идентификатор (PID) хранится в файле qhbmaster.pid в каталоге данных. Это позволяет предотвратить запуск нескольких экземпляров сервера в одном каталоге данных, а также может использоваться для отключения сервера.

Сбои при запуске сервера

Существует несколько распространенных причин, по которым сервер может не запуститься. Проверьте файл журнала сервера или запустите его вручную (без перенаправления стандартного вывода или стандартной ошибки) и посмотрите, какие сообщения об ошибках появляются. Ниже мы рассмотрим некоторые из наиболее распространенных сообщений об ошибках более подробно.

LOG:  could not bind IPv4 address "127.0.0.1": Address already in use
HINT:  Is another qhbmaster already running on port 5432? If not, wait a few seconds and retry.
FATAL:  could not create any TCP/IP sockets
-- ЖУРНАЛ: не удалось присвоить адрес IPv4 "127.0.0.1": Адрес уже используется
-- СОВЕТ: На порту 5432 уже запущен другой процесс qhbmaster? Если нет, подождите несколько секунд и повторите попытку.
-- КРИТИЧНО: не удалось создать сокеты TCP/IP

Обычно это означает именно то, что написано: вы попытались запустить сервер на том же порту, на котором уже работает другой. Однако если сообщение об ошибке в ядре не Address already in use или какой-то его вариант, возможно, это другая проблема. Например, на попытку запустить сервер с зарезервированным номером порта может быть выдано что-то вроде этого:

$ qhb -p 777
LOG:  could not bind IPv4 address "127.0.0.1": Permission denied
HINT:  Is another qhbmaster already running on port 777? If not, wait a few seconds and retry.
FATAL:  could not create any TCP/IP sockets
-- ЖУРНАЛ: не удалось присвоить адрес IPv4 "127.0.0.1": Отсутствие прав доступа
-- СОВЕТ: На порту 777 уже запущен другой процесс qhbmaster? Если нет, подождите несколько секунд и повторите попытку.
-- КРИТИЧНО: не удалось создать сокеты TCP/IP

Сообщение вроде:

FATAL:  could not create shared memory segment: Invalid argument
DETAIL:  Failed system call was shmget(key=5440001, size=4011376640, 03600).
-- КРИТИЧНО: не удалось создать сегмент разделяемой памяти: Недопустимый аргумент
-- ДЕТАЛИЗАЦИЯ: Сбой произошел в системном вызове shmget(key=5440001, size=4011376640, 03600).

вероятно, означает, что предельный размер разделяемой памяти в вашем ядре меньше, чем рабочая область, которую пытается создать QHB (в этом примере 4011376640 байт). Чаще всего это происходит, если вы установили в параметре shared_memory_type значение sysv. В этом случае можно попробовать запустить сервер с меньшим, чем обычно, числом буферов ( shared_buffers) или перенастроить ядро, чтобы увеличить допустимый размер разделяемой памяти. Кроме того, это сообщение может появиться при попытке запустить несколько серверов на одном компьютере, если их общее запрошенное пространство превышает ограничение ядра.

Ошибка вроде:

FATAL:  could not create semaphores: No space left on device
DETAIL:  Failed system call was semget(5440126, 17, 03600).
-- КРИТИЧНО: не удалось создать семафоры: Недостаточно места на устройстве
-- ДЕТАЛИЗАЦИЯ: Сбой произошел в системном вызове semget(5440126, 17, 03600).

не означает, что вам не хватает места на диске. Это означает, что предельное число семафоров System V в вашем ядре меньше, чем число семафоров, которое хочет создать QHB. Как и в случае выше, эту проблему можно обойти, запустив сервер с меньшим количеством разрешенных соединений (max_connections), но в конечном итоге будет необходимо увеличить ограничение ядра.

Подробную информацию о конфигурировании средств IPC в стиле System V см. в подразделе Разделяемая память и семафоры.

Проблемы с подключением клиента

Хотя возможные состояния ошибки на стороне клиента довольно разнообразны и зависят от приложения, некоторые из них могут быть напрямую связаны с тем, как был запущен сервер. Состояния, отличные от указанных ниже, должны быть описаны в документации соответствующего клиентского приложения.

psql: error: connection to server at "server.joe.com" (123.123.123.123), port 5432 failed: Connection refused
        Is the server running on that host and accepting TCP/IP connections?
-- ошибка: не удалось подключиться к серверу "server.joe.com" (123.123.123.123), порт 5432: в подключении отказано
        -- Сервер действительно работает на этом хосте и принимает подключения по TCP/IP?

Это частый сбой «Я не могу найти сервер для взаимодействия». Похоже, здесь была попытка установить связь с TCP/IP. Распространенная ошибка — забыть сконфигурировать сервер так, чтобы разрешить TCP/IP-соединения.

Кроме того, при попытке подключения к локальному серверу с помощью сокета домена Unix вы можете получить:

psql: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: No such file or directory
        Is the server running locally and accepting connections on that socket?
-- ошибка: не удалось подключиться к серверу через сокет "/tmp/.s.PGSQL.5432": Такого файла или каталога не существует
        -- Сервер действительно работает локально и принимает подключения через этот сокет?

Если сервер действительно работает, проверьте, что указываемый клиентом путь сокета (здесь /tmp) соответствует значению параметра unix_socket_directories на сервере.

В сообщении об ошибке подключения всегда выводится адрес сервера или путь сокета, что помогает проверить, действительно ли клиент пытается подключиться по нужному адресу. Если в действительности там не указано сервера, обычно выдается сообщение об ошибке от ядра Connection refused или No such file or directory, как показано выше. (Важно понимать, Connection refused в данном контексте не означает, что сервер получил ваш запрос на подключение и отверг его. В этом случае было бы выдано другое сообщение, показанное в разделе Проблемы аутентификации.) Другие сообщения об ошибках, например Connection timed out (Тайм-аут подключения), могут указывать на более фундаментальные проблемы, например на отсутствие возможности сетевого соединения или блокировку подключения брандмауэром.

Управление ресурсами ядра

Иногда QHB может исчерпывать ряд ресурсов операционной системы до предела, особенно при запуске в одной системе нескольких копий сервера или в очень больших дистрибутивах. В этом разделе описываются ресурсы ядра, используемые QHB, и шаги, которые можно предпринять для решения проблем, связанных с исчерпанием ресурсов ядра.

Разделяемая память и семафоры

QHB требует, чтобы операционная система предоставляла средства межпроцессного взаимодействия (inter-process communication, IPC), в частности, разделяемую память и семафоры. Дочерние системы Unix обычно предоставляют средства IPC в стиле «System V», в стиле «POSIX» или и те, и другие.

По умолчанию QHB резервирует очень малый объем разделяемой памяти System V и гораздо больший объем анонимной разделяемой памяти mmap. Как вариант, возможно использование одной крупной области разделяемой памяти System V (см. shared_memory_type). Кроме того, при запуске сервера создается значительное число семафоров, которые могут быть в стиле System V либо POSIX. В настоящее время семафоры POSIX используются в системах Linux и FreeBSD, тогда как на других платформах используются семафоры System V.

Средства IPC в стиле System V обычно ограничены лимитами резервирования на уровне системы. Когда QHB превышает один из таких лимитов, сервер отказывается запускаться и должен выдать информативное сообщение об ошибке, описывающее проблему и что с ней делать. (См. также подраздел Сбои при запуске сервера.) Соответствующие параметры ядра в разных системах называются единообразно (с ними можно ознакомиться в Таблице 1), однако методы их настройки различаются. Ниже даны предложения для некоторых платформ.

Таблица 1. Параметры IPC в стиле System V

Имя | Описание | Значения, необходимые для запуска одного экземпляра QHB
SHMMAX | Максимальный размер сегмента разделяемой памяти (в байтах) | как минимум 1 КБ, но обычно значение по умолчанию гораздо больше
SHMMIN | Минимальный размер сегмента разделяемой памяти (в байтах) | 1
SHMALL | Общий объем доступной разделяемой памяти (в байтах или страницах) | если в байтах, то же, что и SHMMAX; если в страницах, то ceil(SHMMAX/PAGE_SIZE) плюс место для других приложений
SHMSEG | Максимальное число сегментов разделяемой памяти на процесс | нужен только один сегмент, но значение по умолчанию гораздо больше
SHMMNI | Максимальное число сегментов разделяемой памяти на всю систему | как SHMSEG плюс место для других приложений
SEMMNI | Минимальное число идентификаторов семафоров (т. е. их наборов) | как минимум ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 5) / 16) плюс место для других приложений
SEMMNS | Максимальное число семафоров на всю систему | ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 5) / 16) * 17 плюс место для других приложений
SEMMSL | Максимальное число семафоров в наборе | как минимум 17
SEMMAP | Число записей в карте семафора | см. текст ниже
SEMVMX | Максимальное значение семафора | как минимум 1000 (по умолчанию обычно 32767; не меняйте его без необходимости)

QHB запрашивает несколько байтов разделяемой памяти System V (обычно 48 байт на 64-битных платформах) для каждой копии сервера. В большинстве современных операционных систем такой объем резервируется с легкостью. Однако если запустить много копий сервера или явно настроить сервер для использования больших объемов разделяемой памяти System V (см. shared_memory_type и dynamic_shared_memory_type), может понадобиться увеличить значение SHMALL, задающее общий объем разделяемой памяти System V для всей системы. Обратите внимание, что во многих системах SHMALL измеряется в страницах, а не в байтах.

Менее вероятны проблемы с минимальным размером сегментов разделяемой памяти (SHMMIN), который для QHB не должен превышать примерно 32 байт (обычно это всего 1 байт). Максимальное число сегментов на всю систему (SHMMNI) или на процесс (SHMSEG) обычно не вызывают проблем, если только в вашей системе они не равны нулю.

Когда QHB использует семафоры System V, она применяет по одному семафору на каждое разрешенное подключение (max_connections), каждый разрешенный рабочий процесс автовакуума (autovacuum_max_workers) и каждый разрешенный фоновый процесс (max_worker_processes), в наборах по 16. В каждом таком наборе также имеется 17-й семафор, содержащий «магическое число» для обнаружения коллизий с наборами семафоров, используемыми другими приложениями. Максимальное число семафоров в системе задается параметром SEMMNS, который, следовательно, должен быть равен как минимум сумме max_connections, autovacuum_max_workers, max_wal_senders и max_worker_processes плюс один дополнительный семафор на каждые 16 разрешенных подключений и рабочих процессов (см. формулу в Таблице 1). Параметр SEMMNI определяет предельное число наборов семафоров, которые могут одновременно существовать в системе. Таким образом, этот параметр должен быть равен как минимум ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 5) / 16). В качестве временного решения проблемы можно уменьшить число разрешенных подключений, но обычно это вызывает нелогичные сообщения «No space left on device» (На устройстве не осталось места) от функции semget.

В некоторых случаях может также возникнуть необходимость увеличить SEMMAP как минимум до уровня SEMMNS. Если в системе есть этот параметр (во многих системах его нет), он определяет размер карты ресурсов семафоров, в которой для каждого непрерывного блока доступных семафоров требуется отдельная запись. Когда набор семафоров освобождается, эта запись либо добавляется к соседней с освобожденным блоком существующей записи, либо регистрируется как новая запись в карте. Если карта переполняется, освобождаемые семафоры теряются (до перезагрузки). Фрагментация пространства семафоров со временем может привести к снижению числа доступных семафоров.

Другие параметры, связанные с «уничтожением семафоров», например SEMMNU и SEMUME, на работу QHB не влияют.

При использовании семафоров в стиле POSIX число необходимых семафоров такое же, как для System V, то есть по одному семафору на каждое разрешенное подключение (max_connections), каждый разрешенный рабочий процесс автоовакуума (autovacuum_max_workers) и каждый разрешенный фоновый процесс (max_worker_processes). На платформах, где предпочитается этот вариант, отсутствует конкретный лимит ядра на число семафоров POSIX.

AIX

Для таких параметров, как SHMMAX, нет необходимости в каком-то особом конфигурировании, поскольку система, по-видимому, настроена так, что позволяет использовать всю память в качестве разделяемой. Конфигурация такого рода обычно используется и для других баз данных, например для DB/2.

Однако может понадобиться изменить глобальную информацию ulimit в /etc/ security/limits, поскольку стандартные жесткие ограничения на размер (fsize) и число (nofiles) файлов могут быть слишком маленькими.

FreeBSD

Стандартные настройки разделяемой памяти обычно вполне приемлемы, если вы не установите в shared_memory_type значение sysv. Семафоры System V на этой платформе не используются.

Стандартные настойки IPC можно изменить с помощью интерфейсов sysctl или loader. Используя sysctl, можно установить следующие параметры:

# sysctl kern.ipc.shmall=32768
# sysctl kern.ipc.shmmax=134217728

Чтобы эти настройки сохранялись после перезагрузки, измените /etc/sysctl.conf.

Если вы установили в shared_memory_type значение sysv, вам также может понадобиться настроить ядро так, чтобы блокировать разделяемую память System V в ОЗУ и не дать ей выгружаться в пространство подкачки. Этого можно добиться, используя в sysctl параметр kern.ipc.shm_use_phys.

При запуске сервера в «камере» FreeBSD, следует установить в его параметре sysvshm значение new, чтобы у сервера было собственное отдельное пространство имен разделяемой памяти System V. (До версии 11.0 во FreeBSD требовалось разрешать общий доступ из камер к пространству имен IPC хоста и принимать меры во избежание коллизий.)

NetBSD

Стандартные настройки разделяемой памяти обычно вполне приемлемы, если вы не установите в shared_memory_type значение sysv. Как правило, стоит увеличить kern.ipc.semmni и kern.ipc.semmns, поскольку их значения по умолчанию в NetBSD чересчур малы.

Параметры IPC можно скорректировать с помощью sysctl, например:

# sysctl -w kern.ipc.semmni=100

Чтобы эти настройки сохранялись после перезагрузки, измените /etc/sysctl.conf.

Если вы установили в shared_memory_type значение sysv, вам также может понадобиться настроить ядро так, чтобы блокировать разделяемую память System V в ОЗУ и не дать ей выгружаться в пространство подкачки. Этого можно добиться, используя в sysctl параметр kern.ipc.shm_use_phys.

OpenBSD

Стандартные настройки разделяемой памяти обычно вполне приемлемы, если вы не установите в shared_memory_type значение sysv. Как правило, стоит увеличить kern.ipc.semmni и kern.ipc.semmns, поскольку их значения по умолчанию в OpenBSD чересчур малы.

Параметры IPC можно скорректировать с помощью sysctl, например:

# sysctl kern.seminfo.semmni=100

Чтобы эти настройки сохранялись после перезагрузки, измените /etc/sysctl.conf.

HP-UX

Настройки по умолчанию, как правило, удовлетворяют нормальным установкам.

Параметры IPC можно установить в менеджере системного администрирования (System Administration Manager, SAM) в разделе Kernel Configuration (конфигурация ядра) \→ Configurable Parameters (конфигурируемые параметры). По окончании настройки выберите операцию Create A New Kernel (создать новое ядро).

Linux

Стандартные настройки разделяемой памяти обычно вполне приемлемы, если вы не установите в shared_memory_type значение sysv, и даже тогда их нужно будет увеличить только для старых версий ядер, которые поставлялись с маленькими значениями параметров по умолчанию. Семафоры System V на этой платформе не используются.

Параметры размера разделяемой памяти можно изменить с помощью интерфейса sysctl. Например, чтобы разрешить объем 16 ГБ, выполните:

$ sysctl -w kernel.shmmax=17179869184
$ sysctl -w kernel.shmall=4194304

Чтобы эти настройки сохранялись после перезагрузки, измените /etc/sysctl.conf.

macOS

Стандартные настройки разделяемой памяти и семафоров обычно вполне приемлемы, если вы не установите в shared_memory_type значение sysv.

Для конфигурирования разделяемой памяти в macOS рекомендуется создать файл с именем /etc/sysctl.conf, содержащий присваивания переменных, например:

kern.sysv.shmmax=4194304
kern.sysv.shmmin=1
kern.sysv.shmmni=32
kern.sysv.shmseg=8
kern.sysv.shmall=1024

Обратите внимания, что в некоторых версиях macOS в /etc/sysctl.conf нужно установить все пять параметров разделяемой памяти, иначе их значения будут игнорироваться.

Значение SHMMAX должно быть кратно 4096.

На этой платформе SHMALL измеряется в страницах размером 4 КБ.

Все параметры, кроме SHMMNI, можно изменить на ходу с помощью sysctl. Но все равно лучше устанавливать желаемые значения через /etc/sysctl.conf, чтобы они сохранялись после перезагрузки.

Solaris
illumos

Стандартные настройки разделяемой памяти и семафоров обычно вполне приемлемы для большинства приложений QHB. По умолчанию Solaris устанавливает в SHMMAX четверть объема системного ОЗУ. Для дополнительной корректировки этого параметра воспользуйтесь параметром проекта, связанного с пользователем qhb. Например, выполните от имени root следующую команду:

projadd -c "QHB DB User" -K "project.max-shm-memory=(privileged,8GB,deny)" -U qhb -G qhb user.qhb

Эта команда добавляет проект user.qhb и задает максимальный объем разделяемой памяти для пользователя qhb, равный 8 ГБ; это изменение вступает в силу при следующем входе этого пользователя или при перезапуске QHB (не при перезагрузке конфигурации). При этом подразумевается, что QHB выполняется пользователем qhb в группе qhb. Перезапуск сервера не требуется.

Для серверов баз данных, к которым будет производиться большое число подключений, рекомендуется изменить следующие параметры ядра:

project.max-shm-ids=(priv,32768,deny)
project.max-sem-ids=(priv,4096,deny)
project.max-msg-ids=(priv,4096,deny)

Кроме того, если QHB работает внутри зоны, может потребоваться также увеличить лимиты на использование ресурсов зоны. Дополнительную информацию о projects и prctl см. в «Главе 2: Проекты и задачи» Руководства системного администратора.

RemoveIPC в systemd

Если используется systemd, следует позаботиться о том, чтобы ресурсы IPC (включая разделяемую память) не удалялись преждевременно операционной системой. Это особенно актуально при установке QHB из исходного кода. Пользователей дистрибутивных пакетов QHB это касается меньше, поскольку тогда пользователь qhb обычно создается как системный пользователь.

Параметр RemoveIPC в logind.conf определяет, будут ли объекты IPC удаляться, когда пользователь полностью выходит из системы. Исключение составляют системные пользователи. В обычной поставке systemd этот параметр по умолчанию включен, но в некоторых дистрибутивах операционных систем он по умолчанию выключен.

Обычный наблюдаемый эффект при включении этого параметра заключается в том, то объекты разделяемой памяти, используемые для параллельного выполнения запросов, удаляются в совершенно случайном порядке, приводя к появлению ошибок и предупреждений при попытке открыть и удалить их, например:

WARNING:  could not remove shared memory segment "/PostgreSQL.1450751626": No such file or directory
-- ПРЕДУПРЕЖДЕНИЕ:  не удалось удалить сегмент разделяемой памяти "/QHB.1450751626": Нет такого файла или каталога

Различные типы объектов IPC (разделяемая память/семафоры, System V/POSIX) обрабатываются в systemd немного по-разному, поэтому можно наблюдать ситуации, когда некоторые ресурсы IPC не удаляются так, как другие. Но полагаться на эти незначительные отличия не стоит.

Событие «выход пользователя из системы» может произойти при выполнении работы по обслуживанию или вручную, когда администратор входит под именем qhb или делает что-то подобное, поэтому в целом это довольно трудно предотвратить.

Что такое «системный пользователь», определяется во время компиляции systemd на основе значения параметра SYS_UID_MAX в /etc/login.defs.

Скрипты упаковывания и развертывания должны позаботиться о создании пользователя qhb как системного пользователя, используя команды useradd -r, adduser --system или равнозначные им.

Как вариант, если аккаунт пользователя был создан неправильно или его нельзя изменить, рекомендуется установить

RemoveIPC=no

в /etc/systemd/logind.conf или ином подходящем файле конфигурации.

ВНИМАНИЕ!
Необходимо выполнить хотя бы одно из этих действий, иначе сервер QHB будет крайне нестабильным.

Лимиты ресурсов

Unix-подобные операционные системы накладывают различные виды ограничений ресурсов, которые могут повлиять на работу вашего сервера QHB. Особую важность представляют ограничения на число процессов для пользователя, число открытых файлов и доступный объем памяти для каждого процесса. Каждый из них имеет «жесткий» и «мягкий» лимит. Реальное значение имеет мягкий лимит, но пользователь может изменить его значение вплоть до жесткого. Жесткий лимит может изменить только пользователь root. За настройку этих параметров отвечает системный вызов setrlimit. Для управления лимитами ресурсов из командной строки используется встроенная команда оболочки ulimit (в оболочках Bourne) или limit (csh). В дочерних системах BSD различными ограничениями ресурсов, устанавливаемыми во время входа в систему, управляет файл /etc/login.conf. Подробную информацию см. в документации операционной системы. К значимым параметрам относятся maxproc, openfiles и datasize. Например:

default:\
...
        :datasize-cur=256M:\
        :maxproc-cur=256:\
        :openfiles-cur=256:\
...

(Здесь -cur обозначает мягкий лимит. Чтобы установить жесткий лимит, нужно вместо него добавить суффикс -max.)

Также ядра могут иметь общесистемные лимиты некоторых ресурсов.

В Linux максимальное число открытых файлов, которые поддержит ядро, определяется параметром fs.file-max. Этот лимит можно изменить с помощью команды sysctl -w fs.file-max=N. Чтобы эта настройка сохранялась после перезагрузки, нужно добавить присваивание в файл /etc/sysctl.conf. Максимальное число файлов на процесс фиксируется во время компиляции ядра; дополнительную информацию см. в /usr/src/linux/Documentation/proc.txt.

Сервер QHB использует по одному процессу на подключение, поэтому в дополнение к тем процессам, которые нужны для остальной системы, вы должны предоставить как минимум столько процессов, сколько имеется разрешенных подключений. Обычно в этом нет проблемы, но если вы запускаете на одном компьютере несколько серверов, лимит может быть достигнут.

В качестве производственного максимума открытых файлов зачастую устанавливают «социально-ориентированные» значения, позволяющие нескольким пользователям сосуществовать на одном компьютере и при этом не задействовать недопустимый процент системных ресурсов. Если вы запускаете по несколько серверов на компьютер, возможно, именно это вам и нужно, но на выделенных серверах может возникнуть необходимость увеличить этот лимит.

С другой стороны, некоторые системы позволяют отдельным процессам открывать очень много файлов; если это делают несколько процессов, то они с легкостью могут превысить общесистемный лимит. Если вы обнаружите подобное и не захотите менять общесистемный лимит, можно ограничить использование открытых файлов, установив параметр конфигурации QHB max_files_per_process.

Еще одним ограничением ядра, имеющим значение при поддержке большого числа клиентских подключений, является максимальная длина очереди подключений к сокету. Если число запросов на подключение за очень короткий промежуток времени превышает этот предел, некоторые из них могут быть отклонены прежде, чем qhbmaster сможет их обработать, и такие клиенты получат бесполезное сообщение об ошибке подключения вроде «Resource temporarily unavailable» (Ресурс временно недоступен) или «Connection refused» (В подключении отказано). Предел длины очереди на многих платформах по умолчанию составляет 128. Чтобы его повысить, настройте соответствующий параметры ядра посредством sysctl, а затем перезапустите qhbmaster. Этот параметр называется net.core.somaxconn в Linux, kern.ipc.soacceptqueue в последних версиях FreeBSD и kern.ipc.somaxconn в macOS и других вариантах BSD.

Избыточное выделение памяти в Linux

В Linux поведение виртуальной памяти по умолчанию не оптимально для QHB. Из-за того, что ядро выделяет избыточный объем памяти, оно может уничтожить qhbmaster (управляющий серверный процесс) QHB, если потребность в памяти процесса QHB или другого процесса приведет к исчерпанию виртуальной памяти в системе.

Если это происходит, вы увидите примерно такое сообщение ядра (где именно искать подобное сообщение, можно узнать в документации вашей системы):

Out of Memory: Killed process 12345 (qhb).
-- Недостаточно памяти: уничтожен процесс 12345 (qhb).

Это указывает на то, что процесс qhb был уничтожен из-за нехватки памяти. Хотя существующие подключения к базе данных продолжат функционировать нормально, новые подключения приниматься не будут. Для восстановления работы сервера понадобится перезапустить QHB.

Один из способов избежать этой проблемы — запускать QHB на машине, где вы можете быть уверены, что другие процессы не исчерпают ее память. При дефиците памяти помочь решить эту проблему может увеличение объема пространства подкачки операционной системы, поскольку OOM killer (out-of-memory killer, уничтожитель процессов при нехватке памяти) вызывается, только когда заканчивается и физическая память, и место в пространстве подкачки.

Если память заканчивается из-за самой QHB, этой проблемы можно избежать, изменив конфигурацию сервера. В некоторых случаях может помочь уменьшение параметров конфигурации, связанных с памятью, в частности shared_buffers, work_mem и hash_mem_multiplier. В других случаях проблема может возникать из-за того, что разрешено слишком много подключений к самому серверу баз данных. Зачастую лучшим выходом будет снизить значение max_connections и вместо этого воспользоваться внешней программой для организации пула подключений.

«Избыточное выделение» памяти можно предотвратить, изменив поведение ядра. Хотя эта настройка и не отключит вызов OOM killer полностью, она значительно снизит вероятность этого, тем самым сделав поведение системы более стабильным. Для этого нужно выбрать режим строгого выделения памяти посредством sysctl:

sysctl -w vm.overcommit_memory=2

или поместить соответствующую запись в /etc/sysctl.conf. Возможно, вам также понадобится изменить связанный параметр vm.overcommit_ratio. Подробную информацию см. в файле документации ядра.

Другой подход, который можно применить как с изменением параметра vm.overcommit_memory, так и без него, заключается в установке для свойства корректировка коэффициента OOM процесса qhbmaster значения -1000; это гарантирует, что данный процесс не станет целью OOM killer. Проще всего это сделать, выполнив

echo -1000 > /proc/self/oom_score_adj

в скрипте запуска qhbmaster непосредственно перед тем, как его запустить. Обратите внимание, что это нужно делать от root, иначе команда не подействует; так что проще всего это сделать в скрипте запуска, принадлежащем root. Если вы делаете это, вам также необходимо перед вызовом qhbmaster установить в скрипте запуска следующие переменные среды:

export PG_OOM_ADJUST_FILE=/proc/self/oom_score_adj
export PG_OOM_ADJUST_VALUE=0

С этими параметрами дочерние процессы qhbmaster будут запускаться с обычной, нулевой корректировкой коэффициента OOM, так что при необходимости OOM killer все равно сможет их уничтожать. Если вы хотите, чтобы дочерние процессы выполнялись с другой корректировкой коэффициента OOM, можно задать для PG_OOM_ADJUST_VALUE другое значение. (Кроме того, PG_OOM_ADJUST_VALUE можно вообще опустить, и в этом случае по умолчанию подразумевается ноль.) Если вы не установите PG_OOM_ADJUST_FILE, дочерние процессы будут выполняться с той же корректировкой коэффициента OOM, что и qhbmaster, что неразумно, поскольку основная цель всего этого — сделать так, чтобы настройки давали qhbmaster преимущество.

Огромные страницы в Linux

Использование огромных страниц снижает издержки при работе с большими непрерывными блоками памяти, как это делает QHB, в частности, когда задействует большие значения shared_buffers. Чтобы воспользоваться такой функциональностью в QHB, вами нужно сконфигурировать ядро с параметрами CONFIG_HUGETLBFS=y и CONFIG_HUGETLB_PAGE=y. Кроме того, вам потребуется сконфигурировать операционную систему, чтобы та могла предоставлять достаточно огромных страниц желаемого размера. Для оценки требуемого числа огромных страниц воспользуйтесь командой qhb, чтобы узнать значение параметра shared_memory_size_in_huge_pages. Обратите внимание, что для просмотра этого параметра, вычисляемого во время выполнения, сервер необходимо остановить. Это может выглядеть примерно так:

$ qhb -D $PGDATA -C shared_memory_size_in_huge_pages
3170
$ grep ^Hugepagesize /proc/meminfo
Hugepagesize:       2048 kB
$ ls /sys/kernel/mm/hugepages
hugepages-1048576kB  hugepages-2048kB

В этом примере стандартный размер составляет 2 МБ, но в huge_page_size можно явно задать 2 МБ или 1 ГБ, чтобы подогнать число страниц, вычисленное в shared_memory_size_in_huge_pages. Хотя в этом примере нам понадобится не менее 3170 огромных страниц, большее значение было бы оправдано, если бы другим программам на этой машине тоже были нужны огромные страницы. Это значение можно задать так:

# sysctl -w vm.nr_hugepages=3170

Не забудьте добавить эту настройку в /etc/sysctl.conf, чтобы она действовала и после перезагрузки. Если размер огромных страниц отличается от стандартного, его можно изменить следующим образом:

# echo 3170 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

Кроме того, эти настройки можно задать во время загрузки операционной системы, используя параметры ядра, например hugepagesz=2M hugepages=3170.

Иногда ядро не может сразу выделить желаемое количество огромных страниц из-за фрагментации, поэтому может потребоваться повторить эту команду или перезагрузить систему. (Сразу после перезагрузки большая часть памяти компьютера должна стать доступной для преобразования в огромные страницы.) Для проверки ситуации с размещением огромных страниц заданного размера воспользуйтесь:

$ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

Также может понадобиться дать пользователю операционной системы сервера баз данных разрешение на использование огромных страниц, добавив его группу в vm.hugetlb_shm_group с помощью sysctl и/или дав разрешение на блокировку памяти, выполнив ulimit -l.

По умолчанию QHB использует огромные страницы стандартного системного размера, когда это возможно, а в противном случае возвращается к использованию обычных страниц. Чтобы заставить ее пользоваться огромными страницами, можно установить для huge_pages значение on в qhb.conf. Обратите внимание, что с этим значением QHB не сможет запуститься, если доступно недостаточно огромных страниц.

Подробное описание механизма огромных страниц в Linux см. в документации ядра.

Завершение работы сервера

Есть несколько способов отключить сервер баз данных. С точки зрения внутренней структуры все они сводятся к передаче различных сигналов управляющему процессу qhb:

SIGTERM
Это режим умного отключения. После получения сигнала SIGTERM сервер запрещает новые подключения, но позволяет существующим сеансам нормально завершить свою работу. Сервер отключается только после завершения всех сеансов. Если сервер находится в состоянии восстановления, то при запросе умного отключения восстановление и потоковая репликация будут остановлены только после завершения всех обычных сеансов.

SIGINT
Это режим быстрого отключения. Сервер запрещает новые подключения и отправляет всем существующим серверным процессам сигнал SIGTERM, заставляя их прервать свои текущие транзакции и быстро завершить работу. Затем он ожидает завершения всех серверных процессов и наконец отключается сам.

SIGQUIT
Это режим немедленного отключения. Сервер отправит сигнал SIGQUIT всем дочерним процессам и будет ждать их завершения. Если они не прекратятся в течение 5 секунд, им будут отправлены сигналы SIGKILL. Управляющий процесс сервера завершается сразу же после завершения всех дочерних процессов, без выполнения обычной процедуры завершения работы базы данных. Это приведет к выполнению процесса восстановления (путем воспроизведения журнала WAL) при следующем запуске. Этот режим отключения рекомендуется использовать только в чрезвычайных ситуациях.

Программа qhb_ctl предоставляет удобный интерфейс для отправки этих сигналов для завершение работы сервера. Кроме того, сигнал можно отправить напрямую командой kill. PID процесса qhb можно найти с помощью программы ps или в файле qhbmaster.pid в каталоге данных. Например, для быстрого отключения можно написать следующее:

$ kill -INT `head -1 /var/lib/qhb/data/qhbmaster.pid`

ВАЖНО!
Лучше не использовать SIGKILL для отключения сервера, так как в этом случае сервер не освободит разделяемую память и семафоры. Кроме того, SIGKILL уничтожает процесс qhb, не позволяя ему передать сигнал своим подпроцессам, поэтому может потребоваться уничтожить вручную и отдельные подпроцессы.

Чтобы завершить отдельный сеанс, при этом не завершая другие сеансы, используйте pg_terminate_backend() (см. таблицу Функции для передачи сигналов серверу) или отправьте сигнал SIGTERM дочернему процессу, связанному с этим сеансом.

Обновление кластера QHB

В этом разделе рассказывается о том, как обновить данные вашей базы данных с одной версии QHB на другую.

Текущие номера версий QHB состоят из номеров основной и дополнительной версий, при этом основная версия образуется группой из первых двух цифр номера версии. Например, в версии 1.4.1 группа 1.4 обозначает основную версию а третья цифра (1) — дополнительную, то есть это первый дополнительный релиз основного релиза 1.4.

В дополнительных релизах никогда не меняется внутренний формат хранения, и они всегда совместимы с предыдущими или последующими дополнительными релизами одного номера основной версии. Например, версия 1.4.1 совместима с версией 1.4.0 и 1.4.2. Для обновления до совместимых версий достаточно просто заменить исполняемые файлы при отключенном сервера и перезапустить его. Обновления дополнительных версий настолько просты, что при этом не затрагивается каталог данных.

При обновлении основных релизов QHB внутренний формат хранения данных может измениться, тем самым усложняя процедуру обновления. Традиционным способом переноса данных в новую основную версию является выгрузка базы данных из старой версии и загрузка ее в новую, хотя это может занять много времени. Более быстрый способ — воспользоваться приложением qhb_upgrade. Также доступны способы с использованием репликации, как описано ниже.

Кроме того, новые основные версии обычно приносят какие-то видимые пользователю несовместимости, поэтому может потребоваться внести изменения в приложения. Все подобные изменения перечисляются в замечаниях к релизу. Хотя вы можете перейти с одной основной версии на другую, не обновляя QHB до промежуточных версий, вам следует обязательно прочитать замечания к каждому пропускаемому релизу.

Осторожные пользователи могут пожелать протестировать свои клиентские приложения на новой версии, прежде чем полностью на нее перейти, поэтому часто имеет смысл установить одновременно старую и новую версии. При тестировании обновления основной версии QHB изучите следующие категории возможных изменений:

Администрирование
Инструменты, доступные администраторам для мониторинга и управления сервером, часто меняются и совершенствуются в каждом основном релизе.

SQL
Обычно сюда входят новые возможности команд SQL, а не изменения в их поведении, если только это специально не указывается в замечаниях к релизу.

API библиотек
Обычно в библиотеки типа libpq только добавляются новые функциональные возможности, опять же, если иное не указывается в замечаниях к релизу.

Системные каталоги
Изменения в системных каталогах обычно влияют только на инструменты управления базами данных.

API сервера для кода на C/RUST
Сюда входят изменения в API серверных функций, которые написаны на языках программирования C/RUST. Подобные изменения влияют на код, обращающийся к серверным функциям глубоко внутри сервера.

Обновление данных посредством qhb_dumpall

Один из методов обновления состоит в выгрузке данных из одной основной версии QHB и загрузке их в другую — для этого необходимо использовать инструмент логического копирования вроде qhb_dumpall; методы резервного копирования на уровне файловой системы не подойдут. (На самом сервере существуют проверки, не дающие использовать каталог данных с несовместимой версией QHB, поэтому попытка запустить с этим каталогом неправильную версию сервера не причинит большого вреда.)

Рекомендуется использовать программы qhb_dump и qhb_dumpall из новой версии QHB, чтобы воспользоваться улучшениями, которые могли в них появиться.

В этих инструкциях предполагается, что ваша существующая установка находится в каталоге /usr/local/qhb, а область данных — в каталоге /var/lib/qhb/data. Подставляйте собственные пути надлежащим образом.

При запуске резервного копирования убедитесь в том, что в базе данных не выполняются изменения. Это не повлияет на целостность копии, но измененные данные в нее, разумеется, не попадут. При необходимости отредактируйте разрешения в файле /var/lib/qhb/data/qhb_hba.conf (или подобном), чтобы запретить доступ к серверу всем, кроме себя. Дополнительную информацию по управлению доступом см. в главе Аутентификация клиентского приложения.

Чтобы получить резервную копию вашей установки базы данных, введите:

qhb_dumpall > выходной_файл

Для создания резервной копии можно воспользоваться командой qhb_dumpall из версии, которая у вас запущена в данный момент; подробную информацию см. в подразделе Использование qhb_dumpall. Однако для наилучшего результата попробуйте выполнить qhb_dumpall из новой версии, поскольку в эту версию могут входить исправления ошибок и усовершенствования, которые отсутствуют в предыдущих. Хотя этот совет может показаться абсурдным, поскольку вы еще не установили новую версию, рекомендуем ему последовать, если вы планируете установить новую версию параллельно старой. В этом случае вы можете выполнить установку как обычно, а потом перенести данные. Это также сократит время простоя.

Остановите старый сервер:

qhb_ctl stop

В системах, где QHB запускается при загрузке, вероятно, должен быть файл запуска, с котором можно добиться того же самого. Например, в системе Red Hat Linux может сработать следующее:

/etc/rc.d/init.d/qhb stop

Подробнее запуск и остановка сервера описаны в разделах Запуск сервера баз данных и Завершение работы сервера выше.

При восстановлении из резервной копии переименуйте или удалите старый каталог установки, если его имя не привязано к версии. Лучше переименовать каталог, чем уничтожить, на случай, если возникнут проблемы и понадобится его восстановить. Учтите, что этот каталог может занимать на диске много места. Чтобы переименовать каталог, воспользуйтесь следующей командой:

mv /usr/local/qhb /usr/local/qhb.old

(Обязательно переносите этот каталог как единое целое, чтобы относительные пути в нем не изменились.)

Установите новую версию QHB, как описано в главе Установка.

При необходимости создайте новый кластер баз данных. Помните, что эти команды следует выполнять, войдя в систему под аккаунтом специального пользователя базы данных (что вы уже сделали, если проводите обновление).

/usr/local/qhb/bin/qhb_bootstrap -D /var/lib/qhb/data

Восстановите изменения, внесенные в предыдущие версии qhb_hba.conf и qhb.conf.

Запустите сервер баз данных, тоже из-под аккаунта специального пользователя базы данных:

/usr/local/qhb/bin/qhb -D /var/lib/qhb/data

Наконец, восстановите данные из резервной копии:

/usr/local/qhb/bin/psql -d qhb -f выходной_файл

используя новый psql.

Минимизировать время простоя сервера можно, установив новый сервер в другой каталог и запустив старый и новый сервера параллельно, но с разными портами. Затем можно выполнить, например, следующее:

qhb_dumpall -p 5432 | psql -d qhb -p 5433

чтобы перенести данные.

Обновление данных посредством qhb_upgrade

Модуль qhb_upgrade позволяет переносить установку с одной основной версии QHB на другую прямо на месте. Обновления могут занять всего несколько минут, особенно в режиме --link. Для него требуются шаги, похожие на описанные выше действия для qhb_dumpall, например, запустить/остановить сервер, выполнить qhb_bootstrap (или initdb). Все необходимые шаги описаны в документации по qhb_upgrade.

Обновление данных посредством репликации

Для создания резервного сервера с обновленной версией QHB также можно использовать методы логической репликации. Это возможно, поскольку логическая репликация поддерживается между разными основными версиями QHB. Резервный сервер может находиться на том же или на другом компьютере. Как только он синхронизируется с основным сервером (где запущена старая версия QHB), вы можете поменять их местами и сделать резервный сервер основным, а старый экземпляр баз данных просто отключить. При таком переключении простой сервера во время обновления длится всего несколько секунд.

Этот метод обновления можно осуществить, используя как встроенные средства логической репликации, так и внешние системы логической репликации, например pglogical, Slony, Londiste и Bucardo.

Предотвращение имитации сервера

Во время работы сервера пользователь-злоумышленник не может подменить нормальный сервер баз данных. Однако когда сервер отключен, локальный пользователь может его имитировать, запустив свой собственный. Поддельный сервер может читать пароли и запросы клиентов, но не может вернуть никаких данных, поскольку каталог PGDATA все равно будет защищен благодаря своим запретам на чтение. Такая имитация возможна, потому что любой пользователь может запустить сервер баз данных; клиент не способен выявить поддельный сервер, если его специально не настроить.

Один из способов предотвратить имитацию локальных подключений (local) состоит в использовании каталога сокетов домена Unix (unix_socket_directories), в который разрешено писать только доверенному локальному пользователю. Это не даст пользователю-злоумышленнику создать в этом каталоге свой файл сокета. Если вас беспокоит, что некоторые приложения все еще могут обращаться к файлу сокета в /tmp и тем самым остаются уязвимыми к подмене, создайте при загрузке операционной системы символическую ссылку /tmp/.s.QHB.5432, указывающую на перемещенный файл сокета. Возможно, вам также понадобится изменить скрипт очистки /tmp, чтобы он не удалял эту ссылку.

Другой вариант для подключений local — использование клиентами параметра requirepeer, чтобы указать имя нужного владельца серверного процесса, подключенного к сокету.

Для предотвращения имитаций TCP-соединений либо используйте SSL-сертификаты и позаботьтесь, чтобы клиенты проверяли сертификат сервера, либо применяйте шифрование GSSAPI (или пользуйтесь и тем, и другим для отдельных подключений).

Для предотвращения имитаций соединения с SSL сервер должен быть настроен так, чтобы принимать только подключения hostssl (раздел Файл qhb_hba.conf) и иметь ключ и файлы сертификатов SSL (раздел Защита TCP/IP-соединений посредством SSL). TCP-клиент должен подключаться к серверу с параметром sslmode=verify-ca или sslmode=verify-full и иметь соответствующий установленный файл корневого сертификата (подраздел Проверка сертификатов сервера на стороне клиента).

Для предотвращения имитаций соединения с GSSAPI сервер должен быть настроен так, чтобы принимать только подключения hostgssenc (раздел Файл qhb_hba.conf) и использовать для них аутентификацию gss. TCP-клиент должен подключаться к серверу с параметром gssencmode=require.

Варианты шифрования

QHB предлагает шифрование на разных уровнях и обеспечивает гибкость в защите данных от раскрытия при краже сервера баз данных, из-за недобросовестных администраторов или в незащищенных сетях. Шифрование также может понадобиться для защиты конфиденциальных данных, например медицинских записей или финансовых транзакций.

Шифрование паролей

Пароли пользователей базы данных хранятся в виде хешей (алгоритм шифрования определяется параметром password_encryption), поэтому администратор не может определить, какой именно пароль принадлежит конкретному пользователю. Если для аутентификации клиента используется шифрование SCRAM или MD5, незашифрованный пароль никогда не присутствует на сервере, даже временно, потому что клиент шифрует его, перед тем как передать по сети. Предпочтительно использовать SCRAM, поскольку это стандарт, принятый в Интернете, и он более безопасен, чем специфический для QHB протокол аутентификации MD5.

Шифрование заданных столбцов

Модуль pgcrypto позволяет хранить зашифрованными определенные поля. Это полезно, если конфиденциальными являются только некоторые данные. Клиент предоставляет ключ дешифрования, и данные дешифруются на сервере, а затем передаются клиенту.

Дешифрованные данные и ключ дешифрования присутствуют на сервере в течение короткого времени, пока происходит дешифрование и обмен данными между клиентом и сервером. Именно в этот момент данные и ключи могут быть перехвачены тем, кто имеет полный доступ к серверу баз данных, например системным администратором.

Шифрование раздела данных

Шифрование хранилища можно осуществить на уровне файловой системы или на уровне блоков. Механизмы шифрования файловых систем в Linux — eCryptfs и EncFS, во FreeBSD — PEFS. Механизмы шифрования на уровне блоков или всего диска в Linux — dm-crypt + LUKS, во FreeBSD — модули GEOM (geli и gbde). Эту функциональность поддерживают и многие другие операционные системы.

Этот механизм предотвращает чтение незашифрованных данных с дисков в случае кражи дисков или всего компьютера. Он не защищает от атак, когда файловая система смонтирована, поскольку смонтированная операционная система обеспечивает незашифрованное представление данных. Однако чтобы смонтировать файловую систему, необходимо найти какой-то способ передать операционной системе ключ шифрования, а этот ключ иногда хранится где-то на хосте, который монтирует диск.

Шифрование данных при передаче по сети

SSL-соединения шифруют все данные, передаваемые по сети: пароль, запросы и возвращаемые данные. Файл qhb_hba.conf позволяет администраторам указать, какие хосты могут использовать незашифрованные соединения (host), а каким требуются соединения, зашифрованные SSL (hostssl). Кроме того, клиенты могут указать, что они подключаются к серверам только через SSL.

Соединения, зашифрованные GSSAPI, шифруют все данные, передаваемые по сети, включая запросы и возвращаемые данные. (Пароли по сети не передаются.) Файл qhb_hba.conf позволяет администраторам указать, какие хосты могут использовать незашифрованные соединения (host), а каким требуются соединения, зашифрованные GSSAPI (hostgssenc). Кроме того, клиенты могут указать, что они подключаются к серверам только через соединения, зашифрованные GSSAPI (gssencmode=require).

Для шифрования передаваемых данных также можно использовать Stunnel или SSH.

Аутентификация хоста SSL

Клиент и сервер могут предоставлять друг другу SSL-сертификаты. Это требует некоторой дополнительной настройки с каждой стороны, но обеспечивает более надежную проверку идентификационных данных, чем простое использование паролей. Такая проверка не позволяет компьютеру имитировать сервер достаточно долго, чтобы прочитать отправленный клиентом пароль. Также она помогает предотвратить атаки с применением технологии «незаконный посредник», когда компьютер между клиентом и сервером имитирует сервер и считывает и передает все данные между клиентом и настоящим сервером.

Шифрование на стороне клиента

Если системный администратор серверной станции не является доверенным, клиенту необходимо шифровать данные; в этом случае на сервере баз данных незашифрованные данные никогда не появятся. Данные шифруются на стороне клиента, прежде чем передаются серверу, а полученные из базы данных результаты клиенту перед использованием требуется дешифровать.

Защита TCP/IP-соединений посредством SSL

В QHB имеется встроенная поддержка использования SSL-соединений для шифрования передаваемых между клиентом и сервером данных, что повышает их защиту. Для этого необходимо, чтобы в системах клиента и сервера был установлен OpenSSL и чтобы при сборке в QHB была включена поддержка SSL (см. главу Установка).

Термины SSL и TLS часто используются взаимозаменяемо для обозначения защищенного зашифрованного соединения по протоколу TLS. Протоколы SSL являются предшественниками протоколов TLS, поэтому термин SSL по-прежнему используется для зашифрованных соединений, хотя сами протоколы SSL уже не поддерживаются. В QHB термины SSL и TLS тоже используются взаимозаменяемо.

Базовая настройка

После компиляции поддержки SSL сервер QHB можно запустить с поддержкой зашифрованных соединений по протоколам TLS, установив для параметра ssl в файле qhb.conf значение on. Этот сервер будет принимать как обычные, так и SSL-соединения в одном порту TCP и будет согласовывать использование SSL с каждым подключающимся клиентом. По умолчанию клиент выбирает режим подключения сам; о том, как настроить сервер, чтобы он требовал использования SSL для всех или некоторых подключений, рассказывается в разделе Файл qhb_hba.conf.

Для запуска сервера в режиме SSL уже должны существовать файлы, содержащие сертификат сервера и закрытый ключ. По умолчанию ожидается, что эти файлы будут называться server.crt и server.key соответственно и находиться в каталоге данных сервера, но с помощью параметров конфигурации ssl_cert_file и ssl_key_file для них можно задать и другие имена и расположения.

В системах Unix в разрешениях для server.key должен быть прописан запрет на любой доступ группы и всех остальных; это ограничение устанавливается командой chmod 0600 server.key. Либо же этот файл может принадлежать пользователю root, а группа будет иметь доступ на чтение (то есть маска разрешений 0640). Такая настройка предназначена для установок, в которых файлами сертификата и ключа управляет операционная система. В этом случае пользователь, от имени которого запускается сервер QHB, должен быть членом группы, имеющей доступ к этим файлам.

Если к каталогу данных разрешен доступ на чтение для группы, возможно, файлы сертификатов следует размещать вне этого каталога для удовлетворения вышеуказанных требований безопасности. В целом, доступ для группы разрешается, чтобы непривилегированные пользователи могли выполнять резервное копирование базы данных, и в этом случае программа для резервного копирования не сможет прочитать файлы сертификатов, что, скорее всего, завершится ошибкой.

Если закрытый ключ защищен парольной фразой, сервер запросит эту фразу и не запустится, пока та не будет введена. По умолчанию использование парольной фразы лишает возможности изменить конфигурацию SSL без перезагрузки сервера, однако, включив параметр ssl_passphrase_command_supports_reload, это можно сделать.

Первым сертификатом в server.crt должен быть сертификат сервера, поскольку он должен соответствовать закрытому ключу сервера. В этот файл также можно добавить сертификаты «промежуточных» центров сертификации. Это избавляет от необходимости хранить промежуточные сертификаты на стороне клиентов при условии, что корневой и промежуточные сертификаты были созданы с расширениями v3_ca. (При этом в основном ограничении сертификата CA устанавливается значение true.) Кроме того, это упрощает работу с промежуточными сертификатами с истекающим сроком действия.

Добавлять корневой сертификат в server.crt нет необходимости. Вместо этого клиенты должны иметь этот сертификат в цепочке сертификатов сервера.

Конфигурация OpenSSL

QHB читает общесистемный файл конфигурации OpenSSL. По умолчанию этот файл называется openssl.cnf и находится в каталоге, который выводится командой openssl version -d. Это можно переопределить, задав в переменной среды OPENSSL_CONF желаемое имя и расположение файла конфигурации.

OpenSSL поддерживает широкий выбор шифров и алгоритмов аутентификации разной степени защищенности. Хотя список шифров можно указать в файле конфигурации OpenSSL, можно задать отдельные шифры для использования конкретно сервером баз данных, изменив параметр ssl_ciphers в qhb.conf.

Примечание
Издержки аутентификации, связанные с шифрованием, можно исключить, используя шифры NULL-SHA или NULL-MD5. Однако незаконный компьютер-посредник сможет читать и передавать трафик между клиентом и сервером. Кроме того, издержки на шифрование минимальны по сравнению с издержками самой аутентификации. По этим причинам использовать шифры NULL не рекомендуется.

Использование клиентских сертификатов

Чтобы обязать клиента предоставлять доверенный сертификат, поместите сертификаты корневых центров сертификации (Certification authority, CA), которым вы доверяете, в файл в каталоге данных, задайте в параметре ssl_ca_file в qhb.conf имя этого файла и добавьте параметр аутентификации clientcert=verify-ca или clientcert=verify-full в соответствующую строку (или строки) hostssl в qhb_hba.conf. После этого в процессе установления SSL-соединения у клиента будет запрашиваться сертификат. (Как настроить сертификаты на стороне клиента, описывается в разделе Поддержка SSL.)

Для записи hostssl с указанием clientcert=verify-ca сервер будет проверять, подписан ли сертификат клиента одним из доверенных центров сертификации. Если указано clientcert=verify-full, сервер будет не только проверять цепочку сертификатов, но и сличать имя пользователя или его сопоставление с общим именем (cn, Common Name) в предоставленном сертификате. Обратите внимание, что при использовании метода аутентификации cert цепочка сертификатов проверяется всегда (см. раздел Аутентификация по сертификату).

Промежуточные сертификаты, образующие цепочку до существующих корневых сертификатов, при желании можно также включить в файл ssl_ca_file, чтобы не хранить их на стороне клиентов (при условии, что корневой и промежуточные сертификаты были созданы с расширениями v3_ca). Если установлен параметр ssl_crl_file или ssl_crl_dir, также проверяются записи в списке отозванных сертификатов (Certificate Revocation List, CRL).

Параметр аутентификации clientcert доступен для всех методов аутентификации, но только в строках qhb_hba.conf типа hostssl. Когда clientcert не задан, сервер проверяет сертификат клиента по своему файлу CA, только если этот сертификат предоставлен, а список CA сконфигурирован.

Существует два способа потребовать от пользователей предоставления сертификатов при входе в систему.

Первый способ заключается в использовании метода аутентификации cert для записей hostssl в qhb_hba.conf, с тем чтобы сам сертификат использовался и для аутентификации, и для защиты SSL-соединения. Подробную информацию см. в разделе Аутентификация по сертификату. (Задавать явно какие-либо значения clientcert при использовании метода аутентификации cert необязательно.) В этом случает предоставленное в сертификате cn сверяется с именем пользователя или применяемым сопоставлением.

Второй способ сочетает использование любого метода аутентификации для записей hostssl с проверкой сертификатов клиента, задаваемой установкой в параметре аутентификации clientcert значения verify-ca или verify-full. Первое значение требует только проверки сертификата, тогда как при втором также проверяется, что cn в сертификате соответствует имени пользователя или применяемому сопоставлению.

Файлы, используемые SSL-сервером

В Таблице 2 кратко описаны файлы, относящиеся к настройке SSL на сервере. (Приведены стандартные имена файлов. В конкретных конфигурациях они могут отличаться.)

Таблица 2. Файлы, используемые SSL-сервером

Файл
Содержимое
Назначение

ssl_cert_file
($PGDATA/server.crt)
сертификат сервера
отправляется клиенту для идентификации сервера

ssl_key_file
($PGDATA/server.key)
закрытый ключ сервера
подтверждает, что сертификат сервера был отправлен его владельцем; не свидетельствует о том, что этому владельцу можно доверять

ssl_ca_file
доверенные центры сертификации
проверяет, подписан ли сертификат клиента доверенным центром сертификации

ssl_crl_file
сертификаты, отозванные центрами сертификации
сертификата клиента не должно быть в этом списке

Сервер читает эти файлы при запуске или при перезагрузке конфигурации. Если в этих файлах ошибка обнаруживается при запуске сервера, сервер откажется запускаться. Но если ошибка обнаруживается при перезагрузке конфигурации, эти файлы игнорируются и продолжает использоваться старая конфигурация SSL. Во всех таких случаях ошибка протоколируется в журнал сервера.

Создание сертификатов

Чтобы создать для сервера простой самоподписанный сертификат, действующий 365 дней, воспользуйтесь следующей командой OpenSSL, заменив хостбд.вашдомен.com именем хоста сервера:

openssl req -new -x509 -days 365 -nodes -text -out server.crt \
  -keyout server.key -subj "/CN=хостбд.вашдомен.com"

Затем выполните:

chmod og-rwx server.key

так как сервер не примет этот файл, если разрешения будут менее строгими, чем указанные. Более подробную информацию о том, как создать собственный закрытый ключ и сертификат, см. в документации OpenSSL.

Хотя самоподписанный сертификат можно использовать при тестировании, в условиях промышленной эксплуатации следует использовать сертификат, подписанный центром сертификации (CA) (обычно это корневой CA уровня предприятия).

Чтобы создать сертификат сервера, подлинность которого смогут проверять клиенты, сначала создайте запрос на подпись сертификата (certificate signing request, CSR) и файлы открытого/закрытого ключа:

openssl req -new -nodes -text -out root.csr \
  -keyout root.key -subj "/CN=корень.вашдомен.com"
chmod og-rwx root.key

Затем подпишите запрос ключом, чтобы создать корневой центр сертификации (используя стандартное расположение файла конфигурации OpenSSL в Linux):

openssl x509 -req -in root.csr -text -days 3650 \
  -extfile /etc/ssl/openssl.cnf -extensions v3_ca \
  -signkey root.key -out root.crt

Наконец, создайте сертификат сервера, подписанный новым корневым центром сертификации:

openssl req -new -nodes -text -out server.csr \
  -keyout server.key -subj "/CN=хостбд.вашдомен.com"
chmod og-rwx server.key

openssl x509 -req -in server.csr -text -days 365 \
  -CA root.crt -CAkey root.key -CAcreateserial \
  -out server.crt

Файлы server.crt и server.key должны храниться на сервере, а файл root.crt — на клиенте, чтобы клиент мог убедиться, что листовой сертификат был подписан доверенным корневым сертификатом. Файл root.key должен храниться на локальной машине, чтобы его можно было использовать для создания сертификатов в будущем.

Также можно создать цепочку доверия, включающую промежуточные сертификаты:

# корневой сертификат
openssl req -new -nodes -text -out root.csr \
  -keyout root.key -subj "/CN=корень.вашдомен.com"
chmod og-rwx root.key
openssl x509 -req -in root.csr -text -days 3650 \
  -extfile /etc/ssl/openssl.cnf -extensions v3_ca \
  -signkey root.key -out root.crt

# промежуточный сертификат
openssl req -new -nodes -text -out intermediate.csr \
  -keyout intermediate.key -subj "/CN=промежуток.вашдомен.com"
chmod og-rwx intermediate.key
openssl x509 -req -in intermediate.csr -text -days 1825 \
  -extfile /etc/ssl/openssl.cnf -extensions v3_ca \
  -CA root.crt -CAkey root.key -CAcreateserial \
  -out intermediate.crt

# листовой сертификат
openssl req -new -nodes -text -out server.csr \
  -keyout server.key -subj "/CN=хостбд.вашдомен.com"
chmod og-rwx server.key
openssl x509 -req -in server.csr -text -days 365 \
  -CA intermediate.crt -CAkey intermediate.key -CAcreateserial \
  -out server.crt

Файлы server.crt и intermediate.crt следует объединить в комплект файлов сертификатов и хранить на сервере. Файл server.key тоже должен храниться на сервере. Файл root.crt должен храниться на клиенте, чтобы клиент мог убедиться, что листовой сертификат был подписан цепочкой сертификатов, ведущей к доверенному корневому сертификату. Файлы root.key и intermediate.key должны храниться на локальной машине, чтобы их можно было использовать для создания сертификатов в будущем.

Защита TCP/IP-соединений посредством шифрования GSSAPI

В QHB также имеется встроенная поддержка применения GSSAPI для шифрования передаваемых между клиентом и сервером данных, что повышает их защиту. Для поддержки необходимо, чтобы как на клиенте, так и на сервере была установлена реализация GSSAPI (например, MIT Kerberos) и чтобы эта поддержка была включена в QHB при сборке (см. главу Установка).

Сервер QHB будет принимать как как обычные, так и зашифрованные GSSAPI соединения в одном порту TCP и будет согласовывать использование GSSAPI для шифрования (и аутентификации) с каждым подключающимся клиентом. По умолчанию клиент выбирает режим подключения сам (это означает, что злоумышленник может от него отказаться); о том, как настроить сервер, чтобы он требовал использования GSSAPI для всех или некоторых подключений, рассказывается в разделе Файл qhb_hba.conf.

При использовании GSSAPI для шифрования его часто применяют также и для аутентификации, поскольку нижележащий механизм в любом случае будет проверять подлинность клиента и сервера (в соответствии с реализацией GSSAPI). Но это необязательно; для проведения дополнительной проверки можно выбрать любой другой метод аутентификации, поддерживаемый QHB.

Кроме конфигурации поведения при согласовании другой настройки для шифрования GSSAPI не требуется, не считая тех, которые необходимы для аутентификации GSSAPI. (Подробную информацию о том, как это настроить, см. в разделе Аутентификация GSSAPI.)

Защита TCP/IP-соединений посредством туннелей SSH

Для шифрования сетевых соединений между клиентом и сервером QHB можно использовать SSH. Сделанное должным образом, оно обеспечивает надлежащую защиту сетевых соединений даже для клиентов, не поддерживающих SSL.

Сначала убедитесь, что на компьютере с сервером QHB нормально работает сервер SSH и что вы можете подключиться к нему с помощью ssh под именем какого-нибудь пользователя; затем вы сможете установить защищенный туннель к этому удаленному серверу. Защищенный туннель прослушивает локальный порт и перенаправляет весь трафик в порт удаленного компьютера. Трафик, отправляемый в удаленный порт, может поступить на его адрес localhost или, при желании, на другой привязанный адрес; при этом он не будет выглядеть как поступивший с вашего компьютера. Следующая команда создает защищенный туннель между клиентским компьютером и удаленным компьютером foo.com:

ssh -L 63333:localhost:5432 joe@foo.com

Первое число в аргументе -L, 63333, — это локальный номер порта туннеля; это может быть любой незанятый порт. (IANA резервирует порты с 49152 по 65535 для частного использования.) Имя или IP-адрес после этого — удаленный привязанный адрес, к которому вы подключаетесь; здесь это localhost (это значение по умолчанию). Третье число, 5432, — удаленная сторона туннеля, например номер порта, который использует ваш сервер баз данных. Чтобы подключиться к данному серверу с помощью этого туннеля, вы подключаетесь к порту 63333 на локальном компьютере:

psql -h localhost -p 63333 qhb

Тогда для сервера баз данных это будет выглядеть так, как будто вы пользователь joe на хосте foo.com, подключающийся к привязанному адресу localhost, и он будет применять ту процедуру аутентификации, которая была установлена для подключений этого пользователя к этому привязанному адресу. Обратите внимание, что сервер не будет считать такое соединение зашифрованным SSL, поскольку на самом деле трафик между сервером SSH и сервером QHB не зашифрован. Это не должно представлять какой-то дополнительной угрозы безопасности, так как эти серверы находятся на одном компьютере.

Чтобы настроенный туннель работал, вам должно быть разрешено подключаться к компьютеру посредством ssh под именем joe@foo.com аналогично тому, как если бы вы пытались использовать ssh для создания сеанса работы с терминалом.

Кроме того, вы можете настроить перенаправление портов следующим образом:

ssh -L 63333:foo.com:5432 joe@foo.com

но тогда сервер баз данных увидит подключение как приходящее на его привязанный адрес foo.com, который не прослушивается вследствие установки по умолчанию listen_addresses = 'localhost'. Обычно это не то, что вам нужно.

Если вам нужно «перескочить» к серверу баз данных через некоторый регистрационный хост, возможная настройка может выглядеть примерно так:

ssh -L 63333:db.foo.com:5432 joe@shell.foo.com

Обратите внимание, что в этом случае трафик между shell.foo.com и db.foo.com не будет зашифрован туннелем SSH. SSH предлагает довольно много вариантов конфигурации, где ограничения сети организованы разными способами. Подробную информацию см. в документации SSH.

Совет
Существует еще несколько приложений, которые могут создавать безопасные туннели, используя процедуру, схожую по сути с описанной выше.

Улучшенная безопасность. Очистка памяти

QHB предоставляет возможность перед удалением данных выполнить соответствующую очистку памяти, то есть выполнить перезапись этих данных нулевыми байтами. Управление данными возможностями осуществляется с помощью соответствующих конфигурационных параметров в файле конфигурации qhb.conf (подробную информацию см. в подразделе Очистка памяти с повышенной безопасностью).

Конфигурация сервера

Существует много параметров конфигурации, которые влияют на поведение системы базы данных. В первом разделе этой главы мы расскажем, как взаимодействовать с этими параметрами. В последующих разделах подробно рассматривается каждый из них.

Содержание

Настройка параметров

Имена и значения параметров

Взаимодействие с параметрами через файл конфигурации

Взаимодействие с параметрами через SQL

Взаимодействие с параметрами через командную оболочку

Управление содержимым файла конфигурации

Расположение файлов

Подключения и аутентификация

Параметры подключения

Аутентификация

SSL

Потребление ресурсов

Память

Диск

Использование ресурсов ядра

Задержка очистки по стоимости

Фоновая запись

Асинхронное поведение

Журнал упреждающей записи

Параметры

Контрольные точки

Архивирование

Восстановление

Восстановление из архива

Целевая точка восстановления

Репликация

Передающие серверы

Главный сервер

Резервные серверы

Подписчики

Планирование запросов

Конфигурация метода планирования

Константы стоимости для планировщика

Параметры генетического оптимизатора запросов

Другие параметры планировщика

Регистрация ошибок и протоколирование

Куда протоколировать

Когда протоколировать

Что протоколировать

Использование вывода журнала в формате CSV

Использование вывода журнала в формате JSON

Заголовок процесса

Статистика времени выполнения

Накопительная статистика запросов и индексов

Мониторинг статистики

Автоматическая очистка

Параметры клиентских соединений по умолчанию

Поведение команд

Языковые стандарты и форматирование

Предварительная загрузка разделяемых библиотек

Другие параметры по умолчанию

Управление блокировками

Совместимость с разными версиями и платформами

Предыдущие версии QHB

Совместимость с разными платформами и клиентами

Обработка ошибок

Предустановленные параметры

Специализированные параметры

Параметры для разработчиков

Краткие аргументы

Параметры модулей и расширений, специфичных для QHB

Общие параметры

Параметры менеджера кеша дисковых блоков TARQ

Включение и управление алгоритмом вытеснения параметра HOLDMEM

Параметры конфигурации расширения Rbytea

2B: Поддержка решений 1С

Очистка памяти с повышенной безопасностью

Запуск планировщика, брокера очереди сообщений при старте СУБД

Автоматическая очистка журнала выполненных задач планировщика

Включение фоновых процессов целостности и безопасности

Настройка параметров

Имена и значения параметров

Имена всех параметров нечувствительны к регистру. Каждый параметр принимает значение одного из пяти типов: логический, строка, целое число, число с плавающей запятой или перечисление. От типа зависит синтаксис, применяемый для установки этого параметра:

Логический: Значения могут вводиться как on, off, true, false, yes, no, 1, 0 (все без учета регистра) или любым однозначным префиксом одного из них.

Строка: Обычно значение заключается в апострофы, при этом внутренние апострофы самого значения дублируются. Однако если значение является простым числом или идентификатором, апострофы обычно можно опустить. (Значения, совпадающие с каким-либо ключевым словом SQL, в некоторых контекстах все же требуют заключения в апострофы.)

Число (целое или с плавающей запятой): Числовые параметры можно указывать в обычных форматах, принятых для целого числа и числа с плавающей запятой; если параметр имеет целочисленный тип, дробные значения округляются до ближайшего целого числа. Кроме того, целочисленные параметры дополнительно принимают шестнадцатеричные (с префиксом 0x) и восьмеричные (с префиксом 0) значения, но эти форматы не могут быть дробными. Разделители разрядов использовать нельзя. Кавычки требуются только для шестнадцатеричного значения.

Число с единицей измерения: Некоторые числовые параметры имеют неявную единицу измерения, поскольку описывают количество памяти или времени. Этой единицей измерения могут быть байты, килобайты, блоки (обычно восемь килобайт), миллисекунды, секунды или минуты. При указании только числового значения для одного из таких параметров будет использована единица измерения, установленная для него по умолчанию, которую можно посмотреть в поле pg_settings.unit. Для удобства параметры можно задать, явно указав единицу измерения, например, '120 ms' для значения времени, которая будет преобразована в фактическую единицу измерения параметра. Обратите внимание, что для применения этой функции значение следует записывать в виде строки (в апострофах). Название единицы измерения чувствительно к регистру, и между ним и числовым значением можно ставить пробел.

Допустимые единицы памяти: B (байты), kB (килобайты), MB (мегабайты), GB (гигабайты) и TB (терабайты). Множитель для единиц памяти равен 1024, а не 1000.

Допустимые единицы времени: us (микросекунды), ms (миллисекунды), s (секунды), min (минуты), h (часы) и d (дни). Если с единицей измерения задается дробное значение, оно будет округлено до кратного следующей меньшей единицы, если таковая имеется. Например, 30.1 GB будут преобразованы в 30822 MB, а не в 32319628902 B. Если параметр имеет целочисленный тип, после любого преобразования единицы измерения значение окончательно округляется до целого.

Перечисление: Параметры перечислимого типа задаются так же, как и строковые параметры, но имеют ограниченный набор значений. Допустимые значения для такого параметра можно найти в поле pg_settings.enumvals. Значения перечислимых параметров нечувствительны к регистру.

Взаимодействие с параметрами через файл конфигурации

Самый основной способ установить эти параметры — отредактировать файл qhb.conf, который обычно хранится в каталоге данных. Его копия по умолчанию устанавливается при инициализации каталога кластера базы данных. Пример того, как может выглядеть этот файл:

# Это комментарий
log_connections = yes
log_destination = 'syslog'
search_path = '"$user", public'
shared_buffers = 128MB

В каждой строке указывается один параметр. Знак равенства между именем и значением необязателен. Пробелы не играют роли (за исключением значения параметра, заключенного в апострофы), а пустые строки игнорируются. Знаки решетки (#) обозначают оставшуюся часть строки как комментарий. Значения параметров, которые не являются простыми идентификаторами или числами, должны быть заключены в апострофы. Чтобы вставить апостроф в само значение параметра, удвойте его (желательно) или добавьте перед ним обратный слэш. Если файл содержит несколько определений одного и того же параметра, то все они, кроме последнего, игнорируются.

Параметры, установленные таким образом, предоставляют значения по умолчанию для данного кластера. Если не переопределять эти значения, они будут настройками, действующими в активных сеансах. В следующих разделах описываются способы, которыми администратор или пользователь могут переопределить эти значения по умолчанию.

Основной процесс сервера перечитывает файл конфигурации всякий раз, когда получает сигнал SIGHUP; этот сигнал легче всего отправить, запустив qhb_ctl reload в командной строке или вызвав функцию SQL pg_reload_conf(). Также основной процесс сервера передает этот сигнал всем запущенным в данный момент процессам сервера, так что существующие сеансы тоже принимают новые значения (это произойдет после того, как они завершат любую выполняемую в данный момент клиентскую команду). Как вариант, этот сигнал можно отправить напрямую одному из серверных процессов. Некоторые параметры можно установить только при запуске сервера; любые изменения их значений в файле конфигурации будут игнорироваться до перезапуска сервера. Неверные значения параметров в файле конфигурации при обработке SIGHUP тоже игнорируются (но протоколируются в журнале).

В дополнение к qhb.conf каталог данных QHB содержит файл qhb.auto.conf, который имеет тот же формат, что и qhb.conf, но предназначен для редактирования автоматически, а не вручную. Этот файл содержит параметры, настраиваемые командой ALTER SYSTEM. Он считывается одновременно с qhb.conf, и заданные в нем параметры действуют аналогичным образом. Параметры в qhb.auto.conf переопределяют параметры в qhb.conf.

Внешние средства тоже могут изменять qhb.auto.conf, но не рекомендуется делать это во время работы сервера, поскольку параллельное выполнение команды ALTER SYSTEM может перезаписать подобные изменения. Такие программы могут просто добавлять новые настройки параметров в конец файла или удалять дубликаты определений и/или комментарии (как делает ALTER SYSTEM).

Системное представление pg_file_settings может быть полезно для предварительной проверки изменений в файлах конфигурации или для диагностики проблем, если сигнал SIGHUP не дает желаемых результатов.

Взаимодействие с параметрами через SQL

QHB предоставляет три команды SQL для установки параметров конфигурации по умолчанию. Уже упомянутая команда ALTER SYSTEM предоставляет средства изменения глобальных значений по умолчанию, доступные для SQL; функционально она равнозначна редактированию qhb.conf. Кроме того, есть еще две команды, которые позволяют устанавливать значения по умолчанию на уровне базы данных или роли:

Команда ALTER DATABASE позволяет переопределять глобальные параметры на уровне базы данных.

Команда ALTER ROLE позволяет переопределять для конкретного пользователя как глобальные параметры, так и параметры на уровне базы данных.

Значения, установленные с помощью команд ALTER DATABASE и ALTER ROLE, применяются только при запуске нового сеанса базы данных. Они переопределяют значения, полученные из файлов конфигурации или командной строки сервера, и назначаются значениями по умолчанию до конца сеанса. Обратите внимание, что некоторые параметры нельзя изменить после запуска сервера, и поэтому их невозможно установить с помощью этих или перечисленных ниже команд.

После подключения клиента к базе данных он может воспользоваться двумя дополнительными командами SQL (и равнозначными им функциями), предоставляемыми QHB для взаимодействия с локальными параметрами конфигурации сеанса:

Команда SHOW позволяет проверить текущее значение всех параметров. Соответствующая функция SQL — current_setting(имя_параметра text) (см. подраздел Функции для параметров конфигурации).

Команда SET позволяет изменять текущее значение тех параметров, которые могут быть установлены локально для сеанса; это не влияет на другие сеансы. Соответствующая функция SQL — set_config(имя_параметра text, новое_значение text, локальный boolean) (см. подраздел Функции для параметров конфигурации).

Кроме того, для просмотра и изменения локальных значений сеанса можно воспользоваться системным представлением pg_settings:

Запрос этого представления аналогичен использованию команды SHOW ALL, но выдает более подробную информацию. Этот подход также более гибкий, так как позволяет задавать условия фильтрации или объединять результат с другими отношениями.

Использование UPDATE в этом представлении, в частности, обновление столбца setting, равнозначно выполнению команд SET. Например, команде

SET configuration_parameter TO DEFAULT;

равнозначен запрос:

UPDATE pg_settings SET setting = reset_val WHERE name = 'configuration_parameter';

Взаимодействие с параметрами через командную оболочку

Помимо установки глобальных значений по умолчанию или добавления переопределений на уровне базы данных или роли параметры QHB можно задавать с помощью средств оболочки. Через оболочку значения параметров принимают и сервер, и клиентская библиотека libpq.

Во время запуска сервера значения параметров можно передать команде qhb посредством параметра командной строки -c. Например:

qhb -c log_connections=yes -c log_destination='syslog'

Параметры, предоставленные таким образом, переопределяют те, что были заданы в qhb.conf или командой ALTER SYSTEM, поэтому их нельзя изменить глобально без перезапуска сервера.

При запуске клиентского сеанса через libpq настройку параметров можно провести с помощью переменной среды PGOPTIONS. Значения, установленные таким образом, назначаются значениями по умолчанию до конца сеанса, но не влияют на другие сеансы. По историческим причинам формат PGOPTIONS похож на тот, что используется при запуске команды qhb; в частности, здесь следует указывать флаг -c. Например:

env PGOPTIONS="-c geqo=off -c statement_timeout=5min" psql

Другие клиенты и библиотеки могут предоставлять собственные механизмы (через оболочку или иным образом), позволяющие пользователю изменять параметры сеанса без непосредственного выполнения команд SQL.

Управление содержимым файла конфигурации

В QHB имеется несколько функций для разбиения сложных файлов qhb.conf на вложенные файлы. Эти функции особенно полезны при управлении несколькими серверами со связанными, но не идентичными конфигурациями.

Помимо значений отдельных параметров файл qhb.conf может содержать директивы включения, благодаря которым другой файл будет прочитан и обработан, как если бы он был вставлен в данное место файла конфигурации. Эта особенность позволяет разделить файл конфигурации на физически отдельные части. Директивы включения записываются просто:

include 'имя_файла'

Если в имени_файла не задается абсолютный путь к нему, то путь рассматривается относительно каталога, содержащего включающий файл конфигурации. Включения файлов могут быть вложенными.

Существует также директива include_if_exists, которая действует так же, как include, за исключением случаев, когда указанный файл не существует или не может быть прочитан. Обычная директива include будет считать это условием возникновения ошибки, но include_if_exists просто выводит сообщение и продолжает обрабатывать предоставленный файл конфигурации.

Файл qhb.conf также может содержать директивы include_dir, где можно задать весь каталог файлов конфигурации, который нужно включить. Они записываются так:

include_dir 'каталог'

Если в параметре каталог не задается абсолютный путь к нему, то путь рассматривается относительно каталога, содержащего включающий файл конфигурации. В указанном каталоге будут включены только файлы, не являющиеся каталогами, с именами, заканчивающимися суффиксом .conf. Имена файлов, начинающиеся с символа «.», также игнорируется во избежание ошибок, поскольку такие файлы являются скрытыми на некоторых платформах. Множество файлов во включаемом каталоге обрабатывается в порядке их имен (в соответствии с правилами локали C, т. е. цифры идут перед буквами, а заглавные буквы — перед строчными).

Включение файлов или каталогов можно использовать для логического разделения конфигурации базы данных на части, вместо того чтобы вести один большой файл qhb.conf. Рассмотрим компанию с двумя серверами баз данных, каждый с разным объемом памяти. Скорее всего, у их конфигураций есть общие элементы, которые будут использоваться, например, для ведения журнала. Но параметры, связанные с памятью, у них будут различаться. Также у каждого сервера могут быть и специфические параметры. Один из способов справиться с этой ситуацией — разбить изменения стандартной конфигурации вашей сети на три файла. Для их включения требуется добавить в конец файла qhb.conf следующую запись:

include 'shared.conf'
include 'memory.conf'
include 'server.conf'

Параметры, общие для всех серверов, будут помещаться в shared.conf. Все серверы с определенным объемом памяти могут использовать общий файл memory.conf; один файл для всех серверов с 8 ГБ ОЗУ, другой — для серверов с 16 ГБ. И, наконец, server.conf может содержать действительно специфические для каждого сервера параметры конфигурации.

Другая возможность — создать каталог с файлами конфигурации и поместить эту информацию в файлы. Например, можно указать каталог conf.d в конце qhb.conf:

include_dir 'conf.d'

Затем можно назвать файлы в каталоге conf.d следующим образом:

00shared.conf
01memory.conf
02server.conf

Это соглашение об именах устанавливает четкий порядок загрузки этих файлов. Это важно, потому что будет использоваться только последнее значение параметра, найденное сервером при чтении файлов конфигурации. В данном примере значение, установленное в conf.d/02server.conf, переопределит значение, заданное в conf.d/01memory.conf.

Вместо этого можно использовать для описательного именования файлов следующий подход:

00shared.conf
01memory-8GB.conf
02server-foo.conf

Такая расстановка дает уникальное имя для каждого варианта файла конфигурации. Это может помочь устранить неоднозначность, когда конфигурации нескольких серверов хранятся в одном месте, например, в репозитории системы управления версиями. (Хранение файлов конфигурации базы данных в системе управления версиями — это еще одна удачная методика, которую стоит рассмотреть.)

Расположение файлов

В дополнение к уже упомянутому файлу qhb.conf QHB использует еще два редактируемых вручную файла конфигурации, которые управляют аутентификацией клиентов (их использование рассматривается в главе Аутентификация клиентского приложения). По умолчанию все три файла конфигурации хранятся в каталоге данных кластера баз данных. Параметры, описанные в этом разделе, позволяют размещать файлы конфигурации в другом месте. (Это может упростить управление. В частности, при хранении файлов конфигурации отдельно зачастую проще обеспечить их правильное резервное копирование.)

data_directory (string)

Задает каталог, в котором будут храниться данные. Этот параметр можно установить только при запуске сервера.

config_file (string)

Задает основной файл конфигурации сервера (обычно он называется qhb.conf). Этот параметр можно установить только в командной строке qhb.

hba_file (string)

Задает файл конфигурации для проверки подлинности по узлу (обычно он называется qhb_hba.conf). Этот параметр можно установить только при запуске сервера.

ident_file (string)

Задает файл конфигурации для сопоставлений имен пользователей (обычно он называется qhb_ident.conf). Этот параметр можно задать только при запуске сервера. См. также раздел Файл сопоставлений имен пользователей.

external_pid_file (string)

Задает имя дополнительного файла с идентификатором процесса (PID), который сервер должен создать для использования программами администрирования сервера. Этот параметр можно установить только при запуске сервера.

При установке по умолчанию ни один из вышеперечисленных параметров не устанавливается явно. Вместо этого каталог данных указывается параметром командной строки -D или переменной среды PGDATA, и все файлы конфигурации загружаются из этого каталога.

Если требуется сохранить файлы конфигурации не в каталоге данных, параметр -D командной строки qhb или переменная среды PGDATA должны указывать на каталог, содержащий файлы конфигурации, а в qhb.conf (или в командной строке) должен быть задан параметр data_directory, чтобы показать, где на самом деле находится каталог данных. Обратите внимание, что data_directory переопределяет путь, заданный в -D и PGDATA как расположение каталога данных, но не расположение файлов конфигурации.

При желании можно задать имена файлов конфигурации и их пути по отдельности, используя параметры config_file, hba_file и/или ident_file. Параметр config_file можно указывать только в командной строке qhb, но остальные параметры можно задать в основном файле конфигурации. Если все три параметра и data_directory установлены явно, указывать -D или PGDATA необязательно.

При установке любого из этих параметров относительный путь будет рассматриваться от каталога, в котором запускается qhb.

Подключения и аутентификация

Параметры подключения

listen_addresses (string)

Задает адреса TCP/IP, по которым сервер должен принимать подключения от клиентских приложений. Значение имеет форму разделенного запятыми списка имен хостов и/или числовых IP-адресов. Специальная запись * соответствует всем доступным IP-интерфейсам. Запись 0.0.0.0 позволяет прослушивать все адреса IPv4, а :: — все адреса IPv6. Если список пуст, сервер вообще не прослушивает ни один IP-интерфейс, и в этом случае для подключения к нему можно использовать только сокеты домена Unix. Значением по умолчанию является localhost, что позволяет устанавливать только локальные «петлевые» подключения по TCP/IP.

Хотя аутентификация клиента (глава Аутентификация клиентского приложения) позволяет скрупулезно управлять доступом к серверу, listen_addresses определяет, через какие именно интерфейсы будут приниматься соединения, что может помочь предотвратить злонамеренные попытки подключения через незащищенные сетевые интерфейсы. Этот параметр можно задать только при запуске сервера.

port (integer)

TCP-порт, который прослушивает сервер; по умолчанию это 5432. Обратите внимание, что этот номер порта используется для всех IP-адресов, через которые сервер принимает подключения. Этот параметр можно задать только при запуске сервера.

max_connections (integer)

Определяет максимальное количество одновременных подключений к серверу баз данных. По умолчанию обычно это 100 подключений, но может быть и меньше, если настройки ядра не будут это поддерживать (что определяется в процессе qhb_bootstrap (или initdb)). Этот параметр можно задать только при запуске сервера.

При запуске резервного сервера следует установить для этого параметра значение большее или равное значению на главном сервере. В противном случае на резервном сервере не будут разрешены запросы.

superuser_reserved_connections (integer)

Определяет количество «слотов» подключений, которые зарезервированы для суперпользователей QHB. В большинстве случаев подключения max_connections могут быть активными одновременно. Всякий раз, когда число активных одновременных подключений больше или равно max_connections минус superuser_reserved_connections, новые подключения будут приниматься только от суперпользователей, а подключения для репликации приниматься не будут.

По умолчанию резервируется три соединения. Это значение должно быть меньше значения max_connections. Этот параметр можно задать только при запуске сервера.

unix_socket_directories (string)

Задает каталог сокетов домена Unix, через которые сервер должен принимать подключения от клиентских приложений. Можно создать несколько сокетов, перечислив несколько каталогов, разделенных запятыми. Пробелы между записями игнорируются; если в имени каталога требуются пробелы или запятые, заключите его в кавычки. Пустое значение указывает, что сервер не принимает подключения через сокеты домена Unix, и в этом случае к нему можно подключиться только по TCP/IP.

Значение, начинающееся с @, указывает, что должен быть создан сокет домена Unix в абстрактном пространстве имен (в настоящее время поддерживается в Linux). В таком случае это значение задает не «каталог», а префикс, из которого вычисляется фактическое имя сокета таким же образом, как и для пространства имен файловой системы. Хотя префикс имени абстрактного сокета можно выбрать произвольно, поскольку он не задает расположение в файловой системе, все равно принято использовать значения как в файловой системе, например @/tmp.

Значением по умолчанию обычно является /tmp, но его можно изменить во время сборки. Этот параметр можно задать только при запуске сервера.

В дополнение к самому файлу сокета, который называется .s.PGSQL.nnnn, где nnnn — это номер порта сервера, в каждом из каталогов unix_socket_directories будет создан обычный файл с именем .s.PGSQL.nnnn.lock. Эти файлы ни в коем случае нельзя удалять вручную. Для сокетов в абстрактном пространстве имен файл блокировки (.lock) не создается.

unix_socket_group (string)

Задает группу-владельца сокета домена Unix. (Пользователем-владельцем сокетов всегда является пользователь, запускающий сервер). В сочетании с параметром unix_socket_permissions этот параметр можно использовать в качестве дополнительного механизма управления доступом для подключений через сокеты домена Unix. По умолчанию это пустая строка, что делает владельцем группу по умолчанию пользователя сервера. Этот параметр можно задать только при запуске сервера.

Сокеты в абстрактном пространстве имен не имеют владельца, поэтому в их случае этот параметр игнорируется.

unix_socket_permissions (integer)

Задает права доступа к сокетам домена Unix. Для этих сокетов применяется обычный набор разрешений файловой системы Unix. Ожидается, что значение параметра будет иметь числовой вид, указанный в формате, принимаемом системными функциями chmod и umask. (Для использования обычного восьмеричного формата число должно начинаться с 0 (нуля).)

Разрешения по умолчанию — 0777, при которых подключаться к сокету могут все. Разумными альтернативами являются 0770 (доступ имеет только пользователь и группа, см. также unix_socket_group) и 0700 (доступ имеет только пользователь). (Обратите внимание, что для сокетов домена Unix имеет значение только право на запись, поэтому нет смысла устанавливать или отзывать права на чтение или выполнение.)

Этот механизм управления доступом не зависит от механизма, описанного в главе Аутентификация клиентского приложения.

Этот параметр можно задать только при запуске сервера.

Этот параметр не применим к системам, которые полностью игнорируют разрешения для сокетов, особенно к Solaris (а именно, к Solaris 10). Там схожего эффекта можно достичь, задав в параметре unix_socket_directories каталог, доступ к которому ограничен желаемой аудиторией.

Сокеты в абстрактном пространстве имен не имеют прав доступа к файлам, поэтому в их случае этот параметр тоже игнорируется.

bonjour (boolean)

Включает оповещение о существование сервера посредством Bonjour. По умолчанию выключен. Этот параметр можно задать только при запуске сервера.

bonjour_name (string)

Задает имя службы в среде Bonjour. Если для этого параметра задана пустая строка '' (это значение по умолчанию), то в качестве имени службы используется имя компьютера. Этот параметр игнорируется, если сервер был скомпилирован без поддержки Bonjour. Этот параметр можно задать только при запуске сервера.

tcp_keepalives_idle (integer)

Задает время отсутствия активности в сети, по истечении которого операционная система должна отправлять клиенту сигнал TCP о поддержании активного состояния. Если это значение указано без единиц измерения, оно считается заданным в секундах. При значении 0 (по умолчанию) выбирается значение, принятое по умолчанию в операционной системе. Этот параметр поддерживается только в системах, поддерживающих TCP_KEEPIDLE или равнозначный ему параметр сокета; в других системах он должен быть равен нулю. В сеансах, подключенных через сокеты Unix-домена, этот параметр игнорируется и всегда считается равным нулю.

tcp_keepalives_interval (integer)

Задает время, по истечении которого следует повторно передать сигнал TCP о поддержании активного состояния, если получение предыдущего сигнала не было подтверждено клиентом. Если это значение указано без единиц измерения, оно считается заданным в секундах. При значении 0 (по умолчанию) выбирается значение, принятое по умолчанию в операционной системе. Этот параметр поддерживается только в системах, поддерживающих TCP_KEEPINTVL или равнозначный ему параметр сокета; в других системах он должен быть равен нулю. В сеансах, подключенных через сокеты домена Unix, этот параметр игнорируется и всегда считается равным нулю.

tcp_keepalives_count (integer)

Задает количество сигналов TCP о поддержании активного состояния, которые могут быть потеряны до того, как соединение сервера с клиентом будет считаться прерванным. При значении 0 (по умолчанию) выбирается значение, принятое по умолчанию в операционной системе. Этот параметр поддерживается только в системах, поддерживающих TCP_KEEPCNT или равнозначный ему параметр сокета; в других системах он должен быть равен нулю. В сеансах, подключенных через сокеты домена Unix, этот параметр игнорируется и всегда считается равным нулю.

tcp_user_timeout (integer)

Задает время, в течение которого передаваемые данные могут оставаться неподтвержденными, прежде чем TCP-соединение будет принудительно закрыто. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. При значении 0 (по умолчанию) выбирается значение, принятое по умолчанию в операционной системе. Этот параметр поддерживается только в системах, поддерживающих TCP_USER_TIMEOUT или равнозначный ему параметр сокета; в других системах он должен быть равен нулю. В сеансах, подключенных через сокеты домена Unix, этот параметр игнорируется и всегда считается равным нулю.

client_connection_check_interval (integer)

Устанавливает интервал времени между необязательными проверками соединения клиента с сервером при выполнении запроса. Эта проверка осуществляется путем упорядоченного опроса сокета и позволяет прерывать длительные запросы раньше, если ядро сообщает, что соединение закрыто.

Этот параметр зависит от событий ядра, имеющихся в Linux, macOS, illumos и операционных системах семейства BSD, и в настоящее время недоступен в других системах.

Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. При значении 0 (по умолчанию) проверка соединения выключена. Без этих проверок сервер обнаружит потерю соединения только при следующем обращении к сокету, когда будет ожидать, получать или передавать данные.

Чтобы само ядро могло надежно и в течение известного промежутка времени обнаружить потерянные TCP-соединения во всех сценариях, включая сбой сети, также может понадобиться скорректировать параметры сохранения TCP-соединения в операционной системе или параметры QHB tcp_keepalives_idle, tcp_keepalives_interval и tcp_keepalives_count.

Аутентификация

authentication_timeout (integer)

Максимальное время, за которое должна завершиться аутентификация клиента. Если потенциальный клиент не завершил протокол аутентификации за это время, сервер закрывает соединение. Это не дает зависшим при подключении клиентам занимать соединение неограниченно долго. Если это значение указано без единиц измерения, оно считается заданным в секундах. Значение по умолчанию составляет одну минуту (1m). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

password_encryption (enum)

Если в CREATE ROLE или ALTER ROLE указан пароль, этот параметр определяет алгоритм, которым тот будет зашифрован. Возможные значения: scram-sha-256, при котором пароль зашифруется алгоритмом SCRAM-SHA-256, и md5, при котором пароль сохраняется в виде хеша MD5. Значение по умолчанию — scram-sha-256.

Обратите внимание, что старые клиенты могут не поддерживать механизм аутентификации SCRAM и, следовательно, не будут работать с паролями, зашифрованными алгоритмом SCRAM-SHA-256. Подробную информацию см. в разделе Аутентификация по паролю.

krb_server_keyfile (string)

Задает путь к файлу ключей Kerberos для данного сервера. Значение по умолчанию — FILE:/usr/local/qhb/etc/krb5.keytab (где в качестве каталога подставляется значение, указанное в параметре sysconfdir при сборке; чтобы его узнать, выполните pg_config --sysconfdir). Если в качестве значения задается пустая строка, то используется значение по умолчанию, зависящее от системы. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Подробную информацию см. в разделе Аутентификация GSSAPI.

krb_caseins_users (boolean)

Определяет, должны ли имена пользователей GSSAPI обрабатываться без учета регистра. По умолчанию установлено значение off (регистр учитывается). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

db_user_namespace (boolean)

Этот параметр позволяет относить имена пользователей к базам данных. По умолчанию он выключен. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Если этот параметр включен, имена пользователей следует создавать в виде имя_пользователя@база_данных. Когда подключающийся клиент передает имя_пользователя, к нему добавляются @ и имя базы данных, и сервер ищет пользователя по этому специфическому для базы данных имени. Обратите внимание, что при создании в среде SQL пользователей с именами, содержащими @, потребуется заключать эти имена в кавычки.

Если этот параметр включен, вы все равно можете создавать обычных глобальных пользователей. Для этого достаточно добавить @ при указании имени пользователя в клиенте, например joe@. Символ @ будет удален раньше, чем сервер найдет имя пользователя.

Параметр db_user_namespace вызывает расхождения в представлении имени пользователя на стороне клиента и сервера. Проверка подлинности всегда выполняется с именем на стороне сервера, поэтому методы аутентификации должны настраиваться по серверному имени пользователя, а не клиентскому. Поскольку метод аутентификации md5 использует имя пользователя в качестве соли как на стороне клиента, так и на стороне сервера, при включенном параметре db_user_namespace этот метод использовать нельзя.

Примечание
Эта функциональность предполагается как временная мера, пока не будет найдено полноценное решение. Тогда этот параметр будет удален.

SSL

Дополнительную информацию о настройке SSL см. в разделе Защита TCP/IP-соединений посредством SSL. Параметры конфигурации для управления шифрованием передачи по протоколам TLS называются ssl по историческим причинам, несмотря на то, что протокол SSL считается устаревшим. В этом контексте SSL используется взаимозаменяемо с TLS.

ssl (boolean)

Разрешает подключения SSL. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Значение по умолчанию — off (выключен).

ssl_ca_file (string)

Задает имя файла, содержащего сертификаты центров сертификации (certificate authority, CA) для SSL-сервера. Относительные пути рассматриваются от каталога данных. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. По умолчанию этот параметр пуст, то есть файл CA не загружается, и проверка клиентских сертификатов не выполняется.

ssl_cert_file (string)

Задает имя файла, содержащего сертификат SSL-сервера. Относительные пути рассматриваются от каталога данных. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Значение по умолчанию — server.crt.

ssl_crl_file (string)

Задает имя файла, содержащего список отозванных сертификатов (Certificate Revocation List, CRL) для SSL-клиента. Относительные пути рассматриваются от каталога данных. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. По умолчанию этот параметр пуст, то есть файл CRL не используется (если только не установлен параметр ssl_crl_dir).

ssl_crl_dir (string)

Задает имя каталога, содержащего список отозванных сертификатов (CRL) для SSL-клиента. Относительные пути рассматриваются от каталога данных. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. По умолчанию этот параметр пуст, то есть файл CRL не используется (если только не установлен параметр ssl_crl_file).

Этот каталог необходимо подготовить с помощью команды OpenSSL openssl rehash или c_rehash. Более подробную информацию см. в документации OpenSSL.

При использовании этого параметра CRL в указанном каталоге загружаются при необходимости во время подключения. В каталог можно добавлять новые CRL, которые сразу же будут использоваться. Это отличает данный параметр от ssl_crl_file, с которым CRL загружается в файл при запуске сервера или перезагрузке конфигурации. Оба эти параметра можно применять вместе.

ssl_key_file (string)

Задает имя файла, содержащего закрытый ключ SSL-сервера. Относительные пути рассматриваются от каталога данных. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Значение по умолчанию — server.key.

ssl_ciphers (string)

Задает список наборов шифров SSL, которые разрешено использовать для SSL- соединений. Синтаксис этого параметра и список поддерживаемых значений можно найти на странице руководства по шифрам в пакете OpenSSL. Этот параметр действует только на подключения TLS версии 1.2 и ниже. Для подключений TLS версии 1.3 параметры, управляющие выбором шифров, в настоящее время отсутствуют. Значение по умолчанию — HIGH:MEDIUM:+3DES:!aNULL. Обычно это разумный выбор, если у вас нет особых требований к безопасности.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Объяснение значения по умолчанию:

HIGH
Наборы шифров, в которых используются шифры из группы HIGH (например AES, Camellia, 3DES)

MEDIUM
Наборы шифров, в которых используются шифры из группы MEDIUM (например RC4, SEED)

+3DES
Порядок OpenSSL по умолчанию для HIGH проблематичен, потому что он ставит 3DES выше, чем AES128. Это неправильно, поскольку 3DES менее безопасен, чем AES128, и работает гораздо медленнее. Добавление +3DES меняет этот порядок, размещая 3DES после всех других шифров групп HIGH и MEDIUM.

!aNULL
Выключает наборы анонимных шифров, не требующие аутентификации. Такие наборы уязвимы для атак через незаконного посредника и поэтому не должны использоваться.

Доступные наборы шифров и их свойства зависят от версии OpenSSL. Чтобы увидеть фактическую информацию о них для текущей установленной версии OpenSSL, выполните команду openssl ciphers -v 'HIGH:MEDIUM:+3DES:!aNULL'. Обратите внимание, что этот список фильтруется во время выполнения, в зависимости от типа ключа сервера.

ssl_prefer_server_ciphers (boolean)

Определяет, следует ли отдавать предпочтение шифрам SSL-сервера, а не клиента. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Значение по умолчанию — on (включен).

Обычно лучше использовать шифры сервера, так как в его конфигурации менее вероятны ошибки.

ssl_ecdh_curve (string)

Задает имя кривой для использования при обмене ключами ECDH. Эту кривую должны поддерживать все подключающиеся клиенты. Это необязательно должна быть та же кривая, с которой был получен ключ эллиптической кривой сервера. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Значение по умолчанию — prime256v1.

Имена наиболее распространенных кривых в OpenSSL: prime256v1 (NIST P-256), secp384r1 (NIST P-384), secp521r1 (NIST P-521). Полный список доступных кривых можно получить с помощью команды openssl ecparam -list_curves. Однако не все из них пригодны для TLS.

ssl_min_protocol_version (enum)

Устанавливает минимальную версию протокола SSL/TLS, которую можно использовать. Допустимые на данный момент значения: TLSv1, TLSv1.1, TLSv1.2, TLSv1.3. Старые версии библиотеки OpenSSL поддерживают не все значения; при выборе неподдерживаемой версии возникнет ошибка. Версии протокола до TLS 1.0, а именно SSL v.2 и v.3, в любом случае не работают.

Значение по умолчанию — TLSv1.2, что соответствует передовым отраслевым стандартам на момент написания данной документации.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

ssl_max_protocol_version (enum)

Задает максимальную версию протокола SSL/TLS, которую можно использовать. Допустимые значения такие же, как у параметра ssl_min_protocol_version, плюс пустая строка, означающая, что допускается любая версия протокола. По умолчанию разрешена любая версия. Установка максимальной версии протокола полезна прежде всего для тестирования или в случаях, когда у некоторых компонентов возникают проблемы при работе с более новым протоколом.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

ssl_dh_params_file (string)

Задает имя файла, содержащего параметры алгоритма Диффи-Хеллмана, используемые для так называемого эфемерного семейства DH-шифров SSL. По умолчанию этот параметр пуст, то есть используются параметры DH, скомпилированные по умолчанию. Применение нестандартных параметров DH повышает защиту, если злоумышленнику удается взломать хорошо известные скомпилированные параметры DH. Собственный файл с параметрами DH можно создать с помощью команды openssl dhparam -out dhparams.pem 2048.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

ssl_passphrase_command (string)

Задает внешнюю команду, которая будет вызываться, когда потребуется получить парольную фразу для расшифровки SSL-файла, например закрытого ключа. По умолчанию этот параметр пуст, то есть используется встроенный механизм запроса.

Эта команда должна вывести парольную фразу на устройство стандартного вывода и завершиться с кодом 0. В значении параметра %p заменяется строкой приглашения. (Напишите %%, чтобы вывести литерал %). Обратите внимание, что строка приглашения, вероятно, будет содержать пробелы, поэтому потребуется заключить ее в кавычки. Если в конце имеется один перевод строки, он будет удален при выводе.

На самом деле команда не обязана запрашивать у пользователя парольную фразу. Она может прочитать ее из файла, получить из системной связки ключей или другими подобными способами. Ответственность за надлежащую безопасность выбранного механизма несет пользователь.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

ssl_passphrase_command_supports_reload (boolean)

Этот параметр определяет, будет ли заданная параметром ssl_passphrase_command команда запроса пароля также вызываться при перезагрузке конфигурации, если для файла ключа нужна парольная фраза. Если этот параметр выключен (по умолчанию), то ssl_passphrase_command будет игнорироваться при перезагрузке, и конфигурация SSL не будет перезагружаться, если требуется парольная фраза. Этот параметр подходит для команды, которой для ввода пароля требуется терминал TTY, который может быть недоступен во время работы сервера. Включение этого параметра может быть целесообразно, если, например, пароль считывается из файла.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Потребление ресурсов

Память

shared_buffers (integer)

Задает объем памяти, который будет использовать сервер баз данных для буферов разделяемой памяти. Значение по умолчанию обычно составляет 128 мегабайт (128MB), но может быть и меньше, если параметры ядра не будут его поддерживать (это определяется в процессе qhb_bootstrap (или initdb)). Значение этого параметра не должно быть меньше 128 килобайт. Однако для поддержания хорошей производительности обычно требуются значения гораздо выше минимальных. Если это значение указано без единиц измерения, оно считается заданным в блоках, размер которых равен BLCKSZ байт (обычно это 8 КБ). (Значения BLCKSZ, отличные от принятых по умолчанию, изменяют минимально допустимое значение.) Этот параметр можно задать только при запуске сервера.

Если у вас есть выделенный сервер баз данных с объемом ОЗУ 1 ГБ или более, разумное начальное значение для shared_buffers составляет 25% от объема памяти в вашей системе. Существуют варианты рабочей нагрузки, при которых эффективны еще большие значения shared_buffers, но поскольку QHB также использует и кэш операционной системы, выделение для shared_buffers более 40% ОЗУ вряд ли повысит эффективность. При увеличении значения shared_buffers обычно требуется соответствующее увеличение max_wal_size, чтобы растянуть процесс записи большого объема новых или измененных данных на более длительный период времени.

В системах с объемом ОЗУ меньше 1 ГБ целесообразно установить меньший процент ОЗУ, чтобы оставить достаточно места для операционной системы.

huge_pages (enum)

Определяет, будут ли запрашиваться огромные страницы из основной области разделяемой памяти. Допустимые значения: try (по умолчанию), on и off. Если в параметре huge_pages установлено try, сервер будет пытаться запросить огромные страницы, но в случае сбоя вернется к поведению по умолчанию. При значении on, если получить огромные страницы не удастся, сервер не запустится. При значении off огромные страницы запрашиваться не будут.

В настоящее время этот параметр поддерживается только в Linux, и только когда параметр shared_memory_type равен mmap (это значение по умолчанию). Во всех других системах значение try игнорируется.

Использование огромных страниц приводит к уменьшению таблиц страниц и сокращению затрат времени ЦП на управление памятью, что повышает производительность. Более подробную информацию об использовании огромных страниц в Linux см. в подразделе Огромные страницы в Linux.

Обратите внимание, что этот параметр влияет только на основную область разделяемой памяти. Операционные системы, такие как Linux, FreeBSD и Illumos, также могут автоматически использовать огромные страницы (также называемые «суперстраницами» или «большими» страницами) для обычного выделения памяти без явного запроса со стороны QHB. В Linux это называется «прозрачными огромными страницами» (Transparent Huge Pages, ТНР). Известно, что эта функция может вызвать снижение производительности QHB для некоторых пользователей в некоторых версиях Linux, поэтому использовать ее в настоящее время не рекомендуется (в отличие от явного использования huge_pages).

huge_page_size (integer)

Определяет размер огромных страниц, когда их использование включено параметром huge_pages. Значение по умолчанию — ноль (0). При таком значении будет использоваться стандартный системный размер огромных страниц. Этот параметр можно установить только при запуске сервера.

Некоторые наиболее распространенные размеры страниц на современных 64-битных серверных архитектурах: 2MB и 1GB (Intel и AMD), 16MB и 16GB (IBM POWER), и 64kB, 2MB, 32MB и 1GB (ARM). Более подробную информацию об их использовании и обслуживании см. в подразделе Огромные страницы в Linux.

В настоящее время отличные от стандартных значения поддерживаются только в Linux.

temp_buffers (integer)

Задает максимальный объем памяти, выделяемый для временных буферов в каждом сеансе базы данных. Это существующие исключительно в рамках сеанса буферы, используемые только для доступа к временным таблицам. Если это значение указано без единиц измерения, оно считается заданным в блоках, размер которых равен BLCKSZ байт (обычно это 8 КБ). По умолчанию используется восемь мегабайт (8MB). (Если BLCKSZ не равен 8 КБ, значение по умолчанию масштабируется пропорционально ему). Этот параметр можно менять в отдельном сеансе, но только до первого обращения к временным таблицам; последующие попытки изменить его значение не будут влиять на этот сеанс.

Сеанс будет выделять временные буферы по мере необходимости до предела, заданного temp_buffers. При установке большого значения в сеансах, которым на самом деле не нужно много временных буферов, память расходуется только на дескриптор буфера, который занимает 64 байта, за приращение в temp_buffers. Однако если буфер действительно используется, он будет дополнительно занимать 8192 байта (или, в общем случае, BLCKSZ байт).

max_prepared_transactions (integer)

Задает максимальное количество транзакций, которые могут одновременно находиться в «подготовленном» состоянии (см. PREPARE TRANSACTION). Установка для этого параметра значения ноль (по умолчанию) выключает функцию подготовленных транзакций. Этот параметр можно задать только при запуске сервера.

Если вы не планируете использовать подготовленные транзакции, следует установить в этом параметре ноль, чтобы предотвратить случайное создание подготовленных транзакций. Если же вы используете подготовленные транзакции, имеет смысл, чтобы max_prepared_transactions был не меньше, чем max_connections, чтобы можно было подготовить транзакцию в каждом сеансе.

При запуске резервного сервера следует установить для этого параметра значение большее или равное значению на главном сервере. В противном случае на резервном сервере не будут разрешены запросы.

work_mem (integer)

Задает максимальный объем памяти, который будет использоваться в операциях при обработке запроса (например для сортировки или хеш-таблиц) перед записью во временные файлы на диске. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. Значение по умолчанию составляет четыре мегабайта (4MB). Обратите внимание, что в сложном запросе могут параллельно выполняться несколько операций сортировки или хеширования, причем указанный в этом значении объем памяти может использоваться в каждой операции, прежде чем та начнет записывать данные во временные файлы. Кроме того, такие операции могут выполняться одновременно в нескольких запущенных сеансах. Следовательно, разделяемая используемая память может многократно превышать значение параметра work_mem; это необходимо учитывать при выборе значения. Операции сортировки применяются для ORDER BY, DISTINCT и объединений слиянием. Хеш-таблицы применяются в хеш-соединениях, агрегировании по хешу и обработке подзапросов IN с использованием хеша.

Операции на основе хеширования в целом более требовательны к доступности памяти, чем равнозначные им операции сортировки. Объем памяти, доступный для хеш-таблиц, вычисляется умножением work_mem на hash_mem_multiplier. Из-за этого объем памяти, используемый для операций основе хеширования, может превышать обычный базовый объем work_mem.

hash_mem_multiplier (floating point)

Используется для вычисления максимального объема памяти, который могут задействовать операции на основе хеширования. Итоговый предел определяется умножением work_mem на hash_mem_multiplier. Значение по умолчанию равно 2.0, из-за чего для операций на основе хеширования используется объем памяти, в два раза превышающий обычный базовый объем work_mem.

Увеличивать значение hash_mem_multiplier имеет смысл в средах, где постоянно происходит вытеснение данных на диск при операциях запросов, особенно когда простое увеличение work_mem приводит к дефициту памяти (обычно дефицит памяти принимает форму периодически возникающих ошибок «нехватка памяти»). Значение по умолчанию 2.0 зачастую эффективно при смешанной нагрузке. Более высокие значения в диапазоне 2.0–8.0 или выше эффективны в средах, где work_mem уже был увеличен до 40 МБ или более.

maintenance_work_mem (integer)

Задает максимальный объем памяти, который будет использоваться операциями обслуживания, такими как VACUUM, CREATE INDEX и ALTER TABLE ADD FOREIGN KEY. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. Значение по умолчанию составляет 64 мегабайта (64MB). Поскольку в один момент времени сеансом базы данных может быть выполнена только одна из этих операций, и обычно они не выполняются параллельно, можно без опасений сделать это значение значительно выше, чем work_mem. Увеличение этого параметра может повысить производительность операций очистки и восстановления дампов базы данных.

Обратите внимание, что при запуске автоочистки этот объем памяти может быть выделен до autovacuum_max_workers раз, поэтому постарайтесь не делать значение по умолчанию слишком большим. Возможно, лучше будет управлять выделяемым для автоочистки объемом памяти отдельно, с помощью параметра autovacuum_work_mem.

Учтите, что для сбора идентификаторов неиспользуемых кортежей команда VACUUM способна использовать не более 1GB памяти.

autovacuum_work_mem (integer)

Задает максимальный объем памяти, который будет использоваться каждым рабочим процессом автовакуума. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. По умолчанию используется значение -1, указывающее, что вместо этого параметра следует использовать значение maintenance_work_mem. Этот параметр не влияет на поведение команды VACUUM при ее запуске в других контекстах. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Для сбора идентификаторов неиспользуемых кортежей процесс автовакуума способен использовать не более 1GB памяти, поэтому установка в autovacuum_work_mem большего значения не повлияет на число неиспользуемых кортежей, которое автоочистка может собрать при сканировании таблицы.

logical_decoding_work_mem (integer)

Задает максимальный объем памяти, который будет использован для логического декодирования, прежде чем некоторые декодированные изменения будут записаны на локальный диск. Это ограничивает объем памяти, используемой подключениями потоковой логической репликации. Значение по умолчанию составляет 64 мегабайта (64MB). Поскольку каждое подключение репликации использует всего один буфер заданного размера, а к установке обычно не производится одновременно много таких подключений (это число ограничено значением max_wal_senders), значение этого параметра можно без опасений сделать намного больше, чем work_mem, тем самым снизив объем записываемых на диск декодированных изменений.

max_stack_depth (integer)

Задает максимальную безопасную глубину стека исполнения сервера. Идеальным значением для этого параметра является фактический предел размера стека, установленный ядром (который задается командой ulimit -s или ее локальным эквивалентом), за вычетом запаса прочности примерно в мегабайт. Запас прочности необходим, потому что глубина стека проверяется не в каждой подпрограмме на сервере, а только в ключевых потенциально рекурсивных подпрограммах. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. Значение по умолчанию составляет два мегабайта (2MB), что дает небольшой запас и вряд ли приведет к сбою. Однако этого может быть недостаточно для выполнения сложных функций. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Если установить max_stack_depth выше фактического лимита ядра, то функция с неограниченной рекурсией может вызвать сбой отдельного процесса сервера. На платформах, где QHB может определить предел, установленный ядром, сервер не допустит, чтобы для этой переменной было задано небезопасное значение. Однако не все платформы предоставляют подобную информацию, поэтому при выборе значения рекомендуется соблюдать осторожность.

shared_memory_type (enum)

Определяет реализацию разделяемой памяти, которую сервер должен использовать при работе с основной областью разделяемой памяти, содержащей разделяемые буферы QHB и другие разделяемые данные. Возможные значения: mmap (для выделения анонимных блоков разделяемой памяти с помощью mmap) и sysv (для выделения разделяемой памяти System V с помощью shmget). Не все варианты поддерживаются на разных платформах; первый из поддерживаемых данной платформой вариантов становится для нее реализацией по умолчанию. Использовать sysv, который нигде не выбирается по умолчанию, в принципе не рекомендуется, потому что при этом для выделения большого объема памяти (см. подраздел Разделяемая память и семафоры) обычно требуются нестандартные настройки ядра.

dynamic_shared_memory_type (enum)

Определяет реализацию динамической разделяемой памяти, которую должен использовать сервер. Возможные значения: posix (для выделения разделяемой памяти POSIX с помощью shm_open), sysv (для выделения разделяемой памяти System V с помощью shmget) и mmap (для эмуляции разделяемой памяти посредством отображения в памяти файлов, хранящихся в каталоге данных). Не все варианты поддерживаются на разных платформах; первый из поддерживаемых данной платформой вариантов становится для нее реализацией по умолчанию. Использовать mmap, который нигде не выбирается по умолчанию, в принципе не рекомендуется, поскольку операционная система может многократно записывать измененные страницы на диск, увеличивая нагрузку на системные потоки ввода/вывода; однако это может быть полезно при отладке, когда каталог pg_dynshmem хранится на RAM-диске или когда другие реализации разделяемой памяти недоступны.

min_dynamic_shared_memory (integer)

Задает объем памяти, который должен быть выделен при запуске сервера для использования параллельными запросами. Когда этой области памяти недостаточно или она переполнена одновременно выполняющимися запросами, новые параллельные запросы пытаются временно выделить себе дополнительную разделяемую память у операционной системы, используя метод, заданный в параметре dynamic_shared_memory_type, что может привести к замедлению их выполнения вследствие издержек на управление памятью. На память, выделенную при запуске сервера с помощью min_dynamic_shared_memory, влияет параметр huge_pages в тех операционных системах, в которых он поддерживается; в системах, где управление большими страницами осуществляется автоматически, их применение скорее всего даст положительный эффект. Значение по умолчанию — 0 (память не выделяется). Этот параметр можно задать только при запуске сервера.

Диск

temp_file_limit (integer)

Задает максимальный объем дискового пространства, который один процесс может использовать для временных файлов, например, при сортировке и хешировании или при сохранении удерживаемого курсора. Транзакция, пытающаяся превысить этот лимит, будет отменена. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. Значение -1 (по умолчанию) означает отсутствие ограничений. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Этот параметр ограничивает общий объем, занимаемый в текущий момент всеми временными файлами, используемыми данным процессом QHB. Следует отметить, что в этом ограничении не учитывается дисковое пространство, занимаемое явно созданными временными таблицами; ограничивается только объем временных файлов, используемых внутренне при выполнении запросов.

Использование ресурсов ядра

max_files_per_process (integer)

Задает максимальное количество одновременно открытых файлов, разрешенное для каждого подпроцесса сервера. По умолчанию это тысяча файлов. Если ядро устанавливает безопасный лимит для каждого процесса, об этом параметре можно не беспокоиться. Но на некоторых платформах (особенно в большинстве систем BSD) ядро позволяет отдельным процессам открывать гораздо больше файлов, чем фактически может поддерживать система в случае, если столько же файлов одновременно открыло бы сразу несколько процессов. Если вы столкнетесь с ошибками «Too many open files» (Слишком много открытых файлов), попробуйте уменьшить значение этого параметра. Этот параметр можно задать только при запуске сервера.

Задержка очистки по стоимости

При выполнении команд VACUUM и ANALYZE система ведет внутренний счетчик, который фиксирует ожидаемую стоимость различных выполняемых операций ввода/ вывода. Когда накопленная стоимость достигает предела (указанного в параметре vacuum_cost_limit), процесс, выполняющий операцию, на некоторое время перейдет в спящий режим, как указано в параметре vacuum_cost_delay. Затем он сбросит счетчик и продолжит выполнять операцию.

Цель этой функции — позволить администраторам уменьшить влияние процессов ввода/ вывода этих команд на параллельную работу базы данных. Во многих ситуациях быстрое завершение команд обслуживания, таких как VACUUM и ANALYZE, не имеет значения; однако, как правило, очень важно, чтобы эти команды не оказывали значительного влияния на способность системы выполнять другие операции с базой данных. Администраторы могут этого добиться с помощью задержки очистки по стоимости.

Эта функция по умолчанию выключена для команд VACUUM, вводимых вручную. Чтобы ее включить, установите для переменной vacuum_cost_delay ненулевое значение.

vacuum_cost_delay (floating point)

Время, в течение которого процесс будет находиться в спящем режиме после превышения предела стоимости. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию равно нулю, то есть функция задержки очистки по стоимости выключена. Положительные значения включают очистку, интенсивность которой зависит от стоимости.

При настройке интенсивности такой очистки подходящие значения для vacuum_cost_delay обычно довольно малы, возможно, даже меньше 1 миллисекунды. Несмотря на то, что в этом параметре можно задать значения в долях миллисекунды, на старых платформах такие задержки могут быть неточными. На таких платформах увеличение интенсивности VACUUM по сравнению с уровнем, получаемым при задержке в 1 мс, потребует изменения других параметров стоимости очистки. Тем не менее стоит сохранять столь малое значение vacuum_cost_delay, какое ваша платформа в состоянии измерить; большие задержки не будут полезны.

vacuum_cost_page_hit (integer)

Ориентировочная стоимость очистки буфера в общем буферном кэше. Это подразумевает стоимость блокировки пула буферов, поиска в общей хеш-таблице и сканирования содержимого страницы. Значение по умолчанию — 1.

vacuum_cost_page_miss (integer)

Ориентировочная стоимость очистки буфера, который нужно прочитать с диска. Это подразумевает попытку заблокировать пул буферов, поиск в общей хеш-таблице, чтение нужного блока с диска и сканирование его содержимого. Значение по умолчанию — 2.

vacuum_cost_page_dirty (integer)

Ориентировочная стоимость очистки, изменяющей блок, который до этого был чистым. Это подразумевает дополнительную стоимость ввода/вывода, необходимого для повторной записи измененного блока на диск. Значение по умолчанию — 20.

vacuum_cost_limit (integer)

Накопленная стоимость, по достижении которой процесс очистки перейдет в спящий режим. Значение по умолчанию — 200.

Примечание
Существуют определенные операции, которые устанавливают критические блокировки и поэтому должны завершаться как можно быстрее. Во время таких операций задержка очистки по стоимости не проводится, поэтому накопленная за это время стоимость может намного превышать указанный предел. Во избежание ненужных длительных задержек в таких случаях, фактическая задержка рассчитывается как vacuum_cost_delay * accumulated_balance / vacuum_cost_limit и ограничивается максимумом vacuum_cost_delay * 4.

Фоновая запись

Среди прочих существует отдельный серверный процесс, называемый фоновой записью, задача которого — производить запись «грязных» (новых или измененных) общих буферов. Когда количество чистых общих буферов считается недостаточным, этот процесс записывает некоторые грязные буферы в файловую систему и помечает их как чистые. Это снижает вероятность того, что серверным процессам, занимающимся запросами пользователя, не удастся найти чистые буферы и придется записывать грязные буферы самостоятельно. Тем не менее процесс фоновой записи вызывает увеличение общей нагрузки на систему ввода/вывода, поскольку он может записывать неоднократно изменяемую страницу несколько раз, в то время как без него эту страницу можно было бы записать всего один раз за интервал контрольной точки. Параметры, рассматриваемые в этом подразделе, позволяют настроить поведение фоновой записи для локальных нужд.

bgwriter_delay (integer)

Задает задержку между раундами активности процесса фоновой записи. В каждом раунде этот процесс производит запись некоторого количества грязных буферов (это настраивается следующими параметрами). Затем он засыпает на время bgwriter_delay, после чего все повторяется. Когда в пуле нет грязных буферов, процесс засыпает на более долгий срок, независимо от значения параметра bgwriter_delay. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — 200 миллисекунд (200ms). Обратите внимание, что во многих системах эффективное разрешение задержек сна составляет 10 миллисекунд; если задать в bgwriter_delay значение, не кратное 10, на практике можно получить такой же результат, что и со следующим за ним значением, кратным 10. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

bgwriter_lru_maxpages (integer)

Максимальное количество буферов, которое процесс фоновой записи будет записывать в каждом раунде. Установка этого значения в ноль выключает фоновую запись. (Обратите внимание, что это не затрагивает контрольные точки, которые управляются отдельным выделенным вспомогательным процессом.) Значение по умолчанию — 100 буферов. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

bgwriter_lru_multiplier (floating point)

Количество грязных буферов, записанных в каждом раунде, основано на количестве новых буферов, которое понадобилось серверным процессам во время последних раундов. Для расчета предварительной потребности в буферах для следующего раунда средняя потребность за последнее время умножается на значение bgwriter_lru_multiplier. Грязные буферы будут записываются до тех пор, пока количество чистых, пригодных для повторного использования буферов не достигнет целевого значения. (Однако в каждом раунде будет записано не более bgwriter_lru_maxpages буферов.) Таким образом, значение этого параметра, равное 1.0, воплощает политику «точно в срок», при которой записывается именно то количество буферов, которое, предполагается необходимым. Увеличение этого значения обеспечивает некоторую страховку от всплесков потребностей, тогда как уменьшение преднамеренно оставляет некоторый объем записи для серверных процессов. Значение по умолчанию — 2.0. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

bgwriter_flush_after (integer)

Всякий раз, когда процесс фоновой записи записывает больше заданного объема данных, сервер пытается заставить ОС произвести запись этих данных в нижележащее хранилище. Это ограничивает количество «грязных» данных в страничном кэше ядра, снижая вероятность зависаний при выполнении fsync в конце контрольной точки или когда ОС выполняет обратную запись большими пакетами в фоновом режиме. Зачастую это приводит к значительному снижению задержки транзакций, но также бывают случаи (особенно когда объем рабочей нагрузки больше shared_buffers, но меньше страничного кэша), когда производительность может снизиться. Этот параметр может действовать не на всех платформах. Если это значение указано без единиц измерения, оно считается заданным в блоках, размер которых равен BLCKSZ байт (обычно это 8 КБ). Допустимый диапазон: от 0 (что выключает принудительную обратную запись) до 2 МБ (2MB). Значение по умолчанию равно 512 КБ (512kB) в Linux и 0 в других ОС. (Если BLCKSZ отличен от 8 КБ, значение по умолчанию и максимальное значение масштабируются пропорционально ему.) Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

С маленькими значениями bgwriter_lru_maxpages и bgwriter_lru_multiplier снижается дополнительная нагрузка на систему ввода/вывода, вызываемая процессом фоновой записи, но повышается вероятность того, что серверным процессам придется самим производить записи, что замедлит интерактивные запросы.

Асинхронное поведение

backend_flush_after (integer)

Всякий раз, когда одним обслуживающим процессом записывается больше этого объема данных, сервер дает указание ОС вынести эти записи в нижележащее хранилище. Это ограничивает объем грязных данных в страничном кэше ядра, снижая вероятность зависаний при выполнении fsync в конце контрольной точки или когда ОС выполняет обратную запись большими пакетами в фоновом режиме. Зачастую это значительно снижает задержки транзакций, но бывают случаи (особенно когда объем рабочей нагрузки больше shared_buffers, но меньше страничного кэша ОС), когда производительность может упасть. Этот параметр может действовать не на всех платформах. Если это значение указано без единиц измерения, оно считается заданным в блоках, размер которых равен BLCKSZ байт (обычно это 8 КБ). Допустимый диапазон: от 0 (что выключает принудительную обратную запись) до 2 МБ (2MB). Значение по умолчанию — 0, т. е. принудительная обратная запись выключена. (Если BLCKSZ отличен от 8 КБ, максимальное значение масштабируется пропорционально ему.)

effective_io_concurrency (integer)

Задает количество параллельных операций ввода/вывода на диске, которые, как ожидает QHB, могут выполняться одновременно. Увеличение этого значения приведет к увеличению числа операций ввода/вывода, которые QHB попытается запустить параллельно в каждом отдельном сеансе. Допустимый диапазон: от 1 до 1000; ноль выключает выполнение асинхронных запросов ввода/вывода. В настоящее время этот параметр влияет только на сканирование по битовой карте.

Для магнитных носителей хорошей начальной точкой для этого параметра является количество отдельных дисков, содержащих массив с чередованием RAID 0 или зеркальный массив RAID 1, в котором размещена база данных. (Для RAID 5 диск четности следует исключить.) Однако если база данных часто обрабатывает несколько запросов, выполняемых в параллельных сеансах, для полной загрузки дискового массива может хватить и небольших значений. Более высокое значение приведет лишь к дополнительной нагрузке на ЦП. Диски SSD и другие виды хранилища в памяти часто могут обрабатывать множество параллельных запросов, поэтому наилучшим значением могут быть сотни.

Асинхронный ввод/вывод зависит от эффективности функции posix_fadvise, которая отсутствует в некоторых операционных системах. В случае ее отсутствия установка этого параметра в любое значение, отличное от нуля, приведет к ошибке. В некоторых операционных системах (например, в Solaris) эта функция имеется, но в действительности ничего не делает.

В системах, где это поддерживается, по умолчанию установлено значение 1, в остальных — 0. Это значение можно переопределить для таблиц в определенном табличном пространстве, установив одноименный параметр табличного пространства (см. ALTER TABLESPACE).

maintenance_io_concurrency (integer)

Этот параметр схож с effective_io_concurrency, но используется в операциях обслуживания, которые выполняются во многих клиентских сеансах.

В системах, где это поддерживается, по умолчанию установлено значение 10, в остальных — 0. Это значение можно переопределить для таблиц в определенном табличном пространстве, установив одноименный параметр табличного пространства (см. ALTER TABLESPACE).

max_worker_processes (integer)

Задает максимальное количество фоновых процессов, которые может поддерживать система. Этот параметр можно задать только при запуске сервера. Значение по умолчанию — 8.

При запуске резервного сервера следует установить для этого параметра значение большее или равное значению на главном сервере. В противном случае на резервном сервере не будут разрешены запросы.

При изменении этого значения также имеет смысл скорректировать параметры max_parallel_workers, max_parallel_maintenance_workers и max_parallel_workers_per_gather.

max_parallel_workers_per_gather (integer)

Задает максимальное количество рабочих процессов, которое может запустить один узел Gather (Сбор) или Gather Merge (Сбор со слиянием). Параллельные рабочие процессы берутся из пула процессов, созданного параметром max_worker_processes и ограниченного параметром max_parallel_workers. Обратите внимание, что запрошенное количество рабочих процессов в действительности может быть недоступно во время выполнения. Если это произойдет, план будет выполняться с меньшим количеством рабочих процессов, чем ожидалось, что может быть неэффективно. Значение по умолчанию — 2. Установка этого значения в 0 выключает параллельное выполнение запросов.

Обратите внимание, что параллельные запросы могут потреблять значительно больше ресурсов, чем непараллельные, потому что каждый рабочий процесс является совершенно отдельным процессом, который оказывает на систему примерно такое же влияние, что и дополнительный пользовательский сеанс. Это следует учитывать при выборе значения этого параметра, а также при настройке других параметров, управляющих использованием ресурсов, например work_mem. Ограничения ресурсов, такие как work_mem, применяются индивидуально к каждому рабочему процессу, что означает, что общая нагрузка для всех процессов может оказаться намного выше, чем она была бы для отдельного процесса в обычной ситуации. Например, параллельный запрос с использованием 4 рабочих процессов может задействовать в 5 раз больше времени процессора, объема памяти, пропускной способности ввода/вывода и т. д., чем запрос, который вообще не использует рабочие процессы.

Дополнительную информацию о параллельных запросах см. в главе Параллельный запрос.

max_parallel_maintenance_workers (integer)

Задает максимальное количество параллельных рабочих процессов, которое может запустить одна служебная команда. В настоящее время единственными параллельными служебными командами, которые поддерживают использование параллельных рабочих процессов, являются CREATE INDEX (и только при построении индекса B-дерева) и VACUUM без параметра FULL. Параллельные рабочие процессы берутся из пула процессов, созданного параметром max_worker_processes и ограниченного параметром max_parallel_workers. Обратите внимание, что запрошенное количество рабочих процессов в действительности может быть недоступно во время выполнения. Если это произойдет, служебная операция будет выполняться с меньшим количеством рабочих процессов, чем ожидалось. Значение по умолчанию — 2. Установка этого значения в 0 выключает использование параллельных рабочих процессов служебными командами.

Обратите внимание, что параллельные служебные команды не должны потреблять значительно больше памяти, чем равнозначные непараллельные операции. Эта стратегия отличается от стратегии параллельного запроса, где ограничения ресурсов обычно применяются для каждого рабочего процесса. Для параллельных служебных команд ограничение ресурса maintenance_work_mem считается применяемым ко всей служебной команде, независимо от количества параллельных рабочих процессов. Тем не менее параллельные служебные команды все равно могут значительно больше нагружать процессор и канал ввода/вывода.

max_parallel_workers (integer)

Задает максимальное количество рабочих процессов, которое система может поддерживать для параллельных операций. Значение по умолчанию — 8. При увеличении или уменьшении этого значения также имеет смысл скорректировать параметры max_parallel_maintenance_workers и max_parallel_workers_per_gather. Также обратите внимание, что если установить для этого параметра значение, превышающее max_worker_processes, это не будет иметь никакого эффекта, поскольку параллельные рабочие процессы берутся из пула рабочих процессов, ограничиваемого этим параметром.

parallel_leader_participation (boolean)

Позволяет ведущему процессу выполнять план запроса ниже узлов Gather и Gather Merge, вместо того чтобы ожидать рабочие процессы. Значение по умолчанию — on (включен). Установка значения off (выключен) снижает вероятность того, что рабочие процессы будут заблокированы из-за того, что ведущий процесс читает кортежи недостаточно быстро, но требует, чтобы ведущий процесс дожидался запуска рабочих процессов, прежде чем выдать первые кортежи. Степень положительного или отрицательного влияния ведущего процесса на производительность зависит от типа плана, числа рабочих процессов и длительности запроса.

old_snapshot_threshold (integer)

Задает минимальный период времени, в течение которого можно использовать снимок состояния для запроса без риска возникновения ошибки «snapshot too old» (снимок слишком старый). Данные, потерявшие актуальность и находящиеся в таком состоянии дольше этого периода, могут быть вычищены. Это может помочь предотвратить захламление снимками, которые остаются задействованными долгое время. Во избежание неверных результатов из-за очистки данных, которые в противном случае были бы видны в снимке, если снимок старше этого предела и используется для чтения страницы, измененной с момента его создания, то генерируется ошибка.

Если это значение указано без единиц измерения, оно считается заданным в минутах. Значение -1 (по умолчанию) выключает эту функцию, фактически делая предельный срок снимков бесконечным. Этот параметр можно задать только при запуске сервера.

Полезные для производственной работы значения могут варьироваться от нескольких часов до нескольких дней. Малые значения (например 0 или 1min) допускаются только потому, что иногда они могут быть полезны при тестировании. Несмотря на то, что допустимо значение до 60 дней (60d), имейте в виду, что при многих рабочих нагрузках критичное «разбухание» или зацикливание идентификаторов транзакций может происходить в гораздо меньшие сроки.

Когда эта функциональность включен, освобожденное пространство в конце отношения нельзя отдать операционной системе, так как при этом может быть удалена информация, необходимая для выявления условия «snapshot too old». Все пространство, выделенное отношению, остается связанным с ним и повторно используется только в нем, пока не будет явно освобождено (например, командой VACUUM FULL).

Этот параметр не дает гарантии, что данная ошибка будет генерироваться при всех возможных обстоятельствах. В действительности, если можно получить верные результаты, например, из курсора, который материализовал результирующий набор, ошибка не будет выдана, даже если нижележащие строки в ссылочной таблице были вычищены. Некоторые таблицы, например системные каталоги, невозможно безопасно очистить досрочно, поэтому данный параметр на них не повлияет. Для таких таблиц этот параметр не уменьшает раздувание, но и не чреват ошибкой «snapshot too old» при сканировании.

Журнал упреждающей записи

Дополнительную информацию о настройке этих параметров см. в разделе Конфигурация WAL.

Параметры

wal_level (enum)

wal_level определяет, сколько информации записывается в WAL. Значение по умолчанию — replica, при котором в журнал записывается достаточно данных для поддержки архивирования WAL и репликации, включая выполнение запросов только на чтение на резервном сервере. Значение minimal удаляет всю информацию, кроме необходимой для восстановления после сбоя или аварийного отключения. Наконец, logical добавляет информацию, необходимую для поддержки логического декодирования. Каждый последующий уровень включает информацию, регистрируемую на всех уровнях ниже его. Этот параметр можно задать только при запуске сервера.

На уровне minimal генерируется наименьший объем WAL. В журнал не протоколируется информация о постоянных отношениях в транзакциях, которые их создают или перезаписывают. Это позволяет значительно ускорить такие операции (см. раздел Выключите архивацию WAL и потоковую репликацию). Такая оптимизация возможна в следующих операциях:

ALTER ... SET TABLESPACE
CLUSTER
CREATE TABLE
REFRESH MATERIALIZED VIEW (без указания CONCURRENTLY)
REINDEX
TRUNCATE

Но минимальный WAL не содержит достаточно информации для восстановления на момент времени, поэтому для включения непрерывного архивирования (archive_mode) и потоковой двоичной репликации необходимо установить уровень replica или выше. На самом деле сервер даже не запустится в этом режиме, если у max_wal_senders ненулевое значение. Обратите внимание, что при смене значения с wal_level на minimal все базовые резервные копии, созданные до этого, становятся непригодными для восстановления на момент времени и для резервных серверов.

На уровне logical протоколируется та же информация, что и на уровне replica, плюс информация, необходимая для извлечения из WAL наборов логических изменений. Установка уровня logical увеличит объем WAL, особенно если многие таблицы сконфигурированы с параметром REPLICA IDENTITY FULL и выполняется множество команды UPDATE и DELETE.

fsync (boolean)

Если этот параметр включен, сервер QHB попытается добиться, чтобы изменения были физически записаны на диск, выполняя системные вызовы fsync() или иные схожие методы (см. wal_sync_method). Это гарантирует, что кластер баз данных сможет вернуться в согласованное состояние после сбоя операционной системы или оборудования.

Хотя выключение fsync часто повышает производительность, это может привести к неустранимому повреждению данных в случае отключения питания или сбоя системы. Поэтому рекомендуется выключать fsync только в том случае, если вы легко можете восстановить всю базу данных из внешнего источника.

К примерам безопасных условий для выключения fsync относятся начальная загрузка нового кластера баз данных из файла резервной копии, использование кластера баз данных для обработки пакета данных, после которой база данных будет удалена и создана заново, или задействование клона базы данных только для чтения, который часто пересоздается и не используется для отработки отказа. Высококачественное оборудование само по себе не является достаточным основанием для выключения fsync.

Для надежного восстановления при смене значения fsync с off на on необходимо принудительно перенести все измененные буферы из ядра в стабильное хранилище. Это можно сделать, когда остановлен кластер или включен режим fsync, запустив команду qhb_bootstrap --sync-only или команду sync, размонтировав файловую систему или перезагрузив сервер.

Во многих ситуациях выключение параметра synchronous_commit для некритичных транзакций может обеспечить больший потенциальный выигрыш в производительности, чем выключение fsync, и при этом без сопутствующего риска повреждения данных.

Параметр fsync можно задать только в файле qhb.conf или в командной строке сервера. Если вы выключите этот параметр, возможно, также имеет смысл выключить параметр full_page_writes.

synchronous_commit (enum)

Определяет, после завершения какого этапа обработки WAL сервер баз данных вернет клиенту сообщение об успешном выполнении операции. Допустимые значения: remote_apply (применено удаленно), on (включен, значение по умолчанию), remote_write (записано удаленно), local (локально) и off (выключен).

Если значение synchronous_standby_names не задано, для данного параметра имеют смысл только значения on и off; при значениях remote_apply, remote_write и local уровень синхронизации будет тот же, что и с вариантом on. Локальное поведение всех режимов, отличных от off, заключается в ожидании локального сброса WAL на диск. В режиме off из-за отсутствия ожидания возможна задержка между моментом, когда клиенту сообщается об успешном выполнении, и моментом, когда транзакция действительно гарантированно защищена от сбоя сервера. (Максимальная задержка равна утроенному значению wal_writer_delay.) В отличие от fsync, значение off этого параметра не создает риска несогласованности базы данных: сбой операционной системы или базы данных может привести к потере некоторых последних предположительно зафиксированных транзакций, но состояние базы данных будет таким же, как если бы эти транзакции были прерваны обычным образом. Поэтому выключение параметра synchronous_commit может быть полезной альтернативой выключению fsync, когда производительность важнее точной уверенности в устойчивости транзакции. Дополнительную информацию см. в разделе Асинхронная фиксация.

Если значение synchronous_standby_names не пустое, synchronous_commit также определяет, будет ли сервер при фиксировании транзакций ожидать, пока их записи WAL будут обработаны на резервном сервере (или серверах).

Если задано значение remote_apply, фиксирование не завершится, пока ответы от текущих синхронных резервных серверов не укажут, что они получили запись о фиксировании транзакции, применили эту транзакцию, так что она стала видимой для запросов на резервном сервере (или серверах), а также сохранили ее в стабильном хранилище. Это вызовет куда большую задержку фиксирования, нежели предыдущие параметры, поскольку нужно будет дождаться воспроизведения WAL. Если задано значение on, фиксирование не завершится, пока ответы от текущих синхронных резервных серверов не укажут, что они получили запись о фиксировании транзакции и сохранили ее в стабильном хранилище. Это гарантирует, что транзакция будет потеряна, только если хранилище базы данных будет повреждено и на основном, и на всех синхронных резервных серверах. Если задано значение remote_write, фиксирование не завершится, пока ответы от текущих синхронных резервных серверов не укажут, что они получили запись о фиксировании транзакции и сохранили ее в своих файловых системах. Этот вариант гарантирует сохранение данных даже в случае сбоя резервного сервера QHB, но не в случае сбоя на уровне его операционной системы, поскольку данные могут не достичь стабильного хранилища на этом сервере. Со значением local фиксирование завершится после локального сброса данных на диск, не дожидаясь репликации. Обычно этот вариант нежелателен при синхронной репликации, но он предоставлен для полноты.

Этот параметр можно изменить в любое время; поведение каждой конкретной транзакции определяется значением, действующим на момент ее фиксирования. Таким образом, есть возможность и смысл, чтобы некоторые транзакции фиксировались синхронно, а другие — асинхронно. Например, чтобы одна транзакция с несколькими состояниями фиксировалась асинхронно, когда по умолчанию задано противоположное значение, выполните в этой транзакции команду SET LOCAL synchronous_commit TO OFF.

Характеристики различных значений synchronous_commit приведены в Таблице 1.

Таблица 1. Режимы synchronous_commit

Значение synchronous_commit | Стабильная локальная фиксация | Стабильная фиксация на резервном сервере после сбоя QHB | Стабильная фиксация на резервном сервере после сбоя ОС | Согласованность запросов на резервном сервере
remote_apply | • | • | • | •
on | • | • | • |
remote_write | • | • |  |
local | • |  |  |
off |  |  |  |

wal_sync_method (enum)

Метод, используемый для принудительного сохранения изменений WAL на диск. Если режим fsync выключен, то этот параметр не учитывается, поскольку принудительные сохранения файла WAL вообще не будут проводиться. Возможные значения этого параметра:

open_datasync (записать файлы WAL функцией open() с параметром O_DSYNC)

fdatasync (вызывать fdatasync() при каждом фиксировании)

fsync (вызывать fsync() при каждом фиксировании)

fsync_writethrough (вызывать fsync() при каждом фиксировании, форсируя сквозную запись любого дискового кэша)

open_sync (записать файлы WAL функцией open() с параметром O_SYNC)

Варианты open_* также применяют флаг O_DIRECT, если он доступен. Не все эти методы доступны на всех платформах. По умолчанию выбирается первый метод из приведенного выше списка, который поддерживается платформой, за исключением того, что в Linux и FreeBSD значением по умолчанию является fdatasync. Выбираемый по умолчанию вариант не обязательно идеален; для создания отказоустойчивой конфигурации или достижения оптимальной производительности может потребоваться изменить это значение или другие аспекты конфигурации вашей системы. Эти аспекты рассматриваются в разделе Надежность. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

full_page_writes (boolean)

Когда этот параметр включен, сервер QHB записывает в WAL все содержимое каждой страницы диска при первом изменении этой страницы после контрольной точки. Это необходимо, поскольку запись страницы, которая выполнялась во время сбоя операционной системы, может оказаться завершена только частично, что приводит к появлению на диске страницы, содержащей смесь старых и новых данных. Информации об изменениях на уровне строк, обычно хранящейся в WAL, будет недостаточно для полного воссоздания такой страницы при восстановлении после сбоя. Сохранение образа полной страницы гарантирует, что ее можно восстановить корректно, но ценой будет увеличение объема данных, которые должны быть записаны в WAL. (Поскольку воспроизведение WAL всегда начинается с контрольной точки, достаточно сделать это при первом изменении каждой страницы после контрольной точки. Поэтому одним из способов снижения затрат на запись полных страниц является увеличение интервалов контрольных точек.)

Выключение этого параметра ускоряет обычные операции, но может привести к неисправимому или незаметному повреждению данных после сбоя системы. Поскольку риски при этом схожи с рисками при выключении fsync, хотя и в меньшей степени, его следует выключать только при тех же обстоятельствах, которые рекомендованы для вышеприведенного параметра.

Выключение этого параметра не влияет на применение архивирования WAL для восстановления состояния на определенный момент времени (PITR) (см. раздел Непрерывное архивирование и восстановление на момент времени (PITR)).

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Значение по умолчанию — on (включен).

wal_log_hints (boolean)

Когда этот параметр имеет значение on (включен), сервер QHB записывает в WAL все содержимое каждой страницы диска при первом изменении этой страницы после контрольной точки, даже при второстепенных изменениях так называемых вспомогательных битов.

Если включен расчет контрольных сумм данных, изменения вспомогательных битов всегда регистрируются в WAL, и этот параметр игнорируется. С помощью этого параметра можно проверить, насколько больше информации будет запротоколировано в WAL, если в вашей базе данных будет включен расчет контрольных сумм данных.

Этот параметр можно задать только при запуске сервера. Значение по умолчанию — off (выключен).

wal_compression (boolean)

Этот параметр включает сжатие WAL с помощью указанного метода. Когда он имеет значение on (включен), сервер QHB сжимает образы полных страниц, записываемые в WAL, когда включен параметр full_page_writes или при создании базовой резервной копии. Сжатый образ страницы будет распакован во время воспроизведения WAL. Поддерживаемые методы: pglz, lz4 (если QHB была скомпилирована с параметром --with-lz4) и zstd (если QHB была скомпилирована с параметром --with-zstd). Значение по умолчанию — off (выключен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Включение сжатия может уменьшить объем WAL, не повышая риск неисправимого повреждения данных, но ценой некоторой дополнительной нагрузки на процессор, связанной со сжатием данных при записи в WAL и их разворачиванием при воспроизведении WAL.

wal_init_zero (boolean)

Если этот параметр имеет значение on (включен; это значение по умолчанию), вновь создаваемые файлы WAL заполняются нулями. В некоторых файловых системах благодаря этому пространство выделяется заранее, до того, как потребуется делать записи WAL. Однако в файловых системах, работающих по принципу копирования при записи (Copy-On-Write, COW), этот метод может не давать никаких преимуществ, поэтому данный параметр позволяет пропустить ненужную в таком случае операцию. Со значением off (выключен) при создании файла WAL записывается только последний байт, чтобы файл имел желаемый размер.

wal_recycle (boolean)

Если этот параметр имеет значение on (включен; это значение по умолчанию), файлы WAL переименовываются и используются повторно, что избавляет от необходимости создавать новые файлы. В файловых системах с COW, возможно, будет быстрее создать новые файлы, поэтому данный параметр позволяет выключить это поведение.

wal_buffers (integer)

Объем разделяемой памяти, используемой для данных WAL, которые еще не были записаны на диск. Значение -1 (по умолчанию) задает размер, равный 1/32 (около 3%) от shared_buffers, но не меньше 64 КБ (64kB) и не больше, чем размер одного сегмента WAL (обычно 16 МБ (16MB)). Это значение можно задать вручную, если выбираемое автоматически слишком велико или мало, но любое положительное значение меньше 32 КБ (32kB) будет восприниматься как 32 КБ. Если это значение указано без единиц измерения, оно считается заданным в блоках WAL, размер которых равен XLOG_BLCKSZ байт (обычно это 8 КБ). Этот параметр можно задать только при запуске сервера.

Содержимое буферов WAL записывается на диск при каждом фиксировании транзакции, поэтому очень большие значения вряд ли обеспечат значительное преимущество. Однако значение как минимум в несколько мегабайт может повысить производительность при записи на загруженном сервере, где одновременно множество клиентов фиксируют транзакции. Автонастройка, заданная по умолчанию (-1), в большинстве случаев должна давать удовлетворительные результаты.

wal_writer_delay (integer)

Определяет, с какой периодичностью процесс записи WAL будет сбрасывать WAL на диск. После каждого сброса WAL он переходит в режим ожидания на время, заданное в wal_writer_delay, но может активироваться раньше, если произойдет асинхронное фиксирование транзакции. Если предыдущий сброс произошел раньше, чем закончился период, указанный в wal_writer_delay, и полученный после этого объем WAL не достиг значения wal_writer_flush_after, то WAL только записывается в операционной системе, но не сбрасывается на диск. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — 200 миллисекунд (200ms). Обратите внимание, что во многих системах эффективное разрешение таймера сна составляет 10 миллисекунд; если задать в wal_writer_delay значение, не кратное 10, на практике можно получить такой же результат, что и со следующим за ним значением, кратным 10. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

wal_writer_flush_after (integer)

Определяет, при каком объеме процесс записи WAL будет сбрасывать WAL на диск. Если предыдущий сброс произошел раньше, чем закончился период, указанный в wal_writer_delay, и полученный после этого объем WAL не достиг значения wal_writer_flush_after, то WAL только записывается в операционной системе, но не сбрасывается на диск. Если в wal_writer_flush_after установлено значение 0, то данные WAL всегда сбрасываются на диск немедленно. Если это значение указано без единиц измерения, оно считается заданным в блоках WAL, размер которых равен XLOG_BLCKSZ байт (обычно это 8 КБ). Значение по умолчанию — 1 МБ (1MB). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

wal_after_error_sleep_delay (integer)

Определяет период, на который процесс сброса WAL останавливается после того, как произойдет ошибка записи на диск. Большие значения этого периода (не более одной секунды) позволяют избежать чрезмерного заполнения журнала, но могут приводить к критическим задержкам работы высоконагруженных систем. Возможные значения: от 0 (отсутствие задержек) до 1000000 (1 секунда). Значение по умолчанию — 100 (100 микросекунд). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Изменение значения потребует перезапуска сервера.

wal_skip_threshold (integer)

Когда wal_level равен minimal и транзакция фиксируется после создания или перезаписи постоянного отношения, этот параметр определяет, как будут сохраняться новые данные. Если объем данных меньше заданного значения, они записываются в журнал WAL; в противном случае к затронутым файлам применяется fsync. В зависимости от свойств вашего хранилища, увеличение или уменьшение этого значения может быть полезным, если такие фиксации замедляют параллельные транзакции. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. Значение по умолчанию — два мегабайта (2MB).

commit_delay (integer)

Параметр commit_delay добавляет паузу перед началом сброса WAL на диск. Это может увеличить быстродействие при групповом фиксировании, позволяя зафиксировать большее число транзакций за один сброс WAL, если загрузка системы достаточно высока и за заданное время успевают подготовиться к фиксированию другие транзакции. Однако это также увеличивает задержку вплоть до commit_delay при каждом сбросе WAL на диск. Поскольку в паузе нет смысла, если никакие другие транзакции не готовы к фиксированию, задержка происходит только в том случае, если на момент запуска сброса WAL активно не менее commit_siblings других транзакций. Кроме того, эти задержки не происходят при выключенном fsync. Если это значение указано без единиц измерения, оно считается заданным в микросекундах. По умолчанию значение commit_delay равно нулю (без задержки). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Обратите внимание, что заданное время ожидает только первый процесс, готовый сбросить данные WAL на диск, а все последующие процессы ждут только до тех пор, пока тот не завершит операцию сброса.

commit_siblings (integer)

Минимальное число одновременных открытых транзакций, которое требуется для добавления задержки commit_delay. Чем больше это значение, тем выше вероятность того, что как минимум еще одна транзакция окажется готова к фиксированию за время задержки. По умолчанию это пять транзакций.

Контрольные точки

checkpoint_timeout (integer)

Максимальное время между автоматическими контрольными точками WAL. Если это значение указано без единиц измерения, оно считается заданным в секундах. Допустимый диапазон составляет от 30 секунд до одного дня. Значение по умолчанию — пять минут (5min). Увеличение этого параметра может привести к увеличению времени, необходимого для восстановления после сбоя. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

checkpoint_completion_target (floating point)

Задает целевое время завершения процедуры контрольной точки, как долю от общего времени между контрольными точками. Значение по умолчанию — 0.9, что распределяет контрольную точку почти по всему доступному интервалу, обеспечивая довольно стабильную нагрузку ввода/вывода, а также оставляя некоторое время для издержек на завершение процесса контрольной точки. Уменьшать значение этого параметра не рекомендуется, поскольку тогда контрольная точка будет завершаться быстрее. Это приведет к высокой интенсивности ввода/вывода в процессе контрольной точки, за которым последует период низкой интенсивности ввода/вывода между завершением контрольной точки и следующей запланированной контрольной точкой. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

checkpoint_flush_after (integer)

Всякий раз, когда во время выполнения контрольной точки записывается больше заданного объема данных, сервер пытается заставить ОС произвести запись этих данных в нижележащее хранилище. Это ограничивает количество «грязных» данных в страничном кэше ядра, снижая вероятность зависаний при выполнении fsync в конце контрольной точки или когда ОС выполняет обратную запись большими пакетами в фоновом режиме. Зачастую это приводит к значительному снижению задержки транзакций, но также бывают случаи (особенно когда объем рабочей нагрузки больше shared_buffers, но меньше страничного кэша ОС), когда производительность может снизиться. Этот параметр может действовать не на всех платформах. Если это значение указано без единиц измерения, оно считается заданным в блоках, размер которых равен BLCKSZ байт (обычно это 8 КБ). Допустимый диапазон: от 0 (что выключает принудительную обратную запись) до 2 МБ (2MB). Значение по умолчанию равно 256kB в Linux и 0 в других ОС. (Если BLCKSZ отличен от 8 КБ, значение по умолчанию и максимальное значение масштабируются пропорционально ему.) Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

checkpoint_warning (integer)

Записывать сообщение в журнал сервера, если контрольные точки, вызванные заполнением файлов сегментов WAL, выполняются быстрее, чем через заданное время (что говорит о том, что нужно увеличить max_wal_size). Если это значение указано без единиц измерения, оно считается заданным в секундах. Значение по умолчанию — 30 секунд (30s). При нуле это предупреждение выключается. Если checkpoint_timeout меньше, чем checkpoint_warning, предупреждения генерироваться не будут. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

max_wal_size (integer)

Максимальный размер, до которого может вырасти WAL во время автоматических контрольных точек. Это мягкий предел; размер WAL может превышать max_wal_size при особых обстоятельствах, например, при большой нагрузке, сбое в archive_command или большом значении wal_keep_size. Если это значение указано без единиц измерения, оно считается заданным в мегабайтах. Значение по умолчанию — 1 ГБ. Увеличение этого параметра может привести к увеличению времени, необходимого для восстановления после сбоя. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

min_wal_size (integer)

Пока WAL занимает на диске меньше объема, заданного в этом параметре, старые файлы WAL в контрольных точках всегда перерабатываются для дальнейшего использования, а не удаляются. Это позволяет зарезервировать достаточно пространства для WAL, чтобы справиться с пиками его использования, например, при выполнении больших пакетных заданий. Если это значение указано без единиц измерения, оно считается заданным в мегабайтах. Значение по умолчанию — 80 МБ. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Архивирование

archive_mode (enum)

Когда параметр archive_mode включен, заполненные сегменты WAL отправляются в архивное хранилище с помощью команды archive_command или функций archive_library. Помимо значения off, выключающего архивацию, есть еще два: on (включен) и always (всегда). В нормальных условиях между этими двумя режимами нет никакой разницы, но в режиме always архивирование WAL активно также во время восстановления из архива и при работе с резервным сервером. В режиме always все файлы, восстановленные из архива или переданные с потоковой репликацией, будут заархивированы (снова). Подробную информацию см. в подразделе Непрерывное архивирование на резервном сервере.

Параметр archive_mode отделен от параметров archive_command и archive_library, чтобы последние можно было изменять, не выходя из режима архивирования. Этот параметр можно задать только при запуске сервера. archive_mode нельзя включить, если в параметре wal_level задано значение minimal.

archive_command (string)

Это команда локальной оболочки, выполняемая для архивирования завершенного сегмента файла WAL. Любое вхождение %p в этой строке заменяется путем к файлу, который нужно заархивировать, а любое вхождение %f — только именем этого файла. (Путь задается относительно рабочего каталога сервера, т. е. каталога данных кластера.) Чтобы вставить в команду символ %, его нужно записать как %%. Важно, чтобы команда возвращала нулевой код завершения, только если она завершается успешно. Подробную информацию см. в подразделе Настройка архивирования WAL.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Он игнорируется, если при запуске сервера не был включен параметр archive_mode, а в параметре archive_library задана пустая строка. Если в archive_command задана пустая строка (по умолчанию), а archive_mode включен (и в archive_library задана пустая строка), архивирование WAL временно выключается, но сервер продолжает накапливать файлы сегментов WAL в ожидании, что команда будет вскоре предоставлена. Если в качестве команды archive_command задать команду, которая ничего не делает, но возвращает значение true (успешное завершение), например /bin/true, архивирование по сути выключается, но при этом разрывается цепочка файлов WAL, необходимых для восстановления архива, поэтому такой вариант следует использовать только в исключительных случаях.

archive_library (string)

Библиотека, используемая для архивирования завершенных сегментов файла WAL. Если в параметре установлена пустая строка (по умолчанию), применяется архивирование посредством оболочки и используется archive_command. В противном случае для архивирования используется заданная разделяемая библиотека. При изменении этого параметра qhbmaster перезапускает процесс архивирования WAL. Более подробную информацию см. в подразделе Настройка архивирования WAL и в главе Модули архивирования.

Это параметр можно задать только в файле qhb.conf или в командной строке сервера.

archive_timeout (integer)

Команда archive_command или функции archive_library вызываются только для завершенных сегментов WAL. Поэтому, если ваш сервер генерирует небольшой трафик WAL (или запись данных чередуется с периодами простоя), от завершения транзакции до ее безопасного сохранения в архивном хранилище может пройти довольно много времени. Для ограничения времени существования неархивированных данных, можно задать значение archive_timeout, чтобы сервер периодически переключался на новый файл сегмента WAL. Когда этот параметр больше нуля, сервер будет переключаться на новый файл сегмента, если с момента последнего переключения на новый файл прошел заданный промежуток времени и наблюдалась какая-то активность базы данных, пусть даже это была одна контрольная точка (контрольные точки пропускаются, если в базе данных отсутствует активность). Обратите внимание, что архивные файлы, которые из-за принудительного переключения закрываются досрочно, все равно будут иметь тот же размер, что и полностью заполненные. Поэтому устанавливать для archive_timeout очень маленькое значение неразумно — это приведет к переполнению архивного хранилища. Обычно имеет смысл задавать значение около минуты. Если вы хотите, чтобы данные копировались с главного сервера быстрее, следует подумать о переходе с архивирования на потоковую репликацию. Если это значение указано без единиц измерения, оно считается заданным в секундах. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Восстановление

В этом разделе описываются общие параметры, имеющие отношение к восстановлению и влияющие на восстановление после сбоя, потоковую репликацию и репликацию с использованием архива.

recovery_prefetch (enum)

Определяет, выполнять ли во время восстановления предварительную выборку блоков, на которые есть ссылки в WAL и которых еще нет в пуле буферов. Допустимые значения: off (выключен), on (включен) и try (попробовать; это значение по умолчанию). Значение try включает предварительную выборку, только если операционная система предоставляет функцию posix_fadvise, которая в настоящее время используется для реализации предварительной выборки. Обратите внимание, что некоторые операционные системы предоставляют эту функцию, но она ничего не делает.

Предварительная выборка блоков, которые вскоре понадобятся, может снизить время ожидания ввода/вывода во время восстановления при определенной рабочей нагрузке. См. также параметры wal_decode_buffer_size и maintenance_io_concurrency, ограничивающие активность предварительной выборки.

wal_decode_buffer_size (integer)

Ограничение того, насколько далеко сервер будет читать наперед в WAL, чтобы найти блоки для предварительной выборки. Если это значение указано без единиц изменения, оно считается заданным в байтах. Значение по умолчанию — 512 КБ.

Восстановление из архива

В этом разделе описываются параметры, которые применяются только в процессе восстановления. Они должны быть сброшены для любой последующей операции восстановления.

Под «восстановлением» понимается как использование сервера в качестве резервного, так и выполнение целевого восстановления данных. Как правило, резервный сервер используется для обеспечения высокой доступности и/или масштабируемости чтения, тогда как целевое восстановление используется в случае потери данных.

Чтобы запустить сервер в режиме резервного, создайте в каталоге данных файл с именем standby.signal. Сервер перейдет к восстановлению, но не остановится по достижении конца заархивированного WAL, а продолжит пытаться осуществить восстановление дальше, подключившись для этого к передающему серверу, используя параметры в primary_conninfo, и/или получив новые сегменты WAL с помощью команды из restore_command. Для данного режима представляют интерес параметры, описанные в этом разделе и подразделе Резервные серверы. Параметры, описанные в подразделе Целевая точка восстановления, также будут действовать, но в этом режиме они, как правило, бесполезны.

Чтобы запустить сервер в режиме целевого восстановления, создайте в каталоге данных файл с именем recovery.signal. В случае одновременного создания файлов standby.signal и recovery.signal приоритет имеет режим резервного сервера. Режим целевого восстановления завершается после полного воспроизведения заархивированного WAL или при достижении recovery_target (целевой точки). В этом режиме используются параметры, описанные в этом разделе и подразделе Целевая точка восстановления.

restore_command (string)

Команда локальной оболочки, выполняемая для извлечения заархивированного сегмента набора файлов WAL. Этот параметр необходим для восстановления из архива, но необязателен для потоковой репликации. Любое вхождение %f в этой строке заменяется именем файла, извлекаемого из архива, а любое вхождение %p — путем назначения на сервере. (Путь указывается относительно текущего рабочего каталога, т. е. каталога данных кластера.) Любое вхождение %r заменяется именем файла, содержащего последнюю действительную точку перезапуска. Это самый ранний файл, который необходимо хранить, чтобы обеспечить возможность запуска восстановления, поэтому эту информацию можно использовать для усечения архива до минимального размера, необходимого для поддержки перезапуска. %r обычно используется только в конфигурациях с теплым резервированием (см. раздел Доставка журналов на резервные серверы). Чтобы вставить в команду символ %, его нужно записать как %%.

Важно, чтобы команда возвращала нулевой код завершения, только если она завершается успешно. У команды будут запрошены имена файлов, которых нет в архиве; в этом случае она должна возвращать ненулевой статус. Пример:

restore_command = 'cp /mnt/server/archivedir/%f "%p"'

Если команда была прервана сигналом (отличным от SIGTERM, который используется как часть отключения сервера баз данных) или ошибкой со стороны оболочки (например, если команда не найдена), то процесс восстановления будет остановлен и сервер не запустится.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

archive_cleanup_command (string)

Этот необязательный параметр задает команду оболочки, которая будет выполняться при каждой точке перезапуска. Назначение параметра archive_cleanup_command — предоставить механизм для очистки от старых архивных файлов WAL, которые больше не нужны резервному серверу. Любое вхождение %r заменяется именем файла, содержащего последнюю действительную точку перезапуска. Это самый ранний файл, который необходимо хранить, чтобы обеспечить возможность запуска восстановления, поэтому все более старые файлы можно безопасно удалить. Эту информацию можно использовать для усечения архива до минимального размера, необходимого для поддержки перезапуска. Модуль qhb_archivecleanup часто используется в качестве archive_cleanup_command в конфигурациях с одним резервным сервером, например:

archive_cleanup_command = 'qhb-archivecleanup /mnt/server/archivedir %r'

Однако обратите внимание, что если несколько резервных серверов восстанавливаются из одного и того же архивного каталога, следует проследить за тем, чтобы файлы WAL удалялись только после того, как ими воспользуются все серверы. archive_cleanup_command часто используется в конфигурациях с теплым резервом (см. раздел Доставка журналов на резервные серверы). Чтобы вставить в команду символ %, его нужно записать как %%.

Если команда возвращает ненулевой код завершения, в журнал будет записано сообщение с предупреждением. Если же команда была прервана сигналом или ошибкой со стороны оболочки (например, если команда не найдена), возникнет критическая ошибка.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

recovery_end_command (string)

Этот параметр задает команду оболочки, которая будет выполнена только один раз в конце процесса восстановления. Этот параметр не является обязательным. Назначение параметра recovery_end_command — предоставить механизм очистки после репликации или восстановления. Любое вхождение %r заменяется именем файла, содержащего последнюю действительную точку перезапуска, аналогично параметру archive_cleanup_command.

Если команда возвращает ненулевой код завершения, в журнал будет записано сообщение с предупреждением и запуск базы данных все равно продолжится. Если же команда была прервана сигналом или ошибкой со стороны оболочки (например, если команда не найдена), запуск базы данных будет прерван.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Целевая точка восстановления

По умолчанию восстановление производится вплоть до конца журнала WAL. Чтобы остановить процесс восстановления в более ранней точке, можно использовать один из следующих параметров: recovery_target, recovery_target_lsn, recovery_target_name, recovery_target_time или recovery_target_xid. Если в файле конфигурации указано более одного из них, возникнет ошибка. Эти параметры можно задать только при запуске сервера.

recovery_target = 'immediate'

Этот параметр указывает, что восстановление должно завершиться по достижении согласованного состояния, т. е. как можно раньше. При восстановлении из оперативной резервной копии это будет точкой, в которой завершился процесс резервного копирования.

Технически это строковый параметр, но в данный момент допустимым является только значение 'immediate'.

recovery_target_name (string)

Этот параметр задает именованную точку восстановления (созданную с помощью pg_create_restore_point()), до которой будет производиться восстановление.

recovery_target_time (timestamp)

Этот параметр задает метку времени, до которой будет производиться восстановление. Точная точка остановки также зависит от значения параметра recovery_target_inclusive.

Значение этого параметра задается в том же формате, который принимается типом данных timestamp with time zone, за исключением того, что в нем нельзя использовать аббревиатуру часового пояса (если только переменная timezone_abbreviations не была установлена в файле конфигурации выше). Поэтому рекомендуется задавать числовое смещение от UTC или записывать название часового пояса полностью, например Europe/Helsinki (но не EEST).

recovery_target_xid (string)

Этот параметр задает идентификатор транзакции, до которого будет производиться восстановление. Имейте в виду, что значения идентификаторов отражают последовательность именно старта транзакций, а выполняться они могут и в другом порядке. Будут восстановлены все транзакции, совершенные до указанной (и, возможно, включая ее). Точная точка остановки также зависит от значения параметра recovery_target_inclusive.

recovery_target_lsn (pg_lsn)

Этот параметр задает номер LSN позиции в журнале упреждающей записи, до которого будет производиться восстановление. Точная точка остановки также зависит от значения параметра recovery_target_inclusive. Этот параметр принимает значение системного типа данных pg_lsn.

Следующие параметры уточняют целевую точку восстановления и определяют, что будет происходить при ее достижении:

recovery_target_inclusive (boolean)

Указывает, следует ли останавливаться сразу после заданной целевой точки восстановления (on) или непосредственно перед ней (off). Применяется, когда задан параметр recovery_target_lsn, recovery_target_time или recovery_target_xid. Этот параметр определяет, будут ли восстановлены транзакции, у которых позиция в WAL (LSN), время фиксации или идентификатор в точности совпадают с соответствующим заданным значением. Значение по умолчанию — on (включен).

recovery_target_timeline (string)

Указывает точную временную шкалу для восстановления. Значение этого параметра может задаваться числовым идентификатором временной шкалы или специальным значением. При значении current восстанавливается та временная шкала, которая действовала при создании базовой резервной копии. При значении latest восстанавливается последняя временная шкала, найденная в архиве, что полезно для резервного сервера. Значение по умолчанию — latest.

Обычно этот параметр требуется задавать только в сложных ситуациях повторного восстановления, когда необходимо вернуться к состоянию, которое само было достигнуто после восстановления на момент времени. Это рассматривается в подразделе Временная шкала.

recovery_target_action (enum)

Указывает, какое действие должен предпринять сервер после достижения целевой точки восстановления. Вариант по умолчанию — pause, означающий, что восстановление будет приостановлено. Вариант promote означает, что процесс восстановления завершится и сервер начнет принимать подключения. Наконец, при варианте shutdown сервер остановится по достижении целевой точки восстановления.

Предполагается, что вариант pause позволит выполнить запросы к базе данных и проверить, является ли эта целевая точка восстановления наиболее желаемой. Для снятия с паузы можно воспользоваться функцией pg_wal_replay_resume() (см. таблицу Функции управления восстановлением), которая приведет к завершению восстановления. Если эта целевая точка не является желаемой, нужно выключить сервер, установить параметры на более позднюю цель и перезапустить сервер для продолжения восстановления.

Вариант shutdown полезен для получения готового экземпляра сервера в желаемой точке воспроизведения. Данный экземпляр по-прежнему сможет воспроизводить дополнительные записи WAL (и на самом деле при следующем запуске должен будет воспроизводить записи WAL с момента последней контрольной точки).

Обратите внимание, что поскольку recovery.signal не удаляется, если в recovery_target_action задано значение shutdown, последующий запуск закончится немедленным завершением работы, если только не изменить конфигурацию или не удалить файл recovery.signal вручную.

Этот параметр не действует, если целевая точка восстановления не установлена. Если не включен режим hot_standby, вариант pause будет действовать так же, как shutdown. Если целевая точка восстановления достигается в процессе повышения, pause будет действовать так же, как promote.

В любом случае, если целевая точка восстановления задана, но восстановление архива заканчивается до ее достижения, сервер завершит работу с критической ошибкой.

Репликация

Эти параметры управляют поведением встроенной функциональности потоковой репликации (см. подраздел Потоковая репликация). При этом одни серверы становятся главными, другие — резервными. Главные серверы могут передавать данные, тогда как резервные всегда принимают реплицированные данные. Когда используется каскадная репликация (см. подраздел Каскадная репликация), резервные серверы могут не только получать, но и передавать данные. Эти параметры в основном относятся к передающим и резервным серверам, хотя некоторые из них имеют смысл только для главного сервера. При необходимости все эти параметры вполне могут быть разными в рамках одного кластера.

Передающие серверы

Эти параметры можно установить на любом сервере, который должен передавать данные репликации на один или несколько резервных серверов. Главный сервер всегда является передающим, поэтому на нем эти параметры должны задаваться всегда. Роль и значение этих параметров не меняются после того, как резервный сервер становится главным.

max_wal_senders (integer)

Задает максимально допустимое число одновременных подключений резервных серверов или клиентов потокового базового резервного копирования (т. е. максимальное число одновременно работающих процессов передачи WAL). Значение по умолчанию — 10. При значении 0 репликация выключается. При неожиданном отключении клиента потоковой передачи потерянный слот подключения может игнорироваться вплоть до истечения тайм-аута, поэтому значение этого параметра должно быть немного выше максимального числа ожидаемых клиентов, чтобы отключившиеся клиенты могли немедленно восстановить соединение. Этот параметр можно задать только при запуске сервера. Кроме того, чтобы к данному серверу могли подключаться резервные, значение параметра wal_level должно быть replica или выше.

При запуске резервного сервера следует установить для этого параметра значение большее или равное значению на главном сервере. В противном случае на резервном сервере не будут разрешены запросы.

max_replication_slots (integer)

Задает максимальное число слотов репликации (см. подраздел Слоты репликации), которое сможет поддерживать сервер. Значение по умолчанию — 10. Этот параметр можно задать только при запуске сервера. Если заданное значение будет меньше, чем число существующих в настоящее время слотов репликации, сервер не запустится. Кроме того, чтобы к данному серверу могли подключаться резервные, значение параметра wal_level должно быть replica или выше.

На стороне подписчика этот параметр определяет, сколько источников репликации (см. главу Отслеживание процесса репликации) можно отслеживать одновременно, по сути, ограничивая количество подписок на логическую репликацию, которые можно создать на сервере. Если заданное значение будет меньше, чем текущее число отслеживаемых источников репликации (показываемое в представлении pg_replication_origin_status, а не в каталоге pg_replication_origin), сервер не запустится.

wal_keep_size (integer)

Задает минимальный объем прошлых сегментов файла журнала, которые будут сохраняться в каталоге pg_wal на случай, если резервному серверу понадобится выбрать их для потоковой репликации. Если резервный сервер, подключенный к передающему, отстает больше чем на wal_keep_size мегабайт, передающий сервер может удалить сегменты WAL, все еще нужные резервному, и в этом случае соединение репликации прервется. В результате этого впоследствии также будут прерваны зависимые соединения. (Однако резервный сервер может восстановиться, выбрав этот сегмент из архива, если проводится архивирование WAL.)

Этот параметр задает только минимальный объем сегментов, который будет сохраняться в каталоге pg_wal; для архивации WAL или для восстановления с момента контрольной точки системе может потребоваться сохранить больше сегментов. Если wal_keep_size равен нулю (это значение по умолчанию), система не сохраняет никакие дополнительные сегменты для резервных серверов, поэтому число доступных для них старых сегментов WAL зависит от положения предыдущей контрольной точки и состояния архивации WAL. Если это значение указано без единиц измерения, оно считается заданным в мегабайтах. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

max_slot_wal_keep_size (integer)

Задает максимальный объем файлов WAL, который может оставаться в каталоге pg_wal для слотов репликации после выполнения контрольной точки. Со значением max_slot_wal_keep_size, равным -1 (по умолчанию), для слотов репликации может сохраняться неограниченный объем файлов WAL. При неотрицательном значении, если позиция restart_lsn слота репликации отстает от текущего LSN более чем на заданный объем, использующий этот слот резервный сервер может лишиться возможности продолжить репликацию вследствие удаления нужных ему файлов WAL. Доступность WAL для слотов репликации можно узнать в представлении pg_replication_slots. Если это значение указано без единиц измерения, оно считается заданным в мегабайтах. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

wal_sender_timeout (integer)

Задает промежуток времени, по истечении которого прерываются неактивные соединения репликации. Это помогает передающему серверу обнаружить сбой резервного сервера или разрывы сети. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — 60 секунд. Значение ноль выключает механизм тайм-аута.

Если узлы кластера распределены по нескольким географическим местоположениям, управление им можно сделать более гибким, используя разные значения в разных расположениях. Маленькие значения помогают быстрее обнаружить потерю резервного сервера, подключенного через сеть с низкими задержками, а большие позволяют точнее определить состояние удаленного резервного сервера, соединение с которым характеризуется большими задержками.

track_commit_timestamp (boolean)

Включает запись времени фиксации транзакций. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Значение по умолчанию — off (выключен).

Главный сервер

Эти параметры можно задать на главном (основном) сервере, который должен передавать данные репликации на один или несколько резервных серверов. Обратите внимание, что помимо этих параметров на главном сервере должен быть надлежащим образом установлен wal_level, а также может быть включено архивирование WAL (см. подраздел Архивирование). Значения этих параметров на резервных серверах не важны, хотя их можно задать там заранее, на случай, если понадобится сделать резервный сервер главным.

synchronous_standby_names (string)

Задает список резервных серверов, которые могут поддерживать синхронную репликацию, как описано в разделе Синхронная репликация. Активных синхронных резервных серверов может быть один или несколько; транзакциям, ожидающим фиксации, будет позволено завершиться только после того, как эти резервные серверы подтвердят получение своих данных. Синхронными резервными серверами будут те, имена которых указаны в этом списке и которые в настоящее время подключены и передают данные в режиме реального времени (что показывает характеристика streaming в представлении pg_stat_replication). Указание более одного синхронного резервного сервера может обеспечить очень высокую доступность и защиту от потери данных.

Именем резервного сервера в этом контексте является значение параметра application_name резервного сервера, задаваемое в его свойствах подключения. В случае осуществления физической репликации его следует задавать в параметре primary_conninfo; по умолчанию это значение параметра cluster_name, если оно задано, иначе — walreceiver. Для логической репликации его можно задать в свойствах подключения подписки; по умолчанию это имя подписки. Чтобы узнать, как задать его для других потребителей потока репликации, ознакомьтесь с их документацией.

Этот параметр задает список резервных серверов, используя любую из следующих форм:

[FIRST] число_синхронных ( имя_резервного [, ...] )
ANY число_синхронных ( имя_резервного [, ...] )
имя_резервного [, ...]

где число_синхронных — это число синхронных резервных серверов, от которых транзакции должны дождаться ответов, а имя_резервного — это имя резервного сервера. FIRST и ANY указывают способ выбора синхронных резервных из перечисленных серверов.

Ключевое слово FIRST в сочетании с числом_синхронных задает синхронную репликацию согласно приоритетам, и транзакции не фиксируются, пока их записи WAL не будут реплицированы на число_синхронных синхронных резервных серверов, выбранных, исходя из их приоритетности. Например, если задать FIRST 3 (s1, s2, s3, s4), то каждая фиксация будет происходить только после получения ответов от трех наиболее приоритетных из резервных серверов s1, s2, s3 и s4. Резервные серверы, имена которых стоят в списке выше, имеют более высокий приоритет и будут рассматриваться как синхронные. Серверы, следующие в списке за ними, считаются потенциальными синхронными. Если какой-либо из текущих синхронных резервных серверов по какой-то причине отключится, он будет немедленно заменен следующим резервным сервером с наивысшим приоритетом. Ключевое слово FIRST является необязательным.

Ключевое слово ANY в сочетании с числом_синхронных задает синхронную репликацию согласно кворуму, и транзакции не фиксируются, пока их записи WAL не будут реплицированы как минимум на число_синхронных перечисленных резервных серверов. Например, если задать ANY 3 (s1, s2, s3, s4), то каждая фиксация будет происходить сразу после получения ответов от как минимум трех из резервных серверов s1, s2, s3 и s4.

Ключевые слова FIRST и ANY нечувствительны к регистру. Если эти же слова используются в качестве имени резервного сервера, его имя_резервного следует заключить в кавычки.

Специальному элементу * соответствует имя любого резервного сервера.

Не существует механизма, обеспечивающего уникальность имен резервных серверов. В случае дублирования более приоритетным будет считаться один из резервных серверов с подходящим именем, хотя какой именно, не определено.

Примечание
Каждое имя_резервного должно задаваться в форме допустимого идентификатора SQL, кроме *. При необходимости его можно заключать в кавычки. Но обратите внимание, что имена_резервного сравниваются с именами приложений резервных серверов без учета регистра, независимо от того, заключены они в кавычки или нет.

Если не задать в этом параметре имена синхронных резервных серверов, то синхронная репликация не включится и фиксируемые транзакции не будут ждать репликации. Это конфигурация по умолчанию. Даже когда синхронная репликация включена, отдельные транзакции можно настроить так, чтобы они не ожидали репликации, задав для параметра synchronous_commit значение local или off.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

vacuum_defer_cleanup_age (integer)

Задает число транзакций, на которое будут откладывать очистку неиспользуемых версий строк команда VACUUM и изменения HOT. По умолчанию это число равно нулю, то есть неиспользуемые версии строк могут быть удалены сразу, как только перестанут быть видимыми для любой открытой транзакции. Можно установить ненулевое значение на основном сервере, который работает с серверами горячего резерва, как описано в разделе Горячий резерв. Это дает больше времени для выполнения запросов на резервном сервере без возникновения конфликтов из-за преждевременной очистки строк. Тем не менее поскольку это значение определяется числом записывающих транзакций, выполняющихся на основном сервере, трудно предсказать, сколько дополнительного времени получат резервные серверы. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

В качестве альтернативы использованию этого параметра следует также рассмотреть возможность установки hot_standby_feedback на резервных серверах.

Этот параметр не предотвращает очистку неиспользуемых строк, которые достигли возраста, заданного параметром old_snapshot_threshold.

Резервные серверы

Эти параметры управляют поведением резервного сервера, который должен получать данные репликации. На главном сервере их значения не играют роли.

primary_conninfo (string)

Задает строку подключения, которая будет использоваться резервным сервером для соединения с передающим. Эта строка имеет формат, описанный в подразделе Строки подключения. Если в этой строке не указан никакой параметр, то проверяется соответствующая переменная среды (см. раздел Переменные среды). Если переменная среды также не установлена, используются значения по умолчанию.

В строке подключения должно задаваться имя хоста (или адрес) передающего сервера, а также номер порта, если он не совпадает с номером по умолчанию для резервного сервера. Также указывается имя пользователя, соответствующее роли с требуемыми правами на передающем сервере (см. подраздел Аутентификация). Кроме того, если передающий сервер требует аутентификации по паролю, следует дополнительно задать пароль. Его можно указать в строке primary_conninfo или в отдельном файле ~/.pgpass на резервном сервере (для базы данных с именем replication). Имя базы данных в строке primary_conninfo указывать не нужно.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Если значение этого параметра меняется во время работы процесса, принимающего WAL, этот процесс получает сигнал завершиться, и ожидается, что он перезапустится с новой настройкой (за исключением случаев, когда значением primary_conninfo является пустая строка). Этот параметр действует, только если сервер работает в резервном режиме.

primary_slot_name (string)

Дополнительно задает существующий слот репликации, который будет использоваться при подключении к передающему серверу посредством потоковой репликации для управления удалением ресурсов в вышестоящем узле (см. подраздел Слоты репликации). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Если значение этого параметра меняется во время работы процесса, принимающего WAL, этот процесс получает сигнал завершиться, и ожидается, что он перезапустится с новой настройкой. Этот параметр действует, только если задан параметр primary_conninfo и сервер работает в режиме резерва.

promote_trigger_file (string)

Задает триггерный файл, присутствие которого завершает восстановление на резервном сервере. Даже если это значение не задано, резервный сервер все равно можно назначить главным с помощью команды qhb_ctl promote или вызова функции pg_promote(). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

hot_standby (boolean)

Определяет, можно ли будет подключаться к серверу и выполнять запросы в процессе восстановления, как описано в разделе Горячий резерв. Значение по умолчанию — on (подключения разрешены). Этот параметр можно задать только при запуске сервера. Он действует только во время восстановления из архива или в режиме резервного сервера.

max_standby_archive_delay (integer)

В режиме горячего резерва этот параметр определяет, как долго резервный сервер должен ждать, прежде чем отменять свои запросы, конфликтующие с готовыми к применению записями WAL, как описано в подразделе Обработка конфликтов запросов. Задержка max_standby_archive_delay применяется, когда данные WAL считываются из архива (то есть не являются текущими). Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — 30 секунд. При значении -1 резервный сервер может ждать завершения конфликтующих запросов неограниченное время. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Обратите внимание, что max_standby_archive_delay — это не максимальное время, которое отводится на выполнение каждому запросу; скорее, это максимальное общее время, за которое должны применяться данные из одного сегмента WAL. Таким образом, если один запрос привел к значительной задержке, у последующих конфликтующих запросов отсрочка будет гораздо меньше.

max_standby_streaming_delay (integer)

В режиме горячего резерва этот параметр определяет, как долго резервный сервер должен ждать, прежде чем отменять свои запросы, конфликтующие с готовыми к применению записями WAL, как описано в подразделе Обработка конфликтов запросов. Задержка max_standby_streaming_delay применяется, когда данные WAL поступают при потоковой репликации. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — 30 секунд. При значении -1 резервный сервер может ждать завершения конфликтующих запросов неограниченное время. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Обратите внимание, что max_standby_streaming_delay — это не максимальное время, которое отводится на выполнение каждому запросу; скорее, это максимальное общее время, за которое должны применяться данные из одного сегмента WAL после получения от главного сервера. Таким образом, если один запрос привел к значительной задержке, у последующих конфликтующих запросов будет гораздо меньше времени, пока резервный сервер снова не догонит главный.

wal_receiver_create_temp_slot (boolean)

Определяет, должен ли процесс, принимающий WAL, создавать временный слот репликации на удаленном сервере, если постоянный слот репликации не был сконфигурирован (с помощью primary_slot_name). Значение по умолчанию — off (выключен). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Если значение этого параметра меняется во время работы процесса, принимающего WAL, этот процесс получает сигнал завершиться, и ожидается, что он перезапустится с новой настройкой.

wal_receiver_status_interval (integer)

Задает минимальную частоту, с которой процесс, принимающий WAL на резервном сервере, будет отправлять информацию о ходе выполнения репликации на главный или вышестоящий резервный сервер, где эту информацию можно будет увидеть в представлении pg_stat_replication. В сообщении резервного сервера будут передаваться сведения о последней позиции, которую он записал в журнал упреждающей записи, о последней позиции, которую он сохранил на диск, и о последней позиции, которую он применил. Значение этого параметра — максимальный интервал между отчетами. Сообщения отправляются при каждом изменении позиции записи или сохранения или с частотой, указанной в этом параметре (если в нем установлено ненулевое значение). Существуют отдельные случаи, при которых изменения отправляются, игнорируя этот параметр; например, когда завершается обработка существующего WAL или когда synchronous_commit имеет значение remote_apply. Таким образом, последняя переданная позиция может немного отставать от фактической. Если это значение указано без единиц измерения, оно считается заданным в секундах. Значение по умолчанию — 10 секунд. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

hot_standby_feedback (boolean)

Определяет, будет ли сервер горячего резерва передавать главному или вышестоящему резервному серверу отчет о запросах, выполняющихся на нем в данный момент. Этот параметр позволяет исключить необходимость отмены запросов, вызванную очисткой записей, но при некоторых рабочих нагрузках может вызвать раздувание базы данных на главном сервере. Сообщения с отчетом о запросах будут отправляться не чаще, чем один раз за интервал, задаваемый параметром wal_receiver_status_interval. Значение по умолчанию — off (выключен). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Если используется каскадная репликация, сообщения о запросах передаются выше, пока в конце концов не достигнут главного сервера. Резервные серверы эту информацию больше никак не используют.

Этот параметр не переопределяет поведение old_snapshot_threshold на главном сервере; снимок на резервном сервере, возраст которого превышает порог, заданный на главном, может стать недействительным, что приведет к отмене транзакций на резервном сервере. Это связано с тем, что параметр old_snapshot_threshold предназначен для указания абсолютного ограничения времени, в течение которого могут накапливаться неиспользуемые строки, которое иначе было бы нарушено из-за конфигурации резервного сервера.

wal_receiver_timeout (integer)

Задает промежуток времени, по истечении которого прерываются неактивные соединения репликации. Это позволяет принимающему резервному серверу обнаружить сбой главного сервера или разрыв сети. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — 60 секунд. При нулевом значении механизм тайм-аута выключается. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

wal_retrieve_retry_interval (integer)

Определяет, как долго резервный сервер должен ждать поступления данных WAL из любых источников (потоковая репликация, локальный pg_wal или архив WAL), прежде чем снова попытаться получить данные WAL. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — 5 секунд. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Этот параметр полезен в конфигурациях, где узлу в процессе восстановления необходимо контролировать время ожидания новых данных WAL. Например, при восстановлении архива можно ускорить реакцию при обнаружении нового файла WAL, уменьшив значение этого параметра. В системе с низкой активностью WAL увеличение этого параметра уменьшает количество запросов, необходимых для доступа к архивам WAL, что полезно, например, в облачных средах, где учитывается число обращений к инфраструктуре.

recovery_min_apply_delay (integer)

По умолчанию резервный сервер восстанавливает записи WAL с передающего сервера в максимально сжатые сроки. Может быть полезно задавать задержку при копировании этих данных, получая тем самым возможность исправить ошибки, связанные с потерей данных. Этот параметр позволяет отложить восстановление на указанное количество времени. Например, если установить для этого параметра значение 5min, резервный сервер будет воспроизводить фиксацию каждой транзакции не раньше, чем через 5 минут (согласно его системным часам) после времени фиксации, сообщенного главным сервером. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — ноль, то есть задержка не добавляется.

Бывает так, что задержка репликации между серверами превышает значение этого параметра, и в этом случае дополнительная задержка не добавляется. Обратите внимание, что задержка рассчитывается как разница между отметкой времени, записанной в WAL на главном сервере, и текущим временем на резервном сервере. Задержки передачи из-за запаздывания в сети или каскадной репликации могут значительно сократить фактическое время ожидания. Если системные часы на главном и резервном серверах не синхронизированы, это может привести к применению записей ранее ожидаемого; но это не столь важно, потому что полезные значения этого параметра намного выше обычной разницы во времени между серверами.

Задержка происходит только в записях WAL для фиксаций транзакций. Все прочие записи воспроизводятся максимально быстро, что не представляет проблемы, поскольку правила видимости MVCC гарантируют, что эффекты этих записей не будут видны до применения соответствующей записи о фиксации транзакции.

Задержка добавляется, когда восстанавливаемая база данных достигает согласованного состояния, и исключается, когда резервный сервер переходит в режим главного. После этого резервный сервер завершает восстановление незамедлительно.

Записи WAL должны храниться на резервном сервере, пока они не будут готовы к применению. Таким образом, более длительные задержки приведут к накапливанию файлов WAL, тем самым увеличивая объем дискового пространства, необходимого для каталога pg_wal резервного сервера.

Этот параметр предназначен для использования в установках с потоковой репликацией; однако если он задан, то будет учитываться во всех случаях, кроме восстановления после сбоя. Заданная этим параметром задержка будет влиять и на параметр hot_standby_feedback, что может привести к раздуванию базы данных на главном сервере; использовать эти параметры одновременно следует с осторожностью.

ПРЕДУПРЕЖДЕНИЕ
Этот параметр влияет на синхронную репликацию, когда для synchronous_commit задано значение remote_apply; каждой команде COMMIT придется ожидать применения.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Подписчики

Эти параметры управляют поведением подписчика логической репликации. На публикующем сервере их значения не играют роли.

Обратите внимание, что параметры конфигурации wal_receiver_timeout, wal_receiver_status_interval и wal_retrieve_retry_interval влияют на рабочие процессы логической репликации.

max_logical_replication_workers (int)

Задает максимально допустимое число рабочих процессов логической репликации. Сюда входят как процессы, применяющие изменения, так и процессы, синхронизирующие таблицы.

Рабочие процессы логической репликации берутся из пула, заданного параметром max_worker_processes.

Значение по умолчанию — 4. Этот параметр можно задать только при запуске сервера.

max_sync_workers_per_subscription (integer)

Максимально допустимое число рабочих процессов, выполняющих синхронизацию, на одну подписку. Этот параметр управляет степенью параллелизма копии исходных данных во время инициализации подписки или при добавлении новых таблиц.

В настоящее время одну таблицу может обрабатывать только один рабочий процесс синхронизации.

Рабочие процессы синхронизации берутся из пула, заданного параметром max_logical_replication_workers.

Значение по умолчанию — 2. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Планирование запросов

Конфигурация метода планирования

Эти параметры конфигурации предоставляют грубый метод воздействия на планы запросов, выбранные оптимизатором запросов. Если выбранный оптимизатором стандартный план конкретного запроса оказался неоптимален, в качестве временного решения можно использовать один из этих параметров конфигурации, чтобы заставить оптимизатор выбрать другой план. К более удачным способам улучшения качества выбираемых оптимизатором планов относятся корректировка констант стоимости для планировщика (см. подраздел Константы стоимости для планировщика), выполнение ANALYZE вручную, увеличение значения параметра конфигурации default_statistics_target, а также увеличение объема статистики, собираемой для отдельных столбцов, с помощью команды ALTER TABLE SET STATISTICS.

enable_async_append (boolean)

Включает или выключает использование планировщиком запросов планов с асинхронным добавлением данных. Значение по умолчанию — on (включен).

enable_bitmapscan (boolean)

Включает или выключает использование планировщиком запросов планов сканирования по битовой карте. Значение по умолчанию — on (включен).

enable_gathermerge (boolean)

Включает или выключает использование планировщиком запросов планов слияния посредством сбора. Значение по умолчанию — on (включен).

enable_hashagg (boolean)

Включает или выключает использование планировщиком запросов планов агрегирования по хешу. Значение по умолчанию — on (включен).

enable_hashjoin (boolean)

Включает или выключает использование планировщиком запросов планов соединения по хешу. Значение по умолчанию — on (включен).

enable_incremental_sort (boolean)

Включает или выключает использование планировщиком запросов шагов с инкрементальной сортировкой. Значение по умолчанию — on (включен).

enable_indexscan (boolean)

Включает или выключает использование планировщиком запросов планов сканирования по индексу. Значение по умолчанию — on (включен).

enable_indexonlyscan (boolean)

Включает или выключает использование планировщиком запросов планов сканирования только по индексу (см. раздел Сканирование только по индексу и покрывающие индексы). Значение по умолчанию — on (включен).

enable_material (boolean)

Включает или выключает использование планировщиком запросов материализации. Полностью убрать материализацию невозможно, но выключение этого параметра не позволит планировщику вставлять узлы материализации, за исключением случаев, когда это требуется для правильности. Значение по умолчанию — on (включен).

enable_memoize (boolean)

Включает или выключает использование планировщиком запросов планов с мемоизацией для кэширования результатов параметризованного сканирования внутри соединений с вложенным циклом. Этот тип плана позволяет пропускать сканирование нижележащих планов, когда результаты для текущих параметров уже находятся в кэше. Менее просматриваемые результаты могут быть удалены из кэша, когда требуется больше места для новых записей. Значение по умолчанию — on (включен).

enable_mergejoin (boolean)

Включает или выключает использование планировщиком запросов планов соединения слиянием. Значение по умолчанию — on (включен).

enable_nestloop (boolean)

Включает или выключает использование планировщиком запросов планов соединения с вложенными циклами. Полностью убрать соединения с вложенными циклами невозможно, но выключение этого параметра не позволит планировщику использовать этот метод, если доступны другие. Значение по умолчанию — on (включен).

enable_parallel_append (boolean)

Включает или выключает использование планировщиком запросов планов с распараллеливанием добавления данных. Значение по умолчанию — on (включен).

enable_parallel_hash (boolean)

Включает или выключает использование планировщиком запросов планов соединения по хешу с распараллеливанием хеширования. Не действует, если не включены планы соединения по хешу. Значение по умолчанию — on (включен).

enable_partition_pruning (boolean)

Включает или выключает в планировщике запросов возможность исключать партиции партиционированной таблиц из планов запросов. Также контролирует способность планировщика генерировать планы запросов, позволяющие исполнителю пропускать (игнорировать) партиции во время выполнения запроса. Значение по умолчанию — on (включен). Подробную информацию см. в подразделе Отсечение партиций.

enable_partitionwise_join (boolean)

Включает или выключает использование планировщиком запросов соединения с учетом партиционирования, что позволяет выполнять соединение партиционированных таблиц путем соединения соответствующих партиций. В настоящее время соединение с учетом партиционирования применяется только в том случае, если в условия соединения входят все ключи разбиения, которые при этом должны быть одного типа данных и иметь точно совпадающие наборы дочерних партиций. Поскольку для планирования соединения с учетом партиционирования может понадобиться гораздо больше процессорного времени и памяти, по умолчанию этот параметр выключен (off).

enable_partitionwise_aggregate (boolean)

Включает или выключает использование планировщиком запросов группировки или агрегирования с учетом партиционирования, что позволяет выполнять группировку или агрегирование в партиционированных таблицах по отдельности для каждой партиции. Если предложение GROUP BY не включает ключи разбиения, на уровне партиций может быть выполнено только частичное агрегирование, а позже должна быть выполнена итоговая обработка. Поскольку для планирования группировки или агрегирования с учетом партиционирования может понадобиться гораздо больше процессорного времени и памяти, по умолчанию этот параметр выключен (off).

enable_seqscan (boolean)

Включает или выключает использование планировщиком запросов планов последовательного сканирования. Полностью убрать последовательное сканирование невозможно, но выключение этого параметра не позволит планировщику использовать этот метод, если доступны другие. Значение по умолчанию — on (включен).

enable_sort (boolean)

Включает или выключает использование планировщиком запросов шагов с явной сортировкой. Полностью убрать явную сортировку невозможно, но выключение этого параметра не позволит планировщику использовать этот метод, если доступны другие. Значение по умолчанию — on (включен).

enable_tidscan (boolean)

Включает или выключает использование планировщиком запросов планов сканирования TID. Значение по умолчанию — on (включен).

Константы стоимости для планировщика

Переменные стоимости, описанные в этом разделе, измеряются в произвольном масштабе. Важны только их относительные значения, поэтому умножение или деление всех переменных на один коэффициент не приведет к изменению выбора планировщика. По умолчанию эти переменные стоимости определяются исходя из стоимости последовательных выборок страниц; то есть seq_page_cost обычно задается равной 1.0, а другие переменные стоимости определяются относительно нее. При желании можно использовать другой масштаб, например фактическое время выполнения запросов в миллисекундах на конкретной машине.

Примечание
К сожалению, нет четко определенного метода определения идеальных значений для переменных стоимости. Лучше всего рассматривать их как средние показатели для всего набора запросов, которые получит конкретная СУБД. Это означает, что менять их на основании всего нескольких экспериментов очень рискованно.

seq_page_cost (floating point)

Задает для планировщика стоимость выборки одной страницы с диска, которая выполняется в серии последовательных выборок. Значение по умолчанию — 1.0. Это значение можно переопределить для таблиц и индексов в определенном табличном пространстве, установив одноименный параметр табличного пространства (см. ALTER TABLESPACE).

random_page_cost (floating point)

Задает для планировщика стоимость непоследовательной выборки одной страницы с диска. Значение по умолчанию — 4.0. Это значение можно переопределить для таблиц и индексов в определенном табличном пространстве, установив одноименный параметр табличного пространства (см. ALTER TABLESPACE).

Уменьшение этого значения относительно seq_page_cost приведет к тому, что система предпочтет сканирование по индексу; при его увеличении сканирование по индексу будет выглядеть сравнительно более затратным. Также оба эти значения можно увеличивать или уменьшать одновременно, тем самым изменяя стоимость операций ввода/вывода на диск относительно стоимости процессорных операций, которая определяется следующими параметрами.

Произвольный доступ к механическому дисковому хранилищу обычно гораздо дороже (более чем в 4 раза), чем последовательный. Однако по умолчанию используется более низкое значение (4.0), поскольку предполагается, что большая часть данных при произвольных обращениях к диску, например при чтении индекса, окажется в кэше. Значение по умолчанию можно рассматривать как моделирование ситуации, когда произвольный доступ в 40 раз медленнее последовательного, и при этом ожидается, что 90% операций произвольного чтения будут кэшироваться.

Если вы считаете, что для вашей рабочей нагрузки 90% является неверным допущением, вы можете увеличить параметр random_page_cost, чтобы он лучше отражал реальную стоимость произвольного чтения из хранилища. Соответственно, если ваши данные могут полностью поместиться в кэше, например, когда объем базы данных меньше общего объема памяти сервера, целесообразно будет уменьшить значение random_page_cost. Хранилище, у которого стоимость произвольного чтения низкая относительно последовательного, например у твердотельных накопителей, также лучше будет смоделировать с более низким значением random_page_cost, например 1.1.

Совет
Хотя система позволит вам установить random_page_cost меньше, чем seq_page_cost, это лишено физического смысла. Однако сделать их равными имеет смысл, если база данных полностью кэшируется в ОЗУ, поскольку в этом случае непоследовательное обращение к страницам не вызовет дополнительных издержек. Кроме того, в сильно кэшированной базе данных оба этих параметра необходимо снизить относительно параметров ЦП, поскольку стоимость извлечения страницы, уже находящейся в ОЗУ, намного меньше, чем обычно.

cpu_tuple_cost (floating point)

Задает для планировщика стоимость обработки каждой строки при выполнении запроса. Значение по умолчанию — 0.01.

cpu_index_tuple_cost (floating point)

Задает для планировщика стоимость обработки каждой записи индекса при сканировании индекса. Значение по умолчанию — 0.005.

cpu_operator_cost (floating point)

Задает для планировщика стоимость обработки каждой команды или функции, выполняемых при запросе. Значение по умолчанию — 0.0025.

parallel_setup_cost (floating point)

Задает для планировщика стоимость запуска параллельных рабочих процессов. Значение по умолчанию — 1000.

parallel_tuple_cost (floating point)

Задает для планировщика стоимость переноса одного кортежа из параллельного рабочего процесса в другой процесс. Значение по умолчанию — 0.1.

min_parallel_table_scan_size (integer)

Задает минимальный объем данных таблицы, подлежащий сканированию, при котором можно применить параллельное сканирование. При параллельном последовательном сканировании объем сканируемых данных таблицы всегда равен объему таблицы, но при использовании индексов этот объем обычно бывает меньше. Если это значение указано без единиц измерения, оно считается заданным в блоках, размер которых равен BLCKSZ байт (обычно это 8 КБ). Значение по умолчанию — 8 мегабайт (8MB).

min_parallel_index_scan_size (integer)

Задает минимальный объем данных индекса, подлежащий сканированию, при котором можно применить параллельное сканирование. Обратите внимание, что параллельное сканирование индекса обычно не затрагивает весь индекс; это число страниц, которое, по мнению планировщика, будет действительно затронуто при сканировании. Также этот параметр применяется для того, чтобы определить, может ли конкретный индекс участвовать в параллельной очистке. См. VACUUM. Если это значение указано без единиц измерения, оно считается заданным в блоках, размер которых равен BLCKSZ байт (обычно это 8 КБ). Значение по умолчанию — 512 килобайт (512kB).

effective_cache_size (integer)

Определяет предположение планировщика об эффективном размере дискового кэша, доступном для одного запроса. Это предположение учитывается при оценке стоимости использования индекса; чем выше это значение, тем больше вероятность того, что будет применено сканирование по индексу, чем ниже, тем больше вероятность того, что будет применено последовательное сканирование. При установке этого параметра следует учитывать как объем общих буферов QHB, так и процент дискового кэша ядра, который будут занимать файлы данных QHB, хотя некоторые данные могут находиться и там, и там. Также примите во внимание ожидаемое количество параллельных запросов к разным таблицам, так как им придется совместно использовать доступное пространство. Этот параметр не влияет на размер разделяемой памяти, выделяемой QHB, и не задает размер резервируемого дискового кэша ядра; он используется только для ориентировочной оценки. Кроме того, система не предполагает, что данные могут оставаться в дисковом кэше между запросами. Если это значение указано без единиц измерения, оно считается заданным в блоках, размер которых равен BLCKSZ байт (обычно это 8 КБ). Значение по умолчанию — 4 гигабайта (4GB). (Если BLCKSZ не равен 8 КБ, значение по умолчанию масштабируется пропорционально ему).

jit_above_cost (floating point)

Задает предел стоимости запроса, при превышении которого активируется JIT-компиляция, если она включена (см. главу JIT-компиляция). Применение JIT занимает время при планировании, но может ускорить выполнение запроса. При значении -1 JIT-компиляция выключается. Значение по умолчанию — 100000.

jit_inline_above_cost (floating point)

Задает предел стоимости запроса, при превышении которого в процессе JIT-компиляции возможно встраивание функций и операторов. Встраивание увеличивает время планирования, но может ускорить выполнение. Присваивать этому параметру значение меньше, чем jit_above_cost, не имеет смысла. При значении -1 встраивание выключается. Значение по умолчанию — 500000.

jit_optimize_above_cost (floating point)

Задает предел стоимости запроса, при превышении которого в процессе JIT-компиляции будут применяться дорогостоящие оптимизации. Такие оптимизации увеличивают время планирования, но могут ускорить выполнение. Присваивать этому параметру значение меньше, чем jit_above_cost, не имеет смысла, а значение больше, чем jit_inline_above_cost, вряд ли даст положительный эффект. При значении -1 дорогостоящие оптимизации выключаются. Значение по умолчанию — 500000.

Параметры генетического оптимизатора запросов

Генетический оптимизатор запросов (Genetic Query Optimizer, GEQO) — это алгоритм, который выполняет планирование запросов с помощью эвристического поиска. Это сокращает время планирования сложных запросов (тех, в которых соединяется множество отношений) за счет создания планов, иногда уступающих по качеству планам, выявляемым обычным алгоритмом полного перебора. Дополнительную информацию см. в главе Генетический оптимизатор запросов.

geqo (boolean)

Включает или выключает генетическую оптимизацию запросов. По умолчанию она включена. В производственной среде ее лучше не выключать; более детальное управление GEQO дает переменная geqo_threshold.

geqo_threshold (integer)

Задает минимальное число элементов во FROM, при котором для планирования запросов будет использоваться генетический оптимизатор запросов. (Обратите внимание, что конструкция FULL OUTER JOIN считается одним элементом FROM). Значение по умолчанию — 12. Для более простых запросов, как правило, лучше использовать обычный планировщик, выполняющий полный перебор, но для запросов со многими таблицами полный перебор занимает слишком много времени, зачастую больше, чем тратится на выполнение неоптимального плана. Таким образом, ограничение размера запроса является удобным способом управления GEQO.

geqo_effort (integer)

Управляет балансом между временем планирования и качеством плана запроса в GEQO. Эта переменная должна задаваться целым числом в диапазоне от 1 до 10. Значение по умолчанию — пять. Значения выше этого увеличивают время, затрачиваемое на планирование запросов, но при этом повышают вероятность выбора эффективного плана запросов.

В действительности параметр geqo_effort сам по себе ничего не делает; он используется только для вычисления значений по умолчанию для других переменных, влияющих на поведение GEQO (они описаны ниже). При желании эти параметры можно задать вручную.

geqo_pool_size (integer)

Управляет размером пула, используемого GEQO, то есть числом особей в генетической популяции. Это число должно быть не меньше двух, а полезные значения обычно лежат в диапазоне от 100 до 1000. Если оно равно нулю (значение по умолчанию), то подходящее число выбирается, исходя из значения geqo_effort и количества таблиц в запросе.

geqo_generations (integer)

Управляет количеством поколений, используемых GEQO, то есть числом итераций этого алгоритма. Это число должно быть не меньше одного, а полезные значения лежат в том же диапазоне, что и размер пула. Если оно равно нулю (значение по умолчанию), то подходящее число выбирается, исходя из значения geqo_pool_size.

geqo_selection_bias (floating point)

Управляет смещением выбора, используемым GEQO. Смещение выбора — это избирательное давление в популяции. Допустимые значения лежат в диапазоне от 1.50 до 2.00 (это значение по умолчанию).

geqo_seed (floating point)

Управляет начальным значением генератора случайных чисел, используемого GEQO для выбора случайных путей в пространстве поиска порядка соединений. Значение может варьироваться от нуля (по умолчанию) до единицы. Изменение значения меняет набор анализируемых путей соединения и может привести к тому, что будет найден либо более, либо менее оптимальный путь.

Другие параметры планировщика

default_statistics_target (integer)

Задает целевой показатель статистики по умолчанию для столбцов таблицы, у которых командой ALTER TABLE SET STATISTICS не заданы целевые значения. Большие значения увеличивают время, необходимое для выполнения ANALYZE, но при этом могут улучшить качество оценок планировщика. Значение по умолчанию — 100. Дополнительную информацию об использовании статистики планировщиком запросов QHB см. в разделе Статистика, используемая планировщиком.

constraint_exclusion (enum)

Управляет использованием в планировщике запросов ограничений таблицы для оптимизации запросов. Допустимые значения constraint_exclusion: on (включен) — проверять ограничения для всех таблиц, off (выключен) — никогда не проверять ограничения и partition (партиции) — проверять ограничения только для дочерних таблиц и подзапросов UNION ALL. По умолчанию установлен вариант partition. Он часто применяется с традиционными деревьями наследования для улучшения производительности.

Когда данный параметр разрешает это для конкретной таблицы, планировщик сравнивает условия запроса с ограничениями CHECK этой таблицы и не сканирует ее, если условия противоречат ограничениям. Например:

CREATE TABLE parent(key integer, ...);
CREATE TABLE child1000(check (key between 1000 and 1999)) INHERITS(parent);
CREATE TABLE child2000(check (key between 2000 and 2999)) INHERITS(parent);
...
SELECT * FROM parent WHERE key = 2400;

При включенном исключении по ограничению команда SELECT вообще не будет сканировать таблицу child1000, тем самым улучшая производительность.

В настоящее время исключение по ограничению включено по умолчанию только для сценариев, часто используемых для реализации партиционирования таблиц через деревья наследования. Включение его для всех таблиц приведет к дополнительным издержкам на планирование, довольно заметным при простых запросах и чаще всего не дающим никакого выигрыша. Если у вас нет таблиц, партиционированных через традиционную иерархию наследования, имеет смысл полностью выключить это ограничение. (Обратите внимание, что похожая возможность для партиционированных таблиц управляется отдельным параметром enable_partition_pruning.)

Дополнительную информацию об использовании исключения по ограничению для реализации партиционирования см. в подразделе Партиционирование и исключение по ограничению.

cursor_tuple_fraction (floating point)

Задает для планировщика оценку процента строк, которые будут извлечены через курсор. Значение по умолчанию — 0.1. При уменьшении значения этого параметра планировщик будет склонен использовать для курсоров планы «быстрого запуска», которые будут быстро извлекать первые несколько строк, хотя для извлечения всех строк потребуется много времени. При увеличении значения акцент сместится в сторону оптимизации общего расчетного времени запроса. При максимальном значении, равном 1,0, работа с курсорами планируется так же, как и обычные запросы, с учетом только общего расчетного времени, а не скорости доставки первых строк.

from_collapse_limit (integer)

Задает максимальное число элементов в итоговом списке FROM, до которого планировщик будет объединять подзапросы с запросами верхнего уровня. При меньших значениях сокращается время планирования, но планы запросов могут стать менее эффективными. Значение по умолчанию — восемь. Дополнительную информацию см. в разделе Управление планировщиком с помощью явных предложений JOIN.

Если сделать это значение равным geqo_threshold или больше, то может включиться планировщик GEQO и в результате будут получены неоптимальные планы. См. подраздел Параметры генетического оптимизатора запросов.

jit (boolean)

Определяет, может ли QHB задействовать JIT-компиляцию, если та поддерживается (см. главу JIT-компиляция). Значение по умолчанию — on (включен).

join_collapse_limit (integer)

Задает максимальное число элементов в списке FROM, до достижения которого планировщик будет переписывать в него явные конструкции JOIN (кроме FULL JOIN). При меньших значениях сокращается время планирования, но планы запросов могут стать менее эффективными.

По умолчанию значение этой переменной равно значению from_collapse_limit, что приемлемо в большинстве случаев. При значении 1 порядок явных предложений JOIN меняться не будет. Таким образом, явно заданный в запросе порядок соединений определит фактический порядок, в котором будут соединяться отношения. Поскольку планировщик запросов не всегда выбирает оптимальный порядок соединений, опытные пользователи могут временно задать для этой переменной значение 1, а затем явно указать желаемый порядок соединения. Дополнительную информацию см. в разделе Управление планировщиком с помощью явных предложений JOIN.

Если сделать это значение равным geqo_threshold или больше, то может включиться планировщик GEQO и в результате будут получены неоптимальные планы. См. подраздел Параметры генетического оптимизатора запросов.

plan_cache_mode (enum)

Подготовленные операторы (явно подготовленные или неявно сгенерированные, например, в PL/pgSQL) могут выполняться с использованием специализированных или общих планов. Специализированные планы создаются заново для каждого выполнения с определенным набором значений параметров, в то время как общие планы не зависят от значений параметров и могут использоваться повторно. Таким образом, общий план экономит время планирования, но может быть неэффективным, если идеальный план во многом зависит от значений параметров. Выбор между этими параметрами обычно производится автоматически, но его можно переопределить с помощью параметра plan_cache_mode. Допустимые значения: auto (по умолчанию), force_custom_plan (принудительно использовать специализированные планы) и force_generic_plan (принудительно использовать общие планы). Этот параметр учитывается при выполнении кэшированного плана, а не при его подготовке. Дополнительную информацию см. на справочной странице команды PREPARE.

recursive_worktable_factor (floating point)

Задает для планировщика оценку среднего размера рабочей таблицы рекурсивного запроса как множитель предполагаемого размера начальной нерекурсивной части запроса. Это помогает планировщику выбрать наиболее подходящий метод соединения этой рабочей таблицы с другими таблицами запроса. Значение по умолчанию — 10.0. Меньшее значение, например 1.0, может быть полезно, когда рекурсия слабо «разветвляется» от одного шага к другому, как, например, в запросах кратчайшего пути. Значения же более высокие, чем по умолчанию, могут принести пользу в запросах по аналитической обработке графов.

Регистрация ошибок и протоколирование

Куда протоколировать

log_destination (string)

QHB поддерживает несколько методов протоколирования сообщений сервера, включая stderr, csvlog, jsonlog и syslog. Значение этого параметра указывается в виде списка желаемых расположений журнала, разделенных запятыми. По умолчанию сообщения сервера протоколируются только в stderr. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Если в log_destination входит метод csvlog, то записи журнала выводятся в формате «значения, разделенные запятыми» (comma separated value, CSV), что удобно для загрузки журналов в программы. Подробную информацию см. в подразделе Использование вывода журнала в формате CSV. Для генерации вывода журнала в формате CSV должен быть включен logging_collector.

Если в log_destination входит метод jsonlog, то записи журнала выводятся в формате JSON, что удобно для загрузки журналов в программы. Подробную информацию см. в подразделе Использование вывода журнала в формате JSON. Для генерации вывода журнала в формате JSON должен быть включен logging_collector.

Если в список включены методы stderr, csvlog или jsonlog, то создается файл current_logfiles для записи местоположения файла (или файлов) журнала, который в данный момент используется сборщиком сообщений, и соответствующего места назначения. Это обеспечивает удобный способ поиска журналов, используемых в данный момент экземпляром сервера. Пример содержимого такого файла:

stderr log/qhb.log
csvlog log/qhb.csv
jsonlog log/qhb.json

current_logfiles воссоздается, когда при ротации создается новый файл журнала и когда повторно загружается log_destination. Он удаляется, когда в log_destination не входят ни stderr, ни csvlog и когда сборщик сообщений выключен.

Примечание
В большинстве систем Unix потребуется изменить конфигурацию демона syslog, чтобы использовать вариант syslog для log_destination. QHB может протоколировать с помощью syslog-программ с LOCAL0 по LOCAL7 (см. syslog_facility), но на большинстве платформ конфигурация syslog по умолчанию не учитывает сообщения такого типа. Чтобы это работало, понадобится добавить в конфигурацию демона syslog что-то вроде этого:

local0.* /var/log/qhb

logging_collector (boolean)

Этот параметр включает сборщик сообщений, который является фоновым процессом, собирающим сообщения, отправленные в stderr, и перенаправляющим их в файлы журнала. Такой подход зачастую более полезен, чем запись в syslog, поскольку некоторые типы сообщений могут не отображаться в выводе syslog. (Один из типичных примеров — сообщения об ошибках динамического компоновщика; другой — сообщения об ошибках в скриптах типа archive_command). Этот параметр можно задать только при запуске сервера.

Примечание
Можно обойтись без сборщика сообщений и просто протоколировать в stderr; сообщения журнала будут отправляться туда, куда направлен поток серверного stderr. Однако этот метод подходит только для небольших объемов протоколирования, поскольку не предоставляет удобного способа ротации файлов журналов. Кроме того, на некоторых платформах выключение сборщика сообщений может привести к потере или искажению выходных данных журнала, так как несколько процессов, одновременно пишущих в один файл журнала, могут перезаписывать сообщения друг друга.

Примечание
Сборщик сообщений спроектирован так, чтобы никогда не терять сообщения. Это означает, что при крайне высокой нагрузке серверные процессы могут быть заблокированы при попытке отправить дополнительные сообщения, если сборщик отстал. С другой стороны, syslog предпочитает удалять сообщения, если не может их записать, то есть в таких случаях часть сообщений может потеряться, но остальная часть системы не будет блокироваться.

log_directory (string)

При включенном logging_collector этот параметр определяет каталог, в котором будут создаваться файлы журнала. Его можно задавать как абсолютный путь или относительный от каталога данных кластера. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Значение по умолчанию — log.

log_filename (string)

При включенном logging_collector этот параметр задает имена создаваемых файлов журнала. Значение рассматривается как шаблон функции strftime, поэтому в нем можно использовать спецификаторы % для включения в имена файлов даты и времени. (Обратите внимание, что при наличии зависящих от часового пояса спецификаторов % вычисления будут выполняться в поясе, заданном в параметре log_timezone). Поддерживаемые спецификаторы % аналогичны тем, что перечислены в описании strftime спецификации Open Group. Обратите внимание, что системная функция strftime напрямую не используется, поэтому нестандартные, специфичные для платформы расширения не работают. Значение по умолчанию — qhb-%Y-%m-%d_%H%M%S.log.

Если вы указываете имя файла без спецификаторов, то во избежание заполнения всего диска следует запланировать использование утилиты ротации файлов журналов.

Если в log_destination включен вывод в формате CSV, то к имени файла журнала с меткой времени будет добавлено расширение .csv. (Если log_filename оканчивается на .log, то это расширение заменяется на .csv.)

Если в log_destination включен вывод в формате JSON, то к имени файла журнала с меткой времени будет добавлено расширение .json. (Если log_filename оканчивается на .log, то это расширение заменяется на .json.)

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

log_file_mode (integer)

В системах Unix при включенном logging_collector этот параметр задает права доступа к файлам журнала. Значение параметра должно быть числовым, введенным в формате, принимаемом системными командами chmod и umask. (Для обычного восьмеричного формата число должно начинаться с 0 (нуля).)

Право доступа по умолчанию — 0600, т. е. читать или писать в файлы журнала может только владелец сервера. Другое часто используемое полезное значение — 0640, позволяющее читать файлы членам группы владельца. Однако обратите внимание, что для установки такого значения нужно перенести каталог для хранения файлов журнала (log_directory) за пределы каталога данных кластера. В любом случае неразумно открывать для всех доступ на чтение файлов журналов, поскольку они могут содержать конфиденциальные данные.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

log_rotation_age (integer)

При включенном logging_collector этот параметр определяет максимальное время использования отдельного файла журнала, после которого будет создан новый файл. Если это значение указано без единиц измерения, оно считается заданным в минутах. Значение по умолчанию — 24 часа. При нулевом значении повременное создание новых файлов журнала выключается. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

log_rotation_size (integer)

При включенном logging_collector этот параметр определяет максимальный размер отдельного файла журнала. По достижении этого размера будет создан новый файл. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. Значение по умолчанию — 10 мегабайт. При нулевом значении поразмерное создание новых файлов журнала выключается. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

log_truncate_on_rotation (boolean)

При включенном logging_collector этот параметр заставит QHB опустошать (перезаписывать) существующие файлы журнала с одинаковым именем, а не дописывать в них. Однако опустошение будет происходить только при открытии нового файла вследствие ротации по времени, а не при запуске сервера или ротации по размеру. При выключенном параметре в любом случае будет происходить добавление записей в уже существующий файл. Например, использование этого параметра в сочетании с log_filename равным qhb-%H.log приведет к генерации 24-часовых файлов журнала, которые будут циклически перезаписываться. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Пример: для хранения файлов журнала в течение 7 дней, по одному файлу на каждый день с именами вида server_log.Mon, server_log.Tue и т. д., и автоматической перезаписью файлов журнала прошлой недели на эту неделю, нужно установить в log_filename значение server_log.%a, в log_truncate_on_rotation — on и в log_rotation_age — 1440.

Пример: для хранения файлов журнала в течение 24 часов, по одному файлу на каждый час, но также с возможностью переключения файла раньше этого срока при превышении размера 1 ГБ, нужно установить в log_filename значение server_log.%H%M, в log_truncate_on_rotation — on, в log_rotation_age — 60 и в log_rotation_size — 1000000. Добавление %M в log_filename позволит при ротации по размеру указать имя файла, отличное от исходного имени файла данного часа.

syslog_facility (enum)

При включенном протоколировании в syslog этот параметр определяет значение «facility» (syslog-программы), которая будет использоваться. Допустимые значения: LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7; значение по умолчанию — LOCAL0. Подробную информацию см. в документации на системный демон syslog. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

syslog_ident (string)

При включенном протоколировании в syslog этот параметр определяет имя программы, которое будет использоваться в журналах syslog для идентификации сообщений QHB. Имя по умолчанию — qhb. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

syslog_sequence_numbers (boolean)

При включенном протоколировании в syslog и включении этого параметра (по умолчанию), все сообщения будут предваряться последовательно возрастающими номерами (например, [2]). Это позволяет обойти подавление повторов «--- last message repeated N times ---» (последнее сообщение повторилось N раз), которое по умолчанию осуществляется во многих реализациях syslog. В более современных реализациях syslog подавление повторных сообщений можно настроить (например, в rsyslog есть директива $RepeatedMsgReduction), так что это может оказаться необязательно. Кроме того, если вам действительно нужно, чтобы повторы подавлялись, этот параметр можно выключить.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

syslog_split_messages (boolean)

При включенном протоколировании в syslog этот параметр определяет способ передачи сообщений. Если этот параметр включен (по умолчанию), сообщения разделяются на строки, а длинные строки разбиваются так, чтобы уложиться в 1024 байта, что является типичным ограничением размера для традиционных реализаций syslog. Когда он выключен, сообщения сервера QHB передаются в службу syslog как есть, и она должна сама корректно справляться с потенциально громоздкими сообщениями.

Если в итоге syslog протоколирует в текстовый файл, результат в любом случае будет одинаковым, и лучше оставить этот параметр включенным, поскольку большинство реализаций syslog либо не могут обрабатывать большие сообщения, либо требуют для этого специальной настройки. Но если в итоге syslog записывает данные в какую-то другую среду, возможно, будет необходимо или более полезно сохранять логическую целостность сообщений.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

event_source (string)

При включенном протоколировании в журнал событий этот параметр определяет имя программы, которое будет использоваться в нем для идентификации сообщений QHB. Имя по умолчанию — QHB. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Когда протоколировать

log_min_messages (enum)

Определяет уровень важности сообщений, записываемых в журнал сервера. Допустимые значения: DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL и PANIC. Каждый из перечисленных уровней включает в себя все уровни, идущие после него. Чем дальше в этом списке уровень сообщения, тем меньше сообщений отправляется в журнал. Значение по умолчанию — WARNING. Обратите внимание, что здесь у LOG другая позиция, нежели в параметре client_min_messages. Только суперпользователи Enables updating of the process title every time a new SQL command is received by the server. This setting defaults to on on most platforms, but it defaults to off on Windows due to that platform's larger overhead for updating the process title. Only superusers and users with the appropriate SET privilege can change this setting. могут изменять этот параметр.

log_min_error_statement (enum)

Определяет, какие команды SQL, вызвавшие ошибку, записываются в журнал сервера. Текущая команда SQL вносится в запись журнала, если сообщение имеет указанный уровень важности или выше. Допустимые значения: DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL и PANIC. По умолчанию установлено ERROR, то есть в журнал будут записываться команды, завершившиеся сообщением с уровнем важности ERROR, LOG, FATAL и PANIC. Чтобы действительно выключить запись команд с ошибками, установите для этого параметра значение PANIC. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

log_min_duration_statement (integer)

Инициирует запись в журнал продолжительности выполнения каждой завершенной команды, если та работала в течение указанного времени или дольше. Например, если установить значение 250ms, в журнал будут записаны все команды SQL, выполняющиеся 250 миллисекунд и дольше. С помощью этого параметра можно отслеживать неоптимизированные запросы в приложениях. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. При нулевом значении записывается продолжительность выполнения всех команд. Значение -1 (по умолчанию) выключает запись продолжительности выполнения команд. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Этот параметр переопределяет log_min_duration_sample, то есть запросы с продолжительностью выше значения этого параметра не подвергаются выборке и всегда протоколируются.

Для клиентов, использующих расширенный протокол запросов, будет записываться продолжительность каждой фазы (разбор, связывание и выполнение).

Примечание
При использовании этого параметра совместно с log_statement текст команд, которые записываются вследствие использования log_statement, не будет дублироваться в сообщении о продолжительности выполнения. Если вы не пользуетесь syslog, рекомендуется включить в log_line_prefix идентификатор процесса или сеанса, чтобы с их помощью можно было связать текст команды с сообщением о продолжительности выполнения, которое появится позже.

log_min_duration_sample (integer)

Позволяет сделать выборку по продолжительности завершенных команд, выполнявшихся в течение как минимум указанного промежутка времени. При этом в журнал вносятся такие же записи, как и при включенном log_min_duration_statement, но только для подмножества выполнившихся команд, определенного параметром log_statement_sample_rate. Например, если задать значение 100ms, то для выборки будут отобраны все команды SQL, выполняющиеся 100 мс и дольше. Включение этого параметра может быть полезно, когда трафик слишком высок, чтобы протоколировать все запросы. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. При нулевом значении производится выборка команд с любой продолжительностью. Значение -1 (по умолчанию) выключает выборку по продолжительности. Только суперпользователи и пользователи с подходящим правом SET могут изменить этот параметр.

Приоритет этого параметра ниже, чем у log_min_duration_statement, то есть команды с продолжительностью выше значения log_min_duration_statement не подвергаются выборке и всегда протоколируются.

Другие примечания, касающиеся log_min_duration_statement, применимы и к этому параметру.

log_statement_sample_rate (floating point)

Определяет долю команд с продолжительностью, превысившей log_min_duration_sample, которая будет протоколироваться в журнал. Выборка носит стохастический характер, например, значение 0.5 означает, что статистически шанс попадания любой отдельно взятой команды в журнал равен один к двум. Значение по умолчанию — 1.0, то есть протоколируются все выбранные команды. При нулевом значении выключается протоколирование команд из выборки по длительности, так же как при установке в log_min_duration_sample значения -1. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

log_transaction_sample_rate (real)

Задает долю транзакций, команды из которых будут записываться в журнал помимо команд, записываемых по другим причинам. Этот параметр действует на все новые транзакции, независимо от продолжительности выполнения их команд. Выборка носит стохастический характер, например, значение 0.1 означает, что статистически шанс попадания любой отдельно взятой транзакции в журнал равен один к десяти. Этот параметр полезен для создания выборки транзакций. Значение по умолчанию — 0, то есть команды из транзакций дополнительно не записываются. При значении 1 записываются все команды из всех транзакций. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Примечание
Как и все параметры, управляющие протоколированием команд, этот параметр может значительно увеличить издержки.

log_startup_progress_interval (integer)

Задает интервал времени, по истечении которого процесс запуска будет протоколировать сообщение о все еще выполняющейся длительной операции, а также интервал между последующими сообщениями о ходе выполнения этой операции. Значение по умолчанию — 10 секунд. Значение 0 отключает эту функциональность. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Заданный интервал применяется к каждой операции в отдельности. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Например, если синхронизация каталога данных занимает 25 секунд, а последующий сброс нежурналируемых отношений — 8 секунд, и если для этого параметра установлено значение по умолчанию в 10 секунд, то будут протоколироваться сообщения о синхронизации каталога данных сначала через 10, а потом через 20 секунд после ее начала, но о сбросе нежурналируемых отношений никакие сообщения протоколироваться не будут.

В Таблице 2 расшифровываются уровни важности сообщений, используемые в QHB. Также здесь показано, как эти уровни переводятся в системные при отправке выходных данных журнала в syslog.

Таблица 2. Уровни важности сообщений

Важность | Использование | syslog
DEBUG1..DEBUG5 | Предоставляет более подробную информацию для разработчиков. Чем больше номер, тем подробнее. | DEBUG
INFO | Предоставляет неявно запрошенную пользователем информацию, например вывод команды VACUUM VERBOSE. | INFO
NOTICE | Предоставляет информацию, которая может быть полезна пользователям, например замечание об усечении длинных идентификаторов. | NOTICE
WARNING | Предоставляет предупреждения о возможных проблемах, например COMMIT вне блока транзакции. | NOTICE
ERROR | Сообщает об ошибке, из-за которой прервана текущая команда. | WARNING
LOG | Сообщает информацию, интересующую администраторов, например выполнение контрольных точек. | INFO
FATAL | Сообщает об ошибке, из-за которой прерван текущий сеанс. | ERR
PANIC | Сообщает об ошибке, из-за которой прерваны все сеансы базы данных. | CRIT

Что протоколировать

Примечание
Данные, которые вы выбираете для протоколирования, могут привести к нарушениям безопасности; см. раздел Обслуживание файлов журнала.

application_name (string)

Параметр application_name может быть любой строкой длиной не более NAMEDATALEN символов (64 символа при стандартной сборке). Обычно устанавливается приложением при подключении к серверу. Значение (имя) будет отображено в представлении pg_stat_activity и включено в записи журнала при использовании формата CSV. Также его можно добавить в записи журнала в обычных форматах с помощью параметра log_line_prefix. Значение application_name может содержать только печатные символы ASCII. Прочие символы будут заменены на вопросительные знаки (?).

debug_print_parse (boolean)
debug_print_rewritten (boolean)
debug_print_plan (boolean)

Эти параметры включают вывод различных данных по отладке. При этом выводится результирующее дерево разбора, данные механизма переписывания запросов или план выполнения для каждого выполненного запроса соответственно. Эти сообщения имеют уровень LOG, поэтому по умолчанию они появляются в журнале сервера, но не отправляются клиенту. Это можно изменить, настроив client_min_messages и/или log_min_messages. По умолчанию эти параметры выключены.

debug_pretty_print (boolean)

Этот параметр включает выравнивание сообщений, созданных debug_print_parse, debug_print_rewritten или debug_print_plan. В результате сообщения становятся более удобочитаемыми, но гораздо более длинными, чем при формате «compact», используемом, когда debug_pretty_print выключен. По умолчанию этот параметр включен.

log_autovacuum_min_duration (integer)

Включает протоколирование в журнал каждого действия, выполняемого автоочисткой, если оно продолжается не менее указанного в параметре времени. При нулевом значении протоколируются все действия автоочистки. Значение -1 (по умолчанию) выключает протоколирование действий автоочистки. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Например, если задать значение 250ms, то в журнал будут протоколироваться все операции автоматической очистки и анализа, выполняемые 250 мс и дольше. Кроме того, когда в этом параметре установлено любое значение, отличное от -1, будет протоколироваться сообщение в случае пропуска действия автоочистки вследствие конфликтующей блокировки или параллельного удаления отношения. Значение по умолчанию — 10min (10 минут). Включение этого параметра может быть полезно для отслеживания активности автоочистки. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера; при этом его можно переопределить для отдельных таблиц, изменив их параметры хранения.

log_checkpoints (boolean)

Включает протоколирование выполнения контрольных точек и точек перезапуска сервера. Помимо этого в сообщения включается некоторая статистическая информация, в том числе количество записанных буферов и время, потраченное на их запись. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. По умолчанию этот параметр включен.

log_connections (boolean)

Включает протоколирование всех попыток подключения к серверу, а также успешного завершения аутентификации (при необходимости) и авторизации клиента. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр, причем только в начале сеанса. Значение по умолчанию — off (выключен).

Примечание
Некоторые программы-клиенты, например psql, осуществляют две попытки подключения, при первой попытке определяя, требуется ли пароль, поэтому дублирование сообщения «connection received» не обязательно указывает на наличие проблемы.

log_disconnections (boolean)

Включает протоколирование всех завершений сеанса. В журнал выводится примерно та же информация, что и с log_connections, плюс продолжительность сеанса. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр, причем только в начале сеанса. Значение по умолчанию — off (выключен).

log_duration (boolean)

Включает протоколирование продолжительности всех выполненных команд. Значение по умолчанию — off (выключен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Для клиентов, использующих расширенный протокол запросов, будет записываться продолжительность каждой фазы (разбор, связывание и выполнение).

Примечание
Разница между включением log_duration и установкой нулевого значения для log_min_duration_statement заключается в том, что при превышении значения log_min_duration_statement протоколируется текст запроса, а при включении этого параметра — нет. Таким образом, если log_duration равен on (включен) и у log_min_duration_statement положительное значение, продолжительность протоколируется для всех команд, но текст запроса добавляется только для команд с продолжительностью, превышающей пороговое значение. Такое поведение может быть полезным при сборе статистики в инсталляциях с высокой нагрузкой.

log_error_verbosity (enum)

Управляет объемом подробной информации, записываемой в журнал сервера для каждого протоколируемого сообщения. Допустимые значения: TERSE, DEFAULT и VERBOSE, каждое из которых добавляет дополнительные поля к отображаемым сообщениям. С уровнем TERSE из сообщений об ошибке исключаются поля DETAIL, HINT, QUERY и CONTEXT. С VERBOSE в сообщение включается код ошибки SQLSTATE (см. раздел Коды ошибок QHB), а также имя файла с исходным кодом, имя функции и номер строки, которая вызвала ошибку. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

log_hostname (boolean)

По умолчанию в сообщениях журнала подключений отображается только IP-адрес подключившегося хоста. При включении этого параметра дополнительно будет протоколироваться и имя хоста. Обратите внимание, что, в зависимости от настройки разрешения имен вашего хоста, это может привести к значительному снижению производительности. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

log_line_prefix (string)

Это строка в стиле функции printf, которая выводится в начале каждой строки журнала. С символов % начинаются «управляющие последовательности», которые заменяются информацией о состоянии, как описано ниже. Нераспознанные последовательности игнорируются. Остальные символы напрямую копируются в строку журнала. Некоторые управляющие последовательности распознаются только сеансовыми процессами и будут игнорироваться фоновыми процессами, например основным процессом сервера. Информацию о состоянии можно выровнять влево или вправо, указав число после % и перед этой строкой. При отрицательном значении информация о состоянии будет дополнена до минимально допустимой ширины пробелами справа, а при положительном — слева. Заполнение может быть полезно для облегчения восприятия человеком файлов журнала.

Этот параметр можно установить только в файле qhb.conf или в командной строке сервера. По умолчанию установлено значение '%m [%p] ', при котором протоколируется метка времени и идентификатор процесса.

Последовательность | Действие | Только для сеанса
%a | Имя приложения | да
%u | Имя пользователя | да
%d | Имя базы данных | да
%r | Имя удаленного хоста или IP-адрес, а также номер удаленного порта | да
%h | Имя удаленного хоста или IP-адрес | да
%b | Тип обслуживающего процесса | нет
%p | Идентификатор процесса | нет
%P | Идентификатор ведущего процесса параллельной группы, если этот процесс является рабочим процессом параллельного запроса | нет
%t | Метка времени без миллисекунд | нет
%m | Метка времени с миллисекундами | нет
%n | Метка времени с миллисекундами (в виде времени Unix) | нет
%i | Тег команды: тип текущей команды сеанса | да
%e | Код ошибки SQLSTATE | нет
%c | Идентификатор сеанса: см. ниже | нет
%l | Номер строки журнала для каждого сеанса или процесса, начиная с 1 | нет
%s | Метка времени начала процесса | нет
%v | Виртуальный идентификатор транзакции (backendID/localXID) | нет
%x | Идентификатор транзакции (0, если не присвоен) | нет
%q | Не выводит никаких данных, но указывает несеансовым процессам остановиться в этой точке строки; игнорируется сеансовыми процессами | нет
%Q | Идентификатор текущего запроса. Идентификаторы запросов не вычисляются по умолчанию, поэтому в данном поле будет выводиться ноль, если не включен параметр compute_query_id или не настроен сторонний модуль, вычисляющий идентификаторы запросов. | да
%% | Выводит символ % | нет

Тип обслуживающего процесса соответствует столбцу backend_type в представлении pg_stat_activity, но в журнале могут присутствовать и другие типы, которые не показываются в этом представлении.

Последовательность %c выводит квазиуникальный идентификатор сеанса, состоящий из двух 4-байтных шестнадцатеричных чисел (без начальных нулей), разделенных точкой. Числа — это время начала процесса и его идентификатор, поэтому %c также можно использовать как способ экономить место при выводе этих значений. Например, чтобы сгенерировать идентификатор сеанса из pg_stat_activity, используйте этот запрос:

SELECT to_hex(trunc(EXTRACT(EPOCH FROM backend_start))::integer) || '.' ||
     to_hex(pid)
FROM pg_stat_activity;

Совет
При установке непустого значение для log_line_prefix лучше сделать его последним символом пробел, чтобы визуально отделить от остальной части строки журнала. Также можно использовать знаки пунктуации.

Совет
Syslog создает собственную метку времени и информацию об идентификаторе процесса, так что, вероятно, при протоколировании в syslog нет смысла использовать эти управляющие последовательности.

Совет
Последовательность %q полезна при включении информации, доступной только в контексте сеанса (обслуживающего процесса), например имени пользователя или базы данных. Например:

`log_line_prefix = '%m [%p] %q%u@%d/%a '`

Примечание
Последовательность %Q всегда выдает нулевой идентификатор для строк, выводимых посредством log_statement, поскольку этот параметр генерирует вывод до вычисления этого идентификатора, в том числе и для недопустимых операторов, для которых идентификатор вычислить нельзя.

log_lock_waits (boolean)

Определяет, должно ли создаваться сообщение журнала, когда сеанс ожидает получения блокировки дольше, чем задано в deadlock_timeout. Это помогает определить, не является ли ожидание блокировок причиной низкой производительности. Значение по умолчанию — off (выключен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

log_recovery_conflict_waits (boolean)

Определяет, должно ли создаваться сообщение журнала, когда процесс запуска ожидает дольше, чем задано в deadlock_timeout для конфликтов восстановления. Это полезно при выяснении, препятствуют ли конфликты восстановления применению WAL.

Значение по умолчанию — off (выключен). Этот параметр можно установить только в файле qhb.conf или в командной строке сервера.

log_parameter_max_length (integer)

При положительном значении значения всех привязанных параметров, выводимые в сообщениях об операторах (кроме регистрации ошибок), усекаются на заданное количество байтов. Ноль выключает протоколирование привязанных параметров в таких сообщениях. Значение -1 (по умолчанию) позволяет выводить привязанные параметры в полном объеме. Если это значение указано без единиц измерения, оно считается заданным в байтах. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Этот параметр влияет только на сообщения, выводимые в журнал по результатам установки log_statement, log_duration и других связанных с ними параметров. При ненулевых значениях этого параметра возникают некоторые дополнительные издержки, особенно если параметры передаются в двоичной форме, поскольку их нужно преобразовать в текст.

log_parameter_max_length_on_error (integer)

При положительном значении значения всех привязанных параметров, выводимые в сообщениях об ошибках, усекаются на заданное количество байтов. Ноль (по умолчанию) выключает протоколирование привязанных параметров в таких сообщениях. Значение -1 позволяет выводить привязанные параметры в полном объеме. Если это значение указано без единиц измерения, оно считается заданным в байтах.

При ненулевых значениях этого параметра возникают некоторые дополнительные издержки, поскольку QHB придется сохранять в памяти текстовые представления значений параметров при запуске всех операторов, независимо от того, произойдет ли в итоге ошибка. Издержки возрастают, когда привязанные параметры передаются не в текстовой, а в двоичной форме, поскольку в последнем случае требуется преобразование данных, тогда как в первом достаточно только скопировать строку.

log_statement (enum)

Управляет тем, какие операторы SQL протоколируются в журнал. Допустимые значения: none (выключен), ddl, mod и all (все операторы). В режиме ddl протоколируются все операторы определения данных, такие как CREATE, ALTER и DROP. В режиме mod протоколируются все операторы ddl, а также операторы, изменяющие данные, такие как INSERT, UPDATE, DELETE, TRUNCATE и COPY FROM. Операторы PREPARE, EXECUTE и EXPLAIN ANALYZE тоже протоколируются, если содержащие их команды имеют соответствующий тип. Для клиентов, использующих расширенный протокол запросов, запись в журнал происходит на фазе выполнения и содержит значения привязанных параметров (с дублированием всех имеющихся апострофов).

Значение по умолчанию — none. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Примечание
Операторы, содержащие простые синтаксические ошибки, не протоколируются даже при log_statement = all, поскольку сообщение журнала создается только после выполнения стандартного разбора для определения типа оператора. В случае расширенного протокола запросов этот параметр также не записывает операторы, завершившиеся ошибкой до фазы выполнения (т. е. во время разбора или планирования запроса). Для записи таких операторов установите параметр log_min_error_statement в значение ERROR (или ниже).

Протоколируемые операторы могут раскрывать конфиденциальные данные и даже содержать пароли в незашифрованном виде.

log_replication_commands (boolean)

Включает протоколирование всех команд репликации в журнал сервера. Дополнительную информацию о командах репликации см. в разделе Протокол потоковой репликации. Значение по умолчанию — off (выключен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

log_temp_files (integer)

Управляет протоколированием имен и размеров временных файлов. Временные файлы могут создаваться для сортировки, хеширования и временного хранения результатов запросов. Если этот параметр включен, запись журнала создается для каждого временного файла при его удалении. При нулевом значении записывается информация обо всех временных файлах, а при положительных — только о тех, размер которых не меньше заданной величины. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. Значение по умолчанию — -1, то есть такое протоколирование выключено. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

log_timezone (string)

Задает часовой пояс, используемый для меток времени при записи в журнал сервера. В отличие от TimeZone, это значение одинаково для всех баз данных кластера, поэтому в сообщениях от всех сеансов метки времени будут согласованы. Встроенное значение по умолчанию — GMT, но обычно оно переопределяется в qhb.conf; qhb_bootstrap установит там значение, соответствующее системной среде. Дополнительную информацию см. в подразделе Часовые пояса. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Использование вывода журнала в формате CSV

Добавление csvlog в список log_destination обеспечивает удобный способ импорта файлов журнала в таблицу базы данных. Этот параметр генерирует строки журнала в формате значений, разделенных запятыми (comma-separated-value, CSV), со следующими столбцами: метка времени с миллисекундами; имя пользователя; имя базы данных; идентификатор процесса; хост клиента:номер порта; идентификатор сеанса; номер строки внутри сеанса; тег команды; время начала сеанса; виртуальный идентификатор транзакции; обычный идентификатор транзакции; уровень важности ошибки; код ошибки SQLSTATE; сообщение об ошибке; подробности к сообщению об ошибке; подсказка; внутренний запрос, который привел к ошибке (если есть); номер символа во внутреннем запросе, где произошла ошибка; контекст ошибки; пользовательский запрос, который привел к ошибке (если есть и включен log_min_error_statement); номер символа в пользовательском запросе, где произошла ошибка; расположение ошибки в исходном коде QHB (если в log_error_verbosity установлено verbose); имя приложения; тип обслуживающего процесса; идентификатор ведущего процесса параллельной группы и идентификатор запроса. Пример определения таблицы для хранения вывода журнала в формате CSV:

CREATE TABLE qhb_log
(
  log_time timestamp(3) with time zone,
  user_name text,
  database_name text,
  process_id integer,
  connection_from text,
  session_id text,
  session_line_num bigint,
  command_tag text,
  session_start_time timestamp with time zone,
  virtual_transaction_id text,
  transaction_id bigint,
  error_severity text,
  sql_state_code text,
  message text,
  detail text,
  hint text,
  internal_query text,
  internal_query_pos integer,
  context text,
  query text,
  query_pos integer,
  location text,
  application_name text,
  backend_type text,
  leader_pid integer,
  query_id bigint,
  PRIMARY KEY (session_id, session_line_num)
);

Для импорта файл журнала в такую таблицу используйте команду COPY FROM:

COPY qhb_log FROM '/full/path/to/logfile.csv' WITH csv;

Кроме того, к этому файлу можно обратиться как к сторонней таблице, воспользовавшись поставляемым модулем file_fdw.

Для упрощения импорта файлов журналов в формате CSV необходимо сделать следующее:

Установите для log_filename и log_rotation_age значения, обеспечивающие согласованную и предсказуемую схему именования файлов журнала. Это позволит вам предугадать, каким будет имя файла, и узнать, когда конкретный файл журнала будет завершен и, следовательно, готов к импорту.

Установите для log_rotation_size значение 0, чтобы выключить поразмерную ротацию файлов, поскольку это затрудняет прогнозирование имени файла журнала.

Установите для log_truncate_on_rotation значение on, чтобы старые данные журнала не смешивались с новыми в одном файле.

Приведенное выше определение таблицы содержит спецификацию первичного ключа. Это способствует предотвращению случайного повторного импорта информации. Команда COPY фиксирует все импортируемые данные за один раз, поэтому любая ошибка приведет к сбою всего импорта. Если сначала импортировать неполный файл журнала, а затем снова импортировать его после заполнения, нарушение первичного ключа приведет к сбою импорта. Поэтому, прежде чем импортировать, дождитесь завершения записи и закрытия журнала. Эта процедура также защитит от случайного импорта неполной (частично написанной) строки, что тоже приведет к сбою команды COPY.

Использование вывода журнала в формате JSON

Добавление jsonlog в список log_destination обеспечивает удобный способ импорта файлов журнала во множество различных программ. Этот параметр генерирует строки журнала в формате JSON.

Строковые поля со значениями NULL из вывода исключаются. В будущем могут быть добавлены новые поля. Пользовательские приложения, обрабатывающие вывод jsonlog, должны игнорировать неизвестные для них поля.

Каждая строка журнала сериализуется в виде объекта JSON с набором ключей и связанных с ними значений, перечисленных в Таблице 3.

Таблица 3. Ключи и значения записей журнала JSON

Имя ключа | Тип | Описание
timestamp | строка | Метка времени с миллисекундами
user | строка | Имя пользователя
dbname | строка | Имя базы данных
pid | число | Идентификатор процесса
remote_host | строка | Хост клиента
remote_port | число | Порт клиента
session_id | строка | Идентификатор сеанса
line_num | число | Номер строки внутри сеанса
ps | строка | Текущая строка в ps
session_start | строка | Время начала сеанса
vxid | строка | Виртуальный идентификатор транзакции
txid | строка | Обычный идентификатор транзакции
error_severity | строка | Важность ошибки
state_code | строка | Код SQLSTATE
message | строка | Сообщение об ошибке
detail | строка | Подробности к сообщению об ошибке
hint | строка | Подсказка к сообщению об ошибке
internal_query | строка | Внутренний запрос, который привел к ошибке
internal_position | число | Позиция курсора во внутреннем запросе
context | строка | Контекст ошибки
statement | строка | Строка запроса, предоставленная клиентом
cursor_position | число | Позиция курсора в строке запроса
func_name | строка | Имя функции, в которой произошла ошибка
file_name | строка | Имя файла, в котором произошла ошибка
file_line_num | число | Номер строки в файле, в которой произошла ошибка
application_name | строка | Имя клиентского приложения
backend_type | строка | Тип обслуживающего процесса
leader_pid | число | Идентификатор ведущего процесса параллельных активных рабочих процессов
query_id | число | Идентификатор запроса

Заголовок процесса

Эти параметры определяют, как изменяются заголовки процессов сервера. Заголовки процессов обычно просматриваются с помощью таких программ, как ps. Дополнительную информацию см. в разделе Стандартные инструменты Unix.

cluster_name (string)

Задает имя, которое идентифицирует этот кластер баз данных (экземпляр сервера) для различных целей. Имя кластера появляется в заголовке у всех серверных процессов в этом кластере. Более того, это имя является именем приложения по умолчанию при подключении резервного сервера (см. synchronous_standby_names).

Это имя может быть любой строкой длиной не более NAMEDATALEN символов (64 символа в стандартной сборке). В значении cluster_name можно использовать только печатные символы ASCII. Все остальные символы будут заменены на вопросительные знаки (?). Если для этого параметра задана пустая строка '' (по умолчанию), то никакое имя не отображается. Этот параметр можно задать только при запуске сервера.

update_process_title (boolean)

Позволяет изменять заголовок процесса каждый раз, когда сервер получает новую команду SQL. На большинстве платформ этот параметр по умолчанию имеет значение on (включен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Статистика времени выполнения

Накопительная статистика запросов и индексов

Эти параметры управляют системой накопительной статистики на уровне сервера. Когда сбор статистики включен, доступ к собранным данным можно получить через семейство системных представлений pg_stat и pg_statio. Более подробную информацию см. в главе Мониторинг активности базы данных.

track_activities (boolean)

Включает сбор информации о текущих командах, выполняемых во всех сеансах, в том числе идентификаторов и времени начала выполнения этих команд. По умолчанию этот параметр включен. Обратите внимание, что даже при включенном параметре эта информация видна не всем пользователям, а только суперпользователям, ролям с правами роли pg_read_all_stats и пользователю-владельцу сеанса, о котором сообщается (включая сеансы ролей, права которых они имеют), поэтому это не должно представлять угрозу безопасности. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

track_activity_query_size (integer)

Задает объем памяти, зарезервированный для хранения текста текущих команд, выполняемых во всех активных сеансах, для поля pg_stat_activity.query. Если это значение указано без единиц измерения, оно считается заданным в байтах. Значение по умолчанию — 1024 байта. Этот параметр можно задать только при запуске сервера.

track_counts (boolean)

Включает сбор статистики активности базы данных. По умолчанию этот параметр включен, поскольку собранная информация требуется процессу автовакуума. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

track_io_timing (boolean)

Включает замер времени вызовов ввода/вывода базы данных. По умолчанию этот параметр выключен, поскольку он будет периодически запрашивать у операционной системы текущее время, что может привести к значительному замедлению работы на некоторых платформах. Для измерения затрат времени в вашей системе можно воспользоваться утилитой qhb_test_timing. Информация о времени операций ввода/вывода отображается в представлении pg_stat_database, в выводе EXPLAIN при использовании параметра BUFFERS, в выводе VACUUM при использовании параметра VERBOSE, в процессе автовакуума при выполнении автоочистки и автоанализа, когда установлен параметр log_autovacuum_min_duration, и в представлении pg_stat_statements. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

track_wal_io_timing (boolean)

Включает замер времени вызовов ввода/вывода WAL. По умолчанию этот параметр выключен, поскольку он будет периодически запрашивать у операционной системы текущее время, что может привести к значительному замедлению работы на некоторых платформах. Для измерения затрат времени в вашей системе можно воспользоваться утилитой qhb_test_timing. Информация о времени операций ввода/вывода отображается в представлении pg_stat_wal. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

track_functions (enum)

Включает подсчет вызовов функций и времени их выполнения. При значении pl подсчет будет вестись только для функций процедурного языка, при all — также для функций на языках SQL и C/RUST. Значение по умолчанию — none, т. е. сбор статистики по функциям выключен. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Примечание
Функции на языке SQL, которые достаточно просты для «встраивания» в вызывающий запрос, отслеживаться не будут, независимо от этого параметра.

stats_fetch_consistency (enum)

Определяет поведение при многократном обращении к накопительной статистике в рамках транзакции. Со значением none при каждом обращении счетчики повторно извлекаются из разделяемой памяти. Со значением cache при первом обращении к статистике по какому-либо объекту эта статистика кэшируется до конца транзакции и сбрасывается, только если вызвать pg_stat_clear_snapshot(). Со значением snapshot при первом обращении к статистике вся статистика в текущей базе данных кэшируется до конца транзакции и сбрасывается, только если вызвать pg_stat_clear_snapshot(). Изменение этого параметра в транзакции сбрасывает снимок статистики. Значение по умолчанию — cache.

Примечание
Вариант none лучше всего подходит для систем мониторинга. Он наиболее эффективен, если к значениям обращаются однократно. Вариант cache гарантирует, что при повторном обращении будут получены те же значения, что важно для запросов, содержащих, к примеру, замкнутые соединения. Вариант snapshot может быть полезен при интерактивном просмотре статистики, но вызывает более значительные издержки, особенно если в базе данных существует много объектов.

Мониторинг статистики

compute_query_id (enum)

Включает вычисление идентификатора запроса в ядре. Идентификаторы запроса могут отображаться в представлении pg_stat_activity, в выводе команды EXPLAIN или записываться в журнал, если были сконфигурированы посредством параметра log_line_prefix. Также идентификаторы запросов должны вычисляться для расширения pg_stat_statements. Обратите внимание, что если метод вычисления идентификатора запроса в ядре является неприемлемым, в качестве альтернативы можно использовать внешний модуль. В таком случае вычисление в ядре должно быть всегда выключено. Допустимые значения: off (всегда выключен), on (всегда включен), auto, которое позволяет модулям, например pg_stat_statements, включать это вычисление автоматически, и regress, которое действует так же, как auto, но идентификатор запроса не отображается в выводе EXPLAIN в целях упрощения автоматического регрессионного тестирования. Значение по умолчанию — auto.

Примечание
Чтобы гарантированно вычислялся и отображался только один идентификатор запроса, вычисляющее его расширение должно выдавать ошибку, если идентификатор запроса уже был вычислен.

log_statement_stats (boolean)
log_parser_stats (boolean)
log_planner_stats (boolean)
log_executor_stats (boolean)

Эти параметры включают вывод статистики по производительности соответствующего модуля в журнал сервера. Это грубый инструмент профилирования, похожий на функцию getrusage() операционной системы Unix. log_statement_stats включает вывод общей статистики по командам, в то время как остальные параметры включают вывод статистики по модулям. log_statement_stats нельзя включать одновременно с параметрами для модулей. Все эти параметры по умолчанию выключены. Только суперпользователи и пользователи с подходящим правом SET могут изменять эти параметры.

Автоматическая очистка

Эти параметры управляют поведением функции автоматической очистки. Подробную информацию см. в подразделе Процесс «Автовакуум». Обратите внимание, что многие из этих параметров могут быть переопределены на уровне таблицы; см. параграф Параметры хранения.

autovacuum (boolean)

Управляет тем, должен ли сервер выполнять процесс запуска автовакуума. По умолчанию этот параметр включен, однако для работы автоочистки также необходимо включить track_counts. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее автоматическую очистку можно выключить для отдельных таблиц, изменив их параметры хранения.

Обратите внимание, что даже когда этот параметр выключен, система будет при необходимости запускать процессы автовакуума, чтобы предотвратить зацикливание идентификатора транзакции. Дополнительную информацию см. в подразделе Предотвращение ошибок зацикливания идентификаторов транзакций.

autovacuum_max_workers (integer)

Задает максимальное количество процессов автовакуума (кроме процесса его запуска), которые могут выполняться одновременно. По умолчанию это количество равно трем. Этот параметр можно задать только при запуске сервера.

autovacuum_naptime (integer)

Задает минимальную задержку между запусками автоочистки в любой отдельно взятой базе данных. В каждом раунде процесс «Автовакуум» проверяет базу данных и при необходимости выполняет команды VACUUM и ANALYZE для таблиц в этой базе. Если это значение указано без единиц измерения, оно считается заданным в секундах. Значение по умолчанию — одна минута (1min). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

autovacuum_vacuum_threshold (integer)

Задает минимальное количество измененных или удаленных кортежей, требующееся для запуска команды VACUUM в отдельно взятой таблице. Значение по умолчанию — 50 кортежей. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее его значение можно переопределить для отдельных таблиц, изменив их параметры хранения.

autovacuum_vacuum_insert_threshold (integer)

Задает количество добавленных кортежей, требующееся для запуска команды VACUUM в отдельно взятой таблице. Значение по умолчанию — 1000 кортежей. При значении -1 запуск команды VACUUM не будет зависеть от количества добавленных кортежей. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее его значение можно переопределить для отдельных таблиц, изменив их параметры хранения.

autovacuum_analyze_threshold (integer)

Задает минимальное количество добавленных, измененных или удаленных кортежей, требующееся для запуска команды ANALYZE в отдельно взятой таблице. Значение по умолчанию — 50 кортежей. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее его значение можно переопределить для отдельных таблиц, изменив их параметры хранения.

autovacuum_vacuum_scale_factor (floating point)

Задает долю от размера таблицы, добавляемую к autovacuum_vacuum_threshold при принятии решения о запуске команды VACUUM. Значение по умолчанию — 0.2 (20% от размера таблицы). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее его значение можно переопределить для отдельных таблиц, изменив их параметры хранения.

autovacuum_vacuum_insert_scale_factor (floating point)

Задает долю от размера таблицы, добавляемую к autovacuum_vacuum_insert_threshold при принятии решения о запуске команды VACUUM. Значение по умолчанию — 0.2 (20% от размера таблицы). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее его значение можно переопределить для отдельных таблиц, изменив их параметры хранения.

autovacuum_analyze_scale_factor (floating point)

Задает долю от размера таблицы, добавляемую к autovacuum_analyze_threshold при принятии решения о запуске команды ANALYZE. Значение по умолчанию — 0.1 (10% от размера таблицы). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее его значение можно переопределить для отдельных таблиц, изменив их параметры хранения.

autovacuum_freeze_max_age (integer)

Задает максимальный возраст (в транзакциях), которого может достичь поле pg_class.relfrozenxid некоторой таблицы, прежде чем будет запущена операция VACUUM для предотвращения зацикливания идентификаторов транзакций в этой таблице. Обратите внимание, что система будет запускать процессы автовакуума для предотвращения зацикливания, даже если в остальном автоматическая очистка выключена.

Очистка также позволяет удалять старые файлы из подкаталога pg_xact, поэтому значение по умолчанию относительно невелико — 200 миллионов транзакций. Этот параметр можно задать только при запуске сервера. Тем не менее его значение можно уменьшить для отдельных таблиц, изменив их параметры хранения. Дополнительную информацию см. в подразделе Предотвращение ошибок зацикливания идентификаторов транзакций.

autovacuum_multixact_freeze_max_age (integer)

Задает максимальный возраст (в мультитранзакциях), которого может достичь поле pg_class.relminmxid некоторой таблицы, прежде чем будет запущена операция VACUUM для предотвращения зацикливания идентификаторов мультитранзакций в этой таблице. Обратите внимание, что система будет запускать процессы автовакуума для предотвращения зацикливания, даже если в остальном автоматическая очистка выключена.

Очистка мультитранзакций также позволяет удалять старые файлы из подкаталогов pg_multixact/members и pg_multixact/offsets, поэтому значение по умолчанию относительно невелико — 400 миллионов мультитранзакций. Этот параметр можно задать только при запуске сервера. Тем не менее его можно уменьшить для отдельных таблиц, изменив их параметры хранения. Дополнительную информацию см. в подразделе Мультитранзакции и зацикливание.

autovacuum_vacuum_cost_delay (floating point)

Задает задержку при превышении предела стоимости, которая будет использоваться при автоматических операциях VACUUM. При значении -1 будет применена обычная задержка vacuum_cost_delay. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — 2 миллисекунды. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее его значение можно переопределить для отдельных таблиц, изменив их параметры хранения.

autovacuum_vacuum_cost_limit (integer)

Указывает предельное значение стоимости, которое будет использоваться при автоматических операциях VACUUM. При значении -1 (по умолчанию) будет применена обычная задержка vacuum_cost_limit. Обратите внимание, что если запущенных рабочих процессов автовакуума больше одного, это значение пропорционально распределяется между ними, так что суммарный предел стоимости для всех рабочих процессов не превышает значения этой переменной. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера. Тем не менее его значение можно переопределить для отдельных таблиц, изменив их параметры хранения.

Параметры клиентских соединений по умолчанию

Поведение команд

client_min_messages (enum)

Управляет уровнем сообщений, отправляемых клиенту. Допустимые значения: DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, LOG, NOTICE, WARNING и ERROR. Каждый из перечисленных уровней включает в себя все уровни, идущие после него. Чем дальше в этом списке уровень сообщения, тем меньше сообщений отправляется клиенту. Значение по умолчанию — NOTICE. Обратите внимание, что здесь у LOG другая позиция, нежели в параметре log_min_messages.

Сообщения уровня INFO отправляются клиенту всегда.

search_path (string)

Эта переменная задает порядок, в котором просматриваются схемы, когда к объекту поиска (таблице, типу данных, функции и т. д.) обращаются просто по имени, без указания схемы. Если объекты с одинаковыми именами находятся в разных схемах, используется тот, который был найден первым в пути поиска. К объекту, который не входит ни в одну из схем в пути поиска, можно обратиться только по полному имени (с точкой), с указанием содержащей его схемы.

Значением search_path должен быть список имен схем, разделенных запятыми. Любое имя, которое не соответствует существующей схеме или принадлежит схеме, для которой у пользователя нет права USAGE, молча игнорируется.

Если список содержит специальное имя $user, то вместо него подставляется схема с именем, возвращаемым функцией CURRENT_USER, если такая схема существует и у пользователя есть для нее право USAGE. (В противном случае имя $user игнорируется).

Схема системных каталогов, pg_catalog, просматривается всегда, независимо от того, упоминается она в пути или нет. Если ее имя указано в пути, она будет просматриваться в заданном порядке. Если pg_catalog в пути отсутствует, она будет просматриваться перед всеми остальными элементами пути.

Аналогично всегда просматривается схема временных таблиц текущего сеанса, pg_temp_nnn, если она существует. Ее можно явно указать в пути поиска с помощью псевдонима pg_temp. Если эта схема в пути отсутствует, она будет просматриваться первой (даже до pg_catalog). Однако во временной схеме производится только поиск отношений (таблиц, представлений, последовательностей и т. д.) и типов данных, но не функций или операторов.

Когда объекты создаются без указания конкретной целевой схемы, они помещаются в первую подходящую схему, указанную в search_path. Если путь поиска пуст, выдается ошибка.

Значение по умолчанию для этого параметра — "$user", public. При таком значении поддерживается совместное использование базы данных (когда пользователи не имеют личных схем и все используют схему public), использование личных схем и комбинации обоих вариантов. Другие результаты можно получить, изменяя значение пути поиска по умолчанию, либо глобально, либо отдельно для каждого пользователя.

Дополнительную информацию об обработке схем см. в разделе Схемы. В частности, конфигурация схем по умолчанию подходит только для баз данных с одним пользователем или несколькими доверяющими друг другу пользователями.

Текущее действующее значение пути поиска можно проверить с помощью функции SQL current_schemas (см. раздел Системные информационные функции и операторы). Результат будет несколько отличаться от значения в search_path, поскольку current_schemas показывает, как были преобразованы элементы, находящиеся в search_path.

row_security (boolean)

Эта переменная определяет, будет ли выдаваться ошибка при применении политики защиты строк. При значении on (включен) политики применяются в обычном режиме. При значении off (выключен) запросы, ограниченные хотя бы одной политикой, будут выдавать ошибку. Значение по умолчанию — on. Задавать значение off имеет смысл, когда ограниченная видимость строки может привести к некорректным результатам; например, qhb_dump задает это значение по умолчанию. Эта переменная не влияет на роли, которые обходят все политики защиты строк, а именно, на суперпользователей и роли с атрибутом BYPASSRLS.

Дополнительную информацию о политиках защиты строк см. на справочной странице команды CREATE POLICY.

default_table_access_method (string)

Этот параметр задает табличный метод доступа по умолчанию, который используется при создании таблиц или материализованных представлений, если тот не указан явно в команде CREATE, либо при выполнении команды SELECT ... INTO, которая не позволяет явно задать метод доступа. Значение по умолчанию — heap.

default_tablespace (string)

Эта переменная задает табличное пространство по умолчанию, в котором будут создаваться объекты (таблицы и индексы), когда в команде CREATE табличное пространство не указано явно.

Ее значением является либо имя табличного пространства, либо пустая строка, подразумевающая использование табличного пространства по умолчанию для текущей базы данных. Если это значение не соответствует имени какого-либо существующего табличного пространства, QHB автоматически будет использовать табличное пространство по умолчанию для текущей базы данных. Если указано не табличное пространство по умолчанию, у пользователя должно быть право CREATE для него, иначе попытки создания объектов не увенчаются успехом.

Эта переменная не используется для временных таблиц; вместо этого для них используется temp_tablespaces.

Кроме того, эта переменная не используется при создании баз данных. По умолчанию новая база данных наследует настройки по выбору табличного пространства от базы- шаблона, из которой она копируется.

Если при создании партиционированной таблицы в этом параметре установлено значение, отличное от пустого, оно задает табличное пространство этой партиционированной таблицы, в котором по умолчанию будут располагаться ее создаваемые впоследствии партиции, даже если потом значение default_tablespace будет изменено.

Дополнительную информацию о табличных пространствах см. в разделе Табличные пространства.

default_toast_compression (enum)

Эта переменная задает метод сжатия TOAST по умолчанию для значений сжимаемых столбцов. (Это можно переопределить для отдельных столбцов, установив параметр столбца COMPRESSION в CREATE TABLE или ALTER TABLE.) Поддерживаемые методы сжатия: pglz и lz4 (если QHB была скомпилирована с параметром --with-lz4). Значение по умолчанию — pglz.

temp_tablespaces (string)

Эта переменная задает табличные пространства, в которых будут создаваться временные объекты (временные таблицы и индексы для временных таблиц), когда в команде CREATE табличное пространство не указано явно. Также в этих табличных пространствах создаются временные файлы для таких целей, как сортировка больших наборов данных.

Ее значением является список имен табличных пространств. Когда в этом списке содержится более одного имени, QHB выбирает оттуда случайный элемент каждый раз при создании временного объекта; однако последующие создаваемые внутри транзакции временные объекты помещаются в табличные пространства, выбираемые из списка последовательно. Если выбранный элемент списка представляет собой пустую строку, QHB автоматически будет использовать табличное пространство по умолчанию для текущей базы данных.

Когда temp_tablespaces задается интерактивно, указание несуществующего табличного пространства является ошибкой, равно как и указание табличного пространства, для которого у пользователя нет права CREATE. Однако при использовании значения, заданного ранее, несуществующие табличные пространства и табличные пространства, для которых у пользователя нет права CREATE, игнорируются. В частности, это правило применяется в отношении значения, заданного в qhb.conf.

Значение по умолчанию — пустая строка, поэтому все временные объекты создаются в табличном пространстве по умолчанию текущей базы данных.

См. также default_tablespace.

check_function_bodies (boolean)

Обычно этот параметр включен. Если задать значение off (выключен), выключается проверка строки с телом функции во время выполнения команд CREATE FUNCTION и CREATE PROCEDURE. Выключение проверки позволяет избежать побочных эффектов процесса проверки, в частности, избежать ложных срабатываний из-за таких проблем, как ссылки вперед. Перед загрузкой функций от имени других пользователей этому параметру нужно присваивать значение off; qhb_dump делает это автоматически.

default_transaction_isolation (enum)

Для каждой транзакции в SQL устанавливается один из следующих уровней изоляции: «read uncommitted», «read committed», «repeatable read» или «serializable». Этот параметр определяет уровень изоляции, который будет устанавливаться по умолчанию для новых транзакций. Значение по умолчанию — «read committed».

Дополнительную информацию см. в главе Управление параллельным доступом и на справочной странице команды SET TRANSACTION.

default_transaction_read_only (boolean)

Транзакция SQL в режиме «только чтение» не может изменять временные таблицы. Этот параметр определяет, будут ли новые транзакции иметь по умолчанию статус «только чтение». Значение по умолчанию — off (разрешено чтение/запись).

Дополнительную информацию см. на справочной странице команды SET TRANSACTION.

default_transaction_deferrable (boolean)

Откладываемая транзакция SQL в режиме «только чтение», выполняющаяся на уровне изоляции serializable, может быть задержана, прежде чем ей будет разрешено возобновить работу. Однако как только она начнет выполняться, для обеспечения сериализуемости не потребуется никаких дополнительных затрат, поэтому у кода сериализации не будет причин прерывать ее из-за параллельных изменений. Как следствие, эта особенность подходит для длительных транзакций в режиме «только чтение».

Этот параметр определяет, будут ли новые транзакции по умолчанию откладываемыми. В настоящее время он не влияет на транзакции в режиме «чтение/запись» или с уровнем изоляции ниже serializable. Значение по умолчанию — off (выключен).

Дополнительную информацию см. на справочной странице команды SET TRANSACTION.

transaction_isolation (enum)

Данный параметр отражает уровень изоляции текущей транзакции. В начале каждой транзакции ему присваивается текущее значение default_transaction_isolation. Последующая попытка изменить значение этого параметра равнозначна команде SET TRANSACTION.

transaction_read_only (boolean)

Данный параметр отражает статус режима «только чтение» для текущей транзакции. В начале каждой транзакции ему присваивается текущее значение default_transaction_read_only. Последующая попытка изменить значение этого параметра равнозначна команде SET TRANSACTION.

transaction_deferrable (boolean)

Данный параметр отражает статус «откладываемости» для текущей транзакции. В начале каждой транзакции ему присваивается текущее значение default_transaction_deferrable. Последующая попытка изменить значение этого параметра равнозначна команде SET TRANSACTION.

session_replication_role (enum)

Управляет срабатыванием связанных с репликацией триггеров и правил в текущем сеансе. Допустимые значения: origin (по умолчанию), replica и local. Установка этого параметра приводит к сбросу всех ранее кэшированных планов запросов. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Предполагается, что системы логической репликации будут устанавливать для этого параметра значение replica, применяя реплицированные изменения. В результате триггеры и правила (у которых не менялась конфигурация по умолчанию) не будут срабатывать в репликах. Дополнительную информацию см. в описании предложений ENABLE TRIGGER и ENABLE RULE команды ALTER TABLE.

В QHB значения origin и local воспринимаются как равнозначные. Сторонние системы репликации могут использовать эти два значения для своих внутренних целей, например, обозначая с помощью local сеанс, изменения в котором не должны реплицироваться.

Поскольку внешние ключи реализованы в виде триггеров, присвоение этому параметру значения replica также выключает все проверки внешних ключей, что в случае неправильного использования может привести к нарушению согласованности данных.

statement_timeout (integer)

Прерывать любую команду, выполняющуюся дольше заданного в этом параметре времени. Если в log_min_error_statement установлено значение ERROR или ниже, команда, прервавшаяся по времени, также будет записана в журнал. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. При нулевом значении (по умолчанию) это прерывание по времени выключается.

Время ожидания отсчитывается с момента поступления команды на сервер до завершения ее выполнения. Если в одном сообщении простого запроса находятся несколько операторов SQL, тайм-аут применяется к каждому из них по отдельности. В расширенном протоколе запросов время ожидания начинает отсчет с момента поступления любого сообщения, связанного с запросом (разбор, связывание, выполнение, описание), и обнуляется по завершению обработки сообщения о выполнении или синхронизации.

Задавать значение statement_timeout в qhb.conf не рекомендуется, поскольку это повлияет на все сеансы.

lock_timeout (integer)

Прерывать любую команду, которая при получении блокировки таблицы, индекса, строки или другого объекта базы данных ожидает дольше заданного в этом параметре времени. Это ограничение по времени применяется к каждой попытке получения блокировки по отдельности. Ограничение применяется как к явным запросам блокировки (например к LOCK TABLE или SELECT FOR UPDATE без NOWAIT), так и к блокировкам, получаемым неявно. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. При нулевом значении (по умолчанию) это прерывание по времени выключается.

В отличие от statement_timeout, это прерывание по времени может происходить только при ожидании блокировок. Обратите внимание, что при ненулевом значении statement_timeout бессмысленно устанавливать для lock_timeout такое же или большее значение, поскольку прерывание по времени для команды всегда срабатывает первым. Если в log_min_error_statement установлено значение ERROR или ниже, команда, прервавшаяся по времени, будет записана в журнал.

Задавать значение lock_timeout в qhb.conf не рекомендуется, поскольку это повлияет на все сеансы.

idle_in_transaction_session_timeout (integer)

Завершать любой сеанс, простаивающий (то есть ожидающий запрос от клиента), дольше заданного времени, в открытой транзакции. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. При нулевом значении (по умолчанию) это завершение по времени выключается.

Этот параметр можно использовать для предотвращения ситуаций, когда простаивающие сеансы чересчур долго удерживают блокировки. Даже если удерживаются незначимые блокировки, открытая транзакция препятствует очистке недавно ставших неиспользуемыми кортежей, которые могут быть видимыми только для этой транзакции; поэтому длительный простой может привести к разбуханию таблиц. Более подробную информацию см. в разделе Регулярная очистка.

idle_session_timeout (integer)

Завершать любой сеанс, простаивающий (то есть ожидающий запрос от клиента), дольше заданного времени, но вне открытой транзакции. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. При нулевом значении (по умолчанию) это завершение по времени выключается.

В отличие от ситуации с открытой транзакцией, сеанс, простаивающий вне транзакции, не вызывает значительных издержек на сервере, поэтому необходимость во включении этого тайм-аута возникает реже, чем idle_in_transaction_session_timeout.

Включать этот тайм-аут для подключений, устанавливаемых через программу пула соединений или другое ПО промежуточного слоя, следует с осторожностью, поскольку такой слой может некорректно реагировать на неожиданное закрытие соединений. Может иметь смысл включать этот тайм-аут только для интерактивных сеансов, возможно, применяя его только у определенных пользователей.

vacuum_freeze_table_age (integer)

Если поле pg_class.relfrozenxid таблицы достигает возраста, заданного в этом параметре, VACUUM будет выполнять агрессивное сканирование. Агрессивное сканирование отличается от обычного выполнения VACUUM тем, что проводится на каждой странице, которая может содержать незамороженные XID или MXID, а не только на тех, которые могут содержать неиспользуемые кортежи. Значение по умолчанию — 150 миллионов транзакций. Хотя пользователи могут задать любое значение в диапазоне от нуля до двух миллиардов, VACUUM будет молча ограничивать действующее значение до предела, равного 95% от autovacuum_freeze_max_age, чтобы периодически запускаемая вручную команда VACUUM имела шанс выполниться до того, как для таблицы будет запущена автоочистка для предотвращения зацикливания. Дополнительную информацию см. в подразделе Предотвращение ошибок зацикливания идентификаторов транзакций.

vacuum_freeze_min_age (integer)

Задает возрастную границу (в транзакциях), которая должна использоваться в команде VACUUM при принятии решения о заморозке версий строк в процессе сканирования таблицы. Значение по умолчанию — 50 миллионов транзакций. Хотя пользователи могут задать любое значение в диапазоне от нуля до одного миллиарда, VACUUM будет молча ограничивать действующее значение до предела, равного половине значения autovacuum_freeze_max_age, чтобы перерывы между принудительными автоочистками не были неоправданно короткими. Дополнительную информацию см. в подразделе Предотвращение ошибок зацикливания идентификаторов транзакций.

vacuum_failsafe_age (integer)

Задает максимальный возраст (в транзакциях), которого может достичь поле pg_class.relfrozenxid некоторой таблицы, прежде чем VACUUM примет чрезвычайные меры во избежание глобального системного сбоя из-за зацикливания идентификаторов транзакций. Это стратегия применяется VACUUM в случае крайней необходимости. Обычно защитный режим запускается, когда автоматическая очистка для предотвращения зацикливания идентификаторов транзакций уже выполнялась в течение некоторого времени, хотя может запуститься во время любой операции VACUUM.

При запуске защитного режима любая действующая задержка на основе стоимости больше не применяется, а дальнейшие несущественные задачи обслуживания (например очистка индекса) пропускаются.

Значение по умолчанию — 1.6 миллиарда транзакций. Хотя пользователи могут установить любое значение от нуля до 2.1 миллиарда, VACUUM молча поправит действующее значение, задав как минимум 105% от autovacuum_freeze_max_age.

vacuum_multixact_freeze_table_age (integer)

Если поле pg_class.relminmxid некоторой таблицы достигает возраста, заданного в этом параметре, VACUUM будет выполнять агрессивное сканирование. Агрессивное сканирование отличается от обычного выполнения VACUUM тем, что проводится на каждой странице, которая может содержать незамороженные XID или MXID, а не только на тех, которые могут содержать неиспользуемые кортежи. Значение по умолчанию — 150 миллионов мультитранзакций. Хотя пользователи могут задать любое значение в диапазоне от нуля до двух миллиардов, VACUUM будет молча ограничивать действующее значение до предела, равного 95% от autovacuum_multixact_freeze_max_age, чтобы периодически запускаемая вручную команда VACUUM имела шанс выполниться до того, как для таблицы будет запущена автоматическая очистка для предотвращения зацикливания. Дополнительную информацию см. в подразделе Мультитранзакции и зацикливание.

vacuum_multixact_freeze_min_age (integer)

Задает возрастную границу (в мультитранзакциях), которая должна использоваться в команде VACUUM при принятии решения о замене идентификатора мультитранзакции более новым идентификатором транзакции или мультитранзакции в процессе сканирования таблицы. Значение по умолчанию — 5 миллионов мультитранзакций. Хотя пользователи могут задать любое значение в диапазоне от нуля до одного миллиарда, VACUUM будет молча ограничивать действующее значение до предела, равного половине значения autovacuum_multixact_freeze_max_age, чтобы перерывы между принудительными автоочистками не были неоправданно короткими. Дополнительную информацию см. в подразделе Мультитранзакции и зацикливание.

vacuum_multixact_failsafe_age (integer)

Задает максимальный возраст (в мультитранзакциях), которого может достичь поле pg_class.relfrozenxid некоторой таблицы, прежде чем VACUUM примет чрезвычайные меры во избежание глобального системного сбоя из-за зацикливания идентификаторов мультитранзакций. Это стратегия применяется VACUUM в случае крайней необходимости. Обычно защитный режим запускается, когда автоматическая очистка для предотвращения зацикливания идентификаторов транзакций уже выполнялась в течение некоторого времени, хотя может запуститься во время любой операции VACUUM.

При запуске защитного режима любая действующая задержка на основе стоимости больше не применяется, а дальнейшие несущественные задачи обслуживания (например очистка индекса) пропускаются.

Значение по умолчанию — 1.6 миллиарда мультитранзакций. Хотя пользователи могут установить любое значение от нуля до 2.1 миллиарда, VACUUM молча поправит действующее значение, задав как минимум 105% от autovacuum_multixact_freeze_max_age.

bytea_output (enum)

Устанавливает формат вывода для значений типа bytea. Допустимые значения: hex (по умолчанию) и escape (традиционный формат QHB). Дополнительную информацию см. в разделе Двоичные типы данных. На входе данные типа bytea всегда воспринимаются в обоих форматах, независимо от этого параметра.

xmlbinary (enum)

Устанавливает способ кодирования двоичных значений в XML. Это применимо, например, когда значения типа bytea конвертируются в XML с помощью функций xmlelement или xmlforest. Допустимые значения, определенные в стандарте XML-схем: base64 и hex. Значение по умолчанию — base64. Дополнительную информацию о функциях для работы с XML, см. в разделе Функции XML.

Фактический выбор здесь в основном дело вкуса, ограничения могут накладываться только клиентскими приложениями. Оба метода поддерживают все возможные значения, хотя в шестнадцатеричной кодировке результат будет несколько объемнее, чем в кодировке base64.

xmloption (enum)

Устанавливает подразумеваемый по умолчанию режим преобразования между значениями XML и символьными строками. Описание этого преобразования см. в разделе Тип XML. Допустимые значения: DOCUMENT и CONTENT. Значение по умолчанию — CONTENT.

Согласно стандарту SQL этот параметр задается командой

SET XML OPTION { DOCUMENT | CONTENT };

Этот синтаксис также поддерживается в QHB.

gin_pending_list_limit (integer)

Устанавливает максимальный размер очереди записей индекса GIN, который используется, когда включен режим fastupdate. Если размер очереди превышает этот предел, она очищается путем массового переноса записей в основную структуру данных индекса GIN. Если это значение указано без единиц измерения, оно считается заданным в килобайтах. Значение по умолчанию — четыре мегабайта (4MB). Этот параметр можно переопределить для отдельных индексов GIN, изменив их параметры хранения. Дополнительную информацию см. в подразделе Методика быстрого обновления GIN и разделе Советы и приемы для индексов GIN.

Языковые стандарты и форматирование

DateStyle (string)

Устанавливает формат отображения значений даты и времени, а также правила интерпретации неоднозначных значений вводимых дат. По историческим причинам эта переменная содержит два независимых компонента: спецификацию выходного формата (ISO, Postgres, SQL или German) и спецификацию, касающуюся порядка год(Y)/ месяц(M)/день(D) при вводе/выводе (DMY, MDY или YMD). Эти компоненты можно задавать вместе или по отдельности. Ключевые слова Euro и European являются синонимами для DMY; ключевые слова US, NonEuro и NonEuropean являются синонимами для MDY. Дополнительную информацию см. в разделе Типы даты/времени. Встроенное значение по умолчанию — ISO, MDY, но qhb_bootstrap (или initdb) инициализирует файл конфигурации со значением, соответствующим поведению выбранной локали lc_time.

IntervalStyle (enum)

Устанавливает формат отображения для значений интервалов. В формате sql_standard интервал будет выводиться в виде, соответствующем стандарту SQL (+Y-M +D +H:MM:SS). В формате postgres (используемом по умолчанию) интервал будет выводиться в виде Y years M mons D days HH:MM:SS. В формате postgres_verbose интервал будет выводиться в виде @ Y years M mons D days H hours M mins S secs. В формате iso_8601 интервал будет выводиться в виде, соответствующем временному интервалу «формат с кодами», определенному в разделе 4.4.3.2 стандарта ISO 8601.

Параметр IntervalStyle также влияет на интерпретацию неоднозначных вводимых интервалов. Дополнительную информацию см. в подразделе Ввод интервалов.

TimeZone (string)

Устанавливает часовой пояс для отображения и интерпретации меток времени. Встроенное значение по умолчанию — GMT, но обычно оно переопределяется в qhb.conf; qhb_bootstrap (или initdb) установит в нем значение, соответствующее системному окружению. Дополнительную информацию см. в подразделе Часовые пояса.

timezone_abbreviations (string)

Устанавливает набор сокращений часовых поясов, которые будут приниматься сервером при вводе даты и времени. Значение по умолчанию — 'Default', которое представляет собой набор сокращений, принятых в большинстве стран мира; также допускаются значения 'Australia' и 'India'. Кроме того, для конкретной установки можно определить и другие наборы. Дополнительную информацию см. в разделе Файлы конфигурации даты/времени.

extra_float_digits (integer)

Этот параметр корректирует количество цифр, используемых для текстового вывода чисел с плавающей запятой, включая значения типов float4, float8, а также геометрических типов.

При значении 1 (по умолчанию) или выше числа с плавающей запятой выводятся в кратчайшем точном формате; см. подраздел Типы с плавающей запятой. Фактическое количество генерируемых цифр зависит только от выводимого числа, а не от значения этого параметра. Для чисел типа float8 требуется не более 17 цифр, а для чисел типа float4 — не более 9. Этот формат является быстрым и точным, при правильном прочтении в точности сохраняя исходное двоичное число с плавающей запятой. В целях исторической совместимости этот параметр принимает значения до 3 включительно.

Если его значение равно нулю или отрицательно, то выводимое число округляется до заданной десятичной точности. Применяемая точность определяется как стандартное количество цифр для типа (FLT_DIG или DBL_DIG, в зависимости от ситуации), уменьшенное на значение этого параметра. (Например, при значении -1 числа типа float4 будут выводиться округленными до 5 значащих цифр, а числа типа float8 — до 14). Преобразование в этот формат происходит медленнее и не сохраняет все биты двоичного числа с плавающей запятой, но может быть более удобным для восприятия человеком. Дополнительную информацию о поведении этого параметра см. в подразделе Типы с плавающей запятой.

client_encoding (string)

Устанавливает кодировку (набор символов) на стороне клиента. По умолчанию используется кодировка базы данных. Наборы символов, поддерживаемые сервером QHB, описаны в подразделе Поддерживаемые кодировки.

lc_messages (string)

Устанавливает язык, на котором отображаются сообщения. Допустимые значения зависят от системы; дополнительную информацию см. в разделе Поддержка языковых стандартов. Если для этой переменной задана пустая строка (по умолчанию), то значение наследуется от среды выполнения сервера системно-зависимым способом.

В некоторых системах эта категория локали отсутствует. Эту переменную все равно можно задать, но действовать она не будет. Кроме того, есть вероятность, что переведенных сообщений на заданном языке не существует. В этом случае сообщения по-прежнему будут отображаться на английском.

Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

lc_monetary (string)

Устанавливает локаль для форматирования денежных сумм, например, с использованием функций семейства to_char. Допустимые значения зависят от системы; дополнительную информацию см. в разделе Поддержка языковых стандартов. Если для этой переменной задана пустая строка (по умолчанию), то значение наследуется от среды выполнения сервера системно-зависимым способом.

lc_numeric (string)

Устанавливает локаль для форматирования чисел, например, с использованием функций семейства to_char. Допустимые значения зависят от системы; дополнительную информацию см. в разделе Поддержка языковых стандартов. Если для этой переменной задана пустая строка (по умолчанию), то значение наследуется от среды выполнения сервера системно-зависимым способом.

lc_time (string)

Устанавливает локаль для форматирования даты и времени, например, с использованием функций семейства to_char. Допустимые значения зависят от системы; дополнительную информацию см. в разделе Поддержка языковых стандартов. Если для этой переменной задана пустая строка (по умолчанию), то значение наследуется от среды выполнения сервера системно-зависимым способом.

default_text_search_config (string)

Выбирает конфигурацию текстового поиска, которая будет использоваться вариантами функций текстового поиска, не имеющими аргумента, явно задающего конфигурацию. Дополнительную информацию см. в главе Полнотекстовый поиск. Встроенное значение по умолчанию — pg_catalog.simple, но qhb_bootstrap (или initdb) инициализирует файл конфигурации со значением, соответствующим выбранной локали lc_ctype, если получится определить ее конфигурацию.

Предварительная загрузка разделяемых библиотек

Для настройки предварительной загрузки на сервер разделяемых библиотек доступно несколько параметров, позволяющих подключить дополнительные функции или повысить производительность. Например, при значении ’$libdir/mylib’ в память будет загружена mylib.so (или на некоторых платформах mylib.sl) из стандартного каталога библиотек данной установки. Различия между этими параметрами заключаются в том, когда они вступают в силу и какие права требуются для их изменения.

Таким образом можно предварительно загрузить библиотеки на процедурных языках QHB, обычно используя синтаксис '$libdir/plXXX' где XXX — это имя языка: pgsql, perl, tcl или python.

Только разделяемые библиотеки, предназначенные специально для использования с QHB, могут быть загружены таким образом. Каждая библиотека, поддерживаемая QHB, имеет «магический блок», который проверяется на совместимость. Поэтому загрузить таким способом библиотеки не для QHB нельзя. Для этого можно воспользоваться средствами операционной системы, например LD_PRELOAD.

В целом, чтобы узнать рекомендуемый способ загрузки конкретного модуля, следует обратиться к документации этого модуля.

local_preload_libraries (string)

Эта переменная задает одну или несколько разделяемых библиотек, которые будут предварительно загружаться при установке соединения. Она содержит разделенный запятыми список имен библиотек, где каждое имя должно восприниматься командой LOAD. Пробелы между именами игнорируются; если требуется включить в имя пробелы или запятые, заключите его в кавычки. Заданное значение этого параметра действует только в начале соединения. Последующие изменения ни на что не влияют. Если указанная в списке библиотека не найдена, попытка подключения завершится неудачно.

Этот параметр может задать любой пользователь. Из-за этого загружать можно только библиотеки, находящиеся в подкаталоге plugins стандартного каталога библиотек данной установки. (Ответственность за то, чтобы там находились только «безопасные» библиотеки, несет администратор.) В local_preload_libraries этот каталог можно задать явно (например $libdir/plugins/mylib), или просто задать имя библиотеки — mylib (оно будет воспринято как $libdir/plugins/mylib).

Цель этого механизма — позволить пользователям, не имеющим специальных прав, загружать отладочные библиотеки или библиотеки для оценки производительности в конкретные сеансы, обходясь без явной команды LOAD. Для этой цели данный параметр обычно устанавливается в переменной среды PGOPTIONS на клиенте или с помощью команды ALTER ROLE SET.

Однако обычно этот параметр не следует использовать, если только модуль не предназначен специально для такой загрузки обычными пользователями. В качестве альтернативы рекомендуется рассмотреть session_preload_libraries (см. ниже).

session_preload_libraries (string)

Эта переменная задает одну или несколько разделяемых библиотек, которые будут предварительно загружаться при установке соединения. Она содержит разделенный запятыми список имен библиотек, где каждое имя должно восприниматься командой LOAD. Пробелы между именами игнорируются; если требуется включить в имя пробелы или запятые, заключите его в кавычки. Заданное значение этого параметра действует только в начале соединения. Последующие изменения ни на что не влияют. Если указанная в списке библиотека не найдена, попытка подключения завершится неудачно. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Цель этого механизма — позволить загружать отладочные библиотеки или библиотеки для оценки производительности в конкретные сеансы, обходясь без явной команды LOAD. Например, можно загрузить модуль auto_explain во всех сеансах пользователя с заданным именем, установив этот параметр с помощью команды ALTER ROLE SET. Кроме того, этот параметр можно менять без перезапуска сервера (но изменения вступают в силу только при запуске нового сеанса), поэтому таким образом проще подгружать новые модули, даже если это нужно сделать для всех сеансов.

В отличие от shared_preload_libraries (см. ниже), загрузка библиотеки при запуске сеанса, а не при первом использовании не дает большого выигрыша в производительности. Однако при использовании пула соединений некоторое преимущество возникает.

shared_preload_libraries (string)

Эта переменная задает одну или несколько разделяемых библиотек, которые будут предварительно загружаться при запуске сервера. Она содержит разделенный запятыми список имен библиотек, где каждое имя должно восприниматься командой LOAD. Пробелы между именами игнорируются; если требуется включить в имя пробелы или запятые, заключите его в кавычки. Этот параметр можно задать только при запуске сервера. Если указанная в списке библиотека не будет найдена, сервер не запустится.

Некоторые библиотеки должны выполнять определенные операции, которые могут иметь место только при запуске процесса qhbmaster, например выделение разделяемой памяти, резервирование легких блокировок или запуск фоновых рабочих процессов. Такие библиотеки должны загружаться при запуске сервера посредством этого параметра. За подробной информацией обратитесь к документации библиотек.

Также можно предварительно загрузить и другие библиотеки. Предварительная загрузка позволяет избавиться от задержки, возникающей при первом использовании библиотеки. Однако при этом может немного увеличиться время запуска каждого нового серверного процесса, даже если тот никогда не будет использовать эту библиотеку. Поэтому этот параметр рекомендуется применять только для библиотек, которые будут использоваться в большинстве сеансов. Кроме того, для изменения этого параметра требуется перезапуск сервера, поэтому он не подходит например, для краткосрочных задач отладки. Для подобных целей лучше применить параметр session_preload_libraries.

jit_provider (string)

Эта переменная представляет собой имя библиотеки провайдера JIT-компиляции, которая будет использоваться (см. подраздел Подключаемые провайдеры JIT-компиляции). Значение по умолчанию — llvmjit. Этот параметр можно задать только при запуске сервера.

Если указывается несуществующая библиотека, JIT-компиляция будет недоступна, но ошибки не возникнет. Это позволяет устанавливать поддержку JIT-компиляции отдельно от основного пакета QHB.

Другие параметры по умолчанию

dynamic_library_path (string)

Если необходимо открыть динамически загружаемый модуль и его имя, заданное в команде CREATE FUNCTION или LOAD, не содержит имени каталога (т. е. в имени нет слэша), система будет искать требуемый файл в этом пути.

Значением dynamic_library_path должен быть список абсолютных путей к каталогам, разделенных двоеточием. Если элемент списка начинается со специальной строки $libdir, вместо нее подставляется указанный при компиляции каталог пакета библиотек QHB; сюда устанавливаются модули, предоставляемые стандартным дистрибутивом QHB. (Чтобы узнать имя этого каталога, выполните pg_config --pkglibdir.) Например:

dynamic_library_path = '/usr/local/qhb/lib:/home/my_project/lib:$libdir'

Значение по умолчанию для этого параметра — '$libdir'. Если в качестве значения задана пустая строка, автоматический поиск по заданному пути выключается.

Суперпользователи и пользователи с подходящим правом SET могут изменить этот параметр в процессе работы, но изменение, выполненное таким образом, будет действовать только до завершения клиентского соединения, поэтому этот метод следует оставить для целей разработки. Для прочих целей этот параметр рекомендуется задавать в файле конфигурации qhb.conf.

gin_fuzzy_search_limit (integer)

Мягкий верхний предел для размера набора, возвращаемого при сканировании индексов GIN. Дополнительную информацию см. в разделе Советы и приемы для индексов GIN.

Управление блокировками

deadlock_timeout (integer)

Это время ожидания блокировки, по истечении которого будет произведена проверка состояния взаимоблокировки. Проверка на взаимоблокировку является относительно дорогой, поэтому сервер не выполняет ее каждый раз при ожидании блокировки. Мы с оптимизмом предполагаем, что взаимоблокировки не часто случаются в производственных приложениях, и просто ждем некоторое время, прежде чем проверять наличие взаимоблокировок. Увеличение значения этого параметра сокращает время, затрачиваемое на ненужные проверки взаимоблокировок, но замедляет передачу сообщений о реальных взаимоблокировках. Если это значение указано без единиц измерения, оно считается заданным в миллисекундах. Значение по умолчанию — одна секунда (1s), что, наиболее близко к минимальному значению, которое желательно применять на практике. На сильно загруженном сервере его можно увеличить. В идеале значение этого параметра должно превышать типичное время транзакции, чтобы повысить вероятность того, что блокировка будет снята раньше, чем ожидающая транзакция решит проверить состояние взаимоблокировки. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Когда включен параметр log_lock_waits, этот параметр также определяет время ожидания, по истечении которого в журнал сервера будет записано сообщение об ожидании блокировки. Если вы пытаетесь исследовать задержки, вызванные блокировками, имеет смысл задать параметру deadlock_timeout более низкое значение, чем обычно.

max_locks_per_transaction (integer)

Разделяемая таблица блокировок отслеживает блокировки для max_locks_per_transaction * (max_connections + max_prepared_transactions) объектов (например, таблиц); таким образом, в любой момент времени может быть заблокировано не более этого количества различных объектов. Этот параметр управляет средним количеством блокировок объектов, выделяемым для каждой транзакции; отдельные транзакции могут заблокировать больше объектов, если все блокировки помещаются в таблице. Это не количество строк, которые могут быть заблокированы; их количество не ограничено. Значение по умолчанию, 64, как показало время, является достаточным, но может возникнуть необходимость его увеличить, если запросы обращаются ко множеству различных таблиц в одной транзакции, как например, запрос к родительской таблице со множеством потомков. Этот параметр можно задать только при запуске сервера.

При запуске резервного сервера следует установить для этого параметра значение большее или равное значению на главном сервере. В противном случае на резервном сервере не будут разрешены запросы.

max_pred_locks_per_transaction (integer)

Разделяемая таблица предикатных блокировок отслеживает блокировки для max_pred_locks_per_transaction * (max_connections + max_prepared_transactions) объектов (например, таблиц); таким образом, в любой момент времени может быть заблокировано не более этого количества различных объектов. Этот параметр управляет средним количеством блокировок объектов, выделяемым для каждой транзакции; отдельные транзакции могут заблокировать больше объектов, если все блокировки помещаются в таблице. Это не количество строк, которые могут быть заблокированы; их количество не ограничено. Значение по умолчанию, 64, при тестировании было в целом удовлетворительным, но может возникнуть необходимость его увеличить, если клиенты обращаются ко множеству различных таблиц в одной сериализуемой транзакции. Этот параметр можно задать только при запуске сервера.

max_pred_locks_per_relation (integer)

Этот параметр определяет, сколько страниц или кортежей одного отношения может быть предикатно заблокировано, прежде чем будет запрошена блокировка для всего отношения. Значения, большие или равные нулю, означают абсолютный предел, а при отрицательном значении предел будет равен значению max_pred_locks_per_transaction, поделенному на модуль этого значения. По умолчанию установлено значение -2, при котором сохраняется поведение, наблюдавшееся в предыдущих версиях QHB. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

max_pred_locks_per_page (integer)

Этот параметр определяет, сколько строк на одной странице может быть предикатно заблокировано, прежде чем будет запрошена блокировка для всей страницы. Значение по умолчанию — 2. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Совместимость с разными версиями и платформами

Предыдущие версии QHB

array_nulls (boolean)

Этот параметр определяет, будет ли синтаксический анализатор вводимого массива распознавать строку NULL без кавычек как элемент массива, равный NULL. Значение по умолчанию — on (включен), что позволяет вводить элементы массива, содержащие значения NULL. Для обратной совместимости с приложениями, требующими для этой цели введения NULL в кавычках (“NULL”), эту переменную можно выключить (задав значение off).

Обратите внимание, что массивы, содержащие значения NULL, можно создавать, даже если эта переменная имеет значение off.

backslash_quote (enum)

Этот параметр определяет, можно ли будет представить знак апострофа в строковом литерале в виде \'. В стандарте SQL представлен другой, предпочитаемый способ представления знака апострофа путем его дублирования (''), но исторически QHB также принимала \'. Однако использование варианта \' создает риски безопасности, потому что в некоторых клиентских кодировках в наборе символов существуют многобайтовые символы, последний байт которых численно равнозначен коду ASCII \. Если код на стороне клиента выполнит экранирование некорректно, это может привести к атаке SQL-инъекцией. Этот риск можно предотвратить, заставив сервер отклонять запросы, в которых апостроф экранируется обратным слэшем. Допустимые значения параметра backslash_quote: on (всегда принимать \'), off (всегда отклонять) и safe_encoding (принимать только в том случае, если клиентская кодировка не допускает наличия кода ASCII \ в многобайтовых символах). Значение по умолчанию — safe_encoding.

Обратите внимание, что в строковом литерале, соответствующем стандарту, знаки \ в любом случае обозначают просто \. Этот параметр влияет только на восприятие нестандартных литералов, включая синтаксис с экранированием строки (E'...').

escape_string_warning (boolean)

Когда этот параметр включен, выдается предупреждение, если в обычном строковом литерале (с синтаксисом '...') появляется обратный слэш (\), а параметр standard_conforming_strings выключен. Значение по умолчанию — on (включен).

Приложения, которые желают использовать обратный слэш в качестве управляющего символа, необходимо модифицировать для использования синтаксиса с экранированными строками (E'...'), поскольку по умолчанию теперь в обычных строках обратный слэш воспринимается как обычный символ, в соответствии со стандартом SQL. Включение этой переменной помогает найти код, требующий изменения.

lo_compat_privileges (boolean)

Если задать для этой переменной значение on (включен), то в целях совместимости с предыдущими версиями QHB выключаются проверки прав для больших объектов. Значение по умолчанию — off (выключен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

Установка этой переменной выключает не все проверки безопасности, связанные с большими объектами.

quote_all_identifiers (boolean)

Когда база данных генерирует SQL, этот параметр принудительно заключает в кавычки все идентификаторы, даже если это не ключевые слова (в настоящее время). Это затрагивает вывод EXPLAIN а также результаты функций типа pg_get_viewdef. См. также описание параметра --quote-all-identifiers утилит qhb_dump и qhb_dumpall.

standard_conforming_strings (boolean)

Этот параметр определяет, будут ли обычные строковые литералы ('...') воспринимать обратный слэш буквально, как указано в стандарте SQL. Значение по умолчанию — on (включен). Приложения могут проверять этот параметр, чтобы определить, как будут обрабатываться строковые литералы. Также наличие этого параметра может рассматриваться как признак того, что поддерживается синтаксис экранируемых строк (E'...'). Этот синтаксис (подраздел Строковые константы с управляющими последовательностями в стиле C) следует использовать, если приложению нужно, чтобы обратные слэши воспринимались как спецсимволы.

synchronize_seqscans (boolean)

Этот параметр позволяет синхронизировать обращения при последовательном сканировании больших таблиц, чтобы эти обращения считывали один блок примерно в одно и то же время и тем самым разделяли рабочую нагрузку ввода-вывода данных. Когда этот параметр включен, сканирование может начаться в середине таблицы, чтобы синхронизироваться с уже выполняющимся сканированием, а достигнув конца, «завернуться» к началу, чтобы обработать оставшиеся строки. Это может привести к непредсказуемым изменениям порядка строк, возвращаемых запросами, в которых нет предложения ORDER BY. При значении off (выключен) последовательное сканирование всегда начинается с начала таблицы. Значение по умолчанию — on (включен).

Совместимость с разными платформами и клиентами

transform_null_equals (boolean)

Когда этот параметр включен, выражения вида выражение = NULL (или NULL = выражение) обрабатываются как выражение IS NULL, то есть они возвращают true, если выражение дает значение NULL, и false в противном случае. Согласно спецификации SQL, сравнение выражение = NULL должно всегда возвращать NULL (неизвестное значение). Поэтому по умолчанию этот параметр выключен (установлен в off).

Обратите внимание, что этот параметр влияет только на точную форму сравнения = NULL, но не на другие операторы сравнения или другие выражения, которые в вычислительном отношении равнозначны некоторому выражению с оператором равенства (например IN). Таким образом, этот параметр не может быть универсальным решением проблем плохого программирования.

Сопутствующую информацию см. в разделе Функции и операторы сравнения.

Обработка ошибок

exit_on_error (boolean)

Если этот параметр включен, любая ошибка приведет к прерыванию текущего сеанса. По умолчанию он выключен, так что только сеанс будет прерываться только при критических (FATAL) ошибках.

restart_after_crash (boolean)

Если этот параметр включен (по умолчанию), QHB будет автоматически перезагружаться после сбоя серверного процесса. Обычно это лучший способ обеспечить максимальную доступность базы данных. Однако в некоторых обстоятельствах, например, когда QHB вызывается кластерным ПО, такую перезагрузку лучше выключить, чтобы кластерное ПО могло получить контроль и выполнить действия, которые сочтет целесообразными.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

data_sync_retry (boolean)

Если этот параметр выключен (по умолчанию), QHB будет выдавать ошибку уровня PANIC (паника) в случае неудачи при попытке сохранить файлы с измененными данными в файловой системе. Это приведет к сбою сервера баз данных. Этот параметр можно задать только при запуске сервера.

В некоторых операционных системах состояние данных в кэше страниц ядра после ошибки обратной записи неизвестно. В некоторых случаях эти данные могут полностью пропасть, и становится опасно повторять попытку записи: вторая попытка может быть объявлена успешной, тогда как в действительности данные были утеряны. В подобных обстоятельствах единственный способ избежать потери данных — после любого сбоя восстанавливать их из WAL, предпочтительно перед этим выяснив основную причину сбоя и заменив неисправное оборудование.

При включении этого параметра QHB все так же будет выдавать ошибку, но при этом продолжит работу, чтобы иметь возможность повторить операцию сохранения данных на более поздней контрольной точке. Включать его следует только после выяснения того, что именно операционная система делает с данными в буфере при ошибке обратной записи.

recovery_init_sync_method (enum)

Когда в этом параметре установлено значение fsync (по умолчанию), QHB будет рекурсивно открывать и синхронизировать все файлы в каталоге данных до начала восстановления после сбоя. Поиск файлов будет проводиться по символическим ссылкам для каталога WAL и всех сконфигурированных табличных пространств (но не по другим символическим ссылкам). Это делается для того, чтобы убедиться, что все файлы WAL и данных надежно сохранены на диске до воспроизведения изменений. Такой подход применяется при запуске кластера баз данных, работа которого не была завершена должным образом (это касается и копий, созданных qhb_basebackup).

В Linux вместо этого может быть использовано значение syncfs, при котором операционная система целиком синхронизирует файловые системы, содержащие каталог данных, файлы WAL и табличные пространства (но не другие файловые системы, которые тоже могут быть доступны через символические ссылки). Это вариант может работать гораздо быстрее, чем fsync, поскольку при нем не нужно открывать каждый файл по отдельности. С другой стороны, он может быть медленнее, если файловая система совместно используется другими приложениями, изменяющими множество файлов, поскольку эти файлы также будут записываться на диск. Более того, Linux версий до 5.8 может не сообщать QHB об ошибках ввода/вывода, произошедших во время записи данных на диск, и соответствующие сообщения об ошибках могут выводиться только в журналы ядра.

Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Предустановленные параметры

Следующие «параметры» доступны только для чтения. В связи с этим они были исключены из примера файла qhb.conf. Эти параметры сообщают о различных аспектах поведения QHB, которые могут представлять интерес для определенных приложений, в частности, административных интерфейсов. Большинство из них определяется при компиляции или установке QHB.

block_size (integer)

Сообщает размер блока на диске. Он определяется значением BLCKSZ при сборке сервера. Значение по умолчанию — 8192 байта. От значения block_size зависят некоторые другие переменные конфигурации (например, shared_buffers). Информацию об этом можно найти в разделе Потребление ресурсов.

data_checksums (boolean)

Сообщает, включено ли в этом кластере применение контрольных сумм для проверки данных. Дополнительную информацию см. в описании параметра data-checksums.

data_directory_mode (integer)

В системах Unix этот параметр сообщает о разрешениях для каталога данных (определяемого параметром data_directory), установленных при запуске сервера. Дополнительную информацию см. в описании параметра allow-group-access.

debug_assertions (boolean)

Сообщает, была ли QHB собрана с включенными проверочными утверждениями. Это происходит, если при сборке QHB определяется макрос USE_ASSERT_CHECKING (например, посредством параметра --enable-cassert скрипта configure). По умолчанию QHB собирается без проверочных утверждений.

integer_datetimes (boolean)

Сообщает, была ли QHB собрана с поддержкой даты и времени в 64-битных целых. Значение по умолчанию — on (включен).

in_hot_standby (boolean)

Сообщает, находится ли сервер в данный момент в режиме горячего резерва. При значении on (включен) все транзакции принудительно переводятся в режим «только чтение». В рамках сеанса это может измениться, только если сервер повышается до основного. Подробную информацию см. в разделе Горячий резерв.

lc_collate (string)

Сообщает локаль, по правилам которой выполняется сортировка текстовых данных. Дополнительную информацию см. в разделе Поддержка языковых стандартов. Это значение определяется при создании базы данных.

lc_ctype (string)

Сообщает локаль, определяющую классификацию символов. Дополнительную информацию см. в разделе Поддержка языковых стандартов. Это значение определяется при создании базы данных. Обычно оно совпадает со значением lc_collate, но для отдельных приложений может быть установлено по-другому.

max_function_args (integer)

Сообщает максимальное количество аргументов функции. Оно определяется значением константы FUNC_MAX_ARGS при сборке сервера. Значение по умолчанию — 100 аргументов.

max_identifier_length (integer)

Сообщает максимальную длину идентификатора. Она определяется числом на единицу меньше значения NAMEDATALEN при сборке сервера. По умолчанию значение константы NAMEDATALEN равно 64; следовательно, значение max_identifier_length по умолчанию — 63 байта, но в многобайтовых кодировках число символов будет меньше.

max_index_keys (integer)

Сообщает максимальное количество ключей индекса. Оно определяется значением константы INDEX_MAX_KEYS при сборке сервера. Значение по умолчанию — 32 ключа.

segment_size (integer)

Сообщает количество блоков (страниц), которые можно сохранить в сегменте файла. Оно определяется значением константы RELSEG_SIZE при сборке сервера. Максимальный размер сегмента в байтах равен произведению segment_size и block_size; по умолчанию это 1 ГБ.

server_encoding (string)

Сообщает кодировку (набор символов) базы данных. Она определяется при создании базы данных. Обычно клиентов должно интересовать только значение параметра client_encoding.

server_version (string)

Сообщает номер версии сервера. Он определяется значением константы PG_VERSION при сборке сервера.

server_version_num (integer)

Сообщает номер версии сервера в виде целого числа. Он определяется значением константы PG_VERSION_NUM при сборке сервера.

shared_memory_size (integer)

Сообщает размер основной области разделяемой памяти, округленный до ближайшего мегабайта.

shared_memory_size_in_huge_pages (integer)

Сообщает количество огромных страниц, необходимых для основной области разделяемой памяти, исходя из заданного значения huge_page_size. Если огромные страницы не поддерживаются, будет выдано значение -1.

Этот параметр поддерживается только в Linux. На других платформах в нем всегда установлено значение -1. Более подробную информацию об использовании огромных страниц в Linux см. в подразделе Огромные страницы в Linux.

ssl_library (string)

Сообщает имя библиотеки SSL, с которой был собран этот сервер QHB (даже если на данный момент SSL для данного экземпляра не сконфигурирован или не используется), например OpenSSL, либо выводит пустую строку, если сборка проводилась без библиотеки.

wal_block_size (integer)

Сообщает размер блока WAL на диске. Он определяется значением константы XLOG_BLCKSZ при сборке сервера. Значение по умолчанию — 8192 байта.

wal_segment_size (integer)

Сообщает размер сегментов журнала упреждающей записи. Значение по умолчанию — 16 МБ. Дополнительную информацию см. в разделе Конфигурация WAL.

Специализированные параметры

Эта функциональность была разработана для того, чтобы дополнительные модули (например, процедурные языки) могли добавлять параметры, обычно неизвестные QHB. Это позволяет настраивать модули расширения стандартными способами.

Имена специализированных параметров состоят из двух частей: имя расширения, затем, после точки, собственно имя параметра, по аналогии с полными именами в SQL. Например: plpgsql.variable_conflict.

Поскольку установка специализированных параметров может потребоваться в процессах, не загрузивших соответствующий модуль расширения, QHB примет значения для любого имени параметра, состоящего из двух частей. Такие переменные рассматриваются как местозаполнители и не действуют до тех пор, пока не будет загружен модуль, который их определяет. Когда модуль расширения загружается, он добавляет свои определения переменных, преобразует любые значения местозаполнителей в соответствии с этими определениями. Если имеются какие-либо нераспознанные местозаполнители, начинающиеся с его имени расширения, выдаются предупреждения, а эти местозаполнители удаляются.

Параметры для разработчиков

Следующие параметры предназначены для использования разработчиками в процессе тестирования и никогда не должны применяться в рабочей базе данных. Тем не менее некоторые из них могут помочь при восстановлении сильно поврежденных баз данных. По этим причинам они были исключены из примера файла qhb.conf. Обратите внимание, что для работы со многими из этих параметров требуются специальные флаги компиляции исходного кода.

allow_in_place_tablespaces (boolean)

Позволяет создавать табличные пространства как каталоги в pg_tblspc, когда в команде CREATE TABLESPACE в качестве строки расположения каталога задана пустая строка. Это предназначено для тестирования сценариев репликации, когда основной и резервный серверы запущены на одном компьютере. Такие каталоги, скорее всего, собьют с толку инструменты резервного копирования, которые ожидают найти в этом месте только символические ссылки. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

allow_system_table_mods (boolean)

Разрешает модификации структуры системных таблиц, а также некоторые другие рискованные действия с системными таблицами. Если этот параметр отключен, эти действия не разрешены даже суперпользователям. Необдуманное применение этого параметра может вызвать неисправимую потерю данных или серьезно повредить СУБД. Только суперпользователи могут изменять этот параметр. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

backtrace_functions (string)

Этот параметр содержит список имен функций C/RUST через запятую. Если возникает ошибка и имя внутренней функции C/RUST, где произошли ошибка, соответствует значению в этом списке, то вместе с сообщением об ошибке в журнал сервера будет записываться обратная трассировка. Это можно использовать для отладки определенных областей исходного кода.

Поддержка обратной трассировки доступна не на всех платформах, а качество этой трассировки зависит от параметров компиляции.

Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

debug_discard_caches (integer)

При значении 1 каждая запись кэша системного каталога становится недействительной при первой же возможности, независимо от того, произошло ли в действительности что-то, что могло бы привести ее в недействительное состояние. В результате кэширование системных каталогов по сути отключается, поэтому сервер будет работать крайне медленно. При более высоких значениях инвалидация кэша выполняется рекурсивно, приводя к еще большему замедлению, что полезно только для тестирования собственно логики кэширования. При значении 0 (по умолчанию) выбирается обычное поведение кэширования каталога.

Этот параметр может быть очень полезен при попытках спровоцировать трудновоспроизводимые ошибки, связанные с параллельными изменениями в каталоге, но в остальном требуется редко. Подробную информацию см. в файлах исходного кода inval.c и pg_config_manual.h.

Этот параметр поддерживается, если во время компиляции был определен макрос DISCARD_CACHES_ENABLED (что происходит автоматически при использовании параметра конфигурации --enable-cassert). В производственных сборках он всегда будет иметь значение 0, и попытки изменить его вызовут ошибку.

force_parallel_mode (enum)

Позволяет использовать параллельные запросы в целях тестирования, даже в случаях, когда не ожидается никакого улучшения производительности. Допустимые значения force_parallel_mode: off (использовать параллельный режим, только когда от этого ожидается улучшение производительности), on (принудительно использовать параллельный режим для всех запросов, для которых это считается безопасным) и regress (похоже на on, но с дополнительными изменениями поведения, описанными ниже).

Говоря точнее, при значении on в вершину любого плана, для которого это считается безопасным, добавляется узел Gather (Сбор), так что запрос выполняется внутри параллельного рабочего процесса. Даже когда параллельный рабочий процесс недоступен или его нельзя использовать, такие операции, как запуск субтранзакции, которые были бы запрещены в контексте параллельного запроса, будут запрещены, если только планировщик не решит, что это вызовет ошибку запроса. Если при установке этого параметр возникают ошибки или выдаются неожиданные результаты, возможно, некоторые функции, применяемые в этом запросе, нужно будет пометить как PARALLEL UNSAFE (или, возможно, PARALLEL RESTRICTED).

Значение regress имеет тот же эффект, что и значение on, плюс некоторые дополнительные эффекты, призванные упростить автоматическое регрессионное тестирование. Обычно сообщения от параллельного рабочего процесса включают строку контекста, отмечающую это, но значение regress подавляет эту строку, поэтому вывод будет такой же, как и при не параллельном выполнении. Кроме того, узлы Gather, добавляемые в планы при установке этого значения, скрываются в выводе EXPLAIN, так что этот вывод соответствует тому, который был бы получен при значении off (выключен).

ignore_system_indexes (boolean)

Игнорировать системные индексы при чтении системных таблиц (но при этом индексы все равно будут меняться при изменении этих таблиц). Это полезно для восстановления системы при поврежденных системных индексах. Этот параметр нельзя изменить после запуска сеанса.

post_auth_delay (integer)

Время задержки при запуске нового серверного процесса после проведения им процедуры аутентификации. Этот параметр предназначен для того, чтобы разработчики имели возможность присоединить отладчик к серверному процессу. Если это значение указано без единиц измерения, оно считается заданным в секундах. При нулевом значении (по умолчанию) задержка выключается. Этот параметр нельзя изменить после запуска сеанса.

pre_auth_delay (integer)

Время задержки сразу после ветвления нового серверного процесса, до проведения им процедуры аутентификации. Этот параметр предназначен для того, чтобы разработчики имели возможность присоединить отладчик к серверному процессу в целях выявления нарушений аутентификации. Если это значение указано без единиц измерения, оно считается заданным в секундах. При нулевом значении (по умолчанию) задержка выключается. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

trace_notify (boolean)

Генерирует и выводит очень подробную отладочную информацию при выполнении команд LISTEN и NOTIFY. Чтобы отправлять эти данные клиенту или в журнал сервера, параметр client_min_messages или log_min_messages соответственно должен иметь значение DEBUG1 или ниже.

trace_recovery_messages (enum)

Включает запись в журнал отладочной информации, касающейся восстановления, которая иначе не протоколируется. Этот параметр позволяет пользователю переопределить обычное значение log_min_messages, но только для конкретных сообщений. Он предназначен для использования при отладке режима горячего резерва. Допустимые значения: DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1 и LOG. Значение по умолчанию, LOG, никак не влияет на решения о протоколировании. С другими значениями связанные с восстановлением отладочные сообщения, имеющие заданный приоритет или выше, записываются так, как если бы они имели приоритет LOG; при обычных значениях log_min_messages это подразумевает их обязательную передачу в журнал сервера. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

trace_sort (boolean)

Если этот параметр включен, то во время операций сортировки будет выводиться информация об использовании ресурсов. Этот параметр доступен, только если при компиляции QHB был определен макрос TRACE_SORT. (Хотя в настоящее время TRACE_SORT определяется по умолчанию.)

trace_locks (boolean)

Если этот параметр включен, будет выводиться информация об использовании блокировок. Эта информация включает тип операции блокировки, тип самой блокировки и уникальный идентификатор заблокированного или разблокированного объекта. Также включены битовые маски для типов блокировки, уже полученных для этого объекта, а также для типов блокировки, ожидающих его. Кроме того, для каждого типа блокировки выводится количество полученных и ожидающих блокировок, а также их общее число. Пример вывода в файл журнала:

LOG:  LockAcquire: new: lock(0xb7acd844) id(24688,24696,0,0,0,1)
      grantMask(0) req(0,0,0,0,0,0,0)=0 grant(0,0,0,0,0,0,0)=0
      wait(0) type(AccessShareLock)
LOG:  GrantLock: lock(0xb7acd844) id(24688,24696,0,0,0,1)
      grantMask(2) req(1,0,0,0,0,0,0)=1 grant(1,0,0,0,0,0,0)=1
      wait(0) type(AccessShareLock)
LOG:  UnGrantLock: updated: lock(0xb7acd844) id(24688,24696,0,0,0,1)
      grantMask(0) req(0,0,0,0,0,0,0)=0 grant(0,0,0,0,0,0,0)=0
      wait(0) type(AccessShareLock)
LOG:  CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1)
      grantMask(0) req(0,0,0,0,0,0,0)=0 grant(0,0,0,0,0,0,0)=0
      wait(0) type(INVALID)

Этот параметр доступен, только если при компиляции QHB был определен макрос LOCK_DEBUG.

trace_lwlocks (boolean)

Если этот параметр включен, будет выводиться информация об использовании легких блокировок. Такие блокировки предназначены главным образом для обеспечения взаимоисключающего доступа к структурам данных разделяемой памяти.

Этот параметр доступен, только если при компиляции QHB был определен макрос LOCK_DEBUG.

trace_userlocks (boolean)

Если этот параметр включен, будет выводиться информация об использовании пользовательских блокировок. Она выводится в той же форме, что и с trace_locks, но для рекомендательных блокировок.

Этот параметр доступен, только если при компиляции QHB был определен макрос LOCK_DEBUG.

trace_lock_oidmin (integer)

Если этот параметр установлен, блокировки для таблиц ниже заданного OID трассироваться не будут (это используется, чтобы исключить выведение информации по системным таблицам).

Этот параметр доступен, только если при компиляции QHB был определен макрос LOCK_DEBUG.

trace_lock_table (integer)

Обязательно трассировать блокировки для таблицы с заданным OID.

Этот параметр доступен, только если при компиляции QHB был определен макрос LOCK_DEBUG.

debug_deadlocks (boolean)

Если этот параметр установлен, по истечении времени ожидания взаимоблокировки будет выводиться информация обо всех текущих блокировках.

Этот параметр доступен, только если при компиляции QHB был определен макрос LOCK_DEBUG.

log_btree_build_stats (boolean)

Если этот параметр установлен, будет протоколироваться статистика использования системных ресурсов (памяти и ЦП) при различных операциях с B-деревом.

Этот параметр доступен, только если при компиляции QHB был определен макрос BTREE_BUILD_STATS.

wal_consistency_checking (string)

Этот параметр предназначен для проверки ошибок в процедурах воспроизведения WAL. Когда этот параметр включен, в запись WAL добавляются полностраничные изображения всех изменяемых вместе с ней буферов. Если эта запись впоследствии воспроизводится, система сначала применяет каждую запись, а затем проверяет, соответствуют ли измененные этой записью буферы сохраненным образам. В некоторых случаях (например, во вспомогательных битах) незначительные изменения допустимы и будут игнорироваться. Любые неожиданные различия приведут к фатальной ошибке, и восстановление прекратится.

Пустая строка (значение этого параметра по умолчанию) выключает эту функцию. Значением параметра также может быть all для проверки всех записей или список имен менеджеров ресурсов через запятую для проверки только записей, поступающих от этих менеджеров. В настоящее время поддерживаются следующие менеджеры ресурсов: heap, heap2, btree, hash, gin, gist, sequence, spgist, brin и generic. Расширения могут определить дополнительные менеджеры ресурсов. Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

wal_debug (boolean)

Если этот параметр включен, будет выводиться отладочная информация, касающаяся WAL. Этот параметр доступен, только если при компиляции QHB был определен макрос WAL_DEBUG.

ignore_checksum_failure (boolean)

Этот параметр действует, только если включен параметр data-checksums.

При обнаружении ошибки контрольной суммы во время чтения QHB обычно сообщает об ошибке, прерывая текущую транзакцию. Если параметр ignore_checksum_failure включен, система игнорирует ошибку (но все равно выдает предупреждение) и продолжает обработку. Такое поведение может привести к сбою, распространению или сокрытию повреждения данных и другим серьезным проблемам. Однако оно же позволяет обойти ошибку и извлечь неповрежденные кортежи, которые все еще могут находиться в таблице, если заголовок блока адекватен. Если заголовок поврежден, будет выдана ошибка, даже если этот параметр включен. Значение по умолчанию — off (выключен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

zero_damaged_pages (boolean)

При обнаружении поврежденного заголовка страницы QHB обычно сообщает об ошибке, прерывая текущую транзакцию. Если параметр zero_damaged_pages включен, система вместо этого выдаст предупреждение, обнулит поврежденную страницу в памяти и продолжит обработку. Такое поведение разрушит данные, а именно все строки на поврежденной странице. Однако оно же позволяет обойти ошибку и извлечь строки из любых неповрежденных страниц, которые могут находиться в таблице. Это полезно для восстановления данных, поврежденных из-за аппаратной или программной ошибки. Обычно его стоит включать, только если не осталось никакой другой надежды восстановить данные с поврежденных страниц таблицы. Обнуленные страницы не сохраняются на диск, поэтому прежде чем снова выключить этот параметр, рекомендуется пересоздать такие таблицы или индексы. Значение по умолчанию — off (выключен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

ignore_invalid_pages (boolean)

При значении off (выключен; это значение по умолчанию), обнаружение записей WAL со ссылками на некорректные страницы во время восстановления приведет к возникновению в QHB ошибки уровня PANIC, прерывающей это восстановление. Установка в ignore_invalid_pages значения on (включен) заставляет систему игнорировать ссылки на некорректные страницы в записях WAL (но все равно выдавать предупреждения) и продолжать восстановление. Это поведение может привести к сбою, потере данных, распространению или сокрытию повреждения данных и другим серьезным проблемам. Однако это может позволить обойти ошибку уровня PANIC и закончить восстановление, чтобы сервер запустился. Этот параметр можно установить только при запуске сервера. Он действует только во время восстановления или в резервном режиме.

jit_debugging_support (boolean)

Если LLVM обладает необходимой функциональностью, регистрировать сгенерированные функции в GDB. Это облегчает отладку. Значение по умолчанию — off (выключен). Этот параметр можно задать только при запуске сервера.

jit_dump_bitcode (boolean)

Записывает сгенерированный LLVM IR-код в файловую систему, в каталог, определенный параметром data_directory. Это полезно только для работы с внутренними компонентами реализации JIT. Значение по умолчанию — off (выключен). Только суперпользователи и пользователи с подходящим правом SET могут изменять этот параметр.

jit_expressions (boolean)

Определяет, скомпилированы ли выражения JIT, когда JIT-компиляция активирована (см. раздел Когда применять JIT-компиляцию?). Значение по умолчанию — on (включен).

jit_profiling_support (boolean)

Если LLVM обладает необходимой функциональностью, выдавать данные, необходимые perf для профилирования функций, сгенерированных JIT. Выходные файлы записываются в ~/.debug/jit/; удалять их при желании должен сам пользователь. Значение по умолчанию — off (выключен). Этот параметр можно задать только при запуске сервера.

jit_tuple_deforming (boolean)

Определяет, будет ли JIT-компилироваться преобразование кортежей, когда JIT-компиляция активирована (см. раздел Когда применять JIT-компиляцию?). Значение по умолчанию — on (включен).

remove_temp_files_after_crash (boolean)

При значении on (включен; это значение по умолчанию) QHB будет автоматически удалять временные файлы после сбоя сервера. Если этот параметр выключен, файлы будут сохранены и могут использоваться, например, для отладки. Однако повторяющиеся сбои могут привести к накоплению бесполезных файлов. Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Краткие аргументы

Для удобства для некоторых параметров также доступны сопоставимые однобуквенные аргументы командной строки. Они описаны в Таблице 4. Некоторые из этих аргументов существуют по историческим причинам, поэтому их наличие в однобуквенном виде не обязательно свидетельствует о частом их использовании.

Таблица 4. Ключ краткого аргумента

Краткий аргумент | Эквивалент
-B x | shared_buffers = x
-d x | log_min_messages = DEBUGx
-e | datestyle = euro
-fb, -fb, -fh, -fm, -fn, -fo, -fs, -ft | enable_bitmapscan = off, enable_hashjoin = off, enable_indexscan = off, enable_mergejoin = off, enable_nestloop = off, enable_indexonlyscan = off, enable_seqscan = off, enable_tidscan = off
-F | fsync = off
-h x | listen_addresses = x
-i | listen_addresses = '*'
-k x | unix_socket_directories = x
-l | ssl = on
-N x | max_connections = x
-O | allow_system_table_mods = on
-p x | port = x
-P | ignore_system_indexes = on
-s | log_statement_stats = on
-S x | work_mem = x
-tpa, -tpl, -te | log_parser_stats = on, log_planner_stats = on, log_executor_stats = on, log_executor_stats = on
-W x | post_auth_delay = x

Параметры модулей и расширений, специфичных для QHB

Общие параметры

mask_pg_version (boolean)

Значение по умолчанию — off (выключен). При значении on (включен) QHB на запросы версии сервера SELECT version(); будет выдавать версию ядра PostgreSQL вместо версии QHB.

Рекомендовано использовать параметр в случаях, когда внешняя система или клиент требует поддержки специфичной версии СУБД.

Для вступления в силу требуется перезагрузка СУБД.

mask_version_template (string)

По умолчанию никак не влияет на значение имени и версии, то есть если значение закомментировано или убрано, то представление версии стандартное и зависит только от mask_pg_version (boolean).

В случае задания администратором в конфигурационном файле специфического выражения, например:

mask_version_template = 'This is QHB {qv} mimicking PG {pv}!\n {lv}'

где:

{qv} - текущая версия QHB;

{pv} - версия текущего ядра PostgreSQL;

{lv} - информация о лицензии QHB.

подменяет стандартное представление версии и имени продукта в соответствии с указанным выражением.

mask_version_template имеет более высокий приоритет, чем mask_pg_version. То есть при наличии в конфигурационном файле значения у mask_version_template значение mask_pg_version не учитывается.

Рекомендовано использовать параметр в случаях, когда внешняя система или клиент требует поддержки специфичной версии СУБД.

Для вступления в силу требуется перезагрузка СУБД.

enable_backtrace (boolean)

Значение по умолчанию — true (включен). Предназначен для автоматического создания файлов трассировки при возникновении ошибок уровня FATAL.

Отключение параметра может производиться на отключенной базе данных, с помощью изменения значения в qhb.conf на false.

Отключение на работающей базе выполняется командой ALTER SYSTEM SET enable_backtrace TO false;. В результате выполнения команды выдается предупреждение: «WARNING: disabling this will limit support or even make it impossible» (выключение этого ограничит поддержку или даже сделает ее невозможной). Для вступления изменений в силу требуется перезагрузить конфигурацию сервера, вызвав функцию SQL pg_reload_conf(), выполнив qhb_ctl reload или отправив сигнал SIGHUP главному серверному процессу.

Если параметр отключен любым способом, то при каждом перезапуске базы данных будет появляться предупреждение об ограниченной поддержке.

Включение параметра также можно осуществлять на отключенной (изменение значения на true) или включенной базе (команда ALTER SYSTEM SET enable_backtrace TO true; и pg_reload_conf()).

Файлы трассировки не содержат какой-либо персональной или конфиденциальной информации. Они необходимы для передачи в тех поддержку с целью определения части кода, которая привела к сбою.

Параметры менеджера кеша дисковых блоков TARQ

use_qhb_cache (boolean)

Логический параметр. При установке значения в true будет использоваться новая версия кеша.

qhb_cache_size (integer)

Общий размер буферного кеша (при включении use_qhb_cache=true значение shared_buffers не используется).

shared_buffers_partitions (integer)

Размер фрагментов кеша (партиций; не имеют отношения к партициям таблицы). Обращение к каждому фрагменту происходит независимо от остальных. Слишком большой размер приведет к возрастанию конкуренции за блокировки, слишком маленький может привести к задержкам, если в партиции не окажется пригодных к вытеснению блоков (все грязные или занятые фоновым процессом).

Рекомендуемое значение: 128.

tarq_cache.touch_queue_ignore (integer)

Процент заполнения фрагмента кеша, при котором обращения начинают приводить к операциям балансировки. Может принимать значения от 1 до 100 (целые числа). Для незаполненного или заполненного частично фрагмента балансировок не требуется.

Рекомендуемое значение: 50.

Ребалансировка — затратная операция, в нагруженной среде к некоторым буферам (содержимое системных таблиц, словари, последовательности) может быть много тысяч обращений в секунду, существенно не влияющих на общий баланс. Для сглаживания можно использовать следующие параметры:

tarq_cache.touch_window (integer)

Время в секундах. Все обращения в течение этого периода считаются одним обращением.

Рекомендуемое значение: 3.

tarq_cache.touch_threshold (integer)

Количество обращений к буферу, по достижении которого происходит операция ребалансировки.

Рекомендуемое значение: 5.

Включение и управление алгоритмом вытеснения параметра HOLDMEM

Для включения и управления алгоритмом вытеснения для таблиц, по возможности хранящихся в памяти, используются следующие параметры в файле конфигурации:

use_possible_buffer (boolean)

Логический параметр. При установке значения в true будет использоваться буферный кеш.

qhb_possible_buffers_size (integer)

Общий размер буферного кеша.

shared_buffers_partitions (integer)

Используется общий параметр с TARQ — размер фрагментов кеша (партиций; не имеют отношения к партициям таблицы). Обращение к каждому фрагменту происходит независимо от остальных. Слишком большой размер партиций приведет к возрастанию конкуренции за блокировки, слишком маленький может привести к задержкам, если в партиции не окажется пригодных к вытеснению блоков (все «грязные» или занятые фоновым процессом).

Рекомендуемое значение: 128.

use_qhb_onlymem_cache (boolean)

Логический параметр. При установке значения в true будет использоваться кеш памяти.

qhb_onlymem_cache_size (integer)

Общий размер кеша памяти.

Параметры конфигурации расширения Rbytea

rbytea.filesystem_storage_path (string)

Определение каталога (точки монтирования файловой системы/тома) для сохранения образов данных.

Двоичные данные будут сохраняться в данном каталоге сервера. Для каждой базы будет создаваться свой подкаталог (по OID базы), а внутри него, во множестве подкаталогов, — собственно файлы с данными.

Имена вложенных каталогов от каталога базы данных до файла будут составлять uuid типа rbytea, а расширение файла — номер транзакции, в которой данные впервые появились в системе.

Каталог должен быть доступен на чтение и запись в него для пользователя, от имени которого запускается сервер баз данных.

По умолчанию, если параметр опущен или пуст, каталогом для сохранения двоичных данных назначается '<каталог_базы_данных>'.

shared_preload_libraries (string)

Загрузка разделяемой библиотеки при старте QHB.

Данный параметр обеспечивает загрузку разделяемой библиотеки при старте QHB и инициализацию фонового процесса для очистки устаревших образов. В противном случае автоочистка устаревших образов производится не будет.

Необходимо добавить значение 'librbytea'.

Примечание
Если параметр shared_preload_libraries уже содержит указание на загрузку других библиотек, нужно не перезаписать его значение, а добавить через разделитель librbytea.

rbytea.worker_restart_time (integer)

Интервал запуска фонового процесса очистки.

Фоновый процесс не работает постоянно. Поскольку данные достаточно статичны, не требуется запускать процесс очистки слишком часто. Задержка от окончания предыдущего запуска до следующего запуска задается в данном параметре. Значение указывается в секундах. Значение по умолчанию — 86400 секунд (сутки).

rbytea.databases_for_vacuuming (string)

Задание баз данных для фонового процесса очистки.

Параметр указывает, какие базы данных подлежат очистке. Указываются названия баз данных через запятую. Главный процесс базы данных будет запускать столько фоновых процессов очистки, сколько баз данных перечислено в данном параметре. Значение по умолчанию — qhb.

rbytea.filesystem_qss_mode (integer)

Примечание
Параметр зависит от наличия QSS и связанных его парамеметров, см. Параметры QSS.

Включение фонового шифрования.

Если в системе доступно фоновое шифрование при записи на диск, данный параметр позволяет зашифровывать также и двоичные данные rbytea. Для шифрования в параметре необходимо установить значение 2. Значение по умолчанию — 0.

Вопросы смены ключа шифрования должны решаться администратором базы данных.

2B: Поддержка решений 1С

Конфигурационные параметры немедленного обновления статистики

online_analyze.enable (boolean)

Включает функцию немедленного анализа. Значение по умолчанию — false (выключен).

online_analyze.verbose (boolean)

Включает возможность протоколирования в журнал при сборе статистики (аналогично команде SQL ANALYZE VERBOSE). Значение по умолчанию — false (выключен).

online_analyze.table_type (enum)

Указывает тип таблиц, для которых следует применять немедленный анализ. Возможные варианты значений: all (все), persistent (постоянные), temporary (временные), none (никакие). Значение по умолчанию — temporary.

online_analyze.exclude_tables (string)

Указывает список таблиц, исключаемых из немедленного анализа. Например, "имя_таблицы_1, имя_таблицы_2 ...". Значение по умолчанию — пустая строка ('').

online_analyze.include_tables (string)

Задает список таблиц, подлежащих немедленному анализу (этот параметр переопределяет параметр online_analyze.exclude_tables). Значение по умолчанию — пустая строка ('').

Примечание
Значения этих двух параметров записываются в виде списка имен таблиц через запятую.

online_analyze.local_tracking (boolean)

Включает хранение статистики временных таблиц в локальном кеше. Значение по умолчанию — false (выключен).

online_analyze.capacity_threshold (integer)

Задает максимальное число временных таблиц, сохраняемых в локальном кеше. Значение по умолчанию — 100000.

Примечание
По умолчанию используется системная статистика, но имеет смысл хранить ее в локальном кеше процесса, обслуживающего сессию.

online_analyze.scale_factor (floating point)

Задает процент измененных строк от всей таблицы, при котором начинается немедленный анализ. Значение по умолчанию — 0.1.

online_analyze.threshold (integer)

Задает минимальное число измененных строк, после которого может начаться немедленный анализ. Значение по умолчанию — 50.

online_analyze.lower_limit (integer)

Указывает минимальное число строк в таблице, при котором может начаться немедленный анализ. Значение по умолчанию — 0.

online_analyze.min_interval (integer)

Минимальный интервал времени между вызовами команды ANALYZE для конкретной таблицы (в миллисекундах). Значение по умолчанию — 10000.

Конфигурационные параметры для управления использованием индексов

plantuner.disable_index (string)

Задает список индексов, которые нельзя использовать.

plantuner.enable_index (string)

Задает список индексов, которые все-таки можно использовать (этот параметр переопределяет параметр plantuner.disable_index).

plantuner.only_index (string)

Задает точный список индексов, которые можно использовать (этот параметр переопределяет параметры plantuner.disable_index и plantuner.enable_index).

Примечание
Значения этих трех параметров записываются в виде списка имен индексов через запятую.

Разрешение имени индекса происходит для конкретного пользователя, даже если параметр задан на уровне сервера. Если индекс не найден по имени, например, когда индекс вообще не существует, он игнорируется.

plantuner.fix_empty_table (boolean)

Этот параметр меняет оценку планировщиком совсем пустых таблиц (только что созданных или опустошенных при помощи команды TRUNCATE). Если значение равно true, то QHB планирует в предположении, что эти таблицы так и будут оставаться пустыми. При значении false QHB по умолчанию будет считать, что таблицы содержат некоторое количество записей (обычно 20).

Очистка памяти с повышенной безопасностью

QHB предоставляет возможность перед удалением данных выполнить соответствующую очистку памяти, то есть выполнить перезапись этих данных нулевыми байтами. Управление данными возможностями осуществляется с помощью соответствующих конфигурационных параметров в файле конфигурации qhb.conf.

В стандартной версии данные параметры по умолчанию отключены (false) для исключения торможения. В сертифицированной версии эти параметры должны быть включены (true) в обязательном порядке.

wipe_file_on_delete (boolean)

Включает удаление файлов из внешней памяти.

Для SQL-команд, выполняющих удаление файлов и возврат операционной системе соответствующего дискового пространства, включение данного параметра обеспечит заполнение файлов нулевыми байтами перед удалением. К данным командам относятся:

команды по удалению объектов базы данных: DROP DATABASE, DROP INDEX, DROP MATERIALIZED VIEW, DROP SCHEMA,DROP TABLE, DROP TEMPORARY TABLE, TRUNCATE;

команды по пересозданию объектов базы данных: ALTER TABLE ADD COLUMN, ALTER TABLE ALTER COLUMN TYPE, REINDEX, VACUUM FULL.

wipe_heaptuple_on_delete (boolean)

Включает очистку страниц.

Включение данного параметра обеспечит заполнение нулевыми байтами всех мест на странице, которые помечаются как свободные при выполнении команды VACUUM.

wipe_memctx_on_free (boolean)

Включает очистку блоков памяти в ОЗУ.

Включение данного параметра обеспечит заполнение нулевыми байтами освобождаемой области оперативной памяти (ОЗУ), соответствующей удаляемому контексту.

wipe_xlog_on_free (boolean)

Включает очистку файлов WAL.

Включение данного параметра обеспечит заполнение нулевыми байтами сегментов журнала упреждающей записи (WAL) перед удалением или переиспользованием.

Запуск планировщика, брокера очереди сообщений при старте СУБД

with_qsched (string)

Включает запуск планировщика задач при старте СУБД.

with_mq_broker (string)

Включает запуск брокера очереди сообщений при старте СУБД.

Примечание
Значения этих параметров записываются в виде списка пар владельцев баз данных и имен баз данных через запятую. Например, 'db_owner1:dbname1, db_owner2:db_name2' или 'db_owner3:dbname3', где db_owner$ — имя владельца базы данных, а db_name$ — имя базы данных.

Автоматическая очистка журнала выполненных задач планировщика

Включение очистки журнала выполненных задач требует указания параметров qsched_log_rotation_max_lifetime и/или qsched_log_rotation_max_entries, а также qsched_log_rotation_period.

qsched_log_rotation_max_lifetime (string)

Максимальное время жизни записи в журнале, указывается в формате '1d 1h 1m'.

qsched_log_rotation_max_entries (string)

Максимальное количество записей в журнале, указывается в формате '1d 1h 1m'.

qsched_log_rotation_period (string)

Периодичность удаления записей в журнале, указывается в формате '1d 1h 1m'.

qsched_log_rotation_strategy (integer)

Стратегия очистки журнала, 0 — DELETE, 1 — TRUNCATE, 2 — автоматически. Значение по умолчанию — 0.

Примечание
Поскольку TRUNCATE системных таблиц требует разрешения на модификацию системных таблиц, для работ стратегий 1 и 2 требуется дополнительный параметр конфигурации allow_system_table_mods = true.

qsched_log_rotation_adjustment_period (integer)

Периодичность подстройки автоматической стратегии. Значение по умолчанию — 10.

Включение фоновых процессов целостности и безопасности

Включение фонового процесса контроля целостности требует указания параметра integrity_checks.

Подсистема контроля целостности поддерживает конфигурацию периодичности проверок целостности на запущенной СУБД: используется параметр integrity_period.

Включение фонового процесса контроля профилей безопасности требует указания параметра logon_jobs.

integrity_checks (bool)

Включение фонового процесса контроля целостности, указывается в формате 'on off'. Значение по умолчанию — off (выключен).

integrity_period (string)

Период проведения проверок целостности на данной СУБД, указывается в формате временных единиц '1d 1h 1m'. Значение по умолчанию — 12 часов (12h).

logon_jobs (bool)

Включение фонового процесса контроля профилей безопасности, указывается в формате 'on off'. Значение по умолчанию — off (выключен).

Примечание
Перед обновлением кластера утилитами qhb_upgrade следует выключить параметр logon_jobs, выставив его в off, и перезапустить СУБД. Иначе стабильность процесса обновления кластера не гарантируется (см. раздел Профили безопасности).

Примечание
Перед обновлением кластера утилитами qhb_upgrade следует выключить параметр integrity_checks, выставив его в off, и перезапустить СУБД. Иначе стабильность процесса обновления кластера не гарантируется.

Параметры модуля безопасного хранения QSS

Примечание
Параметры, описанные в данном разделе, имеют смысл, если QHB использует возможности QSS, который должен быть предварительно установлен. В противном случае использование шифрования будет приводить к ошибкам. При этом без установленного QSS QHB остается полнофункциональной и работоспособной, если при этом не производится попыток обращения к возможностям QSS.

qss_mode (integer)

Значение 0 (по умолчанию) означает отказ от использования возможностей QSS. При значении 2 QHB может использовать возможности QSS. Значение 1 является зарезервированным и устаревшим.

qss_encrypt_statistic (bool)

Шифровать ли статистики по данным таблиц. При включении параметра будут зашифрованы статистики по всем таблицам, а не только по шифрованным. Если qss_mode выключено, то параметр игнорируется.

Значение по умолчанию false.

После изменения значения параметра нужно пересобрать статистику всех таблиц, подробнее смотрите в разделе QSS.

qss_shmem_size_cap (integer)

Устанавливает память, зарезервированную для связи с сервером QSS. Использование памяти в любом случае начинается с 8 Кб, но может вырасти до этого предела.

Каждый бэкэнд имеет собственную память. Объекты большего размера могут быть зашифрованы, но для обработки каждого из них память будет выделяться и освобождаться заново.

Советы по производительности

На производительность запроса могут влиять многие факторы. Некоторыми из них может управлять пользователь, тогда как другие имеют основополагающее значение для базовой структуры системы. В этой главе даются некоторые советы, помогающие разобраться в них и настроить производительность QHB.

Создание демонстрационных таблиц

Для создания демонстрационных таблиц используйте команды:

CREATE TABLE tenk1 (
	unique1		int4,
	unique2		int4,
	two			int4,
	four		int4,
	ten			int4,
	twenty		int4,
	hundred		int4,
	thousand	int4,
	twothousand	int4,
	fivethous	int4,
	tenthous	int4,
	odd			int4,
	even		int4,
	stringu1	name,
	stringu2	name,
	string4		name
);

CREATE TABLE tenk2 (
	unique1 	int4,
	unique2 	int4,
	two 	 	int4,
	four 		int4,
	ten			int4,
	twenty 		int4,
	hundred 	int4,
	thousand 	int4,
	twothousand int4,
	fivethous 	int4,
	tenthous	int4,
	odd			int4,
	even		int4,
	stringu1	name,
	stringu2	name,
	string4		name
);

CREATE TABLE onek (
	unique1		int4,
	unique2		int4,
	two			int4,
	four		int4,
	ten			int4,
	twenty		int4,
	hundred		int4,
	thousand	int4,
	twothousand	int4,
	fivethous	int4,
	tenthous	int4,
	odd			int4,
	even		int4,
	stringu1	name,
	stringu2	name,
	string4		name
);

Для заполнения используйте команды SQL из приложения Данные для анализа производительности.

ВНИМАНИЕ!
Приведенный выше файл, содержащий демонстрационные данные для анализа производительности, имеет довольно большой размер, поэтому открытие его для просмотра и попытка выполнить находящиеся в нем команды при помощи графических утилит может занять значительное время и закончится ошибкой. В связи с этим рекомендуется скачать файл на диск и использовать утилиту psql для загрузки данных из интерфейса командной строки.

После загрузки данных необходимо выполнить заполнение таблиц и создание необходимых индексов при помощи команд:

INSERT INTO tenk2 SELECT * FROM tenk1;

CREATE INDEX tenk1_unique1 ON tenk1 USING btree(unique1 int4_ops);

CREATE INDEX tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops);

CREATE INDEX tenk1_hundred ON tenk1 USING btree(hundred int4_ops);

CREATE INDEX tenk1_thous_tenthous ON tenk1 (thousand, tenthous);

CREATE INDEX tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops);

CREATE INDEX tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops);

CREATE INDEX tenk2_hundred ON tenk2 USING btree(hundred int4_ops);

CREATE INDEX onek_unique1 ON onek USING btree(unique1 int4_ops);

CREATE INDEX IF NOT EXISTS onek_unique1 ON onek USING btree(unique1 int4_ops);

CREATE INDEX IF NOT EXISTS ON onek USING btree(unique1 int4_ops);

CREATE INDEX onek_unique2 ON onek USING btree(unique2 int4_ops);

CREATE INDEX onek_hundred ON onek USING btree(hundred int4_ops);

CREATE INDEX onek_stringu1 ON onek USING btree(stringu1 name_ops);

Теперь необходимо провести очистку таблиц при помощи команд:

VACUUM ANALYZE tenk1;

VACUUM ANALYZE tenk2;

VACUUM ANALYZE onek;

Таблицы созданы и заполнены.

Использование EXPLAIN

QHB строит план запросов для каждого полученного запроса. Выбор правильного плана, соответствующего структуре запроса и свойствам данных, крайне важен для хорошей производительности, поэтому в систему входит сложный планировщик, старающийся подобрать хорошие планы. Для просмотра плана запроса, создаваемого планировщиком для какого-либо запроса, можно воспользоваться командой EXPLAIN. Чтение планов — это искусство, и для овладения им требуется некоторый опыт, но в этом разделе описаны только основные принципы.

Примеры в этом разделе взяты из базы данных, используемой для регрессионных тестов исходного кода QHB, с предварительно выполненной VACUUM ANALYZE (см. раздел выше). Вы должны получить схожие результаты, если сами опробуете эти примеры, но ожидаемые стоимость и число строк могут слегка отличаться, поскольку статистика ANALYZE рассчитывается по случайным примерам, а не конкретным, а оценка стоимости по своей природе в некоторой степени зависит от платформы.

В примерах используется установленный по умолчанию текстовый формат вывода EXPLAIN, который компактен и удобен для чтения пользователем. Если вы желаете передать вывод EXPLAIN программе для дальнейшего анализа, вместо него следует использовать один из машиночитаемых форматов вывода (XML, JSON или YAML).

Основы EXPLAIN

Структура плана запроса представляет собой дерево узлов плана. Узлы на самом нижнем уровне дерева являются узлами сканирования: они возвращают необработанные строки таблицы. Для разных табличных методов доступа существуют разные типы узлов сканирования: последовательное сканирование, сканирование индекса и сканирование индекса по битовой карте. Помимо таблиц имеются и другие источники строк, например, предложения VALUES и функции, возвращающие множества, во FROM, у которых есть собственные типы узлов сканирования. Если запрос требует соединения, агрегации, сортировки или других операций с необработанными строками, то над узлами сканирования появляются дополнительные узлы для выполнения этих операций. Опять же, обычно эти операции можно выполнить несколькими способами, поэтому здесь узлы тоже могут быть разных типов. В выводе EXPLAIN для каждого узла в дереве плана содержится отдельная строка, показывающая базовый тип узла плюс оценку стоимости, которую рассчитал для этого узла планировщик. Также могут выводиться дополнительные строки с отступом от строки сводки по узлу, показывающие его дополнительные свойства. В самой первой строке (строке сводки самого верхнего узла) содержится общая ожидаемая стоимость выполнения плана; именно это число планировщик старается минимизировать.

Вот простой пример, только чтобы продемонстрировать формат вывода:

EXPLAIN SELECT * FROM tenk1;

                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..458.00 rows=10000 width=244)

Поскольку в этом запросе нет предложения WHERE, он должен просканировать все строки таблицы, поэтому планировщик решил использовать простой план последовательного сканирования. Числа в скобках (слева направо) означают следующее:

Ожидаемая стоимость запуска. Это время, затрачиваемое до начала фазы вывода, например, время на выполнение сортировки в узле сортировки.

Ожидаемая общая стоимость. Она выставляется, исходя из предположения, что узел плана выполняется до конца, то есть извлечены все доступные строки. На практике родительский узел может досрочно прервать чтение всех доступных строк (см. пример с LIMIT ниже).

Ожидаемое количество строк, выводимых этим узлом плана. Опять же, предполагается, что узел выполняется до конца.

Ожидаемый средний размер строк, выводимых этим узлом плана (в байтах).

Стоимость измеряется в произвольных единицах, определяемых параметрами затрат планировщика (см. подраздел Константы стоимости для планировщика). Традиционно единицей стоимости считается операция извлечения страницы с диска, то есть в параметре seq_page_cost обычно задается 1.0, а другие параметры стоимости устанавливаются относительно него. Примеры в этом разделе выполняются с параметрами стоимости по умолчанию.

Важно понимать, что стоимость узла верхнего уровня включает стоимость всех его дочерних узлов. Также важно понимать, что стоимость отражает только то, что интересует планировщик. В частности, стоимость не учитывает время, потраченное на передачу строк результатов клиенту, хотя оно может составлять значимую часть реального времени выполнения. Планировщик игнорирует это время, потому что не может изменить его, скорректировав план. (Считается, что каждый правильный план будет выводить один и тот же набор строк).

Значение rows немного сложнее, потому что это не количество строк, обработанных или отсканированных узлом плана, а количество, выдаваемое узлом. Оно зачастую меньше количества сканируемых строк вследствие фильтрации по любым условиям предложения WHERE, применяемым в узле. В идеальном случае ожидаемое число строк верхнего уровня будет приблизительно соответствовать количеству строк, фактически возвращенных, измененных или удаленных запросом.

Возвращаясь к нашему примеру:

EXPLAIN SELECT * FROM tenk1;

                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..458.00 rows=10000 width=244)

Эти числа получаются очень просто. Если вы выполните:

SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';

то обнаружите, что tenk1 имеет 358 страниц на диске и 10000 строк. Ожидаемая стоимость рассчитывается как (число чтений страниц диска * seq_page_cost ) + (число просканированных строк * cpu_tuple_cost). По умолчанию seq_page_cost равен 1.0, а cpu_tuple_cost — 0.01, поэтому ожидаемая стоимость равна (358 * 1,0) + (10000 * 0,01) = 458.

Теперь давайте изменим запрос, добавив условие WHERE:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 7000;

                         QUERY PLAN
------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=7001 width=244)
   Filter: (unique1 < 7000)

Обратите внимание, что вывод EXPLAIN показывает предложение WHERE, применяемое в качестве условия-«фильтра», присоединенного к узлу плана Seq Scan. Это означает, что узел плана проверяет это условие для каждой сканируемой им строки и выводит только те, которые ему соответствуют. Из-за предложения WHERE ожидаемое количество выходных строк было снижено. Тем не менее при сканировании все равно придется просмотреть все 10000 строк, поэтому стоимость не снизилась; на самом деле она немного увеличилась (на 10000 * cpu_operator_cost, если точнее), чтобы отразить дополнительное время, затраченное ЦП на проверку условия WHERE.

Фактическое количество строк, которое выберет этот запрос, равно 7000, но число rows является только приблизительным значением. Если вы попытаетесь повторить этот эксперимент, то, вероятно, получите немного другую оценку — более того, она может меняться после каждого выполнения команды ANALYZE, поскольку статистика, полученная с помощью ANALYZE, берется из случайной выборки таблицы.

Теперь давайте сделаем условие более ограничительным:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100;

                                  QUERY PLAN
-------------------------------------------------------------------​-----------
 Bitmap Heap Scan on tenk1  (cost=5.07..229.20 rows=101 width=244)
   Recheck Cond: (unique1 < 100)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=101 width=0)
         Index Cond: (unique1 < 100)

Здесь планировщик решил использовать двухэтапный план: дочерний узел плана просматривает индекс, чтобы найти местоположение строк, соответствующих условию индекса, а затем родительский узел плана непосредственно выбирает эти строки из самой таблицы. Извлечение строк по отдельности гораздо дороже, чем их последовательное чтение, но поскольку просматривать нужно не все страницы таблицы, это все же дешевле, чем последовательное сканирование. (Причина использования двух уровней плана состоит в том, что верхний узел плана перед считыванием сортирует определенные индексом местоположения строк в некоторый физический порядок, чтобы минимизировать стоимость отдельных выборок. Слово «bitmap» (битовая карта), указанное в имени узла, и означает механизм, который выполняет сортировку.)

Теперь давайте добавим к предложению WHERE еще одно условие:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND stringu1 = 'xxx';

                                  QUERY PLAN
------------------------------------------------------------------------------
 Bitmap Heap Scan on tenk1  (cost=5.04..229.43 rows=1 width=244)
   Recheck Cond: (unique1 < 100)
   Filter: (stringu1 = 'xxx'::name)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=101 width=0)
         Index Cond: (unique1 < 100)

Добавленное условие stringu1 = 'xxx' уменьшает ожидаемое количество выходных строк, но не стоимость, поскольку все равно приходится просматривать тот же набор строк. Обратите внимание, что предложение stringu1 нельзя применять в качестве условия индекса, поскольку этот индекс построен только по столбцу unique1. Вместо этого оно применяется в качестве фильтра для строк, извлекаемых индексом. Как следствие, на самом деле стоимость немного увеличилась, чтобы отразить эту дополнительную проверку.

В некоторых случаях планировщик предпочтет «простой» план сканирования индекса:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 = 42;

                                 QUERY PLAN
-----------------------------------------------------------------------------
 Index Scan using tenk1_unique1 on tenk1  (cost=0.29..8.30 rows=1 width=244)
   Index Cond: (unique1 = 42)

В этом плане строки таблицы выбираются в порядке индекса, что делает их еще более дорогими для чтения, но их так мало, что дополнительные затраты на сортировку местоположений строк не влияют на производительность. Чаще всего план такого типа используется для запросов, извлекающих только одну строку. Также он часто применяется для запросов с условием ORDER BY, соответствующим порядку индекса, поскольку в этом случае для удовлетворения условию ORDER BY не требуется дополнительного этапа сортировки. В этом примере при добавлении ORDER BY unique1 будет использоваться тот же план, потому что индекс уже неявно обеспечивает требуемый порядок.

Планировщик может реализовать предложение ORDER BY несколькими способами. В примере выше показано, что такое упорядочивающее предложение можно реализовать неявно. Кроме того, планировщик может добавить явный этап sort (сортировку):

EXPLAIN SELECT * FROM tenk1 ORDER BY unique1;
                            QUERY PLAN
-------------------------------------------------------------------
 Sort  (cost=1109.39..1134.39 rows=10000 width=244)
   Sort Key: unique1
   ->  Seq Scan on tenk1  (cost=0.00..445.00 rows=10000 width=244)

Если часть плана гарантирует упорядочивание по префиксу требуемых ключей сортировки, то вместо этого планировщик может решить использовать этап incremental sort (инкрементальную сортировку):

EXPLAIN SELECT * FROM tenk1 ORDER BY four, ten LIMIT 100;
                                              QUERY PLAN
-------------------------------------------------------------------​-----------------------------------
 Limit  (cost=521.06..538.05 rows=100 width=244)
   ->  Incremental Sort  (cost=521.06..2220.95 rows=10000 width=244)
         Sort Key: four, ten
         Presorted Key: four
         ->  Index Scan using index_tenk1_on_four on tenk1  (cost=0.29..1510.08 rows=10000 width=244)

В отличие от обычных сортировок, инкрементальная сортировка позволяет возвращать кортежи до завершения сортировки всего результирующего набора, что, в частности, позволяет оптимизировать запросы с LIMIT. Кроме того, она может снизить объем используемой памяти и вероятность вытеснение сортируемых данных на диск, но ценой этого станет увеличение издержек на разделение результирующего набора на несколько групп отсортированных данных.

Если в нескольких столбцах, на которые есть ссылки в WHERE, имеются отдельные индексы, планировщик может выбрать использование сочетания этих индексов (с AND или OR):

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000;

                                     QUERY PLAN
-------------------------------------------------------------------​------------------
 Bitmap Heap Scan on tenk1  (cost=25.08..60.21 rows=10 width=244)
   Recheck Cond: ((unique1 < 100) AND (unique2 > 9000))
   ->  BitmapAnd  (cost=25.08..25.08 rows=10 width=0)
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=101 width=0)
               Index Cond: (unique1 < 100)
         ->  Bitmap Index Scan on tenk1_unique2  (cost=0.00..19.78 rows=999 width=0)
               Index Cond: (unique2 > 9000)

Но это потребует просмотра обоих индексов, поэтому такой план не обязательно будет выигрышнее, чем использование только одного индекса и обработка другого условия как фильтра. Если вы измените задействованные диапазоны, то увидите, что план изменится соответствующим образом.

Вот пример, показывающий действие LIMIT:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;

                                     QUERY PLAN
-------------------------------------------------------------------​------------------
 Limit  (cost=0.29..14.48 rows=2 width=244)
   ->  Index Scan using tenk1_unique2 on tenk1  (cost=0.29..71.27 rows=10 width=244)
         Index Cond: (unique2 > 9000)
         Filter: (unique1 < 100)

Это тот же запрос, что и выше, но мы добавили LIMIT, чтобы требовалось извлечь не все строки, и планировщик передумал это делать. Обратите внимание, что общая стоимость и количество строк узла Index Scan отображаются так, как если бы он был выполнен до конца. Однако ожидается, что узел Limit остановится после получения только одной пятой этих строк, поэтому его общая стоимость составляет всего одну пятую, и это фактическая ожидаемая стоимость запроса. Такой план предпочтительнее, чем просто добавление узла Limit к предыдущему плану, потому что при этом не удалось бы избежать затрат на запуск сканирования битовой карты, поэтому общая стоимость при таком подходе превышала бы 25 единиц.

Давайте попробуем объединить две таблицы, используя столбцы, которые мы уже рассматривали:

EXPLAIN SELECT *
FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 10 AND t1.unique2 = t2.unique2;

                                      QUERY PLAN
-------------------------------------------------------------------​-------------------
 Nested Loop  (cost=4.65..118.62 rows=10 width=488)
   ->  Bitmap Heap Scan on tenk1 t1  (cost=4.36..39.47 rows=10 width=244)
         Recheck Cond: (unique1 < 10)
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..4.36 rows=10 width=0)
               Index Cond: (unique1 < 10)
   ->  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.29..7.91 rows=1 width=244)
         Index Cond: (unique2 = t1.unique2)

В этом плане у нас есть узел соединения с вложенным циклом с двумя поступившими ему на вход от дочерних узлов результатами сканирования таблиц. Отступ строк сводки узлов отражает эту структуру дерева плана. Первый, или «внешний», потомок соединения — это узел сканирования битовой карты, похожий на те, что мы видели ранее. Его стоимость и количество строк такие же, какие мы получили бы из SELECT ... WHERE unique1 < 10, потому что для этого узла мы применяем предложение WHERE с условием unique1 < 10 . Предложение t1.unique2 = t2.unique2 пока неактуально, поэтому оно не влияет на количество строк внешнего сканирования. Узел соединения с вложенным циклом будет запускать своего второго, или «внутреннего», потомка для каждой строки, полученной из внешнего потомка. Значения столбцов из текущей внешней строки можно включить во внутреннее сканирование; в данном случае доступно значение t1.unique2 из внешней строки, поэтому мы получаем план и затраты аналогичные тем, что мы видели выше для простого запроса SELECT ... WHERE t2.unique2 = константа. (На самом деле ожидаемая стоимость немного ниже, чем было показано ранее, из-за кэширования, которое предположительно произойдет во время повторного сканирования индекса в t2.) Затем стоимость узла цикла устанавливается как стоимость внешнего сканирование, плюс стоимость одного внутреннего сканирования, умноженная на количество внешних строк (здесь это 10 * 7,90), плюс немного процессорного времени для обработки соединения.

В этом примере число выходных строк в соединении совпадает с произведением количества строк в двух узлах сканирования, но это не всегда так, потому что могут быть дополнительные предложения WHERE, в которых упоминаются обе таблицы, и поэтому их можно применить только в точке соединения, а не в каждом узле сканирования входных данных. Например:

EXPLAIN SELECT *
FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 10 AND t2.unique2 < 10 AND t1.hundred < t2.hundred;

                                         QUERY PLAN
-------------------------------------------------------------------​--------------------------
 Nested Loop  (cost=4.65..49.46 rows=33 width=488)
   Join Filter: (t1.hundred < t2.hundred)
   ->  Bitmap Heap Scan on tenk1 t1  (cost=4.36..39.47 rows=10 width=244)
         Recheck Cond: (unique1 < 10)
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..4.36 rows=10 width=0)
               Index Cond: (unique1 < 10)
   ->  Materialize  (cost=0.29..8.51 rows=10 width=244)
         ->  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.29..8.46 rows=10 width=244)
               Index Cond: (unique2 < 10)

Условие t1.hundred < t2.hundred нельзя проверить в индексе tenk2_unique2, поэтому оно применяется в узле соединения. Это уменьшает ожидаемое количество выходных строк узла соединения, но не изменяет количество входных строк в узлах сканирования.

Обратите внимание, что здесь планировщик решил «материализовать» внутреннее отношение соединения, поместив поверх него узел плана Materialize (материализовать). Это означает, что сканирование индекса t2 будет выполнено только один раз, даже если узел соединения с вложенным циклом должен прочитать эти данные десять раз, по одному разу для каждой строки из внешнего отношения. Узел Materialise сохраняет считанные данные в памяти, а затем возвращает их при каждом последующем проходе.

При работе с внешними объединениями вы можете увидеть узлы плана соединения с добавленными условиями «Join Filter» (фильтр соединения) и обычный «Filter». Условия Join Filter взяты из предложения ON внешнего соединения, поэтому строка, не соответствующая условию Join Filter, все равно может быть выдана как строка, дополненная NULL. Но обычное условие Filter применяется после правил внешнего соединения, поэтому удаляет строки полностью. Во внутреннем же соединении между этими типами фильтров нет семантических различий.

Если мы немного изменим избирательность запроса, мы можем получить совсем другой план соединения:

EXPLAIN SELECT *
FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;

                                        QUERY PLAN
-------------------------------------------------------------------​-----------------------
 Hash Join  (cost=230.47..713.98 rows=101 width=488)
   Hash Cond: (t2.unique2 = t1.unique2)
   ->  Seq Scan on tenk2 t2  (cost=0.00..445.00 rows=10000 width=244)
   ->  Hash  (cost=229.20..229.20 rows=101 width=244)
         ->  Bitmap Heap Scan on tenk1 t1  (cost=5.07..229.20 rows=101 width=244)
               Recheck Cond: (unique1 < 100)
               ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=101 width=0)
                     Index Cond: (unique1 < 100)

Здесь планировщик решил использовать хеш-соединение, в котором строки одной таблицы заносятся в хеш-таблицу в памяти, после чего сканируется другая таблица и каждая ее строка проверяется на совпадение по хеш-таблице. Снова обратите внимание, как отступ отражает структуру плана: результат сканирования битовой карты по tenk1 поступает на вход узлу Hash, который строит хеш-таблицу. Затем она возвращается в узел Hash Join, который читает строки из узла своего внешнего дочернего плана и проверяет каждую из них по хеш-таблице.

Другой возможный тип соединения — это показанное здесь соединение слиянием:

EXPLAIN SELECT *
FROM tenk1 t1, onek t2
WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;

                                        QUERY PLAN
-------------------------------------------------------------------​-----------------------
 Merge Join  (cost=198.11..268.19 rows=10 width=488)
   Merge Cond: (t1.unique2 = t2.unique2)
   ->  Index Scan using tenk1_unique2 on tenk1 t1  (cost=0.29..656.28 rows=101 width=244)
         Filter: (unique1 < 100)
   ->  Sort  (cost=197.83..200.33 rows=1000 width=244)
         Sort Key: t2.unique2
         ->  Seq Scan on onek t2  (cost=0.00..148.00 rows=1000 width=244)

Соединение слиянием требует, чтобы его входные данные были отсортированы по ключам соединения. В этом плане данные tenk1 сортируются с использованием сканирования индекса, чтобы строки были просмотрены в правильном порядке, но для onek предпочтительнее последовательное сканирование и сортировка, поскольку в этой таблице нужно просмотреть гораздо больше строк. (При сортировке множества строк последовательное сканирование и сортировка зачастую выполняются быстрее сканирования индекса, поскольку при сканировании индекса происходит непоследовательное обращение к диску.)

Один из способов посмотреть на варианты планов — заставить планировщик игнорировать стратегию, которая, по его мнению, была самой дешевой, используя флаги включения/выключения, описанные в подразделе Конфигурация метода планирования. (Это грубый, но полезный инструмент. См. также раздел Управление планировщиком с помощью явных предложений JOIN). Например, если мы не уверены, что последовательное сканирование и сортировка являются лучшим способом обработки таблицы onek из предыдущего примера, можно попробовать следующее:

SET enable_sort = off;

EXPLAIN SELECT *
FROM tenk1 t1, onek t2
WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;

                                        QUERY PLAN
-------------------------------------------------------------------​-----------------------
 Merge Join  (cost=0.56..292.65 rows=10 width=488)
   Merge Cond: (t1.unique2 = t2.unique2)
   ->  Index Scan using tenk1_unique2 on tenk1 t1  (cost=0.29..656.28 rows=101 width=244)
         Filter: (unique1 < 100)
   ->  Index Scan using onek_unique2 on onek t2  (cost=0.28..224.79 rows=1000 width=244)

Здесь видно, что планировщик считает, что сортировка onek с помощью сканирования по индексу примерно на 12% дороже, чем последовательное сканирование и сортировка. Конечно, следующий вопрос — а правильно ли это? Мы можем разобраться в этом, используя описанную ниже команду EXPLAIN ANALYZE.

EXPLAIN ANALYZE

Точность оценок планировщика можно проверить, используя параметр ANALYZE команды EXPLAIN. С помощью этого параметра EXPLAIN фактически выполняет запрос, а затем отображает истинное количество строк и истинное время выполнения, накопленное в каждом узле плана, вместе с теми оценки, которые показывает обычный EXPLAIN. Например, мы можем получить такой результат:

EXPLAIN ANALYZE SELECT *
FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 10 AND t1.unique2 = t2.unique2;

                                                           QUERY PLAN
-------------------------------------------------------------------​--------------------------------------------------------------
 Nested Loop  (cost=4.65..118.62 rows=10 width=488) (actual time=0.128..0.377 rows=10 loops=1)
   ->  Bitmap Heap Scan on tenk1 t1  (cost=4.36..39.47 rows=10 width=244) (actual time=0.057..0.121 rows=10 loops=1)
         Recheck Cond: (unique1 < 10)
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..4.36 rows=10 width=0) (actual time=0.024..0.024 rows=10 loops=1)
               Index Cond: (unique1 < 10)
   ->  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.29..7.91 rows=1 width=244) (actual time=0.021..0.022 rows=1 loops=10)
         Index Cond: (unique2 = t1.unique2)
 Planning time: 0.181 ms
 Execution time: 0.501 ms

Обратите внимание, что значения «фактического времени» приведены в миллисекундах реального времени, тогда как оценки cost выражены в произвольных единицах, поэтому они вряд ли совпадают. Обычно наиболее важно проверить, насколько ожидаемое количество строк соответствует действительности. В этом примере все оценки были точными, но на практике такого обычно не бывает.

В некоторых планах запросов узел подплана может выполняться более одного раза. Например, в приведенном выше плане вложенного цикла внутреннее сканирование индекса будет выполняться для каждой внешней строки. В таких случаях в значении loops (циклы) отражается общее число выполнений узла, а показанные значения фактического времени и строк представляют собой средние значения для каждого выполнения. Это сделано для того, чтобы эти числа можно было сопоставить с отображаемыми оценками затрат. Чтобы получить общее время, фактически потраченное на узел, умножьте время одного выполнения на значение loops. В приведенном выше примере на выполнение сканирования индекса в tenk2 мы потратили в общей сложности 0,220 миллисекунды.

В некоторых случаях EXPLAIN ANALYZE показывает не только время выполнения узлов плана и количество строк, но и дополнительную статистику по выполнению. Например, для узлов Sort и Hash предоставляется дополнительная информация:

EXPLAIN ANALYZE SELECT *
FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous;

                                                                 QUERY PLAN
-------------------------------------------------------------------​-------------------------------------------------------------------​------
 Sort  (cost=717.34..717.59 rows=101 width=488) (actual time=7.761..7.774 rows=100 loops=1)
   Sort Key: t1.fivethous
   Sort Method: quicksort  Memory: 77kB
   ->  Hash Join  (cost=230.47..713.98 rows=101 width=488) (actual time=0.711..7.427 rows=100 loops=1)
         Hash Cond: (t2.unique2 = t1.unique2)
         ->  Seq Scan on tenk2 t2  (cost=0.00..445.00 rows=10000 width=244) (actual time=0.007..2.583 rows=10000 loops=1)
         ->  Hash  (cost=229.20..229.20 rows=101 width=244) (actual time=0.659..0.659 rows=100 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 28kB
               ->  Bitmap Heap Scan on tenk1 t1  (cost=5.07..229.20 rows=101 width=244) (actual time=0.080..0.526 rows=100 loops=1)
                     Recheck Cond: (unique1 < 100)
                     ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=101 width=0) (actual time=0.049..0.049 rows=100 loops=1)
                           Index Cond: (unique1 < 100)
 Planning time: 0.194 ms
 Execution time: 8.008 ms

Для узла Sort отображается используемый метод сортировки (в частности, проводилась ли сортировка в памяти или на диске) и необходимый объем памяти или дискового пространства. Для узла Hash отображается количество хеш-блоков и пакетов, а также максимальный объем памяти, занятый хеш-таблицей. (Если количество пакетов превышает единицу, будет задействовано и дисковое пространство, но здесь это не показывается).

Другой тип дополнительной информации — это количество строк, удаленных условием фильтра:

EXPLAIN ANALYZE SELECT * FROM tenk1 WHERE ten < 7;

                                               QUERY PLAN
-------------------------------------------------------------------​--------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=7000 width=244) (actual time=0.016..5.107 rows=7000 loops=1)
   Filter: (ten < 7)
   Rows Removed by Filter: 3000
 Planning time: 0.083 ms
 Execution time: 5.905 ms

Эти подсчеты могут быть особенно полезны для условий фильтра, применяемых в узлах соединения. Строка «Rows Removed» появляется только тогда, когда хотя бы одна отсканированная строка или потенциальная пара соединений (в случае узла соединения) отбрасывается условием фильтра.

Ситуация, схожая с условиями фильтра, возникает при сканировании «неточного» индекса. Например, рассмотрим этот поиск многоугольников, содержащих заданную точку:

EXPLAIN ANALYZE SELECT * FROM polygon_tbl WHERE f1 @> polygon '(0.5,2.0)';

                                              QUERY PLAN
-------------------------------------------------------------------​-----------------------------------
 Seq Scan on polygon_tbl  (cost=0.00..1.05 rows=1 width=32) (actual time=0.044..0.044 rows=0 loops=1)
   Filter: (f1 @> '((0.5,2))'::polygon)
   Rows Removed by Filter: 4
 Planning time: 0.040 ms
 Execution time: 0.083 ms

Планировщик считает (и вполне оправданно), что таблица в этом примере слишком мала, чтобы утруждаться сканированием по индексу, поэтому мы получаем простое последовательное сканирование, в котором все строки отбрасываются условием фильтра. Но если мы заставим его применить сканирование по индексу, то получим следующее:

SET enable_seqscan TO off;

EXPLAIN ANALYZE SELECT * FROM polygon_tbl WHERE f1 @> polygon '(0.5,2.0)';

                                                        QUERY PLAN
-------------------------------------------------------------------​-------------------------------------------------------
 Index Scan using gpolygonind on polygon_tbl  (cost=0.13..8.15 rows=1 width=32) (actual time=0.062..0.062 rows=0 loops=1)
   Index Cond: (f1 @> '((0.5,2))'::polygon)
   Rows Removed by Index Recheck: 1
 Planning time: 0.034 ms
 Execution time: 0.144 ms

Здесь мы видим, что индекс вернул одну потенциально подходящую строку, которая затем была отброшена при повторной проверке условия индекса. Это происходит из-за того, что индекс GiST является «неточным» для проверок включений многоугольников: фактически он возвращает строки с многоугольниками, перекрывающими целевую точку, а затем для этих строк приходится проводить точную проверку включения.

У EXPLAIN есть параметр BUFFERS, который можно использовать с ANALYZE для получения еще более подробной статистики времени выполнения запроса:

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000;

                                                           QUERY PLAN
-------------------------------------------------------------------​--------------------------------------------------------------
 Bitmap Heap Scan on tenk1  (cost=25.08..60.21 rows=10 width=244) (actual time=0.323..0.342 rows=10 loops=1)
   Recheck Cond: ((unique1 < 100) AND (unique2 > 9000))
   Buffers: shared hit=15
   ->  BitmapAnd  (cost=25.08..25.08 rows=10 width=0) (actual time=0.309..0.309 rows=0 loops=1)
         Buffers: shared hit=7
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=101 width=0) (actual time=0.043..0.043 rows=100 loops=1)
               Index Cond: (unique1 < 100)
               Buffers: shared hit=2
         ->  Bitmap Index Scan on tenk1_unique2  (cost=0.00..19.78 rows=999 width=0) (actual time=0.227..0.227 rows=999 loops=1)
               Index Cond: (unique2 > 9000)
               Buffers: shared hit=5
 Planning time: 0.088 ms
 Execution time: 0.423 ms

Значения, предоставляемые BUFFERS, помогают определить, в каких частях запроса наиболее интенсивно выполняются операции ввода/вывода.

Имейте в виду, что поскольку EXPLAIN ANALYZE фактически выполняет запрос, то при этом, как и при обычном запросе, возможны побочные эффекты, даже если результаты, которые может выдать запрос, отбрасываются и вместо них выводятся данные EXPLAIN. Если вы хотите проанализировать запрос на изменение данных, не изменяя таблицы, после выполнения эту команду можно откатить, например:

BEGIN;

EXPLAIN ANALYZE UPDATE tenk1 SET hundred = hundred + 1 WHERE unique1 < 100;

                                                           QUERY PLAN
-------------------------------------------------------------------​-------------------------------------------------------------
 Update on tenk1  (cost=5.08..230.08 rows=0 width=0) (actual time=3.791..3.792 rows=0 loops=1)
   ->  Bitmap Heap Scan on tenk1  (cost=5.08..230.08 rows=102 width=10) (actual time=0.069..0.513 rows=100 loops=1)
         Recheck Cond: (unique1 < 100)
         Heap Blocks: exact=90
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.05 rows=102 width=0) (actual time=0.036..0.037 rows=300 loops=1)
               Index Cond: (unique1 < 100)
 Planning Time: 0.113 ms
 Execution Time: 3.850 ms

ROLLBACK;

Как видно из этого примера, когда запрос представляет собой команду INSERT, UPDATE, DELETE или MERGE, фактическая работа по изменению данных таблицы выполняется узлом плана верхнего уровня Insert, Update, Delete или Merge. Узлы плана, лежащие ниже этого узла, выполняют поиск старых строк и/или вычисляют новые данные. Итак, выше мы видим тот же тип сканирования битовой карты таблицы, что и раньше, и его выходные данные передаются в узел Update, где хранятся измененные строки. Стоит отметить, что хотя узел, изменяющий данные, может выполнятся долгое время (в этом примере он занимает львиную долю общего времени выполнения), в настоящее время планировщик не добавляет к оценкам затрат ничего, касающегося этой работы. Это связано с тем, что выполняемая работа будет одинаковой для каждого правильного плана запроса, поэтому она не влияет на решения по планированию.

Когда на иерархию наследования действует команда UPDATE, DELETE или MERGE, вывод может выглядеть так:

EXPLAIN UPDATE parent SET f2 = f2 + 1 WHERE f1 = 101;
                                              QUERY PLAN
-------------------------------------------------------------------​-----------------------------------
 Update on parent  (cost=0.00..24.59 rows=0 width=0)
   Update on parent parent_1
   Update on child1 parent_2
   Update on child2 parent_3
   Update on child3 parent_4
   ->  Result  (cost=0.00..24.59 rows=4 width=14)
         ->  Append  (cost=0.00..24.54 rows=4 width=14)
               ->  Seq Scan on parent parent_1  (cost=0.00..0.00 rows=1 width=14)
                     Filter: (f1 = 101)
               ->  Index Scan using child1_pkey on child1 parent_2  (cost=0.15..8.17 rows=1 width=14)
                     Index Cond: (f1 = 101)
               ->  Index Scan using child2_pkey on child2 parent_3  (cost=0.15..8.17 rows=1 width=14)
                     Index Cond: (f1 = 101)
               ->  Index Scan using child3_pkey on child3 parent_4  (cost=0.15..8.17 rows=1 width=14)
                     Index Cond: (f1 = 101)

В этом примере узлу Update наравне с изначально упомянутой родительской таблицей нужно обработать три дочерние таблицы. Поэтому здесь фигурирует четыре подплана сканирования входных данных, по одному на каждую таблицу. Для ясности к узлу Update добавляется аннотация, показывающая конкретные целевые таблицы, которые будут изменены, в том же порядке, в каком они идут в соответствующих подпланах.

Значением Planning time (время планирования), отображаемого командой EXPLAIN ANALYZE, является время, затраченное на генерирование плана запроса из проанализированного запроса и его оптимизацию. Сюда не включается время непосредственно синтаксического анализа или перезаписи запроса.

Значение Execution time (время выполнения), отображаемое командой EXPLAIN ANALYZE, включает продолжительность запуска и остановки исполнителя запроса, а также время выполнения всех сработавших триггеров, но не включает продолжительность синтаксического анализа, перезаписи и планирования запроса. Время, потраченное на выполнение триггеров BEFORE, если таковые имеются, включается во время соответствующего узла Insert, Update или Delete, но время, потраченное на выполнение триггеров AFTER, здесь не учитывается, потому что триггеры AFTER срабатывают после выполнения всего плана. Общее время, пошедшее на каждый триггер (BEFORE или AFTER), также отображается отдельно. Обратите внимание, что триггеры отложенных ограничений будут выполняться только в конце транзакции, поэтому в EXPLAIN ANALYZE они вообще не учитываются.

Ограничения применимости

Существует два значимых аспекта, обусловливающих отличие времени выполнения, рассчитанного EXPLAIN ANALYZE, от обычного выполнения того же запроса. Во-первых, поскольку выходные строки не доставляются клиенту, затраты на передачу по сети и преобразование ввода/вывода в расчеты не включаются. Во-вторых, добавляемые EXPLAIN ANALYZE издержки на измерения могут быть значительными, особенно на машинах с медленными системными вызовами gettimeofday(). Для измерения этих временных затрат в вашей системе можно воспользоваться утилитой qhb_test_timing.

Результаты EXPLAIN не следует экстраполировать на ситуации, сильно отличающиеся от тех, которые вы фактически проверяете; например, нельзя предполагать, что результаты для крошечной таблицы применимы к большим таблицам. Оценки стоимости планировщика нелинейны, и поэтому он может выбрать разные планы для таблицы большего или меньшего размера. Ярким примером является то, что для таблицы, занимающей только одну страницу диска, почти всегда выбирается план с последовательным сканированием, независимо от того, доступны ли индексы. Планировщик понимает, что для обработки таблицы в любом случае потребуется одно прочтение страницы на диске, поэтому нет смысла тратить время на чтение дополнительной страницы для просмотра индекса. (Мы видели, как это происходило выше в примере с таблицей polygon_tbl.)

Есть случаи, когда фактические и ожидаемые значения не особо совпадают, но на самом деле все в порядке. Один из таких случаев возникает, когда выполнение узла плана прерывается досрочно благодаря LIMIT или похожему эффекту. Например, в запросе с LIMIT, который мы использовали ранее,

EXPLAIN ANALYZE SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;

                                                          QUERY PLAN
-------------------------------------------------------------------​------------------------------------------------------------
 Limit  (cost=0.29..14.71 rows=2 width=244) (actual time=0.177..0.249 rows=2 loops=1)
   ->  Index Scan using tenk1_unique2 on tenk1  (cost=0.29..72.42 rows=10 width=244) (actual time=0.174..0.244 rows=2 loops=1)
         Index Cond: (unique2 > 9000)
         Filter: (unique1 < 100)
         Rows Removed by Filter: 287
 Planning time: 0.096 ms
 Execution time: 0.336 ms

ожидаемая стоимость и количество строк для узла Index Scan отображаются так, как если бы он был выполнен до конца. Но в действительности узел Limit прекратил запрашивать строки после того, как получил две строки, поэтому фактическое количество строк равно 2, а время выполнения меньше, чем предположил планировщик при оценке затрат. Это не ошибка оценки, а лишь несоответствие в способе отображения ожидаемых и истинных значений.

При соединении слиянием тоже возникают артефакты измерения, которые с непривычки могут сбить с толку. Соединение слиянием прекратит чтение одних полученных данных, если исчерпало другие, а следующее значение ключа в первом вводе больше значения последнего ключа другого ввода; в таком случае совпадений больше быть не может, поэтому нет необходимости сканировать оставшуюся часть первого ввода. Это приводит к тому, что полное чтение одного потомка не выполняется, с результатами, подобными рассмотренным для LIMIT. Кроме того, если внешний (первый) потомок содержит строки с дублирующимися значениями ключа, то внутренний (второй) потомок отматывается назад и повторно сканируется на предмет части его строк, соответствующей этому значению ключа. EXPLAIN ANALYZE считает эти повторные исключения одних и тех же внутренних строк, как если бы они были настоящими дополнительными строками. Когда существует много внешних дубликатов, выводимое фактическое число строк для внутреннего дочернего узла плана может быть значительно больше, чем число строк, фактически находящихся во внутреннем отношении.

Для узлов BitmapAnd (пересечение битовых карт) и BitmapOr (объединение битовых карт) всегда выводится, что их фактическое число строк равно нулю, из-за ограничений реализации.

Обычно EXPLAIN отображает каждый узел плана, созданный планировщиком. Однако в некоторых случаях исполнитель может определить, что какие-то узлы не нужно выполнять, поскольку они не могут создавать строки, основываясь на значениях параметров, которые были недоступны во время планирования. (В настоящее время это может произойти только с дочерними узлами узла Append или MergeAppend, сканирующего партиционированную таблицу.) Когда это происходит, эти узлы плана исключаются из вывода EXPLAIN, и вместо этого появляется аннотация Subplans Removed: N (Подпланов удалено: N).

Статистика, используемая планировщиком

Статистика по одному столбцу

Как мы видели в предыдущем разделе, планировщик запросов должен оценить количество строк, извлеченных запросом, чтобы выбрать правильный план запроса. В этом разделе приводится краткий обзор статистики, используемой системой для этих оценок.

Одним из компонентов статистики является общее количество записей в каждой таблице и индексе, а также количество дисковых блоков, занимаемых каждой таблицей и индексом. Эта информация хранится в таблице pg_class, в столбцах reltuples и relpages. Мы можем просмотреть ее с помощью запросов вроде этого:

SELECT relname, relkind, reltuples, relpages
FROM pg_class
WHERE relname LIKE 'tenk1%';

       relname        | relkind | reltuples | relpages
----------------------+---------+-----------+----------
 tenk1                | r       |     10000 |      345
 tenk1_hundred        | i       |     10000 |       30
 tenk1_thous_tenthous | i       |     10000 |       30
 tenk1_unique1        | i       |     10000 |       30
 tenk1_unique2        | i       |     10000 |       30
(5 rows)

Здесь мы можем видеть, что tenk1 содержит 10000 строк, как и ее индексы, но индексы (что неудивительно) гораздо меньше таблицы.

По соображениям эффективности reltuples и relpages не обновляются на ходу, поэтому обычно они содержат несколько устаревших значений. Они обновляются с помощью VACUUM, ANALYZE и нескольких команд DDL, например CREATE INDEX. Операция VACUUM или ANALYZE, сканирующая не всю таблицу (как она обычно и поступает), будет постепенно обновлять число reltuples на основе части таблицы, которую она просканировала, что дает лишь приблизительное значение. В любом случае планировщик будет масштабировать значения, которые найдет в pg_class, чтобы они соответствовали текущему размеру физической таблицы, таким образом получая более точное приближение.

Большинство запросов извлекают в таблице только часть строк из-за предложений WHERE, которые ограничивают число строк, подлежащих проверке. Таким образом, планировщик должен оценить избирательность предложений WHERE, то есть процент строк, соответствующих каждому условию в данном предложении WHERE. Информация, используемая для этой задачи, хранится в системном каталоге pg_statistic. Записи в pg_statistic обновляются командами ANALYZE и VACUUM ANALYZE и всегда являются приблизительными, даже если они были обновлены только что.

При анализе статистики вручную вместо непосредственного просмотра каталога pg_statistic лучше взглянуть на его представление pg_stats. Представление pg_stats предназначено для более удобного чтения. Более того, pg_stats доступно для чтения всем, тогда как каталог pg_statistic — только суперпользователю. (Это не дает непривилегированным пользователям узнавать из статистики что-либо о содержимом таблиц других людей. Представление pg_stats ограничивается отображением строк только о тех таблицах, которые может прочитать текущий пользователь.) Например, мы можем выполнить:

SELECT attname, inherited, n_distinct,
       array_to_string(most_common_vals, E'\n') as most_common_vals
FROM pg_stats
WHERE tablename = 'road';

 attname | inherited | n_distinct |          most_common_vals
---------+-----------+------------+------------------------------------
 name    | f         |  -0.363388 | I- 580                        Ramp+
         |           |            | I- 880                        Ramp+
         |           |            | Sp Railroad                       +
         |           |            | I- 580                            +
         |           |            | I- 680                        Ramp
 name    | t         |  -0.284859 | I- 880                        Ramp+
         |           |            | I- 580                        Ramp+
         |           |            | I- 680                        Ramp+
         |           |            | I- 580                            +
         |           |            | State Hwy 13                  Ramp
(2 rows)

Обратите внимание, что для одного столбца отображаются две строки: одна соответствует полной иерархии наследования, начинающейся в таблице road (inherited=t), а другая включает только саму таблицу road (inherited=f).

Объем информации, сохраняемой в pg_statistic командой ANALYZE, в частности максимальное количество записей в массивах most_common_vals и histogram_bounds для каждого столбца, можно установить для каждого столбца по отдельности с помощью команды ALTER TABLE SET STATISTICS или глобально, установив переменную конфигурации default_statistics_target. Предел по умолчанию в настоящее время составляет 100 записей. Повышение этого предела способно помочь планировщику делать более точные оценки, особенно для столбцов с нерегулярным распределением данных, за счет того, что они занимают больше места в pg_statistic и требуют чуть больше времени для вычисления ожидаемых значений. И, наоборот, сниженного значения предела может быть достаточно для столбцов с простым распределением данных.

Более подробную информацию об использовании статистики планировщиком можно найти в главе Как планировщик использует статистику.

Расширенная статистика

Зачастую можно наблюдать медленные запросы, следующие плохим планам выполнения, поскольку несколько столбцов, используемых в предложениях запросов, скоррелированы. Планировщик обычно предполагает, что несколько условий не зависят друг от друга — предположение, которое не выполняется, когда значения столбцов коррелируют между собой. Регулярная статистика, которая в силу своей природы собирается по отдельным столбцам, не может выявить каких-либо сведений о корреляции между столбцами. Однако у QHB есть возможность вычислять многовариантную статистику, которая может собирать такую информацию.

Поскольку число возможных комбинаций столбцов очень велико, нецелесообразно рассчитывать многовариантную статистику автоматически. Вместо этого можно создавать расширенные объекты статистики, которые чаще всего называют просто объектами статистики, чтобы дать серверу команду получать статистику по интересующим наборам столбцов.

Объекты статистики создаются с помощью команды CREATE STATISTICS. Создание такого объекта просто создает запись в каталоге, обозначающую интерес к этой статистике. Сбор фактических данных выполняется ANALYZE (либо ручным запуском команды, либо при автоматическом анализе в фоновом режиме). Собранные значения можно просмотреть в каталоге pg_statistic_ext_data.

Команда ANALYZE вычисляет расширенную статистику на основе той же выборки строк таблицы, которая используется для вычисления обычной статистики по одному столбцу. Поскольку размер выборки увеличивается за счет увеличения цели статистики для таблицы или любого из ее столбцов (как описано в предыдущем разделе), при более крупной цели статистики обычно получается более точная расширенная статистика, но на ее вычисление уходит больше времени.

В следующих подразделах описываются виды расширенной статистики, поддерживаемые в настоящее время.

Функциональные зависимости

Самый простой вид расширенной статистики отслеживает функциональные зависимости — понятие, используемое в определениях нормальных форм базы данных. Мы говорим, что столбец b функционально зависит от столбца a, если знания значения a достаточно для определения значения b, то есть нет двух строк, имеющих одинаковое значение a но разные значения b. В полностью нормализованной базе данных функциональные зависимости должны существовать только в первичных ключах и суперключах. Однако на практике многие наборы данных не полностью нормализованы по разным причинам; распространенным примером является преднамеренная денормализация из соображений производительности. Даже в полностью нормализованной базе данных между некоторыми столбцами может быть частичная корреляция, которая может выражаться в виде частичной функциональной зависимости.

Наличие функциональных зависимостей напрямую влияет на точность оценок в определенных запросах. Если запрос содержит условия как для независимого, так и для зависимого столбцов, условия для зависимых столбцов не уменьшают размер результата, но без знания функциональной зависимости планировщик запросов будет предполагать, что условия независимы, что приведет к занижению размера результата.

Чтобы сообщить планировщику о функциональных зависимостях, ANALYZE может собирать показатели зависимости между столбцами. Оценка степени зависимости между всеми наборами столбцов была бы чрезмерно дорогой, поэтому сбор данных ограничивается теми группами столбцов, которые находятся вместе в объекте статистики, определенном с помощью параметра dependencies. Желательно создавать статистику с dependencies только для сильно коррелирующих групп столбцов, чтобы избежать ненужных издержек при выполнении ANALYZE и дальнейшем планировании запросов.

Вот пример сбора статистики функциональной зависимости:

CREATE STATISTICS stts (dependencies) ON city, zip FROM zipcodes;

ANALYZE zipcodes;

SELECT stxname, stxkeys, stxddependencies
  FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid)
  WHERE stxname = 'stts';
 stxname | stxkeys |             stxddependencies
---------+---------+------------------------------------------
 stts    | 1 5     | {"1 => 5": 1.000000, "5 => 1": 0.423130}
(1 row)

Здесь видно, что столбец 1 (zip code, почтовый индекс) полностью определяет столбец 5 (city, город), поэтому коэффициент равен 1.0, тогда как город определяет почтовый индекс только в 42% случаев, то есть существует много городов (58%), которые представлены более чем одним почтовым индексом.

При вычислении избирательности для запроса, включающего функционально зависимые столбцы, планировщик корректирует оценки избирательности по условиям, используя коэффициенты зависимости, чтобы не выдавать заниженную оценку.

Ограничения функциональных зависимостей

В настоящее время функциональные зависимости применяются только при рассмотрении простых условий равенства, которые сравнивают столбцы с константами, и предложений IN с константами. Они не используются ни для улучшения оценок условий равенств, сравнивающих два столбца или столбец с выражением, ни для предложений с диапазоном, предложений LIKE или любых других типов условий.

При оценке с использованием функциональных зависимостей планировщик предполагает, что условия на задействованных столбцах совместимы и потому избыточны. Если они несовместимы, правильная оценка будет составлять ноль строк, но эта возможность не рассматривается. Например, при таком запросе:

SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '94105';

планировщик проигнорирует предложение с city как не изменяющее избирательность, что правильно. Тем не менее он сделает такое же предположение о

SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '90210';

даже если на самом деле этому запросу будет удовлетворять ноль строк. Однако статистика функциональной зависимости не дает достаточно информации, чтобы прийти к такому выводу.

Во многих практических ситуациях это предположение обычно удовлетворяется; например, в приложении может быть графический интерфейс, который позволяет выбирать для запроса только совместимые значения города и почтового индекса. Но если это не так, функциональные зависимости могут оказаться неприемлемым вариантом.

Многовариантное количество уникальных значений

Статистика по одному столбцу хранит количество уникальных значений в каждом столбце. Оценки количества уникальных значений при объединении нескольких столбцов (например, для GROUP BY a, b) часто неверны, когда у планировщика есть статистические данные только по одному столбцу, из-за чего он выбирает плохие планы.

Чтобы улучшить такие оценки, ANALYZE может собирать статистику по уникальным значениям для групп столбцов. Как и прежде, это нецелесообразно делать для каждой возможной группировки столбцов, поэтому данные собираются только для тех групп столбцов, которые фигурируют вместе в объекте статистики, определенном с помощью параметра ndistinct. Данные будут собираться для каждой возможной комбинации двух или более столбцов из набора перечисленных столбцов.

Продолжая предыдущий пример, количества уникальных значений в таблице почтовых индексов могут выглядеть следующим образом:

CREATE STATISTICS stts2 (ndistinct) ON city, state, zip FROM zipcodes;

ANALYZE zipcodes;

SELECT stxkeys AS k, stxdndistinct AS nd
  FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid)
  WHERE stxname = 'stts2';
-[ RECORD 1 ]--------------------------------------------------------
k  | 1 2 5
nd | {"1, 2": 33178, "1, 5": 33178, "2, 5": 27435, "1, 2, 5": 33178}
(1 row)

Здесь показано, что существует три комбинации столбцов, имеющих 33178 уникальных значений: почтовый индекс и штат (state), почтовый индекс и город, а также почтовый индекс, город и штат (ожидается, что все они равны, учитывая, что в этой таблице уникален уже сам почтовый индекс). С другой стороны, комбинация города и штата имеет только 27435 уникальных значений.

Целесообразно создавать объекты статистики ndistinct только по тем комбинациям столбцов, которые действительно используются для группировки и для которых неправильная оценка количества групп приводит к выбору плохих планов. В противном случае циклы ANALYZE будут напрасной тратой времени.

Многовариантные списки MCV

Другим типом статистики, хранимой для каждого столбца, являются списки наиболее распространенных значений. Они позволяют получить очень точные оценки для отдельных столбцов, но могут вызвать появление сильно заниженных оценок для запросов с условиями по нескольким столбцам.

Чтобы улучшить такие оценки, ANALYZE может собирать списки MCV по комбинациям столбцов. Подобно функциональным зависимостям и коэффициентам уникальных значений, их нецелесообразно делать для всех возможных группировок столбцов. В данном случае этот постулат особенно верен, поскольку список MCV (в отличие от функциональных зависимостей и коэффициентов уникальных значений) хранит общие значения столбцов. Поэтому данные собираются только для тех групп столбцов, которые находятся вместе в объекте статистики, определенном параметром mcv.

Продолжая предыдущий пример, список MCV для таблицы почтовых индексов может выглядеть следующим образом (в отличие от более простых типов статистики, для проверки содержимого MCV требуется функция):

CREATE STATISTICS stts3 (mcv) ON city, state FROM zipcodes;

ANALYZE zipcodes;

SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
                pg_mcv_list_items(stxdmcv) m WHERE stxname = 'stts3';

 index |         values         | nulls | frequency | base_frequency
-------+------------------------+-------+-----------+----------------
     0 | {Washington, DC}       | {f,f} |  0.003467 |        2.7e-05
     1 | {Apo, AE}              | {f,f} |  0.003067 |        1.9e-05
     2 | {Houston, TX}          | {f,f} |  0.002167 |       0.000133
     3 | {El Paso, TX}          | {f,f} |     0.002 |       0.000113
     4 | {New York, NY}         | {f,f} |  0.001967 |       0.000114
     5 | {Atlanta, GA}          | {f,f} |  0.001633 |        3.3e-05
     6 | {Sacramento, CA}       | {f,f} |  0.001433 |        7.8e-05
     7 | {Miami, FL}            | {f,f} |    0.0014 |          6e-05
     8 | {Dallas, TX}           | {f,f} |  0.001367 |        8.8e-05
     9 | {Chicago, IL}          | {f,f} |  0.001333 |        5.1e-05
   ...
(99 rows)

Эти данные указывают на то, что наиболее распространенной комбинацией города и штата является Washington и DC, с фактической частотой (в выборке) около 0,35%. Базовая частота комбинации (вычисленная по простым частотам в отдельных столбцах) составляет всего 0,0027%, что дает занижение оценки на два порядка.

Желательно создавать объекты статистики MCV только на комбинациях столбцов, которые действительно используются в условиях вместе и для которых неправильная оценка количества групп приводит к выбору плохих планов. В противном случае циклы ANALYZE и планирования будут напрасной тратой времени.

Управление планировщиком с помощью явных предложений JOIN

Планировщиком запросов можно до некоторой степени управлять, используя явный синтаксис JOIN. Чтобы понять, почему это важно, сначала стоит дать небольшую предысторию.

В простом запросе с соединением, например:

SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;

планировщик может соединять таблицы в любом порядке. Например, он может сгенерировать план запроса, который соединяет A с B, используя предложение WHERE с условием a.id = b.id, а затем присоединить к этой объединенной таблице C, используя другое условие WHERE. Или он может соединить B и C, а затем присоединить к этому результату A. Или он может соединить A и C, а затем соединить их с B — но это будет неэффективно, поскольку должно быть сформировано полное декартово произведение A и C из-за отсутствия в предложении WHERE применимого условия, позволяющего оптимизировать соединение. (Все соединения в исполнителе QHB происходят между двумя входными таблицами, поэтому результат нужно получить одним из этих способов). Важным моментом является то, что эти различные возможности соединения дают семантически равнозначные результаты, но затраты на их выполнение могут сильно различаться. Поэтому планировщик изучит их все, чтобы попытаться найти наиболее эффективный план запроса.

Когда в запросе используются только две или три таблицы, возможных вариантов их соединения немного. Но по мере увеличения количества таблиц количество возможных вариантов соединения растет по экспоненте. После десяти входных таблиц становится нецелесообразно проводить исчерпывающий поиск всех возможностей, и даже для шести или семи таблиц планирование может занять очень много времени. Когда входных таблиц слишком много, планировщик QHB переключается с полного поиска на генетический вероятностный поиск с ограниченным числом возможностей. (Порог переключения задается параметром времени выполнения geqo_threshold). Генетический поиск занимает меньше времени, но не обязательно находит наиболее оптимальный план.

Когда запрос включает внешние соединения, у планировщика меньше свободы, чем с простыми (внутренними) соединениями. Например, рассмотрим:

SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

Хотя ограничения этого запроса внешне похожи на предыдущий пример, семантика отличается, потому что для каждой строки A, которая не имеет совпадающей строки в соединении B и C, должна быть выделена строка. Поэтому здесь планировщик не может выбирать порядок соединения: он должен соединить B и C, а затем присоединить к этому результату A. Соответственно, в этом запросе планирование занимает меньше времени, чем в предыдущем. В других случаях планировщик способен определить, что можно безопасно выбрать более одного варианта соединения. Например, здесь:

SELECT * FROM a LEFT JOIN b ON (a.bid = b.id) LEFT JOIN c ON (a.cid = c.id);

сначала можно соединить А с B или с C. В настоящее время только FULL JOIN полностью ограничивает порядок соединения. Большинство практических запросов, включающих LEFT JOIN или RIGHT JOIN, можно в некоторой степени перестроить.

Явный синтаксис внутреннего соединения (INNER JOIN, CROSS JOIN или JOIN) семантически аналогичен перечислению входных отношений в FROM, поэтому он не ограничивает порядок соединения.

Несмотря на то, что большинство видов JOIN не полностью ограничивают порядок соединения, можно поручить планировщику запросов QHB все равно обрабатывать все предложения JOIN как ограничивающие этот порядок. Например, эти три запроса логически равнозначны:

SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

Но если мы скажем планировщику соблюдать порядок JOIN, то для второго и третьего плана потребуется меньше времени, чем для первого. Этот эффект не имеет большого смысла, если работать всего с тремя таблицами, но может быть незаменим для запросов, в которых много таблиц.

Чтобы планировщик следовал порядку соединения, установленному явными JOIN, установите для параметра времени выполнения join_collapse_limit значение 1. (Другие возможные значения рассматриваются ниже.)

Необязательно полностью ограничивать порядок соединения, чтобы сократить время поиска, поскольку можно использовать операторы JOIN в элементах простого списка FROM. Например, рассмотрим:

SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;

При join_collapse_limit = 1 эта запись заставляет планировщик соединить A с B, прежде чем соединять их с другими таблицами, но в остальном его выбор не ограничивает. В этом примере количество возможных вариантов соединения уменьшается в 5 раз.

Ограничение поиска планировщика таким способом является полезным методом как для сокращения времени планирования, так и для указания планировщику на хороший план запросов. Если по умолчанию планировщик выбирает неправильный порядок соединения, с помощью синтаксиса JOIN можно заставить его выбрать лучший порядок — при условии, что вы этот порядок знаете. Рекомендуем сначала провести эксперимент.

Тесно связанным фактором, влияющим на время планирования, является сворачивание подзапросов в их родительский запрос. Например, рассмотрим:

SELECT *
FROM x, y,
    (SELECT * FROM a, b, c WHERE something) AS ss
WHERE somethingelse;

Эта ситуация может возникнуть из-за использования представления, содержащего соединение; вместо ссылки на представление будет вставлено его правило SELECT, в результате чего будет получен запрос, аналогичный приведенному выше. Скорее всего, планировщик попытается свернуть подзапрос в родительский запрос, получив:

SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;

Обычно это позволяет выбрать лучший план, чем планирование подзапроса отдельно. (Например, внешние условия WHERE могут быть такими, что при присоединении X к A сначала из A удаляется много строк, тем самым исключая необходимость формировать полный логический вывод подзапроса.) Но в то же время мы увеличили время планирования, заменив две отдельные проблемы трехстороннего соединения на проблему пятистороннего соединения. Из-за экспоненциального роста числа возможностей соединения это имеет большое значение. Планировщик старается избегать проблем поиска с огромным числом вариантов, не сворачивая подзапрос, если в родительском запросе может оказаться более from_collapse_limit элементов предложения FROM. Уменьшая или увеличивая этот параметр времени выполнения, можно подобрать оптимальное соотношение времени планирования и качества плана.

Параметры from_collapse_limit и join_collapse_limit называются похоже, потому что они выполняют почти одно и то же: один определяет, когда планировщик «раскатает» в предложение FROM подзапросы, а другой — явные соединения. Обычно join_collapse_limit устанавливается равным from_collapse_limit (чтобы явные соединения и подзапросы действовали схожим образом) или 1 (если нужно управлять порядком соединения с помощью явных указаний). Но если вы пытаетесь настроить оптимальное соотношение между временем планирования и временем выполнения, в них можно задать разные значения.

Заполнение базы данных

При первом заполнении базы данных может возникнуть необходимость добавить большой объем данных. В этом разделе содержится несколько предложений, позволяющих сделать этот процесс максимально эффективным.

Выключите автоматическую фиксацию

При использовании нескольких INSERT выключите автоматическую фиксацию и просто сделайте одну фиксацию транзакции в конце. (В обычном SQL это означает выполнение BEGIN в начале и COMMIT в конце. Некоторые клиентские библиотеки могут делать это незаметно для пользователя, и в этом случае следует позаботиться о том, чтобы библиотека делала это только по вашему желанию.) Если позволить фиксировать каждое добавление по отдельности, QHB придется проделать большую работу с каждой добавляемой строкой. Дополнительным преимуществом выполнения всех добавлений в одной транзакции является то, что если при добавлении одной строки произойдет сбой, добавление всех строк до этой точки откатится до исходного состояния, поэтому вы не получите ситуацию с частично загруженными данными.

Используйте команду COPY

Вместо серии команд INSERT используйте команду COPY, чтобы загрузить все строки в одной команде. Команда COPY оптимизирована для загрузки большого количества строк; она не такая гибкая, как INSERT, зато значительно снижает издержки при загрузке большого объема данных. Поскольку COPY — это одна команда, при использовании для заполнения таблицы этого метода нет необходимости выключать автоматическую фиксацию.

Если вы не можете использовать COPY, может быть полезно использовать PREPARE для создания подготовленного оператора INSERT, а затем выполнить EXECUTE столько раз, сколько потребуется. Это позволяет избежать некоторых издержек, связанных с многократным анализом и планированием INSERT. Различные интерфейсы предоставляют эту возможность по-разному; ищите в документации интерфейса фразу «подготовленные операторы».

Обратите внимание, что загрузка большого количества строк с помощью COPY почти всегда быстрее, чем с помощью INSERT, даже если используется PREPARE и несколько добавлений объединяются в одну транзакцию.

Команда COPY работает быстрее всего, когда выполняется в одной транзакции с более ранней командой CREATE TABLE или TRUNCATE. В таких случаях нет нужды протоколировать в WAL, потому что в случае ошибки файлы, содержащие только что загруженные данные, все равно будут удалены. Однако это соображение применимо только в том случае, если wal_level равен minimal, поскольку иначе всем командам придется протоколировать свои действия в WAL.

Удалите индексы

Если вы загружаете только что созданную таблицу, самый быстрый способ — создать таблицу, выполнить массовую загрузку данных таблицы с помощью COPY, а затем создать все индексы, необходимые для этой таблицы. Создание индекса для уже существующих данных происходит быстрее, чем его постепенное обновление по мере загрузки каждой строки.

Если вы добавляете большие объемы данных в существующую таблицу, можно снизить расходы, удалив индексы, загрузив таблицу, а затем пересоздав эти индексы. Разумеется, на время отсутствия индексов производительность базы данных для других пользователей может снизиться. Следует дважды подумать, прежде чем отбрасывать уникальный индекс, поскольку проверка ошибок, предоставляемая ограничением уникальности, в отсутствие индекса выполняться не будет.

Удалите ограничения внешнего ключа

Как и в случае с индексами, «массовая» проверка ограничения внешнего ключа может быть более эффективной, чем построчная. Поэтому может быть полезно удалить ограничения внешнего ключа, загрузить данные и пересоздать эти ограничения. Опять же, здесь существует выбор между скоростью загрузки данных и невыполнением проверки ошибок во время отсутствия ограничения.

Более того, когда вы загружаете данные в таблицу с существующими ограничениями внешнего ключа, каждая новая строка требует записи в списке очереди событий триггера на сервере (так как именно запуск триггера проверяет ограничение внешнего ключа строки). Загрузка многих миллионов строк может вызвать переполнение доступной памяти очередью событий триггера, что приведет к недопустимой перекачке или даже полному отказу команды. Поэтому при загрузке больших объемов данных может быть не просто желательно, а необходимо удалить и повторно применить внешние ключи. Если временное удаление ограничения неприемлемо, единственным возможным выходом может быть разделение операции загрузки на более мелкие транзакции.

Увеличьте maintenance_work_mem

Временное увеличение переменной конфигурации maintenance_work_mem при загрузке больших объемов данных может привести к увеличению производительности. Это поможет ускорить выполнение команд CREATE INDEX и ALTER TABLE ADD FOREIGN KEY. Для самой COPY это не будет иметь большого значения, так что этот совет полезен только при использовании одного или обоих из вышеперечисленных методов.

Увеличьте max_wal_size

Временное увеличение переменной конфигурации max_wal_size также может ускорить загрузку больших объемов данных. Это связано с тем, что в QHB загрузка большого объема данных приводит к тому, что контрольные точки появляются чаще обычной (которая задается в переменной конфигурации checkpoint_timeout). Всякий раз, когда возникает контрольная точка, все «грязные» страницы должны быть сброшены на диск. Временное увеличение max_wal_size во время массовой загрузки данных позволяет уменьшить количество необходимых контрольных точек.

Выключите архивацию WAL и потоковую репликацию

При загрузке больших объемов данных в установку, где используется архивирование WAL или потоковая репликация, возможно, быстрее будет создать новую резервную копию базы после завершения загрузки, чем обрабатывать большой объем инкрементальных данных WAL. Чтобы предотвратить прирастающее протоколирование в WAL при загрузке, выключите архивацию и потоковую репликацию, установив для wal_level значение minimal, для archive_mode — off, а для max_wal_senders — ноль. Но учтите, что изменение этих параметров требует перезагрузки сервера и делает все созданные ранее резервные копии базы непригодными для восстановления архива и резервного сервера, что может привести к потере данных.

Помимо сокращения времени обработки данных WAL архиватором или передатчиком WAL, эта операция значительно ускорит выполнение определенных команд, поскольку если wal_level равен minimal и текущая субтранзакция (или транзакция верхнего уровня) создала или опустошила таблицу или индекс, которые они изменяют, эти команды вообще ничего не протоколируют в WAL. (Они могут гарантировать безопасность данных при сбое, не протоколируя в WAL, а выполнив в конце fsync, что гораздо дешевле.)

Запустите в конце команду ANALYZE

Всякий раз после значительного перераспределения данных в таблице настоятельно рекомендуется выполнить команду ANALYZE. Это касается и массовой загрузки больших объемов данных в таблицу. Запуск ANALYZE (или VACUUM ANALYZE) гарантирует, что планировщик будет иметь актуальную статистику о таблице. Если статистика отсутствует или устарела, планировщик может принимать неправильные решения при планировании запросов, что приведет к низкой производительности для всех таблиц с неточной или отсутствующей статистикой. Обратите внимание, что если включен фоновый процесс «Автовакуум», он может запустить ANALYZE автоматически; дополнительную информацию см. в подразделах Обновление статистики планировщика и Процесс «Автовакуум».

Несколько замечаний о qhb_dump

Скрипты дампа, сгенерированные qhb_dump, автоматически применяют некоторые, но не все из приведенных выше рекомендаций. Чтобы как можно быстрее загрузить дамп, выгруженный qhb_dump, вам нужно будет совершить ряд дополнительных действий вручную. (Обратите внимание, что эти пункты применяются при восстановлении дампа, а не при его создании. Эти пункты применяются при загрузке текстового дампа из файла архива qhb_dump как с помощью psql, так и с помощью qhb_restore.)

По умолчанию утилита qhb_dump использует COPY, и, генерируя полный дамп схемы и данных, она добросовестно загружает данные, прежде чем создавать индексы и внешние ключи. Таким образом, в этом случае несколько рекомендаций обрабатываются автоматически. Вам остается сделать лишь следующее:

Установить соответствующие (т. е. более высокие, чем обычно) значения для maintenance_work_mem и max_wal_size.

Если используется архивация WAL или потоковая репликация, рассмотреть возможность их выключения во время восстановления. Для этого прежде чем загружать дамп, установить для параметра archive_mode значение off, для wal_level — minimal, а для max_wal_senders — ноль. Завершив восстановление, вернуть им правильные значения и создать новую резервную копию базы.

Поэкспериментировать с режимами параллельного резервного копирования и восстановления qhb_dump и qhb_restore и найти оптимальное количество параллельных заданий. Параллельное копирование и восстановление посредством параметра -j должно обеспечить гораздо более высокую производительность, чем последовательный режим.

Рассмотреть возможность восстановления всего дампа за одну транзакцию. Для этого передать в psql или qhb_restore параметр командной строки -1 или --single-transaction. При использовании этого режима даже самые маленькие ошибки будут отменять все восстановление, возможно, сводя на нет многие часы обработки. В зависимости от того, насколько взаимосвязаны данные, этот вариант может оказаться предпочтительнее ручной очистки (или нет). Команды COPY будут выполняться быстрее, если вы используете одну транзакцию и выключили архивацию WAL.

Если на сервере баз данных доступно несколько процессоров, попробовать использовать в qhb_restore параметр --jobs. Это позволит одновременно загружать данные и создавать индексы.

По завершении всех операций запустить ANALYZE.

Для дампа без схемы по-прежнему будет выполняться COPY, но она не удаляет и не пересоздает индексы и обычно не касается внешних ключей.¹ Таким образом, при загрузке дампа без схемы вы сами должны удалить и воссоздать индексы и внешние ключи, если хотите воспользоваться этими методами. По-прежнему имеет смысл увеличить max_wal_size при загрузке данных, но увеличивать maintenance_work_mem необязательно; этот параметр лучше менять при последующем воссоздании индексов и внешних ключей вручную. И не забудьте запустить ANALYZE, когда закончите; дополнительную информацию см. в подразделах Обновление статистики планировщика и Процесс «Автовакуум».

¹

Эффекта выключения внешних ключей можно добиться, используя параметр --disable-triggers, но следует понимать, что это не просто откладывает, а убирает проверку внешнего ключа, и поэтому при использовании этого параметра есть вероятность вставить недопустимые данные.

Настройки, снижающие прочность

Прочность — это свойство базы данных, гарантирующее запись зафиксированных транзакций даже при сбое сервера или отключении питания. Однако прочность добавляет значительную нагрузку на базу данных, поэтому если вам не требуется такой гарантии, QHB можно настроить для гораздо более быстрой работы. Ниже приведены изменения конфигурации, которые в таких случаях можно внести для повышения производительности. Не считая отмеченных ниже исключений, при использовании этих настроек все равно гарантируется прочность в случае сбоя программного обеспечения СУБД; только внезапный сбой операционной системы создает риск потери или повреждения данных.

Поместите каталог данных кластера базы данных в файловую систему, размещенную в памяти (т. е. в RAM-диск). Это исключает все операции ввода/вывода на диске базы данных, но ограничивает объем хранения данных до объема доступной памяти (и, возможно, файла подкачки).

Выключите fsync — нет необходимости сбрасывать данные на диск.

Выключите synchronous_commit — возможно, принудительно записывать WAL на диск при фиксации каждой транзакции не потребуется. Однако это создает риск потери транзакции (но не повреждения данных) в случае сбоя базы данных.

Выключите full_page_writes — нет необходимости принимать меры против частичной записи страницы.

Увеличьте max_wal_size и checkpoint_timeout — это уменьшает частоту контрольных точек, но увеличивает объем хранилища /pg_wal.

Создавайте нежурналируемые таблицы, чтобы избежать протоколирования в WAL (однако это лишит таблицы защиты от сбоя).

Аутентификация клиентского приложения

При подключении к серверу баз данных клиентское приложение указывает, под каким именем пользователя базы данных QHB оно хочет подключиться, почти так же, как при обычном входе пользователя в компьютер с Unix. В среде SQL по имени пользователя базы данных определяется, какие у него есть права доступа к объектам базы данных; более подробную информацию см. в главе Роли в базе данных. Таким образом, крайне важно указать, к каким базам данных пользователи могут подключаться.

Примечание
Как объясняется в главе Роли в базе данных, QHB на самом деле управляет правами посредством так называемых «ролей». Соответственно, в этой главе под термином пользователь базы данных подразумевается «роль с правом LOGIN».

Аутентификация — это процесс, посредством которого сервер баз данных идентифицирует клиента, а в более широком смысле определяет, разрешено ли клиентскому приложению (или пользователю, запустившему это приложение) подключиться с указанным именем пользователя базы данных.

QHB предлагает несколько различных методов аутентификации клиента. Метод аутентификации конкретного клиентского подключения можно выбрать на основе адреса хоста клиента, имени базы данных и имени пользователя.

Имена пользователей базы данных QHB логически отделены от имен пользователей операционной системы, в которой запущен сервер. Если у всех пользователей определенного сервера есть учетные записи в его компьютере, имеет смысл назначить им имена пользователей базы данных, соответствующие именам пользователей операционной системы. Однако у сервера, принимающего удаленные подключения, может быть много пользователей базы данных, у которых нет локальной учетной записи в операционной системе, и в таких случаях в соответствии между именами пользователей базы данных и именами пользователей ОС нет необходимости.

Файл qhb_hba.conf

Аутентификация клиентов управляется файлом конфигурации, который традиционно называется qhb_hba.conf и хранится в каталоге данных кластера баз данных. (HBA расшифровывается как host-based authentication — аутентификация по узлу.) Стандартный файл qhb_hba.conf устанавливается при инициализации каталога данных утилитой qhb_bootstrap (или initdb). Однако файл конфигурации аутентификации можно разместить в любом другом месте; см. параметр конфигурации hba_file.

Общий формат файла qhb_hba.conf — набор записей, по одной на строку. Пустые строки игнорируются, как и любой текст после знака отбивки комментария (#). Запись можно продолжить на следующей строке, завершив текущую строку обратным слэшем. (Обратный слэш является спецсимволом только в конце строки.) Запись состоит из ряда полей, разделенных пробелами и/или табуляциями. Поля могут содержать пробелы, если значение поля заключено в кавычки. Если в кавычки берется одно из ключевых слов в поле базы данных, пользователя или адреса (например all или replication), это слово теряет свое особое значение и просто обозначает базу данных, пользователя или хост с этим именем. Обратный слэш означает перенос строки даже в тексте в кавычках или комментариях.

В каждой записи указывается тип соединения, диапазон IP-адресов клиента (если это важно для типа соединения), имя базы данных, имя пользователя и метод аутентификации, который будет использован для подключения в соответствии с этими параметрами. Первая же запись с соответствующим типом соединения, адресом клиента, требуемой базой данных и именем пользователя используется для аутентификации. Процедуры «передачи управления вниз» или «резервирования» отсутствуют: если выбрана запись, и аутентификация провалилась, последующие записи не рассматриваются. Если ни одна запись не подходит, в доступе будет отказано.

Запись может иметь несколько форматов:

local         база_данных  пользователь  метод-аутентификации [параметры-аутентификации]
host          база_данных  пользователь  адрес     метод-аутентификации  [параметры-аутентификации]
hostssl       база_данных  пользователь  адрес     метод-аутентификации  [параметры-аутентификации]
hostnossl     база_данных  пользователь  адрес     метод-аутентификации  [параметры-аутентификации]
hostgssenc    база_данных  пользователь  адрес     метод-аутентификации  [параметры-аутентификации]
hostnogssenc  база_данных  пользователь  адрес     метод-аутентификации  [параметры-аутентификации]
host          база_данных  пользователь  IP-адрес  IP-маска      метод-аутентификации  [параметры-аутентификации]
hostssl       база_данных  пользователь  IP-адрес  IP-маска      метод-аутентификации  [параметры-аутентификации]
hostnossl     база_данных  пользователь  IP-адрес  IP-маска      метод-аутентификации  [параметры-аутентификации]
hostgssenc    база_данных  пользователь  IP-адрес  IP-маска      метод-аутентификации  [параметры-аутентификации]
hostnogssenc  база_данных  пользователь  IP-адрес  IP-маска      метод-аутентификации  [параметры-аутентификации]

Эти поля имеют следующие значения:

local
Эта запись соответствует попыткам подключения через сокеты домена Unix. Без записи этого типа подключения через сокеты домена Unix не допускаются.

host
Эта запись соответствует попыткам подключения по TCP/IP. Записи host соответствуют попыткам подключения с SSL или без SSL, а также попыткам подключения с криптографической защитой GSSAPI или без GSSAPI.

Примечание
Удаленные подключения по TCP/IP будут невозможны, если сервер запущен без соответствующего значения для параметра конфигурации listen_addresses, поскольку по умолчанию система принимает подключения по TCP/IP только для локального кольцевого адреса localhost.

hostssl
Эта запись соответствует попыткам подключения по TCP/IP, но только когда это подключение устанавливается с шифрованием SSL.

Чтобы воспользоваться этим параметром, сервер должен быть собран с поддержкой SSL. Более того, SSL должен быть включен параметром конфигурации ssl (более подробную информацию см. в разделе Защита TCP/IP-соединений посредством SSL). В противном случае запись hostssl игнорируется, не считая вывода предупреждения о том, что ей не будут соответствовать никакие подключения.

hostnossl
У этого типа записей поведение противоположно hostssl; ему соответствуют только попытки подключения по TCP/IP без шифрования SSL.

hostgssenc
Эта запись соответствует попыткам подключения по TCP/IP, но только когда это подключение устанавливается с шифрованием GSSAPI.

Чтобы воспользоваться этим параметром, сервер должен быть собран с поддержкой GSSAPI. В противном случае запись hostgssenc игнорируется, не считая вывода предупреждения о том, что ей не будут соответствовать никакие подключения.

hostnogssenc
У этого типа записей поведение противоположно hostgssenc; ему соответствуют только попытки подключения по TCP/IP без шифрования GSSAPI.

база_данных
Определяет, какому имени (или именам) базы данных соответствует эта запись. Значение all указывает, что она соответствует всем базам данных. Значение sameuser указывает, что эта запись соответствует, если имя запрашиваемой базы данных совпадает с именем запрашиваемого пользователя. Значение samerole указывает, что запрашиваемый пользователь должен быть членом роли с тем же именем, что и у запрашиваемой базы данных. (samegroup — это устаревший, но все еще принимаемый вариант значения samerole.) Суперпользователи считаются членами роли в контексте samerole, только если они явно входят в такую роль, непосредственно или опосредованно; просто быть суперпользователем для этого недостаточно. Значение replication указывает, что эта запись соответствует, если запрашивается подключение физической репликации, однако при этом не соответствует подключениям логической репликации. Обратите внимание, что для подключения физической репликации не указывается никакая конкретная база данных, тогда как для подключений логической репликации она должна указываться. Любое другое значение воспринимается как имя конкретной базы данных QHB. Можно задать несколько имен баз данных, разделив их запятыми. Кроме того, можно задать отдельный файл с именами баз данных, написав имя этого файла после знака @.

пользователь
Определяет, какому имени (или именам) пользователя базы данных соответствует эта запись. Значение all указывает, что она соответствует всем пользователям. Любое другое значение воспринимается либо как имя конкретного пользователя базы данных, либо как имя группы, если написать его после знака +. (Напомним, что в QHB нет никакой разницы между пользователями и группами; знак + фактически означает «соответствует любым ролям, которые непосредственно или опосредованно являются членами этой роли», тогда как имя без знака + соответствует только этой конкретной роли.) По этой причине суперпользователь рассматривается как член роли, только если он явно является членом этой роли, непосредственно или опосредованно; просто быть суперпользователем для этого недостаточно. Можно задать несколько имен пользователей, разделив их запятыми. Кроме того, можно задать отдельный файл с именами пользователей, написав имя этого файла после знака @.

адрес
Задает адрес (или адреса) клиентской машины, которому соответствует эта запись. Это поле может содержать либо имя хоста, либо диапазон IP-адресов, либо одно из упомянутых ниже ключевых слов.

Диапазон IP-адресов указывается посредством стандартного цифрового представления для начального адреса диапазона, дополненного слэшем (/) и длиной маски CIDR. Длина маски определяет количество старших битов клиентского IP-адреса, которые должны соответствовать. Биты, находящиеся правее, в заданном IP-адресе должны быть нулевыми. Между IP-адресом, знаком / и длиной маски CIDR не должно быть пробелов.

Типичные примеры диапазона адресов IPv4, заданных таким образом: 172.20.143.89/32 для одного хоста, 172.20.143.0/24 для небольшой и 10.6.0.0/16 для более крупной сети. Диапазон адресов IPv6 может выглядеть как ::1/128 для одного хоста (это кольцевой адрес IPv6) или как fe80::7a31:c1ff:0000:0000/96 для небольшой сети. 0.0.0.0/0 представляет все адреса IPv4, а ::0/0 — все адреса IPv6. Чтобы указать один хост, используйте длину маски 32 для IPv4 или 128 для IPv6. Не опускайте замыкающие нули в сетевом адресе.

Запись, сделанная в формате IPv4, будет соответствовать только подключениям по IPv4, а запись в формате IPv6 — только подключениям по IPv6, даже если представленный адрес находится в диапазоне IPv4-в-IPv6. Обратите внимание, что записи в формате IPv6 не будут приниматься, если системная библиотека C не поддерживает адреса IPv6.

Также можно написать значение all для соответствия записи любому адресу IP, samehost для соответствия любым IP-адресам данного сервера или samenet для соответствия любому адресу любой подсети, к которой сервер подключен напрямую.

Если задано имя хоста (все, что не является диапазоном адресов IP или специальным ключевым словом, воспринимается как имя хоста), это имя сравнивается с результатом обратного преобразования имени IP-адреса клиента (например, обратного DNS-поиска, если используется DNS). При сравнении имен хостов регистр не учитывается. Если имена совпали, выполняется прямое преобразование имени хоста (например, прямой DNS-поиск) для проверки, совпадает ли какой-либо из разрешенных адресов с IP-адресом клиента. Если двусторонняя проверка пройдена, запись считается соответствующей хосту. (Имя хоста в файле qhb_hba.conf должно быть тем, что возвращается при преобразовании IP-адреса клиента в имя, иначе строка не будет соответствовать хосту. Некоторые базы имен хостов позволяют связать один IP-адрес с несколькими именами хостов, но операционная система при запросе на разрешение IP-адреса вернет только одно имя.)

Указание имени хоста, начинающееся с точки (.), соответствует суффиксу актуального имени хоста. Так, .example.com будет соответствовать foo.example.com (а не только example.com).

Когда в qhb_hba.conf указываются имена хостов, следует добиться того, чтобы разрешение имен выполнялось достаточно быстро. Для этого может быть полезно установить локальный кэш разрешения имен, например nscd. Также можно включить параметр конфигурации log_hostname, чтобы видеть в журнале имя хоста клиента вместо IP-адреса.

Эти поля не применимы к записям local.

Примечание
Иногда пользователи задаются вопросом, почему имена хостов обрабатываются таким сложным, на первый взгляд, способом, с разрешениями двух имен, включая обратный поиск IP-адреса клиента. Это усложняет использование этой функциональности в случае, если обратная DNS-запись клиента не установлена или выдает какое-нибудь нежелательное имя хоста. Это сделано, в первую очередь, из соображений эффективности: при таком подходе попытка подключения требует максимум два поиска разрешения, один обратный и один прямой. Если есть проблема разрешения с каким-то адресом, она становится проблемой только этого клиента. Гипотетическая альтернативная реализация, при которой выполнялись бы только прямые поиски, могла бы разрешать все имена хостов, упомянутые в qhb_hba.conf, во время каждой попытки подключения. Но если перечислено много имен, этот процесс был бы довольно медленным. И в случае наличия проблемы разрешения у одного имени хоста это становится общей проблемой.

Кроме того, обратный поиск необходим для реализации возможности соответствия суффиксов, поскольку для сопоставления с шаблоном необходимо знать фактическое имя хоста клиента.

Обратите внимание, что это поведение согласуется с другими популярными реализациями управления доступом на основе имен хостов, такими как Apache HTTP Server и TCP Wrappers.

IP-адрес
IP-маска
Эти два поля можно использовать как альтернативу записи IP-адрес/длина-маски. Вместо того чтобы указывать длину маски, в отдельном столбце указывается сама маска. Например, 255.0.0.0 представляет собой маску CIDR для IPv4 длиной 8 бит, а 255.255.255.255 представляет маску CIDR длиной 32 бита.

Эти поля не применимы к записям local.

метод-аутентификации
Задает метод аутентификации, который будет использован, когда подключение соответствует этой записи. Возможные варианты перечислены ниже; подробную информацию см. в разделе Методы аутентификации. Все варианты чувствительны к регистру и записываются в нижнем регистре, даже если это аббревиатуры типа ldap.

trust
Разрешить безусловное подключение. Этот метод позволяет любому, кто может подключиться к серверу баз данных QHB, войти под любым желаемым пользователем QHB без необходимости вводить пароль или какой-либо другой аутентификации. Подробную информацию см. в разделе Аутентификация trust.

reject
Отклонить безусловное подключение. Это полезно для «отфильтровывания» некоторых хостов из группы; например, строка reject может заблокировать подключение от определенного хоста, тогда как следующая строка позволит подключиться остальным хостам в той же сети.

scram-sha-256
Выполнить аутентификацию SCRAM-SHA-256 для проверки пароля пользователя. Подробную информацию см. в разделе Аутентификация по паролю.

md5
Выполнить аутентификацию SCRAM-SHA-256 или MD5 для проверки пароля пользователя. Подробную информацию см. в разделе Аутентификация по паролю.

password
Потребовать для аутентификации введения клиентом незашифрованного пароля. Поскольку пароль посылается открытым текстом через сеть, такой метод нельзя использовать в недоверенных сетях. Подробную информацию см. в разделе Аутентификация по паролю.

gss
Использовать GSSAPI для аутентификации пользователя. Этот метод доступен только для подключений по TCP/IP. Подробную информацию см. в разделе Аутентификация GSSAPI. Его можно применять в сочетании с шифрованием GSSAPI.

ident
Получить имя пользователя операционной системы клиента, связываясь с сервером ident на стороне клиента, и проверить, соответствует ли оно имени пользователя запрашиваемой базы данных. Аутентификацию ident можно использовать только при подключениях по TCP/IP. Если указать этот метод для локальных подключений, вместо него будет применяться аутентификация peer. Подробную информацию см. в разделе Аутентификация ident.

peer
Получить имя пользователя операционной системы клиента из операционной системы и проверить, соответствует ли оно имени пользователя запрашиваемой базы данных. Доступно только для локальных подключений. Подробную информацию см. в разделе Аутентификация peer.

ldap
Провести аутентификацию, используя сервер LDAP. Подробную информацию см. в разделе Аутентификация LDAP.

radius
Провести аутентификацию, используя сервер RADIUS. Подробную информацию см. в разделе Аутентификация RADIUS.

cert
Провести аутентификацию, используя клиентские сертификаты SSL. Подробную информацию см. в разделе Аутентификация по сертификату.

pam
Провести аутентификацию, используя службу подключаемых модулей аутентификации (Pluggable Authentication Modules, PAM), предоставляемую операционной системой. Подробную информацию см. в разделе Аутентификация PAM.

bsd
Провести аутентификацию, используя службу аутентификации BSD, предоставляемую операционной системой. Подробную информацию см. в разделе Аутентификация BSD.

параметры-аутентификации
После поля метод-аутентификации может идти поле (или поля) вида имя=значение, задающее параметры метода аутентификации. Подробнее о том, какие параметры доступны для различных методов аутентификации, рассказывается ниже.

Кроме перечисленных ниже параметров, относящихся к различным методам, есть один общий параметр аутентификации clientcert, который можно задать в любой записи hostssl. Этот параметр может принимать значения verify-ca и verify-full. В обоих случаях требуется, чтобы клиент предоставил действительный (доверенный) сертификат SSL, но вариант verify-full дополнительно требует, чтобы значение cn (Common Name, общее имя) в сертификате соответствовало имени пользователя или применимому сопоставлению. Это поведение схоже с методом аутентификации cert (см. раздел Аутентификация по сертификату), но позволяет связать проверку сертификатов клиента с любым методом аутентификации, поддерживающим записи hostssl.

В любой записи, где используется аутентификация по сертификату клиента (т. е. в записи с методом аутентификации cert или с параметром clientcert), в параметре clientname можно указать, какая часть учетных данных в сертификате клиента будет сопоставляться при проверке. Этот параметр может принимать одно из двух значений. Если задано значение clientname=CN (это значение по умолчанию), имя пользователя сопоставляется с полем сертификата Common Name (CN). Если задано значение clientname=DN, имя пользователя сопоставляется со всем отличительным именем, Distinguished Name (DN), в сертификате. Этот параметр, вероятно, лучше всего применять в сочетании с сопоставлением пользователя. Сравнение с DN выполняется в формате RFC 2253. Чтобы просмотреть в этом формате поле DN сертификата клиента, выполните

openssl x509 -in myclient.crt -noout --subject -nameopt RFC2253 | sed "s/^subject=//"

Использовать этот параметр следует с осторожностью, особенно когда для сопоставлений с DN применяются регулярные выражения.
.

Файлы, включенные в запись конструкциями, начинающимися с @, читаются как списки имен, разделенных пробелами или запятыми. Комментарии предваряются знаками #, как и в qhb_hba.conf, и вложенные конструкции с @ допустимы. Если имя файла, начинающееся с @, не является абсолютным путем, оно считается относящимся к каталогу, содержащему ссылающийся файл.

Поскольку записи в qhb_hba.conf просматриваются последовательно для каждой попытки подключения, порядок записей имеет большое значение. Обычно более ранние записи содержат жесткие параметры соответствия для подключения и более гибкие методы аутентификации, тогда как в более поздних записях параметры соответствия менее требовательны, а методы аутентификации более строгие. Например, некто желает использовать аутентификацию trust для локальных подключений по TCP/IP, но при этом запрашивать пароль для удаленных подключений по TCP/IP. В этом случае запись, задающая аутентификацию trust для подключений с адреса 127.0.0.1 должна предшествовать записи, задающей аутентификацию по пароль для более широкого диапазона клиентских IP-адресов.

Файл qhb_hba.conf прочитывается при запуске системы, а также при получении основным серверным процессом сигнала SIGHUP. При редактировании этого файла во время работы системы необходимо послать сигнал процессу qhbmaster (с помощью команды qhb_ctl reload, вызвав функцию SQL pg_reload_conf() или выполнив kill -HUP), чтобы он заново прочел файл.

Системное представление pg_hba_file_rules может быть полезно для предварительной проверки изменений в файле qhb_hba.conf или для диагностики проблем, если загрузка этого файла не привела к желаемым результатам. Строки в этом представлении, содержащие в поле error не NULL, указывают на проблемы в соответствующих строках файла.

Совет
Чтобы подключиться к определенной базе данных, пользователь должен не только пройти проверки файла qhb_hba.conf, но и иметь право CONNECT для этой базы данных. Если требуется ограничить доступ к каким-либо базам данных для определенных пользователей, как правило, этим проще управлять, предоставляя/ отзывая право CONNECT, нежели устанавливая правила в записях qhb_hba.conf.

Некоторые примеры записей файла qhb_hba.conf показаны в Примере 1 ниже. Более подробную информацию о различных методах аутентификации см. в следующем разделе.

Пример 1. Пример записей qhb_hba.conf

# Позволить любому пользователю локальной системы подключаться к любой базе данных,
# используя любое имя пользователя баз данных, через сокеты домена Unix
# (по умолчанию для локальных подключений).
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
  local all             all                                     trust

# То же самое, но для локальных кольцевых подключений по TCP/IP.
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
  host  all             all             127.0.0.1/32            trust

# То же, что и в предыдущей строке, но с указанием сетевой маски в отдельном столбце
#
# TYPE  DATABASE        USER            IP-ADDRESS      IP-MASK             METHOD
  host  all             all             127.0.0.1       255.255.255.255     trust

# То же самое для IPv6.
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
  host  all             all             ::1/128                 trust

# То же самое, но с использованием имени хоста (обычно охватывает и IPv4, и IPv6).
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
  host  all             all             localhost               trust

# Позволить любому пользователю любого узла с IP-адресом 192.168.93.x подключаться
# к базе данных «qhb» с тем же именем пользователя, которое сообщает для данного
# подключения ident (как правило, имя пользователя операционной системы).
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
  host  qhb             all             192.168.93.0/24         ident

# Позволить любому пользователю узла 192.168.12.10 подключаться к базе данных
# «qhb», если передается правильный пароль пользователя.
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
  host  qhb             all             192.168.12.10/32        scram-sha-256

# Позволить любому пользователю хостов в домене example.com подключаться к
# любой базе данных, если передается правильный пароль пользователя.
#
# Для большинства пользователей требуется аутентификация SCRAM, за исключением
# пользователя 'mike', который использует более старый клиент, не поддерживающий
# аутентификацию SCRAM.
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
  host  all             mike            .example.com            md5
  host  all             all             .example.com            scram-sha-256

# В случае отсутствия предшествующих строчек с «host» эти три строки откажут
# в любых подключениях с 192.168.54.1 (поскольку эта запись будет сопоставлена
# первой), но разрешат подключения с шифрованием GSSAPI с любых других адресов
# интернета. С нулевой маской ни один бит из IP-адреса узла не учитывается,
# так что этой строке соответствует любой узел. Подключения без GSSAPI (которые
# «опускаются» до третьей строки, поскольку записи "hostgssenc" соответствуют
# только подключения с GSSAPI) разрешены, но только с адреса 192.168.12.10.
#
# TYPE       DATABASE        USER            ADDRESS                 METHOD
  host       all             all             192.168.54.1/32         reject
  hostgssenc all             all             0.0.0.0/0               gss
  host       all             all             192.168.12.10/32        gss

# Позволить пользователям с узлов 192.168.x.x подключаться к любой базе данных,
# если они проходят проверку ident.  Если же ident говорит, к примеру, что это
# пользователь "bryanh" и он запрашивает подключение как пользователь QHB "guest1",
# подключение будет разрешено, если в qhb_ident.conf есть запись с сопоставлением
# "omicron", разрешающим пользователю "bryanh" подключаться как "guest1".
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
  host  all             all             192.168.0.0/16          ident map=omicron

# Если для локальных подключений предусмотрены только эти три строки, они позволят
# локальным пользователям подключаться только к их собственным базам данных (базам
# данных с именами, совпадающими с их именами пользователей баз данных), за
# исключением администраторов и членов роли "support", которые могут подключаться
# ко всем базам данных. Список имен администраторов содержится в файле
# $PGDATA/admins. Пароли требуются в любом случае.
#
# TYPE   DATABASE         USER              ADDRESS               METHOD
  local  sameuser         all                                     md5
  local  all              @admins                                 md5
  local  all              +support                                md5

# Последние две строки выше можно объединить в одну:
  local  all              @admins,+support                        md5

# В столбце DATABASE также можно указывать списки и имена файлов:
  local  db1,db2,@demodbs all                                     md5

Файл сопоставлений имен пользователей

При использовании внешней системы аутентификации, например Ident или GSSAPI, имя пользователя операционной системы, устанавливающего подключение, может не совпадать с целевым именем пользователя (роли) базы данных. В этом случае можно применить сопоставление имен пользователей ОС и пользователей базы данных. Чтобы воспользоваться этим сопоставлением, следует указать map=имя-сопоставления в поле параметров в файле qhb_hba.conf. Этот параметр поддерживается для всех методов аутентификации, принимающих внешние имена пользователей. Поскольку для разных подключений могут понадобиться разные сопоставления, имя применяемого сопоставления задается в параметре имя-сопоставления в qhb_hba.conf для каждого отдельного подключения.

Сопоставления имен пользователей определяются в файле сопоставления ident, который по умолчанию называется qhb_ident.conf и хранится в каталоге данных кластера. (Однако этот файл можно поместить и в другое место; см. параметр конфигурации ident_file.) Файл сопоставления ident содержит строки общего вида:

имя-сопоставления имя-пользователя-ос имя-пользователя-бд

Синтаксис комментариев, пробелов и переносов строк такой же, как в файле qhb_hba.conf. имя-сопоставления является произвольным именем, по которому к этому сопоставлению будут обращаться в qhb_hba.conf. Два других поля указывают имя пользователя операционной системы и соответствующее имя пользователя базы данных. имя-сопоставления можно использовать неоднократно, чтобы задать несколько сопоставлений имен пользователей в рамках одного файла сопоставления.

Нет никаких ограничений касательно количества пользователей баз данных, с которыми может соотноситься пользователь операционной системы, и наоборот. В связи с этим записи в файле сопоставления скорее имеют смысл «этому пользователю операционной системы разрешено подключаться под этим именем пользователя базы данных», нежели подразумевают, что эти имена пользователей равнозначны. Подключение будет разрешено, если в файле сопоставления есть запись, соединяющая имя пользователя, полученное от внешней системы аутентификации, с именем пользователя базы данных, под которым этот пользователь хочет подключиться.

Если поле имя-пользователя-ос начинается со слэша (/), оставшаяся часть поля рассматривается как регулярное выражение. (Более подробную информацию о синтаксисе регулярных выражений QHB см. в подразделе Подробное описание регулярных выражений.) Регулярное выражение может включать в себя одно обобщение или заключенное в скобки вложенное выражение, на которое можно сослаться в поле имя-пользователя-бд, написав \1 (обратный слэш-один). Это позволяет сопоставить несколько имен пользователей в одной строке, что особенно полезно для простых синтаксических замен. Например, эти записи

mymap   /^(.*)@mydomain\.com$      \1
mymap   /^(.*)@otherdomain\.com$   guest

удалят доменную часть для пользователей, чье имя пользователя системы оканчивается на @mydomain.com, и позволят всем пользователям, чье имя пользователя системы оканчивается на @otherdomain.com, подключиться как guest.

Совет
Помните, что по умолчанию регулярное выражение может совпасть только с частью строки. Разумным выходом будет использовать символов ^ и $, как показано в примере выше, чтобы обеспечить совпадение со всем именем пользователя ОС.

Файл qhb_ident.conf прочитывается при запуске системы, а также при получении основным серверным процессом сигнала SIGHUP. При редактировании этого файла во время работы системы необходимо послать сигнал процессу qhbmaster (с помощью команды qhb_ctl reload, вызвав функцию SQL pg_reload_conf() или выполнив kill -HUP), чтобы он заново прочел файл.

Системное представление pg_ident_file_mappings может быть полезно для предварительной проверки изменений в файле qhb_ident.conf или для диагностики проблем, если загрузка этого файла не принесла желаемого эффекта. Строки в этом представлении, содержащие в поле error не NULL, указывают на проблемы в соответствующих строках файла.

Файл qhb_ident.conf, который можно использовать в сочетании с файлом qhb_hba.conf из Примера 1, показан в Примере 2. В этом примере любому, выполнившему вход в систему на компьютере в сети 192.168 под именем пользователя операционной системы, отличным от bryanh, ann или robert, будет отказано в доступе. Пользователь ОС Unix robert получит доступ, только когда попытается подключиться как пользователь QHB bob, но не как robert или какой-либо другой пользователь. Пользователь ann сможет подключиться только как ann. Пользователь bryanh сможет подключиться как bryanh или как guest1.

Пример 2. Пример файла qhb_ident.conf

# MAPNAME       SYSTEM-USERNAME         QHB-USERNAME

omicron         bryanh                  bryanh
omicron         ann                     ann
# у пользователя БД bob на этих компьютерах имя пользователя robert
omicron         robert                  bob
# bryanh может также подключаться как guest1
omicron         bryanh                  guest1

Методы аутентификации

QHB предоставляет к использованию разнообразные методы аутентификации пользователей:

Аутентификация trust, которая просто верит, что пользователи именно те, за кого себя выдают.

Аутентификация по паролю, которая требует, чтобы пользователи ввели пароль.

Аутентификация GSSAPI, которая полагается на библиотеку безопасности, совместимую с GSSAPI. Обычно этот метод используется для доступа к серверу аутентификации, например, Kerberos.

Аутентификация ident, которая полагается на службу «Протокол идентификации» (RFC 1413) на машине клиента. (При подключениях через локальные сокеты домена Unix этот метод воспринимается как аутентификация peer.)

Аутентификация peer, которая рассчитывает, что средства операционной системы определят процесс на другой стороне локального подключения. Для удаленных подключений этот метод не поддерживается.

Аутентификация LDAP, которая полагается на сервер аутентификации LDAP.

Аутентификация RADIUS, которая полагается на сервер аутентификации RADIUS.

Аутентификация по сертификату, которая требует подключения SSL и опознает пользователей, проверяя передаваемые ими сертификаты SSL.

Аутентификация PAM, которая полагается на библиотеку PAM (Pluggable Authentication Modules, подключаемые модули аутентификации).

Аутентификация BSD, которая полагается на инфраструктуру аутентификации BSD (в настоящее время доступна только в системе OpenBSD).

Для локальных подключений обычно рекомендуется аутентификация peer, хотя в некоторых обстоятельствах может быть достаточно и аутентификации trust. Для удаленных подключений самой простой будет аутентификация по паролю. Все прочие варианты требуют участия некоторой внешней инфраструктуры безопасности (обычно это сервер аутентификации или центр сертификации, выдающий сертификаты SSL) или поддерживаются не на всех платформах.

Аутентификация trust

Когда указан метод аутентификации trust, QHB предполагает, что любой подключающийся к серверу авторизован для доступа к базе данных, независимо от указанного имени пользователя базы данных (даже если это имя суперпользователя). Разумеется, ограничения, прописанные в столбцах database и user, продолжают действовать. Этот метод следует применять только в том случае, когда на уровне операционной системы обеспечена адекватная защита подключений к серверу.

Аутентификация trust оптимальна и очень удобна для локальных подключений на однопользовательской рабочей станции. Обычно сам по себе этот метод не подходит для машин с несколькими пользователями. Однако его можно использовать даже на многопользовательской машине, если ограничить доступ к серверному файлу сокета домена Unix посредством разрешений файловой системы. Для этого нужно установить параметры конфигурации unix_socket_permissions (и, возможно, unix_socket_group) как описано в разделе Подключения и аутентификация. Или можно установить параметр конфигурации unix_socket_directories, чтобы разместить файл сокета в должным образом защищенном каталоге.

Установка разрешений на уровне файловой системы помогает только в случае подключений через сокеты домена Unix. На локальные подключения по TCP/IP ограничения файловой системы не действуют. Таким образом, если вы хотите использовать разрешения файловой системы для обеспечения локальной безопасности, уберите строку host ... 127.0.0.1 ... из файла qhb_hba.conf или смените метод аутентификации.

Аутентификация trust подойдет для подключений по TCP/IP, только если вы доверяете каждому пользователю на каждом компьютере, который получил разрешение на подключение к серверу строками файла qhb_hba.conf, указывающими метод trust. Не рекомендуется использовать trust для любых подключений по TCP/IP, отличных от localhost (127.0.0.1).

Аутентификация по паролю

Существует несколько методов аутентификации по паролю. Эти методы работают схожим образом, но различаются тем, как пароли пользователей хранятся на сервере и как предоставленный клиентом пароль передается через соединение.

scram-sha-256

С методом scram-sha-256 выполняется аутентификация SCRAM-SHA-256, как описано в RFC 7677. Она производится по схеме запрос-ответ, предотвращающей перехват паролей через недоверенные соединения и поддерживающей хранение паролей на сервере в криптографически хешированной форме, которая считается безопасной.

Это наиболее безопасный из предоставляемых на данный момент методов, но он не поддерживается старыми клиентскими библиотеками.

md5

В методе md5 применяется нестандартный и менее безопасный механизм запрос- ответ. Он предотвращает перехват паролей и не допускает хранение паролей на сервере в виде простого текста, но не обеспечивает защиту, если злоумышленнику удается похитить хеши паролей с сервера. Кроме того, в наши дни алгоритм хеширования MD5 уже не считается способным защитить от целенаправленных атак.

Метод md5 несовместим с функционалом db_user_namespace.

Для облегчения перехода от метода md5 к более новому методу SCRAM, если в качестве метода аутентификации в файле qhb_hba.conf указан md5, но пароль пользователя на сервере зашифрован для SCRAM (см. ниже), то будет автоматически выполняться аутентификация на основе SCRAM.

password

С методом password пароль передается открытым текстом и поэтому является уязвимым для атак с перехватом паролей. По возможности его всегда следует избегать. Однако если подключение защищено шифрованием SSL, то метод password может быть безопасен. (Хотя при использовании SSL, возможно, лучшим выбором будет аутентификация по сертификату SSL.)

Пароли баз данных QHB отделены от паролей пользователей операционной системы. Пароли всех пользователей базы данных хранятся в системном каталоге pg_authid. Управлять паролями можно либо с помощью команд SQL CREATE ROLE и ALTER ROLE, например, CREATE ROLE foo WITH LOGIN PASSWORD 'secret', либо с помощью команды psql \password. Если пароль для пользователя не задан, вместо него хранится NULL, и пройти аутентификацию по паролю этот пользователь не сможет.

Доступность различных методов аутентификации по паролю зависит от того, как пароли пользователей шифруются на сервере (или, если точнее, хешируются). Это определяется параметром конфигурации password_encryption в момент назначения пароля. Если пароль шифруется алгоритмом scram-sha-256, его можно будет использовать для методов аутентификации scram-sha-256 и password (но в последнем случае пароль будет передаваться открытым текстом). При этом в случае указания метода аутентификации md5 произойдет автоматическое переключение на использование метода scram-sha-256, как описано выше, так что этот вариант тоже будет работать. Если пароль шифруется алгоритмом md5, то его можно будет использовать только для методов аутентификации md5 и password (опять же, в последнем случае пароль будет передаваться открытым текстом). Чтобы проверить хранящиеся в данный момент в базе данных хеши паролей, обратитесь к системному каталогу pg_authid.

Чтобы перевести существующую установку с md5 на scram-sha-256, сперва убедитесь о том, что все клиентские библиотеки были обновлены до версий, поддерживающих SCRAM, задайте password_encryption = 'scram-sha-256' в qhb.conf, заставьте всех пользователей сменить свои пароли, а затем поменяйте указание метода аутентификации в qhb_hba.conf на scram-sha-256.

Аутентификация GSSAPI

GSSAPI — это принятый в качестве отраслевого стандарта протокол для безопасной аутентификации, определенный в RFC 2743. QHB поддерживает применение GSSAPI для аутентификации, шифрования соединений, а также для аутентификации с шифрованием. GSSAPI предоставляет автоматическую аутентификацию (система единого входа) для систем, которые ее поддерживают. Сама по себе аутентификация безопасна, но данные, передаваемые через подключение к базе данных, будут кодироваться, только если используется шифрование GSSAPI или SSL.

Поддержку GSSAPI следует включать при сборке QHB; дополнительную информацию см. в главе Установка.

При работе с Kerberos GSSAPI использует стандартные имена администраторов доступа (аутентификационных идентификаторов) в формате имя_службы/имя_хоста @область. Имя администратора доступа, используемое конкретной установкой, не закодировано в сервере QHB ни в каком виде; оно задается в файле keytab, который сервер прочитывает для определения его идентификатора. Если в файле keytab перечислено несколько администраторов доступа, сервер выберет любой из них. В качестве области сервера задается предпочитаемая область, указанная в доступном серверу файле (или файлах) конфигурации Kerberos.

Клиент должен знать имя администратора доступа сервера, к которому он намерен подключиться. Обычно в качестве элемента имя_службы администратора доступа указывается qhb, но в параметре подключения krbsrvname в libpq можно выбрать и другое значение. В элементе имя_хоста задается полное имя хоста, к которому будет подключаться libpq. В элементе область задается предпочитаемая область, указанная в доступном клиенту файле (или файлах) конфигурации Kerberos.

У клиента также должно быть имя администратора доступа для его собственного идентификатора (и он должен иметь действительный билет для этого администратора доступа). Чтобы использовать GSSAPI для аутентификации, администратор доступа клиента должен быть связан с именем пользователя базы данных QHB. Для сопоставления имен администраторов доступа с именами пользователей можно воспользоваться файлом конфигурации qhb_ident.conf; например, qhbusername@realm можно сопоставить с простым qhbusername. Как вариант, можно использовать полное имя администратора доступа username@realm в качестве имени роли в QHB без какого-либо сопоставления.

QHB также поддерживает сопоставление администраторов доступа клиентов с именами пользователей путем простого удаления элемента области из имени администратора доступа. Этот метод оставлен для обратной совместимости и настоятельно не рекомендуется к использованию, поскольку при этом невозможно различить разных пользователей с одинаковыми именами, но приходящих из разных областей. Чтобы включить этот режим, установите для include_realm значение 0. В простых установках с одной областью исключение области в сочетании с установкой параметра krb_realm (который позволяет ограничить область администратора доступа значением, заданным в параметре krb_realm) все же будет безопасным, но менее эффективным подходом по сравнению с явным сопоставлением в qhb_ident.conf.

Расположение файла keytab на сервере задается параметром конфигурации krb_server_keyfile. По соображениям безопасности рекомендуется использовать отдельный файл keytab специально для сервера QHB, а не разрешать серверу читать системный файл keytab. Сделайте так, чтобы пользователь, от имени которого запущен сервер QHB, имел право на чтение этого серверного файла keytab (право на запись давать не стоит). (См. также раздел Учетная запись пользователя QHB.)

Файл keytab генерируется с использованием ПО; подробную информацию см. в документации Kerberos. Следующий пример показывает, как его сгенерировать с помощью программы kadmin в MIT-совместимых реализациях Kerberos 5:

kadmin% addprinc -randkey qhb/server.my.domain.org
kadmin% ktadd -k krb5.keytab qhb/server.my.domain.org

Для метода аутентификации GSSAPI поддерживаются следующие параметры аутентификации:

include_realm
При значении 0 перед проведением сопоставления имен пользователей из администратора доступа аутентифицированного пользователя убирается область (см. раздел Файл сопоставлений имен пользователей). Этот вариант не рекомендуется и поддерживается в основном для обратной совместимости, поскольку он небезопасен в средах с несколькими областями, если только дополнительно не установить krb_realm. Рекомендуется оставить в include_realm значение по умолчанию (1) и задать в qhb_ident.conf явное сопоставление для преобразования имен администраторов доступа в имена пользователей QHB.

map
Разрешает сопоставление администраторов доступа клиента с именами пользователей базы данных. Подробную информацию см. в разделе Файл сопоставлений имен пользователей. Для администратора доступа GSSAPI/Kerberos, такого как username@EXAMPLE.COM (или более редкого username/hostbased@EXAMPLE.COM), именем пользователя в сопоставлении будет username@EXAMPLE.COM (или username/hostbased@EXAMPLE.COM соответственно), если include_realm не равен 0; иначе имя системного пользователя в сопоставлении будет username (или username/hostbased).

krb_realm
Задает область, с которой будут сопоставляться имена администраторов доступа пользователей. Если этот параметр установлен, подключаться смогут только пользователи из этой области. Если он не установлен, подключаться смогут пользователи из любой области, в зависимости от выполняемого сопоставления имен пользователей.

Помимо этих параметров, которые могут отличаться в разных записях файла qhb_hba.conf, существует параметр конфигурации уровня сервера krb_caseins_users. Если он равен true, администраторы доступа пользователей сопоставляются с записями пользователей без учета регистра. Если установлен параметр krb_realm, области тоже сопоставляются без учета регистра.

Аутентификация ident

Метод аутентификации ident работает, получая имя пользователя операционной системы клиента от сервера Ident и используя его в качестве разрешенного имени пользователя базы данных (с возможным сопоставлением имен пользователей). Этот метод поддерживается только для подключений по TCP/IP.

Примечание
При указании ident для локального подключения (не TCP/IP), вместо него будет использован метод подключения peer (см. раздел Аутентификация peer).

Для метода ident поддерживаются следующие параметры конфигурации:

map
Позволяет сопоставить имена пользователей системы и базы данных. Подробную информацию см. в разделе Файл сопоставлений имен пользователей.

«Протокол идентификации» (Ident) описан в RFC 1413. Практически каждая Unix- подобная операционная системы поставляется с сервером Ident, по умолчанию прослушивающим TCP-порт 113. Базовая функциональность этого сервера — отвечать на вопросы вроде «Какой пользователь инициировал подключение, которое идет через твой порт X и подключается к моему порту Y?». Поскольку после установления физического подключения QHB знает и X, и Y, она может опрашивать сервер Ident на хосте клиентского подключения и теоретически может определять пользователя операционной системы при каждом подключении.

Недостаток этой процедуры состоит в том, что она зависит от интеграции с клиентом: если компьютер клиента недоверенный или взломанный, злоумышленник может запустить практически любую программу на порту 113 и вернуть любое имя пользователя по своему выбору. Таким образом, этот метод аутентификации подходит только для закрытых сетей, где каждый компьютер клиента находится под строгим контролем и где администраторы операционных систем и баз данных работают в тесном контакте. Другими словами, вы должны доверять компьютеру, на котором работает сервер Ident. Относитесь серьезно к предупреждению:

Протокол идентификации не предназначен для использования в качестве протокола авторизации или управления доступом.

--RFC 1413

У некоторых серверов Ident есть нестандартная функциональность, позволяющая зашифровывать возвращаемое имя пользователя, используя ключ, который известен только администратору исходного компьютера. Эту функциональность нельзя использовать с QHB, так как у QHB нет никакой возможности расшифровать возвращаемую строку и получить фактическое имя пользователя.

Аутентификация peer

Метод аутентификации peer работает, получая имя пользователя операционной системы клиента из ядра и используя его в качестве разрешенного имени пользователя базы данных (с возможным сопоставлением имен пользователей). Этот метод поддерживается только для локальных подключений.

Для метода peer поддерживаются следующие параметры конфигурации:

map
Позволяет сопоставить имена пользователей системы и базы данных. Подробную информацию см. в разделе Файл сопоставлений имен пользователей.

Аутентификация peer доступна только в операционных системах, предоставляющих функцию getpeereid(), параметр сокета SO_PEERCRED или подобные им механизмы. В настоящее время это Linux, большинство разновидностей BSD, включая macOS, и Solaris.

Аутентификация LDAP

Этот метод аутентификации работает схожим с методом password образом, за исключением того, что он использует LDAP в качестве метода подтверждения пароля. LDAP используется только для подтверждения пар имя пользователя/пароль. Поэтому пользователь должен уже существовать в базе данных до того, как для аутентификации можно будет использовать LDAP.

Аутентификация LDAP может работать в двух режимах. В первом режиме, который мы назовем простым связыванием, сервер связывается с отличительным именем, составленным в виде префикс имя_пользователя суффикс. Как правило, параметр префикс используется для указания cn= или DOMAIN\ в среде Active Directory. Параметр суффикс используется для указания оставшейся части DN в среде, отличной от Active Directory.

Во втором режиме, который мы назовем поиск+связывание, сервер сначала связывается с каталогом LDAP с постоянным именем пользователя и паролем, указанными в ldapbinddn и ldapbindpasswd, и выполняет поиск этого пользователя, пытающегося подключиться к базе данных. Если пользователь и пароль не определены, сервер пытается связаться с каталогом анонимно. Поиск осуществляется в поддереве ldapbasedn с попыткой подобрать точное соответствие атрибуту, заданному в ldapsearchattribute. Как только при поиске находится пользователь, сервер разрывает подключение и заново связывается с каталогом уже как этот пользователь, используя указанный клиентом пароль, чтобы удостовериться, что эта учетная запись корректна. Этот же режим применяется в схемах аутентификации LDAP в другом программном обеспечении, например в mod_authnz_ldap и pam_ldap в Apache. Этот режим дает гораздо больше гибкости в выборе расположения объектов пользователей в каталоге, но при этом требует дважды подключаться к серверу LDAP.

В обоих режимах используются следующие параметры конфигурации:

ldapserver
Имена или IP-адреса серверов LDAP для подключения. Можно указать несколько серверов, разделяя их пробелами.

ldapport
Номер порта для подключения к серверу LDAP. Если порт не указан, будет использован установленный по умолчанию порт библиотеки LDAP.

ldapscheme
Значение ldaps выбирает протокол LDAPS. Это нестандартный способ использования LDAP поверх SSL, поддерживаемый некоторыми реализациями сервера LDAP. Альтернативный вариант см. в описании параметра ldaptls.

ldaptls
При значении 1 в соединении QHB с сервером LDAP применяется шифрование TLS. При этом используется операция StartTLS, описанная в RFC 4513. Альтернативный вариант см. в описании параметра ldapscheme.

Обратите внимание, что при использовании ldapscheme или ldaptls шифруется только трафик между сервером QHB и сервером LDAP. Соединение между сервером QHB и клиентом QHB останется незашифрованным, если только и для него не применяется SSL.

Следующие параметры используются только в режиме простого связывания:

ldapprefix
Эта строка подставляется перед именем пользователя во время формирования DN для связывания при аутентификации в режиме простого связывания.

ldapsuffix
Эта строка подставляется после имени пользователя во время формирования DN для связывания при аутентификации в режиме простого связывания.

Следующие параметры используются только в режиме поиск+связывание:

ldapbasedn
Корневая папка DN, с которой начинается поиск пользователя при аутентификации в режиме поиск+связывание.

ldapbinddn
DN пользователя для связывания с каталогом при выполнении поиска в ходе аутентификации в режиме поиск+связывание.

ldapbindpasswd
Пароль пользователя для связывания с каталогом при выполнении поиска в ходе аутентификации в режиме поиск+связывание.

ldapsearchattribute
Атрибут для сравнения с именем пользователя при выполнении поиска в ходе аутентификации в режиме поиск+связывание. Если атрибут не указан, будет использован атрибут uid.

ldapsearchfilter
Фильтр поиска, используемый при аутентификации в режиме поиск+связывание. Вхождения $username в нем будут заменяться именем пользователя. Это позволяет задавать более гибкие фильтры поиска, чем ldapsearchattribute.

ldapurl
URL-адрес LDAP согласно стандарту RFC 4516. Это альтернативный способ записи некоторых других параметров LDAP в более компактной и стандартной форме. Формат адреса выглядит так:

ldap[s]://хост[:порт]/dn_базы[?[атрибут][?[область_действия][?[фильтр]]]]

Элемент область_действия должен иметь значение base, one или sub, обычно последнее. (Значение по умолчанию, base, обычно не очень полезно при таком применении.) В элементе атрибут можно назначить один атрибут; в этом случае он используется как значение параметра ldapsearchattribute. Если атрибут не указан, в качестве значения параметра ldapsearchfilter можно использовать фильтр.

Схема URL-адреса ldaps выбирает для установления LDAP-подключений поверх SSL метод LDAPS, что равнозначно указанию ldapscheme=ldaps. Для применения шифрованных LDAP-подключений с операцией StartTLS используйте обычную схему URL-адреса ldap и в дополнение к параметру ldapurl укажите ldaptls.

Для неанонимных связываний ldapbinddn и ldapbindpasswd следует указывать как отдельные параметры.

В настоящее время URL-адреса LDAP поддерживаются только с OpenLDAP.
.

Не стоит путать параметры конфигурации для режима простого связывания с параметрами для режима поиск+связывание, это будет ошибкой.

В режиме поиск+связывание поиск может выполняться либо по одному атрибуту, заданному в ldapsearchattribute, либо по произвольному фильтру поиска, заданному в ldapsearchfilter. Указание ldapsearchattribute=foo равнозначно указанию ldapsearchfilter="(foo=$username)". Если ни один параметр не указан, по умолчанию подразумевается ldapsearchattribute=uid.

Если QHB была скомпилирована с OpenLDAP в качестве клиентской библиотеки LDAP, параметр ldapserver может быть опущен. В этом случае за списком имен хостов и портов сервер, согласно стандарту RFC 2782, обращается в DNS, к SRV-записи с именем _ldap._tcp.DOMAIN, где DOMAIN извлекается из ldapbasedn.

Пример конфигурации LDAP для простого связывания:

host ... ldap ldapserver=ldap.example.net ldapprefix="cn=" ldapsuffix=", dc=example, dc=net"

Когда запрашивается подключение к серверу баз данных в качестве пользователя базы данных someuser, QHB пытается связаться с сервером LDAP, используя DN cn=someuser, dc=example, dc=net и пароль, предоставленный клиентом. Если это подключение удалось, то доступ к базе данных будет открыт.

Пример конфигурации для режима поиск+связывание:

host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapsearchattribute=uid

Когда запрашивается подключение к серверу баз данных в качестве пользователя базы данных someuser, QHB пытается связаться с сервером LDAP анонимно (поскольку параметр ldapbinddn не был задан), выполняет поиск для (uid=someuser) под указанным DN базы. Если запись найдена, QHB пытается связаться с использованием этой найденной информации и пароля, предоставленного клиентом. Если это второе подключение удалось, то доступ к базе данных будет открыт.

Пример той же конфигурации для режима поиск+связывание, записанной в виде URL-адреса:

host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub"

Такой же формат URL-адреса используется и другим программным обеспечением, поддерживающим аутентификацию по протоколу LDAP, так что эту конфигурацию будет легче использовать совместно.

Пример конфигурации для режима поиск+связывание, в котором ldapsearchfilter используется вместо ldapsearchattribute для прохождения аутентификации по ID или почтовому адресу пользователя:

host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapsearchfilter="(|(uid=$username)(mail=$username))"

Пример конфигурации для режима поиск+связывание, в котором используется поиск SRV-записи в DNS для получения имен хостов и портов службы LDAP в домене example.net:

host ... ldap ldapbasedn="dc=example,dc=net"

Совет
Поскольку LDAP нередко использует для разделения различных частей DN запятые и пробелы, зачастую при конфигурировании параметров LDAP возникает необходимость заключать их значения в кавычки, как показано в примерах выше.

Аутентификация RADIUS

Этот метод аутентификации работает схожим с методом password образом, за исключением того, что он использует RADIUS в качестве метода проверки пароля. RADIUS используется только для подтверждения пар имя пользователя/пароль. Поэтому пользователь должен уже существовать в базе данных до того, как для аутентификации можно будет использовать RADIUS.

При использовании аутентификации RADIUS настроенному серверу RADIUS посылается сообщение с запросом доступа. Это запрос типа Authenticate Only (только аутентификация), который включает в себя элементы user name (имя пользователя), password (зашифрованный пароль) и NAS Identifier (идентификатор NAS). Запрос шифруется с использованием общего с сервером секрета. Сервер RADIUS отвечает на этот запрос либо Access Accept (Доступ разрешен), либо Access Reject (В доступе отказано). Система ведения учета для RADIUS отсутствует.

Можно указать несколько серверов RADIUS, тогда они будут перебираться по очереди. Если от любого из серверов будет получен отрицательный ответ, произойдет сбой аутентификации. Если ответ не будет получен, последует попытка подключения к следующему серверу в списке. Чтобы указать несколько серверов, следует разделить их имена запятыми и заключить список в кавычки. При этом все остальные параметры RADIUS тоже можно задать в списках через запятую, чтобы каждый сервер получил отдельное значение. Также их можно задавать одним значением, тогда оно будет применяться ко всем серверам.

Для метода RADIUS поддерживаются следующие параметры конфигурации:

radiusservers
DNS-имена или IP-адреса серверов RADIUS для подключения. Это обязательный параметр.

radiussecrets
Общие секреты, используемые при защищенном обращении к серверам RADIUS. Значение этого параметра должно быть одинаковым на серверах QHB и RADIUS. Рекомендуется использовать строку как минимум из 16 символов. Это обязательный параметр.

Примечание
Вектор шифрования будет криптографически эффективен только в том случае, если QHB собрана с поддержкой OpenSSL. В остальных случаях передачу данных серверу RADIUS следует считать замаскированной, но не защищенной, поэтому при необходимости нужно применить дополнительные меры безопасности.

radiusports
Номера портов для подключения к серверам RADIUS. Если никакой порт не задан, будет использован порт RADIUS по умолчанию (1812).

radiusidentifiers
Строки, используемые в запросах RADIUS как NAS Identifier. Этот параметр можно использовать, например, для обозначения того, к какому кластеру баз данных пытается подключиться пользователь, что может быть полезно для выбора соответствующей политики на сервере RADIUS. Если никакой идентификатор не задан, по умолчанию будет использован qhb.

Если в значение параметра RADIUS необходимо добавить запятую или пробел, это можно сделать, заключив это значение в кавычки, но это неудобно, поскольку теперь потребуется два уровня кавычек. Например, так добавляются пробелы в строки секретов RADIUS:

host ... radius radiusservers="server1,server2" radiussecrets="""secret one"",""secret two"""

Аутентификация по сертификату

В данном методе аутентификации используются клиентские сертификаты SSL, поэтому он применим только для подключений SSL; инструкции по конфигурации SSL см. в подразделе Конфигурация OpenSSL. При использовании этого метода аутентификации сервер запросит у клиента предъявления действительного и доверенного сертификата. Пароль у клиента запрашиваться не будет. Атрибут cn (CommonName, Обычное имя) сертификата сравнивается с запрашиваемым именем пользователя базы данных, и если они совпадают, вход будет разрешен. Если cn отличается от имени пользователя базы данных, то можно использовать сопоставление имен пользователей.

Для аутентификации по сертификату SSL поддерживаются следующие параметры конфигурации:

map
Позволяет сопоставить имена пользователей системы и базы данных. Подробную информацию см. в разделе Файл сопоставлений имен пользователей.

Указывать параметр clientcert для аутентификации cert излишне, поскольку метод cert по сути является методом trust с параметром clientcert=verify-full.

Аутентификация PAM

Этот метод аутентификации работает схожим с методом password образом, за исключением того, что он использует PAM (Pluggable Authentication Modules, подключаемые модули аутентификации) в качестве механизма аутентификации. По умолчанию имя службы PAM — qhb. PAM используется только для подтверждения пар имя пользователя/пароль и, при необходимости, имени или IP-адреса подключенного удаленного хоста. Поэтому пользователь должен уже существовать в базе данных до того, как для аутентификации можно будет использовать PAM. Более подробную информацию о PAM см. на странице описания Linux-PAM.

Для метода PAM поддерживаются следующие параметры конфигурации:

pamservice
Имя службы PAM.

pam_use_hostname
Определяет, предоставляется ли модулям PAM через элемент PAM_RHOST IP-адрес или имя удаленного хоста. По умолчанию используется IP-адрес. При значении 1 вместо него используется разрешенное имя хоста. Разрешение имени хоста может привести к задержкам при подключении. (Большинство конфигураций PAM не пользуются этой информацией, так что этот параметр необходим, только если была создана специальная конфигурация PAM, в которой он используется.)

Примечание
Если PAM настроен для чтения /etc/shadow, произойдет сбой аутентификации, потому что сервер QHB запущен не пользователем root. Однако это не имеет значения, когда PAM настроен для использования LDAP или других методов аутентификации.

Аутентификация BSD

Этот метод аутентификации работает схожим с методом password образом, за исключением того, что он использует аутентификацию BSD для подтверждения пароля. Аутентификация BSD используется только для подтверждения пар имя пользователя/ пароль. Поэтому пользователь должен уже существовать в базе данных до того, как для аутентификации можно будет использовать BSD. Инфраструктура аутентификации BSD в настоящее время доступна только в системе OpenBSD.

Для аутентификации BSD в QHB применяется тип входа auth-qhb и проверка с классом входа qhb, если это определено в login.conf. По умолчанию этот класс входа не существует, и QHB использует стандартный класс входа.

Примечание
Для применения аутентификации BSD необходимо сначала добавить учетную запись пользователя QHB (то есть пользователя операционной системы, запускающего сервер) в группу auth. Группа auth существует в системах OpenBSD по умолчанию.

Проблемы аутентификации

Сбои и другие проблемы с аутентификацией обычно проявляют себя посредством сообщений об ошибках, подобных следующему:

FATAL:  no qhb_hba.conf entry for host "123.123.123.123", user "andym", database "testdb"
-- КРИТИЧНО: в qhb_hba.conf отсутствует запись для сервера "123.123.123.123", пользователя "andym", базы данных "testdb"

Это сообщение вы, скорее всего, получите, если сможете связаться с сервером, но он не захочет с вами общаться. В сообщении содержится предположение, что сервер отказал в подключении, потому что не может найти соответствующую запись в своем файле конфигурации qhb_hba.conf.

FATAL:  password authentication failed for user "andym"
-- КРИТИЧНО: аутентификация пользователя "andym" по паролю не удалась

Сообщения вроде этого показывают, что вы связались с сервером и он готов общаться с вами, но только после того, как вы пройдете авторизацию по методу, указанному в файле qhb_hba.conf. Проверьте вводимый вами пароль либо настройки Kerberos или Ident, если в сообщении упоминается один из этих типов аутентификации.

FATAL:  user "andym" does not exist
-- КРИТИЧНО: пользователь "andym" не существует

Указанное имя пользователя базы данных не найдено.

FATAL:  database "testdb" does not exist
-- КРИТИЧНО: база данных "testdb" не существует

База данных, к которой вы пытаетесь подключиться, не существует. Обратите внимание, что если вы не указываете имя базы данных, по умолчанию берется имя пользователя базы данных, что вполне может привести к ошибке.

Совет
В журнале сервера может содержаться больше информации по сбою аутентификации, чем в выдаваемых клиенту сообщениях. Если вам не до конца понятны причины сбоя, проверьте журнал сервера.

Роли в базе данных

QHB управляет правами доступа к базе данных, используя концепцию ролей. Роль может рассматриваться как пользователь базы данных или группа пользователей базы данных, в зависимости от того, как она установлена. Роли могут владеть объектами базы данных (например, таблицами и функциями) и назначать права для этих объектов другим ролям, контролируя то, кто имеет доступ к каким объектам. Кроме того, можно предоставить членство в роли другой роли, что позволяет роли-участнику пользоваться правами, присвоенные другой роли.

Понятие ролей объединяет понятия «пользователи» и «группы». Любая роль может выступать в роли пользователя, группы или и того, и другого.

В этой главе описывается, как создавать и управлять ролями. Более подробную информацию о влиянии прав ролей на различные объекты базы данных можно найти в разделе Права.

Роли базы данных

Роли базы данных концептуально полностью отделены от пользователей операционной системы. На практике может быть удобно поддерживать соответствие между ними, но это необязательно. Роли базы данных являются глобальными для всей инсталляции кластера баз данных (а не для отдельной базы данных). Чтобы создать роль, используйте команду SQL CREATE ROLE:

CREATE ROLE имя;

Синтаксис имени соответствует правилам для идентификаторов SQL: либо простой, без специальных символов, либо в кавычках. (На практике обычно требуется добавить в команду дополнительные параметры, например LOGIN. Подробнее см. ниже) Чтобы удалить существующую роль, используйте аналогичную команду DROP ROLE:

DROP ROLE имя;

Для удобства в качестве оберток вокруг этих команд SQL предоставляются программы createuser и dropuser, которые можно вызывать из командной строки оболочки:

createuser имя
dropuser имя

Чтобы определить набор существующих ролей, просмотрите системный каталог pg_roles, например:

SELECT rolname FROM pg_roles;

Для просмотра существующих ролей также полезна метакоманда \du программы psql.

Для начальной загрузки СУБД недавно инициализированная система всегда содержит одну предопределенную роль. Эта роль всегда является «суперпользователем», и по умолчанию (если это не изменено при запуске qhb_bootstrap (или initdb)) она будет иметь то же имя, что и пользователь операционной системы, инициализирующий кластер баз данных. Обычно эта роль называется qhb. Чтобы создать больше ролей, сначала необходимо подключиться к базе данных под этой начальной ролью.

Каждое соединение с сервером баз данных выполняется с использованием имени какой-то определенной роли, и эта роль определяет начальные права доступа для команд, выполненных в этом соединении. Имя роли, которое будет использоваться для конкретного подключения к базе данных, указывается клиентом, инициирующим запрос на подключение, причем специфичным для клиентского приложения образом. Например, программа psql использует для указания роли для подключения параметр командной строки -U. Многие приложения по умолчанию принимают имя текущего пользователя операционной системы (включая createuser и psql). Поэтому часто удобно поддерживать соответствие имен между ролями и пользователями операционной системы.

Набор ролей базы данных, к которым может подключаться данное клиентское соединение, определяется настройкой аутентификации клиента, описанной в главе Аутентификация клиентского приложения. (Таким образом, клиент не ограничен подключением в качестве роли, соответствующей пользователю операционной системы, так же как логин не обязательно должен совпадать с настоящим именем человека.) Поскольку идентификация роли определяет набор прав, доступных подключенному клиенту, важно тщательно сконфигурировать права при настройке многопользовательской среды.

Атрибуты ролей

Роль в базе данных может иметь ряд атрибутов, которые определяют ее права и взаимодействие с системой аутентификации клиента.

Право входа в систему

Только роли с атрибутом LOGIN могут использоваться в качестве начального имени роли для подключения к базе данных. Роль с атрибутом LOGIN может рассматриваться как «пользователь базы данных». Чтобы создать роль с правом входа в систему, используйте:

CREATE ROLE имя LOGIN;
CREATE USER имя;

(Команда CREATE USER равнозначна команде CREATE ROLE за исключением того, что CREATE USER по умолчанию включает атрибут LOGIN, а CREATE ROLE — нет.)

Статус суперпользователя

Суперпользователь базы данных обходит все проверки разрешений, кроме права на вход в систему. Это опасное право, и его не следует использовать небрежно; лучше всего выполнять большую часть работы в роли, которая не является суперпользователем. Чтобы создать нового суперпользователя базы данных, используйте команду CREATE ROLE имя SUPERUSER. Это следует делать под ролью, которая уже является суперпользователем.

Создание баз данных

Роли должно быть явно дано разрешение на создание баз данных (за исключением суперпользователей, поскольку они обходят все проверки разрешений). Чтобы создать такую роль, используйте команду CREATE ROLE имя CREATEDB.

Создание ролей

Роли должно быть явно дано разрешение на создание других ролей (за исключением суперпользователей, поскольку они обходят все проверки разрешений). Чтобы создать такую роль, используйте команду CREATE ROLE имя CREATEROLE. Роль с правом CREATEROLE тоже может изменять и удалять другие роли, а также предоставлять или отзывать членство в них. Изменение роли включает большинство изменений, которые можно сделать командой ALTER ROLE, в том числе, например, смену пароля, а также модификации роли, которые можно сделать с помощью команд COMMENT и SECURITY LABEL.

Однако право CREATEROLE не позволяет создавать или каким-то образом влиять на уже существующие роли SUPERUSER. Более того, право CREATEROLE не позволяет ни создавать пользователей REPLICATION, ни предоставлять или отзывать право REPLICATION, ни изменять свойства ролей таких пользователей. Тем не менее оно позволяет использовать команды ALTER ROLE ... SET и ALTER ROLE ... RENAME, а также COMMENT ON ROLE, SECURITY LABEL ON ROLE и DROP ROLE к ролям REPLICATION. Наконец, право CREATEROLE не дает возможности предоставлять или отзывать право BYPASSRLS.

Поскольку право CREATEROLE позволяет пользователю предоставлять или отзывать членство даже в тех ролях, к которым он (пока) не имеет доступа, пользователь CREATEROLE может получить доступ к возможностям каждой предопределенной роли в системе, включая роли со множеством прав, такие как pg_execute_server_program и pg_write_server_files.

Инициирование репликации

Роли должно быть явно дано разрешение на инициирование потоковой репликации (за исключением суперпользователей, поскольку они обходят все проверки разрешений). Роль, используемая для потоковой репликации, также должна иметь разрешение LOGIN. Чтобы создать такую роль, используйте команду CREATE ROLE имя REPLICATION LOGIN.

Пароль

Пароль имеет значение только в том случае, если метод аутентификации клиента требует от пользователя ввода пароля при подключении к базе данных. Пароли используются в методах аутентификации password и md5. Пароли базы данных отделены от паролей операционной системы. Укажите пароль при создании роли с помощью команды CREATE ROLE имя PASSWORD 'строка'.

Наследование прав

По умолчанию роли дается разрешение наследовать права той роли, членом которой она является. Тем не менее чтобы создать роль без этого разрешения, достаточно выполнить CREATE ROLE имя NOINHERIT.

Обход защиты на уровне строк

Разрешение обходить все политики защиты на уровне строк (RLS, row-level security) нужно давать роли явно (за исключением суперпользователей, поскольку они обходят все проверки разрешений). Чтобы создать такую роль, выполните CREATE ROLE имя BYPASSRLS от имени суперпользователя.

Ограничение соединений

Ограничение соединение позволяет указать, сколько именно одновременных соединений может создать роль. -1 (значение по умолчанию) означает отсутствие ограничений. Задайте ограничение соединений при создании роли, выполнив CREATE ROLE имя CONNECTION LIMIT 'целое_число'.

Атрибуты роли можно изменить после ее создания, воспользовавшись командой ALTER ROLE. Подробную информацию см. на справочных страницах команд CREATE ROLE и ALTER ROLE.

Роль также может иметь специфичные для нее значения по умолчанию для многих параметров конфигурации времени выполнения, описанных в главе Конфигурация сервера. Например, если по какой-то причине необходимо выключить сканирование индекса (намек: не очень хорошая идея) при каждом подключении, можно использовать:

ALTER ROLE myname SET enable_indexscan TO off;

Это сохранит настройку (но не установит ее сразу). В последующих соединениях с этой ролью будет казаться, будто команда SET enable_indexscan TO off была выполнена непосредственно перед началом сеанса. Этот параметр по-прежнему можно изменить в рамках сеанса; это только значение по умолчанию. Чтобы удалить настройку по умолчанию для конкретной роли, используйте команду ALTER ROLE имя_роли RESET имя_переменной. Обратите внимание, что специфичные для роли значения по умолчанию, закрепленные за ролями без права LOGIN, довольно бесполезны, поскольку они никогда не будут применены.

Членство в роли

Зачастую бывает удобно группировать пользователей, чтобы упростить управление правами: таким образом, права могут быть предоставлены или отозваны для группы в целом. В QHB это достигается созданием роли, представляющей группу, а затем предоставлением членства в групповой роли отдельным ролям пользователей.

Чтобы настроить групповую роль, сначала создайте эту роль:

CREATE ROLE имя;

Обычно роль, используемая в качестве группы, не имеет атрибута LOGIN, хотя при желании его можно установить.

Когда групповая роль уже существует, в нее можно добавлять и удалять участников, используя команды GRANT и REVOKE:

GRANT групповая_роль TO роль1, ... ;
REVOKE групповая_роль FROM роль1, ... ;

Вы можете предоставить членство и другим групповым ролям (поскольку на самом деле между групповыми и не групповыми ролями никакой разницы нет). База данных не позволит настроить замыкание членства в круг. Кроме того, не разрешено предоставлять роли PUBLIC членство в других ролях.

Члены групповой роли могут использовать права этой роли двумя способами. Во-первых, каждый член группы может явно выполнить SET ROLE, чтобы временно «стать» групповой ролью. В этом состоянии сеанс базы данных имеет доступ к правам групповой роли, а не исходной роли входа в систему, и любые созданные объекты базы данных считаются принадлежащими групповой роли, а не роли входа. Во-вторых, роли-участники, имеющие атрибут INHERIT, автоматически используют права ролей, членами которых они являются, включая любые права, унаследованные этими ролями. В качестве примера предположим, что мы выполнили:

CREATE ROLE joe LOGIN INHERIT;
CREATE ROLE admin NOINHERIT;
CREATE ROLE wheel NOINHERIT;
GRANT admin TO joe;
GRANT wheel TO admin;

Сразу после подключения под ролью joe сеанс базы данных будет использовать права, предоставленные непосредственно joe, а также любые права, предоставленные admin, поскольку joe «наследует» права admin. Однако права, предоставленные wheel, ему недоступны, потому что, хотя joe опосредованно является членом wheel, членство осуществляется через роль admin, имеющую атрибут NOINHERIT. После:

SET ROLE admin;

сеанс будет использовать только права, предоставленные admin, но не те, которые предоставлены joe. После:

SET ROLE wheel;

сеанс будет использовать только права, предоставленные wheel, но не те, которые предоставлены joe или admin. Исходное состояние прав можно восстановить любым из следующих способов:

SET ROLE joe;
SET ROLE NONE;
RESET ROLE;

Примечание
Команда SET ROLE всегда позволяет выбрать любую роль, членом которой непосредственно или опосредованно является исходная роль входа в систему. Таким образом, в приведенном выше примере нет необходимости становиться admin, прежде чем стать wheel.

Примечание
В стандарте SQL существует четкое различие между пользователями и ролями, и пользователи не наследуют права автоматически, в то время как роли наследуют. Такое поведение можно получить в QHB, если ролям, используемым в качестве ролей SQL, дать атрибут INHERIT, а ролям, используемым в качестве пользователей SQL, — атрибут NOINHERIT. По умолчанию в QHB всем ролям предоставляется атрибут INHERIT.

Атрибуты роли LOGIN, SUPERUSER, CREATEDB и CREATEROLE могут рассматриваться как специальные права, но они никогда не наследуются, в отличие от обычных прав для объектов базы данных. Чтобы использовать такой атрибут, нужно действительно выполнить SET ROLE для конкретной роли, имеющей один из этих атрибутов. Продолжая приведенный выше пример, мы можем предоставить атрибуты CREATEDB и CREATEROLE роли admin. Тогда подключение к сеансу под ролью joe получит эти права не сразу, а только после выполнения SET ROLE admin.

Чтобы уничтожить групповую роль, используйте команду DROP ROLE:

DROP ROLE имя;

Любое членство в групповой роли автоматически отзывается (но иным образом на роли-участники это не повлияет).

Удаление ролей

Поскольку роли могут владеть объектами базы данных и иметь права для доступа к другим объектам, удаление роли часто представляет собой не просто быстрое выполнение DROP ROLE. Сначала любые объекты, принадлежащие этой роли, должны быть удалены или переназначены другим владельцам, а любые разрешения, предоставленные этой роли, — отозваны.

Права собственности на объекты можно передавать по одному с помощью команд ALTER, например:

ALTER TABLE bobs_table OWNER TO alice;

Как вариант, можно воспользоваться командой REASSIGN OWNED для переназначения другой роли владения всеми объектами, принадлежащими удаляемой роли. Поскольку REASSIGN OWNED не может обращаться к объектам в других базах данных, необходимо запускать ее в каждой базе данных, содержащей объекты, принадлежащие этой роли. (Обратите внимание, что первая такая REASSIGN OWNED изменит владельца любых совместно используемых базами данных объектов, то есть баз данных или табличных пространств, принадлежащих удаляемой роли.)

Как только все ценные объекты были переданы новым владельцам, любые оставшиеся объекты, принадлежащие удаляемой роли, можно удалить командой DROP OWNED. Опять же, эта команда не может обращаться к объектам в других базах данных, поэтому необходимо запускать ее в каждой базе данных, содержащей объекты, принадлежащие этой роли. Кроме того, DROP OWNED не удаляет целые базы данных или табличные пространства, так что если роли принадлежат какие-либо базы данных или табличные пространства, которые не были переданы новым владельцам, это необходимо делать вручную.

Команда DROP OWNED также удаляет все права, предоставленные целевой роли для объектов, которые ей не принадлежат. Поскольку REASSIGN OWNED не затрагивает такие объекты, как правило, для полного удаления зависимостей удаляемой роли нужно выполнить и REASSIGN OWNED, и DROP OWNED (именно в таком порядке!).

Подводя итоги, самый общий рецепт удаления роли, которая использовалась для владения объектами, следующий:

REASSIGN OWNED BY doomed_role TO successor_role;
DROP OWNED BY doomed_role;
-- повторить вышеуказанные команды в каждой базе данных кластера
DROP ROLE doomed_role;

Когда не все принадлежащие роли объекты должны быть переданы одному владельцу- преемнику, лучше всего обработать исключения вручную, а затем завершить процедуру выполнением вышеописанных шагов.

Если попытка выполнить DROP ROLE происходит, пока у роли еще остались зависимые объекты, эта команда выдаст сообщения, указывающие, какие объекты необходимо переназначить или удалить.

Предопределенные роли

QHB предоставляет набор предопределенных ролей, которые обеспечивают доступ к определенным, обычно востребованным, конфиденциальным возможностям и информации. Администраторы (в том числе роли, имеющие право CREATEROLE) могут назначать (командой GRANT) эти роли пользователям и/или другим ролям в их среде, предоставляя этим пользователям доступ к указанным возможностям и информации.

Предопределенные роли описаны в Таблице 1. Обратите внимание, что конкретные разрешения для каждой из этих ролей могут измениться в будущем при добавлении дополнительных возможностей. Администраторы должны отслеживать заметки к выпускам на предмет изменений.

Таблица 1. Предопределенные роли

Роль
Разрешенный доступ

pg_read_all_data
Читать все данные (таблицы, представления, последовательности), как будто роль имеет права SELECT для этих объектов и права USAGE для всех схем, даже если эти права не назначены ей явно. У этой роли не установлен атрибут BYPASSRLS. Если используется технология RLS, администратору может понадобиться задать BYPASSRLS ролям, которым назначена (командой GRANT) эта роль.

pg_write_all_data
Записывать все данные (таблицы, представления, последовательности), как будто роль имеет права INSERT, UPDATE и DELETE для этих объектов и права USAGE для всех схем, даже если эти права не назначены ей явно. У этой роли не установлен атрибут BYPASSRLS. Если используется технология RLS, администратору может понадобиться задать BYPASSRLS ролям, которым назначена (командой GRANT) эта роль.

pg_read_all_settings
Читать все переменные конфигурации, даже те, которые обычно видны только суперпользователям.

pg_read_all_stats
Читать все представления pg_stat_* и использовать различные расширения, связанные со статистикой, даже те, которые обычно видны только суперпользователям.

pg_stat_scan_tables
Выполнять функции мониторинга, которые могут устанавливать блокировки ACCESS SHARE в таблицах, возможно, на долгое время.

pg_monitor
Читать/выполнять различные представления и функции для мониторинга. Эта роль является членом ролей pg_read_all_settings, pg_read_all_stats и pg_stat_scan_tables.

pg_database_owner
Никакого. Членство неявно включает в себя текущего владельца базы данных.

pg_signal_backend
Подавать другому обслуживающему процессу сигнал, чтобы отменить запрос или завершить сеанс.

pg_read_server_files
Разрешить чтение файлов из любого места, к которому база данных может получить доступ на сервере с помощью команды COPY и других функций обращения к файлам.

pg_write_server_files
Разрешить запись в файлы в любом месте, к которому база данных может получить доступ на сервере с помощью команды COPY и других функций обращения к файлам.

pg_execute_server_program
Разрешить выполнение программ на сервере баз данных от имени пользователя, запускающего базу данных, так же, как это делает команда COPY и другие функции, позволяющие выполнять программу на стороне сервера.

pg_checkpoint
Разрешить выполнение команды CHECKPOINT.

Роли pg_monitor, pg_read_all_settings, pg_read_all_stats и pg_stat_scan_tables предназначены для того, чтобы администраторы могли легко сконфигурировать роль для мониторинга сервера баз данных. Они предоставляют набор общих прав, позволяющих такой роли читать различные полезные параметры конфигурации, статистику и другую системную информацию, обычно доступную только суперпользователям.

У роли pg_database_owner имеется только один неявный, определяемый сложившейся ситуацией член, а именно — владелец текущей базы данных. Изначально эта роль не предоставляет никаких прав. Как и любая роль, она может владеть объектами или получать права доступа. Следовательно, как только pg_database_owner приобретет права в базе-шаблоне, каждый владелец базы данных, созданной из этого шаблона, получит эти права. Роль pg_database_owner не может быть членом какой-либо роли и не может явно включать членов. Изначально эта роль владеет схемой public, так что каждый владелец базы данных управляет локальным использованием этой схемы.

Роль pg_signal_backend предназначена для того, чтобы администраторы могли давать доверенным, но не суперпользовательским ролям право отправлять сигналы другим обслуживающим процессам. В настоящее время эта роль позволяет отправлять сигналы для отмены запроса на другом сервере или завершения его сеанса. Однако пользователь, которому предоставлена эта роль, не может отправлять сигналы процессу, принадлежащему суперпользователю. См. подраздел Функции для передачи сигналов серверу.

Роли pg_read_server_files, pg_write_server_files и pg_execute_server_program предназначены для того, чтобы администраторы могли иметь доверенные, но не суперпользовательские роли, которые могут обращаться к файлам и запускать программы на сервере баз данных от имени пользователя, запускающего базу данных. Поскольку эти роли могут обращаться к любому файлу в файловой системе сервера, то при прямом обращении они обходят все проверки разрешений на уровне базы данных и могут использоваться для получения доступа на уровне суперпользователя, поэтому при назначении этих ролей пользователям следует соблюдать особую осторожность.

Назначать эти роли следует с осторожностью, чтобы они гарантированно использовались только там, где это необходимо, и с пониманием, что эти роли предоставляют доступ к конфиденциальной информации.

Администраторы могут предоставить пользователям доступ к этим ролям с помощью команды GRANT, например:

GRANT pg_signal_backend TO admin_user;

Безопасность функций

Функции, триггеры и политики защиты на уровне строк позволяют пользователям добавлять на внутренний сервер код, который могут непреднамеренно выполнить другие пользователи. Следовательно, эти механизмы позволяют пользователям относительно легко запускать другим «троянских коней». Самая сильная защита — жесткий контроль над тем, кто может определять объекты. Там, где это невозможно, пишите запросы, относящиеся только к объектам, имеющим доверенных владельцев. Удалите из параметра search_path общедоступную схему и любые другие схемы, которые позволяют не доверенным пользователям создавать объекты.

Функции выполняются внутри процесса внутреннего сервера с разрешениями операционной системы служебного процесса сервера баз данных. Если язык программирования, используемый для функции, допускает неконтролируемый доступ к памяти, появляется возможность изменить внутренние структуры данных сервера. Следовательно, среди прочего, такие функции могут обойти любые системы контроля доступа. Языки функций, разрешающих такой доступ, считаются «ненадежными», а QHB позволяет создавать функции, написанные на этих языках, только суперпользователям.

Описание ролей ИБ

Добавлены две сущности: роль Администратора СУБД и роль Администратора базы данных, которые реализованы в виде разделяемых каталогов (таблиц схемы pg_catalog):

qhb_dbms_admin для администраторов СУБД;

qhb_db_admin для администраторов конкретных баз данных.

Информация о ролях администраторов ИБ

Наличие в системе ролей администраторов СУБД и администраторов конкретных баз данных позволяет назначать Администраторов Информационной Безопасности, не включая атрибут суперпользователя у соответствующих записей в таблице каталога пользователей и ролей pg_authid.

Привилегии администраторов ИБ

Объект в СУБД | Администратор СУБД | Администратор базы данных
Таблица в базе данных | Права по умолчанию | Полные права в управляемой базе данных: вставка, чтение, редактирование, удаление
Последовательность | Права по умолчанию | Полные права в управляемой базе данных: использование, чтение, редактирование
База данных | Имеет право создавать базы данных | Полные права в управляемой базе данных: создание объектов, логин, работа с планировщиком
Оболочка сторонних данных | Права по умолчанию | Право использования в управляемой базе данных
Функции/процедуры | Права по умолчанию | Право использования в управляемой базе данных
Язык процедур | Права по умолчанию | Право использования в управляемой базе данных
Большие объекты | Права по умолчанию | Полные права в управляемой базе данных: чтение и изменение
ALTER SYSTEM | Полные права в СУБД | Права по умолчанию
Схема | Права по умолчанию | Полные права в управляемой базе данных: использование и создание
Табличное пространство | Полные права на создание | Права по умолчанию
Тип данных | Права по умолчанию | Полные права в управляемой базе данных, включая создание

Особенности реализации

В данной реализации назначение ролей администратора СУБД и администратора конкретных баз данных осуществляется индивидуально каждому пользователю, минуя иерархию ролей в СУБД. Назначить роли администраторов ИБ групповым ролям СУБД нельзя.

Назначение ролей ИБ

Суперпользователь имеет полномочия назначать администраторов СУБД и администраторов конкретных баз данных с помощью встроенных процедур.

Назначение администратора СУБД

Встроенная процедура qhb_make_dbms_admin позволяет назначить пользователя одним из администраторов СУБД, что наделит его дополнительными полномочиями.

SELECT qhb_make_dbms_admin(<oid_пользователя>);

Для передачи идентификатора пользователя в виде параметра в функцию qhb_make_dbms_admin следует найти значение oid в каталоге pg_authid, соответствующее имени требуемого пользователя. Например, с помощью такого запроса SQL:

SELECT oid FROM pg_authid WHERE rolname LIKE '<имя_пользователя>';

Права для назначения пользователя администратором СУБД есть у суперпользователей.

Права для назначения пользователя администратором СУБД отсутствуют:

у обычных пользователей;

у администраторов СУБД и баз данных, не являющихся суперпользователями.

Назначение администратора базы данных

Встроенная процедура qhb_make_db_admin позволяет назначить пользователя одним из администраторов заданной базы данных, что наделит его дополнительными полномочиями в отношении выбранной базы данных и ее объектов.

SELECT qhb_make_db_admin(<oid_пользователя>, <oid_базы_данных>);

Для передачи идентификатора пользователя в виде параметра в функцию qhb_make_db_admin следует найти значение oid в каталоге pg_authid, соответствующее имени требуемого пользователя. Например, с помощью такого запроса SQL:

SELECT oid FROM pg_authid WHERE rolname LIKE '<имя_пользователя>';

Аналогично идентификатор базы данных для передачи в виде параметра можно найти с помощью следующего запроса SQL:

SELECT oid FROM pg_database WHERE datname LIKE '<имя_базы_данных>';

Права для назначения пользователя администратором базы данных есть:

у суперпользователей;

у администраторов СУБД.

Отсутствуют права для назначения пользователя администратором базы данных:

у обычных пользователей;

у администраторов баз данных, не являющихся суперпользователями.

Просмотр текущих администраторов СУБД и баз данных

Текущие администраторы ИБ отображаются в таблицах разделяемого каталога qhb_dbms_admin и qhb_db_admin.

С помощью следующих запросов, использующих встроенные процедуры, можно определить текущий статус пользователя:

SELECT qhb_is_dbms_admin(<oid_пользователя>);

и

SELECT qhb_is_db_admin(<oid_пользователя>, <oid_базы_данных>);

Удаление ролей администраторов ИБ

Для отмены назначения ролей администратора СУБД и администратора базы данных следует воспользоваться процедурами qhb_drop_dbms_admin и qhb_drop_db_admin.

Для удаления назначения пользователя администратором СУБД можно использовать запрос:

SELECT qhb_drop_dbms_admin(<oid_пользователя>);

Для удаления назначения пользователя администратором конкретной базы данных можно использовать запрос:

SELECt qhb_drop_db_admin(<oid_пользователя>, <oid_базы_данных>);

Для передачи идентификатора пользователя в виде параметра в функции qhb_drop_dbms_admin и qhb_drop_db_admin следует найти значение oid в каталоге pg_authid, соответствующее имени требуемого пользователя. Например, с помощью такого запроса SQL:

SELECT oid FROM pg_authid WHERE rolname LIKE '<имя_пользователя>';

Аналогично идентификатор базы данных для передачи в виде параметра можно найти с помощью следующего запроса SQL:

SELECT oid FROM pg_database WHERE datname LIKE '<имя_базы_данных>';

Управление базами данных

Каждый экземпляр работающего сервера QHB управляет одной или несколькими базами данных. Таким образом, базы данных являются высшим иерархическим уровнем для организации объектов SQL («объектов базы данных»). В этой главе описываются свойства баз данных, а также способы их создания, управления и уничтожения.

Обзор

Небольшое число объектов, таких как роли, базы данных и табличные пространства, определяются на уровне кластера и сохраняются в табличном пространстве pg_global. Внутри кластера имеется несколько баз данных, которые изолированы друг от друга, но могут обращаться к объектам уровня кластера. Внутри каждой базы данных имеется несколько схем, содержащих такие объекты, как таблицы и функции. Таким образом, полная иерархия выглядит так: кластер, база данных, схема, таблица (или какой-то другой тип объекта, например функция).

При подключении к серверу баз данных клиент должен указать в своем запросе на подключение имя базы данных, к которой он хочет подключиться. Через одно соединение невозможно получить доступ более чем к одной базе данных. Однако клиенты могут открывать несколько подключений к одной базе данных или к разным. Защита на уровне базы данных состоит из двух компонентов: управление доступом (см. раздел Файл qhb_hba.conf), реализуемое на уровне подключения, и управление авторизацией (см. раздел Права), реализуемое через систему прав. Обертки сторонних данных (см. postgres_fdw) позволяют объектам в одной базе данных выступать в качестве представителей объектов в других базах данных или кластерах. Похожую возможность предоставляет более старый модуль dblink (см. dblink). По умолчанию все пользователи могут подключаться ко всем базам данных, пользуясь любыми методами подключения.

Если один кластер сервера QHB предназначен для размещения несвязанных проектов или пользователей, которые, по большей части, не должны замечать друг друга, рекомендуется поместить их в отдельные базы данных и соответствующим образом настроить управление авторизацией и доступом. Если же проекты или пользователи взаимосвязаны и поэтому должны иметь возможность использовать ресурсы друг друга, они должны быть помещены в одну базу данных, но, возможно, в разные схемы; это создаст модульную структуру с изоляцией пространств имен и управлением авторизацией. Более подробная информация об управлении схемами находится в разделе Схемы.

Хотя в одном кластере можно создать несколько баз данных, сначала рекомендуется тщательно оценить, перевешивают ли получаемые преимущества сопряженные с этим риски и ограничения. В частности, влияние, которое наличие общего WAL (см. главу Надежность и журнал упреждающей записи) имеет на параметры резервного копирования и восстановления. Хотя с позиции пользователя отдельные базы данных в кластере и изолированы друг от друга, но с точки зрения администратора баз данных они тесно связаны.

Базы данных создаются с помощью команды CREATE DATABASE (см. раздел Создание базы данных) и уничтожаются с помощью команды DROP DATABASE (см. раздел Удаление базы данных). Чтобы просмотреть набор существующих баз данных, изучите системный каталог pg_database, например

SELECT datname FROM pg_database;

Метакоманда \l и параметр командной строки -l программы psql также позволяют вывести список существующих баз данных.

Примечание
В стандарте SQL базы данных называются «каталогами», но на практике между ними нет никакой разницы.

Создание базы данных

Для создания базы данных сервер QHB должен быть запущен и успешно функционировать (см. раздел Запуск сервера баз данных).

Базы данных создаются с помощью команды SQL CREATE DATABASE:

CREATE DATABASE имя;

где имя следует обычным правилам для идентификаторов SQL. Владельцем новой базы данных автоматически становится текущая роль. Владелец базы данных имеет право потом ее удалить (что также удаляет все объекты в ней, даже если у них другой владелец).

Создание баз данных — операция ограниченного использования. Как предоставить на нее право, см. в разделе Атрибуты ролей.

Поскольку для выполнения команды CREATE DATABASE необходимо подключиться к серверу баз данных, остается вопрос, как можно создать первую базу данных в любом заданном месте. Первая база данных всегда создается командой qhb_bootstrap при инициализации области хранения данных. (См. раздел Создание кластера баз данных). Эта база данных называется qhb. Таким образом, чтобы создать первую «обычную» базу данных, можно подключиться к qhb.

Две дополнительные базы данных, template1 и template0, тоже создаются во время инициализации кластера баз данных. Всякий раз, когда в кластере создается новая база данных, template1, по сути, клонируется. Это означает, что все изменения, которые вы делаете в template1, распространяются на все впоследствии созданные базы данных. Поэтому в template1 лучше не создавать объекты, если вы не хотите, чтобы они распространялись на каждую вновь созданную базу данных. База данных template0 задумана как нетронутая копия исходного содержимого template1. Ее можно клонировать вместо template1, когда важно создать базу данных без каких-либо локальных дополнений. Более подробную информацию см. в разделе Шаблоны баз данных.

Для удобства имеется программа createdb, которую можно выполнить из оболочки для создания новых баз данных.

createdb имя_бд

Утилита createdb не делает ничего особенного. Она подключается к базе данных qhb и выполняет команду CREATE DATABASE, в точности как описано выше. На справочной странице createdb описаны подробности ее вызова. Обратите внимание, что createdb без аргументов создаст базу данных с именем текущего пользователя.

Примечание
В главе Аутентификация клиентского приложения содержится информация о том, как ограничить права на подключение к заданной базе данных.

Иногда вам может понадобиться создать базу данных для кого-то другого и сделать его владельцем новой базы данных, чтобы он мог сам конфигурировать и управлять ею. Для этого воспользуйтесь одной из следующих команд:

CREATE DATABASE имя_бд OWNER имя_роли;

из среды SQL, или:

createdb -O имя_роли имя_бд

из оболочки ОС. Только суперпользователь может создавать базу данных для кого-то другого (то есть для роли, членом которой он не является).

Шаблоны баз данных

Команда CREATE DATABASE фактически работает путем копирования существующей базы данных. По умолчанию она копирует стандартную системную базу данных с именем template1. Таким образом, эта база данных является «шаблоном», из которого создаются новые базы данных. Если в template1 добавить объекты, они будут скопированы во вновь создаваемые пользовательские базы данных. Такое поведение допускает локальные модификации стандартного набора объектов в базах данных. Например, если вы установите процедурный язык PL/Perl в template1, он автоматически будет доступен в пользовательских базах данных без каких-либо дополнительных действий при создании этих баз.

Однако команда CREATE DATABASE не копирует права GRANT уровня базы данных, привязанные к исходной базе. Новая база данных получает права уровня базы по умолчанию.

Существует и вторая стандартная системная база данных с именем template0. Эта база данных содержит те же данные, что и исходное содержимое template1, то есть только стандартные объекты, предопределенные вашей версией QHB. Шаблон template0 нельзя изменять после инициализации кластера баз данных. Указав CREATE DATABASE скопировать template0 вместо template1, можно создать «нетронутую» пользовательскую базу данных (в которой отсутствуют пользовательские объекты и не были изменены системные объекты), не содержащую никаких локальных дополнений из template1. Это особенно удобно при восстановлении дампа qhb_dump: скрипт дампа следует восстанавливать в нетронутой базе данных, чтобы он точно воссоздавал правильное содержимое скопированной базы данных, без конфликтов с объектами, которые могли быть добавлены в template1 позже.

Другая распространенная причина копирования template0 вместо template1 заключается в том, что при копировании template0 можно указать новые значения кодировки и локали, тогда как для копии template1 должны использоваться значения шаблона. Это связано с тем, что template1 может содержать данные в специфичных кодировках или локалях, а template0 заведомо не может.

Чтобы создать базу данных путем копирования template0, выполните:

CREATE DATABASE имя_бд TEMPLATE template0;

из среды SQL, или:

createdb -T template0 имя_бд

из оболочки ОС

Можно создать дополнительные шаблоны баз данных и, разумеется, скопировать любую базу данных в кластере, указав ее имя в качестве шаблона для CREATE DATABASE. Однако важно понимать, что это (пока) не назначено универсальным средством для реализации возможности «COPY DATABASE». Основным ограничением является то, что при копировании исходной базе данных к ней не могут быть подключены никакие другие сеансы. CREATE DATABASE завершится ошибкой, если при ее запуске существует какое-либо другое подключение; во время операции копирования новые подключения к исходной базе данных запрещены.

В pg_database есть два полезных флага для каждой базы данных: столбцы datistemplate и datallowconn. Столбец datistemplate можно установить, чтобы показать, что база данных может выступать в качестве шаблона для CREATE DATABASE. Если этот флаг установлен, базу данных может клонировать любой пользователь с правами CREATEDB; если он не установлен, клонировать могут только суперпользователи и владелец базы данных. Если datallowconn содержит false, то к этой базе данных не будут разрешены новые подключения (но существующие сеансы одной установкой флага false не закрываются). База данных template0 обычно помечается как datallowconn = false, чтобы предотвратить ее модификацию. И template0, и template1 всегда должны быть помечены флагом datistemplate = true.

Примечание
template1 и template0 не имеют особого статуса, кроме того факта, что имя template1 является именем исходной базы данных по умолчанию для CREATE DATABASE. Например, можно удалить template1 и воссоздать ее из template0 без каких-либо нежелательных последствий. Этот способ может быть целесообразным, если кто-то небрежно замусорил template1. (Чтобы удалить template1, нужно установить pg_database.datistemplate = false).

База данных qhb также создается при инициализации кластера баз данных. Эта база является базой данных по умолчанию при подключении пользователей и приложений. Это просто копия template1, которую при необходимости можно удалить и воссоздать.

Конфигурация базы данных

Как говорилось в главе Конфигурация сервера, сервер QHB предоставляет большое количество переменных конфигурации времени выполнения. Для многих из этих параметров можно установить значения по умолчанию, специфичные для базы данных.

Например, если по какой-то причине вы хотите выключить для какой-то базы данных оптимизатор GEQO, обычно приходится либо выключить его для всех баз данных, либо убедиться, что каждый подключающийся клиент выполняет SET geqo TO off. Чтобы установить этот параметр по умолчанию в конкретной базе данных, можно выполнить команду:

ALTER DATABASE mydb SET geqo TO off;

Это сохранит данный параметр (но не установит его сразу). При последующих подключениях к этой базе данных все будет выглядеть так, как будто команда SET geqo TO off; была выполнена непосредственно перед началом сеанса. Обратите внимание, что пользователи по-прежнему могут изменять этот параметр во время своих сеансов; это будет просто значение по умолчанию. Чтобы отменить любой подобный параметр, используйте ALTER DATABASE имя_бд RESET имя_переменной.

Удаление базы данных

Базы данных удаляются командой DROP DATABASE:

DROP DATABASE имя;

Удалить базу данных может только ее владелец или суперпользователь. Удаление базы данных удаляет все объекты, которые в ней содержались. Уничтожение базы данных нельзя отменить.

Нельзя выполнить команду DROP DATABASE, будучи подключенным к целевой базе данных. Однако можно подключиться к любой другой базе данных, включая template1. При удалении последней пользовательской базы данных указанного кластера template1 может стать единственным вариантом.

Для удобства существует также программа-оболочка для удаления баз данных dropdb:

dropdb имя_бд

(В отличие от createdb, удаление базы данных с именем текущего пользователя не является действием по умолчанию).

Табличные пространства

Табличные пространства в QHB позволяют администраторам баз данных определять места в файловой системе, где могут храниться файлы, представляющие объекты базы данных. После создания на табличное пространство можно ссылаться по имени при создании объектов базы данных.

Используя табличные пространства, администратор может управлять структурой диска установки QHB. Это полезно по крайней мере в двух случаях. Во-первых, если в разделе или томе, на котором был инициализирован кластер, закончилось место и его нельзя расширить, табличное пространство можно создать в другом разделе и использовать до тех пор, пока не получится переконфигурировать систему.

Во-вторых, табличные пространства позволяют администратору применять знания об особенностях использования объектов базы данных для оптимизации производительности. Например, часто используемый индекс можно разместить на очень быстром и высокодоступном диске, например на дорогом твердотельном накопителе. В то же время таблица, содержащая редко используемые или некритичные для производительности архивные данные, может храниться в менее дорогой и более медленной дисковой системе.

ПРЕДУПРЕЖДЕНИЕ
Несмотря на то, что табличные пространства расположены вне основного каталога данных QHB, они являются неотъемлемой частью кластера баз данных и не могут рассматриваться как автономная коллекция файлов данных. Они зависят от метаданных, содержащихся в главном каталоге данных и поэтому не могут быть присоединены к другому кластеру баз данных или копироваться по отдельности. Аналогичным образом при потере табличного пространства (удаление файла, сбой диска и т. д.) кластер баз данных может стать нечитаемым или перестанет запускаться. Размещение табличного пространства во временной файловой системе, например, в RAM-диске, ставит под угрозу надежность всего кластера.

Чтобы определить табличное пространство, используйте команду CREATE TABLESPACE, например:

CREATE TABLESPACE fastspace LOCATION '/ssd1/qhb/data';

Расположение должно быть существующим пустым каталогом, который принадлежит пользователю операционной системы QHB. Все объекты, впоследствии создаваемые в этом табличном пространстве, будут храниться в файлах в этом каталоге. Каталог не должен размещаться в съемном или временном хранилище, поскольку кластер может перестать функционировать, если табличное пространство отсутствует или потеряно.

Примечание
Обычно нет смысла создавать более одного табличного пространства на одну логическую файловую систему, поскольку вы не можете контролировать расположение отдельных файлов в логической файловой системе. Однако QHB не налагает никаких подобных ограничений и на самом деле не знает напрямую о границах файловой системы в вашей ОС. Она просто хранит файлы в каталогах, которые вы указали для использования.

Само создание табличного пространства должно выполняться от имени суперпользователя базы данных, но после этого можно разрешить обычным пользователям базы данных его использовать. Для этого предоставьте им право CREATE.

Таблицы, индексы и целые базы данных могут быть назначены конкретным табличным пространствам. Для этого пользователь с привилегией CREATE в данном табличном пространстве должен передать имя этого табличного пространства в качестве параметра соответствующей команде. Например, следующая команда создает таблицу в табличном пространстве space1:

CREATE TABLE foo(i int) TABLESPACE space1;

Как вариант, можно воспользоваться параметром default_tablespace:

SET default_tablespace = space1;
CREATE TABLE foo(i int);

Если для default_tablespace задано значение, отличное от пустой строки, оно предоставляет неявное предложение TABLESPACE для команд CREATE TABLE и CREATE INDEX, в которых это предложение не задано явно.

Существует также параметр temp_tablespaces, определяющий размещение временных таблиц и индексов, а также временных файлов, которые используются для таких целей, как сортировка больших наборов данных. Это может быть не одно имя, а список имен табличных пространств, поэтому нагрузку, связанную с временными объектами, можно распределить по нескольким табличным пространствам. Каждый раз, когда создается временный объект, выбирается случайное имя из списка.

Табличное пространство, связанное с базой данных, используется для хранения системных каталогов этой базы данных. Более того, это табличное пространство по умолчанию, используемое для таблиц, индексов и временных файлов, созданных в базе данных, если не задано предложение TABLESPACE и в default_tablespace или temp_tablespaces (в зависимости от ситуации) не определены никакие другие параметры. Если база данных создается без указания для нее табличного пространства, она использует то же табличное пространство, что и база-шаблон, из которой она копируется.

При инициализации кластера баз данных автоматически создаются два табличных пространства. Пространство pg_global используется для общих системных каталогов. Пространство pg_default является табличным пространством по умолчанию для баз данных template1 и template0 (и, следовательно, будет табличным пространством по умолчанию и для других баз данных, если это не переопределено предложением TABLESPACE в команде CREATE DATABASE).

После создания табличное пространство можно использовать из любой базы данных при условии, что запрашивающий пользователь имеет необходимые права. Это означает, что табличное пространство нельзя удалить, пока не будут удалены все объекты во всех базах данных, использующих это табличное пространство.

Чтобы удалить пустое табличное пространство, используйте команду DROP TABLESPACE.

Чтобы просмотреть набор существующих табличных пространств, изучите системный каталог pg_tablespace, например:

SELECT spcname FROM pg_tablespace;

Метакоманда \db программы psql также позволяет вывести список существующих табличных пространств.

Для упрощения реализации табличных пространств QHB использует символические ссылки. Это означает, что табличные пространства могут использоваться только в системах, поддерживающих символические ссылки.

Каталог $PGDATA/pg_tblspc содержит символические ссылки, указывающие на каждое из встроенных табличных пространств, определенных в кластере. Хотя это и не рекомендуется, но можно настроить структуру табличных пространств вручную, переопределив эти ссылки. Ни при каких обстоятельствах не выполняйте эту операцию во время работы сервера.

Локализация

В этой главе описываются доступные функции локализации с точки зрения администратора. QHB поддерживает два средства локализации:

Использование средств локализации операционной системы для обеспечения определяемых локалью порядка сортировки, форматирования чисел, перевода сообщений и других аспектов. Это описывается в разделах Поддержка языковых стандартов и Поддержка правил сортировки.

Предоставление ряда различных кодировок для поддержки хранения текста на разных языках и обеспечения перевода кодировок между клиентом и сервером. Это описывается в разделе Поддержка кодировок.

Поддержка языковых стандартов

В привязке к приложениям поддержка языковых стандартов позволяет учитывать культурные предпочтения в отношении алфавитов, сортировки, форматирования чисел и т. д. QHB использует стандартные средства локализации ISO C и POSIX, предоставляемые операционной системой сервера. Для получения дополнительной информации обратитесь к документации вашей системы.

Обзор

Поддержка языковых стандартов автоматически инициализируется при создании кластера баз данных с использованием qhb_bootstrap (или initdb). По умолчанию qhb_bootstrap инициализирует кластер баз данных с настройкой локали из среды выполнения, поэтому если ваша система уже настроена на использование локали, которую вы хотели бы видеть в своем кластере баз данных, то больше ничего не нужно делать. Если же вы хотите использовать другую локаль (или не уверены, для какой локали настроена ваша система), можно указать qhb_bootstrap, какую именно локаль использовать, установив параметр --locale. Например:

qhb_bootstrap --locale=ru_RU

Этот пример для систем Unix устанавливает локаль русского языка (ru), на котором говорят в России (RU). Другие возможности могут включать en_US (американский английский) и fr_CA (французский канадский). Если для локализации можно использовать более одного набора символов, тогда спецификации могут принимать форму язык_страна.кодировка. Например, ru_RU.utf8 представляет русский язык (ru), на котором говорят в России (RU), с кодировкой набора символов UTF-8.

То, какие локали доступны в вашей системе и под какими именами, зависит от того, что было предоставлено поставщиком операционной системы и что было установлено. В большинстве систем Unix команда locale -a выведет список доступных локалей.

Иногда бывает полезно смешать правила из нескольких локалей, например, использовать английские правила сортировки и сообщения на русском. Для поддержки этой возможности существует набор подкатегорий локалей, которые контролируют только определенные аспекты правил локализации:

Категория | Контролируемая область
LC_COLLATE | Порядок сортировки строк
LC_CTYPE | Классификация символов (Что это за буква? Каков ее эквивалент в верхнем регистре?)
LC_MESSAGES | Язык сообщений
LC_MONETARY | Форматирование денежных сумм
LC_NUMERIC | Форматирование чисел
LC_TIME | Форматирование даты и времени

Имена категорий переводятся в имена параметров qhb_bootstrap, чтобы переопределить выбор локали для конкретной категории. Например, чтобы установить локаль на русский, но использовать правила США для форматирования валюты, используйте qhb_bootstrap --locale=ru_RU --lc-monetary=en_US.

Если вы хотите, чтобы система работала без поддержки локализаций, используйте специальное имя локали C или его эквивалент POSIX.

Значения некоторых категорий локалей должны быть заданы при создании базы данных. Для разных баз данных можно использовать разные значения, но как только база данных будет создана, вы больше не сможете их изменить. К этим категориям относятся LC_COLLATE и LC_CTYPE. Они влияют на порядок сортировки индексов, поэтому должны оставаться фиксированными, иначе могут повредиться индексы в текстовых столбцах. (Но это ограничение можно ослабить, используя параметры сортировки, как описано в разделе Поддержка правил сортировки). Значения по умолчанию для этих категорий определяются при запуске qhb_bootstrap, и эти значения используются при создании новых баз данных, если в команде CREATE DATABASE не указано иное.

Другие категории локалей можно изменить в любое время путем установки параметров конфигурации сервера с тем же именем, что и категории локализаций (подробности см. в подразделе Языковые стандарты и форматирование). Значения, выбранные qhb_bootstrap, на самом деле записываются только в файл конфигурации qhb.conf, чтобы служить значениями по умолчанию при запуске сервера. Если удалить эти назначения из qhb.conf, то сервер унаследует настройки из своей среды выполнения.

Обратите внимание, что поведение локали сервера определяется переменными среды, видимыми сервером, а не средой какого-либо клиента. Поэтому не забудьте сконфигурировать правильные параметры локали перед запуском сервера. Если клиент и сервер настроены с разными локалями, сообщения могут показываться на разных языках в зависимости от того, где они возникли.

Примечание
Наследование локали от среды выполнения в большинстве операционных систем означает следующее: для данной локали, скажем, сортировки, следующие переменные среды анализируются в приведенном ниже порядке, пока одна из них не окажется заданной: LC_ALL, LC_COLLATE (или переменная, относящаяся к соответствующей категории), LANG. Если ни одна из этих переменных среды не установлена, то по умолчанию в локали устанавливается значение C.

Некоторые библиотеки локализации сообщений при установке их языка также проверяют переменную среды LANGUAGE, переопределяющую все остальные параметры локали. В случае возникновения затруднений, пожалуйста, обратитесь к документации вашей операционной системы, в частности, к документации по gettext.

Чтобы разрешить перевод сообщений на предпочитаемый пользователем язык, во время сборки следует выбрать NLS (configure --enable-nls). В остальном поддержка языковых стандартов встроена автоматически.

Поведение

Настройки локали влияют на следующие возможности SQL:

Порядок сортировки в запросах с использованием ORDER BY или стандартных операторов сравнения текстовых данных

Функции upper, lower и initcap

Операторы сопоставления с шаблоном (LIKE, SIMILAR TO и регулярные выражения в стиле POSIX); локали влияют как на сопоставление без учета регистра, так и на классификацию символов по регулярным выражениям с классами символов

Семейство функций to_char

Возможность использовать индексы с предложениями LIKE.

Недостатком использования в QHB локалей, отличных от C или POSIX, является их влияние на производительность. Это замедляет обработку символов и не дает LIKE использовать обычные индексы. По этой причине используйте локали, только если они вам действительно нужны.

В качестве обходного пути, позволяющего QHB использовать индексы с предложениями LIKE в локали, отличной от C, существует несколько пользовательских классов операторов. Они позволяют создать индекс, который выполняет строгое посимвольное сравнение, игнорируя правила сравнения локали. Дополнительную информацию см. в разделе Классы операторов и семейства операторов. Другой подход заключается в создании индексов с использованием сортировки C, как описано в разделе Поддержка правил сортировки.

Selecting Locales

Локали могут выбираться в разном масштабе в зависимости от требований. В обзоре выше показано, как локали задаются с помощью qhb_bootstrap, чтобы установить значения по умолчанию для всего кластера. В следующем списке демонстрируется, где еще могут быть выбраны локали. Каждый элемент списка предоставляет значения по умолчанию для последующих элементов, и каждый последующий элемент позволяет переопределить значения по умолчанию с большей степенью детализации.

Как объяснялось выше, переменные среды операционной системы предоставляют значения по умолчанию для локалей в только что инициализированном кластере баз данных. Во многих случаях этого достаточно: если операционная система сконфигурирована для желаемого языка/страны, то QHB по умолчанию также будет вести себя в соответствии с этой локалью.

Как демонстрировалось выше, настройки локали для только что инициализированного кластера можно задать в параметрах командной строки qhb_bootstrap. Используйте этот вариант, если в операционной системе нет конфигурации локали, которую вы хотите установить для своей СУБД.

Локаль можно выбрать для отдельной базы данных. Параметры, позволяющие это сделать, имеются в команде SQL CREATE DATABASE и равнозначной ей командной строке createdb. Используйте этот вариант, если, к примеру, в кластере находятся базы данных для разных клиентов с различными потребностями.

Параметры локали можно установить для отдельных столбцов таблицы. Для этого используются объекты SQL, называемые правилами сортировки, которые описаны в разделе Поддержка правил сортировки. Используйте этот вариант, например, для сортировки данных на разных языках или модификации порядка сортировки отдельной таблицы.

Наконец, локали можно выбирать для отдельного запроса. Опять же, для этого используются объекты правил сортировки SQL. Это можно использовать для изменения порядка сортировки во время выполнения или для ситуативных экспериментов.

Провайдеры локалей

QHB поддерживает различных провайдеров локалей. Эти провайдеры указывают, какая библиотека предоставляет данные локали. Один стандартный провайдер с именем libc использует локали, предоставленные библиотекой C операционной системы. Эти локали используются большинством инструментов, предоставляемых операционной системой. Другим провайдером является icu, который использует внешнюю библиотеку ICU. Локали ICU можно использовать, только если при сборке QHB была настроена поддержка ICU.

У всех команд и утилит, устанавливающих параметры локали, как описано выше, имеется параметр для выбора провайдера локали. Во всех показанных ранее примерах использовался провайдер libc (это вариант по умолчанию). Ниже приведен пример инициализации кластера баз данных с выбором провайдера ICU:

qhb_bootstrap --locale-provider=icu --icu-locale=en

Подробную информацию см. в описании соответствующих команд и программ. Обратите внимание, что вы можете выбирать разных провайдеров локалей на разных уровнях детализации, к примеру, использовать libc по умолчанию для кластера, но иметь одну базу данных, использующую провайдер icu, а еще в этих базах данных иметь объекты правил сортировки, использующие любых провайдеров.

Какой именно провайдер локали использовать, зависит от конкретных требований. Для большинства обычных задач нормальные результаты можно получить с любым провайдером. Что касается провайдера libc, все зависит от того, что предлагает операционная система; в одних операционных системах он работает лучше, чем в других. Для сложных задач больше подходит ICU, который предлагает больше вариантов локалей и возможности персонализации.

Проблемы

Если поддержка языковых стандартов не работает в соответствии с приведенным выше объяснением, проверьте, правильно ли настроена поддержка языковых стандартов в вашей операционной системе. Для проверки того, какие локали установлены в вашей системе, можно воспользоваться командой locale -a, если ваша операционная система ее предоставляет.

Убедитесь, что QHB действительно использует локаль, которая вам нужна. Параметры LC_COLLATE и LC_CTYPE определяются при создании базы данных и могут быть изменены только путем создания новой базы данных. Другие параметры локали, включая LC_MESSAGES и LC_MONETARY, первоначально определяются средой, в которой запущен сервер, но могут быть изменены на ходу. Параметры активной локали можно проверить, выполнив команду SHOW.

Клиентские приложения, обрабатывающие ошибки на стороне сервера путем синтаксического анализа текста сообщения об ошибке, определенно столкнутся с трудностями, когда сообщения сервера будут на другом языке. Авторам подобных приложений рекомендуется использовать вместо этого схему кодов ошибок.

Поддержка правил сортировки

Функциональность правил сортировки позволяет указать порядок сортировки и поведение классификации символов в данных для каждого столбца или даже для каждой операции. Это смягчает имеющееся ограничение, что параметры базы данных LC_COLLATE и LC_CTYPE нельзя изменить после ее создания.

Концепции

Концептуально каждое выражение сортируемых типов данных имеет правило сортировки. (Встроенные сортируемые типы данных — text, varchar и char. Пользовательские базовые типы тоже можно пометить как сортируемые, и, разумеется, домен на основе сортируемого типа данных тоже является сортируемым.) Если выражение представляет собой ссылку на столбец, правило сортировки выражения является правилом сортировки, определенным для столбца. Если выражение является константой, правилом сортировки является правило сортировки по умолчанию для типа данных константы. Правило сортировки более сложного выражения выводится из правил сортировки его входных данных, как описано ниже.

Правило сортировки выражения может быть правилом сортировки «по умолчанию», что означает применение параметров локали, определенных для базы данных. Кроме того, правило сортировки выражения может быть неопределимым. В таких случаях операции упорядочивания и другие операции, которые должны знать правило сортировки, завершатся ошибкой.

Когда СУБД должна выполнить упорядочивание или классификацию символов, она использует правило сортировки входного выражения. Это происходит, например, с предложениями ORDER BY и вызовами функций или операторов вроде <. Правило сортировки, применяемое для предложения ORDER BY, — это просто правило сортировочного ключа. Правило сортировки, применяемое для вызова функции или оператора, выводится из аргументов, как описано ниже. Помимо операторов сравнения правила сортировки учитываются функциями, преобразующими регистр букв из нижнего в верхний и наоборот, например lower, upper и initcap; операторами сопоставления с шаблоном; и функцией to_char и связанными с ней функциями.

При вызове функции или оператора правило сортировки, выводимое путем проверки правил сортировки аргументов, будет использовано во время выполнения указанной операции. Если результат вызова функции или оператора относится к сортируемому типу данных, то правило сортировки также используется во время синтаксического анализа в качестве определенного правила сортировки выражения функции или оператора, если существует окружающее выражение, требующее знания его правила сортировки.

Выведение правила сортировки выражения может быть явным или неявным. Это различие влияет на то, как комбинируются правила сортировки, когда в выражении появляется сразу несколько разных правил. Явное выведение правил сортировки происходит, когда используется предложение COLLATE; все остальные выведения являются неявными. Когда необходимо скомбинировать несколько правил сортировки, например, при вызове функции, используются следующие правила:

Если какое-либо входное выражение имеет явно выведенное правило сортировки, то все явно выведенные правила сортировки для входных выражений должны быть одинаковыми, иначе возникает ошибка. Если присутствует какое-либо явно выведенное правило сортировки, оно является результатом комбинирования правил сортировки.

В противном случае все входные выражения должны иметь одинаковое неявно выведенное правило сортировки или правило сортировки по умолчанию. Если присутствует какое-либо правило сортировки, отличное от правила по умолчанию, оно является результатом комбинирования правил сортировки. В противном случае результатом является правило сортировки по умолчанию.

Если среди входных выражений имеются конфликтующие неявные правила сортировки, отличные от правила по умолчанию, то считается, что комбинация имеет неопределяемое правило сортировки. Это не является условием возникновения ошибки, если конкретной вызываемой функции не требуется знание правила сортировки, которое она должна применять. Если же требуется, ошибка возникнет во время выполнения.

Например, рассмотрим это определение таблицы:

CREATE TABLE test1 (
a text COLLATE "de_DE",
b text COLLATE "es_ES",
...
);

Затем в запросе

SELECT a < 'foo' FROM test1;

сравнение < выполняется в соответствии с правилами de_DE, потому что выражение комбинирует неявно выведенное правило сортировки с правилом по умолчанию. Но в запросе

SELECT a < ('foo' COLLATE "fr_FR") FROM test1;

сравнение выполняется с использованием правил fr_FR, потому что явно выведенное правило сортировки переопределяет выведенное неявно. Более того, в запросе

SELECT a < b FROM test1;

синтаксический анализатор не может определить, какое правило сортировки применить, поскольку столбцы a и b имеют конфликтующие неявные правила сортировки. Поскольку оператору < необходимо знать, какое правило сортировки использовать, это приведет к ошибке. Ошибку можно устранить путем добавления явного спецификатора правила сортировки к любому входному выражению, например:

SELECT a < b COLLATE "de_DE" FROM test1;

или равнозначное ему

SELECT a COLLATE "de_DE" < b FROM test1;

С другой стороны, структурно похожий случай

SELECT a || b FROM test1;

не приводит к ошибке, потому что для оператора || правила сортировки не важны: его результат одинаков независимо от правила сортировки.

Правило сортировки, назначенное объединенным входным выражениям функции или оператора, также считается применимым к результату этой функции или оператора, если эта функция или оператор выдает в результате сортируемый тип данных. Поэтому запрос

SELECT * FROM test1 ORDER BY a || 'foo';

будет выполнен в соответствии с правилами de_DE . Но этот запрос:

SELECT * FROM test1 ORDER BY a || b;

приводит к ошибке, потому что даже если оператору || не нужно знать правило сортировки, предложению ORDER BY оно требуется. Как и выше, конфликт можно разрешить с помощью явного спецификатора правила сортировки:

SELECT * FROM test1 ORDER BY a || b COLLATE "fr_FR";

Управление правилами сортировки

Правило сортировки — это объект схемы SQL, который соотносит SQL-имя с локалями, предоставляемыми библиотеками, установленными в операционной системе. У определения правила сортировки есть провайдер, который указывает, какая библиотека предоставляет данные локали. Один стандартный провайдер с именем libc использует локали, предоставляемые библиотекой C операционной системы. Эти локали используются большинством инструментов, предоставляемых операционной системой. Другим провайдером является icu, который использует внешнюю библиотеку ICU. Локали ICU можно использовать, только если при сборке QHB была настроена поддержка ICU.

Правило сортировки, предоставляемое libc, сопоставляется с комбинацией параметров LC_COLLATE и LC_CTYPE, принимаемой системным вызовом setlocale(). (Как следует из названия, основная цель правила сортировки состоит в том, чтобы установить LC_COLLATE, который управляет порядком сортировки. Но на практике редко требуется устанавливать в LC_CTYPE значение, отличное от LC_COLLATE, поэтому удобнее собирать их согласно одной концепции, чем создавать еще одну инфраструктуру, чтобы устанавливать LC_CTYPE для каждого выражения.) Кроме того, правило сортировки libc связано с кодировкой набора символов (см. раздел Поддержка кодировок). Одно и то же имя правила сортировки может существовать для разных кодировок.

Объект правила сортировки, предоставляемый icu, сопоставляется с именованным сортировщиком, предоставляемым библиотекой ICU. ICU не поддерживает отдельные настройки «collate» и «ctype», поэтому они всегда одинаковы. Кроме того, правила сортировки ICU не зависят от кодировки, поэтому в базе данных всегда есть только одно правило сортировки ICU с конкретным именем.

Стандартные правила сортировки

На всех платформах доступны правила сортировки с именами default, C и POSIX. В зависимости от поддержки операционной системы могут быть доступны дополнительные правила сортировки. Правила сортировки default выбирают значения LC_COLLATE и LC_CTYPE, указанные во время создания базы данных. Правила сортировки C и POSIX задают «традиционное поведение C», при котором только буквы ASCII от «A» до «Z» обрабатываются как буквы, а сортировка выполняется строго по значениям байтов кода символа.

Кроме того, для кодировки UTF8 доступно имя стандартного правила сортировки SQL ucs_basic. Оно равнозначно C и сортирует по кодовой точке Unicode.

Предопределенные правила сортировки

Если операционная система обеспечивает поддержку использования нескольких локалей в рамках одной программы (функция newlocale и связанные с ней функции) или если настроена поддержка ICU, то при инициализации кластера баз данных qhb_bootstrap заполняет системный каталог pg_collation правилами сортировки на основе всех локалей, которые она находит в операционной системе во время инициализации.

Чтобы проверить доступные в настоящее время локали, выполните запрос SELECT * FROM pg_collation или команду \dOS+ в psql.

Правила сортировки libc

Допустим, операционная система может предоставить локаль с именем de_DE.utf8. Тогда qhb_bootstrap создаст правило сортировки с именем de_DE.utf8 для кодировки UTF8, для которого в LC_COLLATE и LC_CTYPE установлено значение de_DE.utf8. Также она создаст правило сортировки с убранным из имени тегом .utf8. Таким образом, вы сможете использовать правило сортировки с менее громоздким и менее зависимым от кодировки именем de_DE. Обратите внимание, что начальный набор имен правил сортировки все равно зависит от платформы.

Набор правил сортировки по умолчанию, предоставляемый libc, сопоставляется непосредственно с локалями, установленными в операционной системе, список которых можно вывести с помощью команды locale -a. В случае, если требуется правило сортировки libc с разными значениями LC_COLLATE и LC_CTYPE или если в операционной системе после инициализации СУБД были установлены новые локали, то с помощью команды CREATE COLLATION можно создать новое правило сортировки. Кроме того, новые локали операционной системы можно массово импортировать с помощью функции pg_import_system_collations().

В любой конкретной базе данных представляют интерес только правила сортировки, использующие кодировку этой базы данных. Другие записи в pg_collation игнорируются. Таким образом, сокращенное имя правила сортировки, например de_DE, может считаться уникальным в отдельной базе данных, даже если оно не будет уникальным в глобальном масштабе. Рекомендуется использовать сокращенные имена правил сортировки, так как если вы решите перейти на другую кодировку базы данных, вам понадобится совершить на одно изменение меньше. Однако учтите, что правила сортировки default, C и POSIX можно использовать независимо от кодировки базы данных.

QHB считает, что отдельные объекты правил сортировки несовместимы, даже если они имеют идентичные свойства. Так, например,

SELECT a COLLATE "C" < b COLLATE "POSIX" FROM test1;

выдаст ошибку, даже если правила сортировки C и POSIX ведут себя одинаково. Поэтому не рекомендуется смешивать сокращенные и полные имена правил сортировки.

Правила сортировки ICU

С ICU не имеет смысла перечислять все возможные имена локалей. ICU использует для локалей определенную систему именования, но вариантов именования локали может быть гораздо больше, чем собственно различных локалей. qhb_bootstrap использует API ICU, чтобы извлечь набор различных локалей и заполнить начальный набор правил сортировки. Правила сортировки, предоставляемые ICU, создаются в среде SQL с именами в формате языкового тега BCP 47 с добавлением расширения «для частного использования», -x-icu, чтобы отличать их от локалей libc.

Вот несколько примеров правил сортировки, которые могут быть созданы:

de-x-icu
Немецкое правило сортировки, вариант по умолчанию

de-AT-x-icu
Немецкое правило сортировки для Австрии, вариант по умолчанию
(Есть также, de-DE-x-icu или de-CH-x-icu, но на момент написания статьи они равнозначны de-x-icu).

und-x-icu («undefined», неопределенная)
«Корневое» правило сортировки ICU. Используйте его, чтобы получить разумный порядок сортировки, не зависящий от языка.

ICU не поддерживает некоторые (реже используемые) кодировки. Когда баз данных имеет одну из таких кодировок, записи правила сортировки ICU в pg_collation игнорируются. Попытка его использовать приведет к появлению ошибки и строки вида «collation "de-x-icu" for encoding "WIN874" does not exist» (правило сортировки «de-x-icu» для кодировки «WIN874» не существует).

Создание новых правил сортировки

Если стандартных и предопределенных правил сортировки недостаточно, пользователи могут создавать собственные правила сортировки с помощью команды SQL CREATE COLLATION.

Стандартные и предопределенные параметры сортировки находятся в схеме pg_catalog, как и все предопределенные объекты. Пользовательские правила сортировки должны создаваться в пользовательских схемах. Это также гарантирует, что они сохраняются qhb_dump.

Правила сортировки libc

Новые правила сортировки libc можно создать следующим образом:

CREATE COLLATION german (provider = libc, locale = 'de_DE');

Точные значения, принимаемые предложением locale в этой команде, зависят от операционной системы. В Unix-подобных системах их список покажет команда locale -a.

Поскольку предопределенные правила сортировки libc уже включают в себя все правила, определенные в операционной системе при инициализации кластера баз данных, нет необходимости создавать новые вручную. Такая необходимость может возникнуть в том случае, если желательна другая система именования (в этом случае см. также подраздел Копирование правил сортировки ) или если операционная система была обновлена, и в ней появились новые определения локалей (в этом случае см. также pg_import_system_collations()).

Правила сортировки ICU

ICU позволяет настраивать правила сортировки вне базового набора язык+страна, который предварительно загружается qhb_bootstrap. Пользователям рекомендуется определять собственные правила сортировки, использующие базовые средства для получения сортировки, соответствующей их требованиями. Информация о именовании локалей ICU доступна на страницах https://unicode-org.github.io/icu/userguide/locale/ и https://unicode-org.github.io/icu/userguide/collation/api.html. Набор допустимых имен и атрибутов зависит от конкретной версии ICU.

Вот некоторые примеры:

CREATE COLLATION "de-u-co-phonebk-x-icu" (provider = icu, locale = 'de-u-co-phonebk');
CREATE COLLATION "de-u-co-phonebk-x-icu" (provider = icu, locale = 'de@collation=phonebook');
Немецкая сортировка с типом сортировки, как в телефонной книге.
В первом примере выбирается локаль ICU с использованием «языкового тега» формата BCP 47. Во втором примере используется традиционный синтаксис локали, специфичный для ICU. Первый стиль предпочтительнее в дальнейшем, но он не поддерживается старыми версиями ICU.
Обратите внимание, что в среде SQL вы можете называть правила сортировки как угодно. В этом примере мы следуем стилю именования, используемым предопределенными правилами сортировки, которые, в свою очередь, также следуют BCP 47, но для пользовательских правил сортировки это не требуется.

CREATE COLLATION "und-u-co-emoji-x-icu" (provider = icu, locale = 'und-u-co-emoji');
CREATE COLLATION "und-u-co-emoji-x-icu" (provider = icu, locale = '@collation=emoji');
Корневое правило сортировки типа эмодзи, соответствующее техническому стандарту Unicode #51.
Заметьте, что в традиционной системе именования локалей ICU корневая локаль выбирается пустой строкой.

CREATE COLLATION latinlast (provider = icu, locale = 'en-u-kr-grek-latn');
CREATE COLLATION latinlast (provider = icu, locale = 'en@colReorder=grek-latn');
Сортировка, в которой греческие буквы идут перед латинскими. (По умолчанию сначала идут латинские.)

CREATE COLLATION upperfirst (provider = icu, locale = 'en-u-kf-upper');
CREATE COLLATION upperfirst (provider = icu, locale = 'en@colCaseFirst=upper');
Сортировка в которой заглавные буквы идут перед строчными. (По умолчанию сначала идут строчные буквы.)

CREATE COLLATION special (provider = icu, locale = 'en-u-kf-upper-kr-grek-latn');
CREATE COLLATION special (provider = icu, locale = 'en@colCaseFirst=upper;colReorder=grek-latn');
Комбинация двух вышеуказанных вариантов.

CREATE COLLATION numeric (provider = icu, locale = 'en-u-kn-true');
CREATE COLLATION numeric (provider = icu, locale = 'en@colNumeric=yes');
Числовой порядок, сортирующий последовательности цифр по их числовому значению, например: A-21 < A-123 (также известный как естественная сортировка).

Подробности см. в описании технического стандарта Unicode #35 и BCP 47. Список возможных типов сортировки (субтег co) можно найти в репозитории CLDR. Для просмотра деталей конкретного определения локали можно использовать ICU Locale Explorer. Для примеров, использующих субтеги k* требуется ICU как минимум версии 54.

Обратите внимание, что хотя эта система позволяет создавать правила сортировки, которые «игнорируют регистр» или «игнорируют ударения» или аналогичные (с помощью ключа ks), для того, чтобы такие правила сортировки действительно работали без учета регистра или ударения, они также должны быть объявлены в CREATE COLLATION как недетерминированные; см. подраздел Недетерминированные правила сортировки. В противном случае любые строки, которые считаются равными согласно правилу сортировки, но не являются равными побайтово, будут отсортированы в соответствии со своими байтовыми значениями.

Примечание
По своей конструкции ICU будет принимать в качестве имени локали практически любую строку и сопоставлять ее с наиболее подходящей из предоставляемых им локалей, используя процедуру нейтрализации неисправности, описанную в его документации. Таким образом, если спецификация правила сортировки составлена с использованием функций, которые данная установка ICU фактически не поддерживает, прямого сообщения об этом не будет. Поэтому для проверки того, что определения правил сортировки удовлетворяют требованиям, рекомендуется создавать контрольные примеры на уровне приложения.

Копирование правил сортировки

Команду CREATE COLLATION также можно использовать для создания нового правила сортировки из уже существующего правила, благодаря чему появляется возможность использовать независимые от операционной системы имена правил в приложениях, создавать совместимые имена или использовать правила, предоставляемые ICU, в более читаемом формате. Например:

CREATE COLLATION german FROM "de_DE";

CREATE COLLATION french FROM "fr-x-icu";

Недетерминированные правила сортировки

Правило сортировки является либо детерминированным, либо недетерминированным. Детерминированное правило использует детерминированные сравнения, то есть оно считает строки равными, только если они состоят из одинаковой последовательности байтов. Недетерминированное сравнение может определить строки как равные, даже если они состоят из разных байтов. Типичные ситуации включают сравнение без учета регистра или ударения, а также сравнение строк в различных нормальных формах Unicode. Фактическая реализация таких сравнений определяется провайдером правил сортировки; флаг детерминированности определяет только то, должно ли равенство решаться побайтовым сравнением. Дополнительную информацию по терминологии см. в описании технического стандарта Unicode 10.

Чтобы создать недетерминированное правило сортировки, укажите в команде CREATE COLLATION свойство deterministic = false, например:

CREATE COLLATION ndcoll (provider = icu, locale = 'und', deterministic = false);

В этом примере стандартное правило сортировки Unicode будет работать недетерминированным образом. В частности, это позволит правильно сравнивать строки в разных нормальных формах. В более интересных случаях используются описанные выше средства настройки ICU. Например:

CREATE COLLATION case_insensitive (provider = icu, locale = 'und-u-ks-level2', deterministic = false);
CREATE COLLATION ignore_accents (provider = icu, locale = 'und-u-ks-level1-kc-true', deterministic = false);

Все стандартные и предопределенные правила сортировки являются детерминированными, все определяемые пользователем параметры сортировки также по умолчанию являются детерминированными. Хотя недетерминированные правила сортировки ведут себя более «правильно», особенно если учитывать все возможности Unicode и его множество особых случаев, у них имеются и некоторые недостатки. Прежде всего, их применение приводит к снижению производительности. Кроме того, с недетерминированными правилами невозможно проводить некоторые операции, например операции сопоставления с шаблоном. Поэтому их следует использовать только в тех случаях, когда они явно необходимы.

Совет
Для эффективной обработки текста в различных формах нормализации Unicode также можно использовать функции/выражения normalize и is normalized, позволяющие предварительно обработать или проверить строки, не прибегая к использованию недетерминированных правил сортировки. Однако каждый подход имеет свои минусы.

Поддержка кодировок

Поддержка кодировок в QHB позволяет хранить текст в различных наборах символов (также называемых кодировками), включая однобайтовые, например серии ISO 8859, и многобайтовые, например EUC (Extended Unix Code, расширенный код Unix), UTF-8 и внутренний код Mule. Все поддерживаемые кодировки могут прозрачно использоваться клиентами, но некоторые из них не поддерживаются для использования на сервере (то есть в качестве кодировки на стороне сервера). Кодировка по умолчанию выбирается при инициализации кластера баз данных QHB с помощью qhb_bootstrap. Ее можно переопределить при создании базы данных, что позволяет иметь несколько баз данных с разными кодировками.

Однако важным ограничением является то, что кодировка каждой базы данных должна быть совместима с параметрами локали базы данных LC_CTYPE (классификация символов) и LC_COLLATE (порядок сортировки строк). Для локали C или POSIX допустима любая кодировка, но для других локалей, предоставляемых библиотекой libc, только одна кодировка будет работать правильно. Если у вас настроена поддержка ICU, то локали, предоставляемые библиотекой ICU, можно использовать пусть и не со всеми, но с большинством кодировок на стороне сервера.

Поддерживаемые кодировки

В Таблице 1 перечислены кодировки, доступные для использования в QHB.

Таблица 1. Кодировки QHB

Имя | Описание | Язык | Поддержка на сервере | ICU? | Байт на символ | Псевдонимы
BIG5 | Big Five | Традиционный китайский | Нет | Нет | 1-2 | WIN950, Windows950
EUC_CN | Расширенный код UNIX-CN | Упрощенный китайский | Да | Да | 1-3 |
EUC_JP | Расширенный код UNIX-JP | Японский | Да | Да | 1-3 |
EUC_JIS_2004 | Расширенный код UNIX-JP, JIS X 0213 | Японский | Да | Нет | 1-3 |
EUC_KR | Расширенный код UNIX-KR | Корейский | Да | Да | 1-3 |
EUC_TW | Расширенный код UNIX-TW | Традиционный китайский, тайваньский | Да | Да | 1-3 |
GB18030 | Национальный стандарт | Китайский | Нет | Нет | 1-4 |
GBK | Расширенный национальный стандарт | Упрощенный китайский | Нет | Нет | 1-2 | WIN936, Windows936
ISO_8859_5 | ISO 8859-5, ECMA 113 | Латинский/Кириллица | Да | Да | 1 |
ISO_8859_6 | ISO 8859-6, ECMA 114 | Латинский/Арабский | Да | Да | 1 |
ISO_8859_7 | ISO 8859-7, ECMA 118 | Латинский/Греческий | Да | Да | 1 |
ISO_8859_8 | ISO 8859-8, ECMA 121 | Латинский/Иврит | Да | Да | 1 |
JOHAB | JOHAB | Корейский (Хангыль) | Нет | Нет | 1-3 |
KOI8R | KOI 8-R | Кириллица (Русский) | Да | Да | 1 | KOI8
KOI8U | KOI 8-U | Кириллица (Украинский) | Да | Да | 1 |
LATIN1 | ISO 8859-1, ECMA 94 | Западноевропейские | Да | Да | 1 | ISO88591
LATIN2 | ISO 8859-2, ECMA 94 | Центральноевропейские | Да | Да | 1 | ISO88592
LATIN3 | ISO 8859-3, ECMA 94 | Южноевропейские | Да | Да | 1 | ISO88593
LATIN4 | ISO 8859-4, ECMA 94 | Североевропейские | Да | Да | 1 | ISO88594
LATIN5 | ISO 8859-9, ECMA 128 | Турецкий | Да | Да | 1 | ISO88599
LATIN6 | ISO 8859-10, ECMA 144 | Скандинавские | Да | Да | 1 | ISO885910
LATIN7 | ISO 8859-13 | Балтийские | Да | Да | 1 | ISO885913
LATIN8 | ISO 8859-14 | Кельтские | Да | Да | 1 | ISO885914
LATIN9 | ISO 8859-15 | LATIN1 с европейскими языками и диакритикой | Да | Да | 1 | ISO885915
LATIN10 | ISO 8859-16, ASRO SR 14111 | Румынский | Да | Нет | 1 | ISO885916
MULE_INTERNAL | Внутренний код Mule | Многоязычный редактор Emacs | Да | Нет | 1-4 |
SJIS | Shift JIS | Японский | Нет | Нет | 1-2 | Mskanji, ShiftJIS, WIN932, Windows932
SHIFT_JIS_2004 | Shift JIS, JIS X 0213 | Японский | Нет | Нет | 1-2 |
SQL_ASCII | не указано (см. текст) | любые | Да | Нет | 1 |
UHC | Унифицированный код Хангыль | Корейский | Нет | Нет | 1-2 | WIN949, Windows949
UTF8 | Unicode, 8 бит | все | Да | Да | 1-4 | Unicode
WIN866 | Windows CP866 | Кириллица | Да | Да | 1 | ALT
WIN874 | Windows CP874 | Тайский | Да | Нет | 1 |
WIN1250 | Windows CP1250 | Центральноевропейские | Да | Да | 1 |
WIN1251 | Windows CP1251 | Кириллица | Да | Да | 1 | WIN
WIN1252 | Windows CP1252 | Западноевропейские | Да | Да | 1 |
WIN1253 | Windows CP1253 | Греческий | Да | Да | 1 |
WIN1254 | Windows CP1254 | Турецкий | Да | Да | 1 |
WIN1255 | Windows CP1255 | Иврит | Да | Да | 1 |
WIN1256 | Windows CP1256 | Арабский | Да | Да | 1 |
WIN1257 | Windows CP1257 | Балтийские | Да | Да | 1 |
WIN1258 | Windows CP1258 | Вьетнамский | Да | Да | 1 | ABC, TCVN, TCVN5712, VSCII

Не все клиентские API поддерживают все перечисленные кодировки. Например, драйвер QHB JDBC не поддерживает MULE_INTERNAL, LATIN6, LATIN8 и LATIN10.

Параметр SQL_ASCII ведет себя совершенно не так, как другие параметры. Когда кодировкой сервера является SQL_ASCII, сервер интерпретирует байтовые значения 0-127 в соответствии со стандартом ASCII, а байтовые значения 128-255 принимаются как неинтерпретированные символы. С установленным параметром SQL_ASCII преобразование кодировки выполняться не будет. Таким образом, этот параметр является не столько объявлением, что используется конкретная кодировка, сколько объявлением об игнорировании кодировки. В большинстве случаев, если вы работаете с любыми данными, отличными от ASCII, использовать параметр SQL_ASCII не имеет смысла, поскольку QHB не сможет помочь вам в преобразовании или проверке символов, отличных от ASCII.

Настройка кодировки

qhb_bootstrap определяет набор символов (кодировку) по умолчанию для кластера QHB. Например,

qhb_bootstrap -E EUC_JP

устанавливает кодировку по умолчанию на EUC_JP (Расширенный код Unix для японского языка). Если вы предпочитаете полные параметры, можно вместо --encoding написать -E. Если параметр -E или --encoding не задан, qhb_bootstrap пытается определить подходящую кодировку в зависимости от указанной локали или локали по умолчанию.

Во время создания базы данных можно задать кодировку, отличную от установленной по умолчанию, при условии, что эта кодировка совместима с выбранной локалью:

createdb -E EUC_KR -T template0 --lc-collate=ko_KR.euckr --lc-ctype=ko_KR.euckr korean

Это создаст базу данных с именем korean, которая использует кодировку EUC_KR и локаль ko_KR. Другой способ сделать это — с помощью команды SQL:

CREATE DATABASE korean WITH ENCODING 'EUC_KR' `LC_COLLATE`='ko_KR.euckr' `LC_CTYPE`='ko_KR.euckr' TEMPLATE=template0;

Обратите внимание, что приведенные выше команды задают копирование базы данных template0. При копировании любой другой базы данных параметры кодировки и локали исходной базы изменить нельзя, поскольку это может привести к повреждению данных. Дополнительную информацию см. в разделе Шаблоны баз данных.

Кодировка базы данных хранится в системном каталоге pg_database. Ее можно увидеть с помощью параметра -l утилиты psql или команды \l.

$ psql -l
                                         List of databases
   Name    |  Owner   | Encoding  |  Collation  |    Ctype    |          Access Privileges
-----------+----------+-----------+-------------+-------------+-------------------------------------
 clocaledb | hlinnaka | SQL_ASCII | C           | C           |
 englishdb | hlinnaka | UTF8      | en_GB.UTF8  | en_GB.UTF8  |
 japanese  | hlinnaka | UTF8      | ja_JP.UTF8  | ja_JP.UTF8  |
 korean    | hlinnaka | EUC_KR    | ko_KR.euckr | ko_KR.euckr |
 qhb       | hlinnaka | UTF8      | fi_FI.UTF8  | fi_FI.UTF8  |
 template0 | hlinnaka | UTF8      | fi_FI.UTF8  | fi_FI.UTF8  | {=c/hlinnaka,hlinnaka=CTc/hlinnaka}
 template1 | hlinnaka | UTF8      | fi_FI.UTF8  | fi_FI.UTF8  | {=c/hlinnaka,hlinnaka=CTc/hlinnaka}
(7 rows)

ВАЖНО
В большинстве современных операционных систем QHB может определить, какая кодировка подразумевается параметром LC_CTYPE, и будет обеспечивать использование только соответствующей кодировки базы данных. В старых системах необходимо самому следить за тем, чтобы использовалась кодировка, подходящая выбранной локали. Ошибка в этой области, скорее всего, приведет к странному поведению зависящих от локали операций, таких как сортировка.

QHB позволит суперпользователям создавать базы данных с кодировкой SQL_ASCII, даже если в LC_CTYPE не установлено C или POSIX. Как отмечалось выше, SQL_ASCII не требует, чтобы данные, хранящиеся в базе данных, имели какую-либо конкретную кодировку, и поэтому этот выбор чреват отклонениями, связанными с локалью. Использование этой комбинации настроек устарело и, возможно, однажды будет полностью запрещено.

Автоматическая перекодировка между сервером и клиентом

QHB поддерживает автоматическое преобразование кодировки между сервером и клиентом для определенных комбинаций наборов символов (они перечисляются в подразделе Доступные перекодировки).

Чтобы включить автоматическую перекодировку, нужно сообщить QHB набор символов (кодировку), который вы бы хотели использовать на стороне клиента. Это можно сделать несколькими способами:

Использование команды \encoding в psql. \encoding позволяет менять клиентскую кодировку на ходу. Например, для изменения кодировки на SJIS введите:

\encoding SJIS

libpq (раздел Функции управления) имеет функции для управления клиентской кодировкой.

Использование SET client_encoding TO. Установить клиентскую кодировку можно этой командой SQL:

SET CLIENT_ENCODING TO 'значение';

Также для этой цели можно использовать стандартные синтаксис SQL SET NAMES:

SET NAMES 'значение';

Запросить текущую клиентскую кодировку:

SHOW client_encoding;

Вернуть кодировку по умолчанию:

RESET client_encoding;

Использование PGCLIENTENCODING. Если в клиентской среде определена переменная среды PGCLIENTENCODING, заданная клиентская кодировка выбирается автоматически при подключении к серверу. (В последующем это можно переопределить с помощью любого из описанных выше методов.)

Использование переменной конфигурации client_encoding. Если установлена переменная client_encoding, заданная клиентская кодировка выбирается автоматически при подключении к серверу. (В последующем это можно переопределить с помощью любого из описанных выше методов.)

Если преобразование определенного символа невозможно — предположим, вы выбрали EUC_JP для сервера и LATIN1 для клиента, и возвращаются некоторые японские иероглифы, у которых нет представления в LATIN1 — выдается сообщение об ошибке.

Если кодировка клиента определена как SQL_ASCII, перекодировка отключается вне зависимости от кодировки сервера. (Однако если кодировка сервера отличается от SQL_ASCII, сервер все равно будет проверять входные данные на допустимость для его кодировки, поэтому конечный результат будет таким же, как если бы кодировка клиента совпадала с кодировкой сервера.) Так же, как и для сервера, использование SQL_ASCII неразумно, кроме случаев, когда данные, с которыми вы работаете, полностью соответствуют ASCII.

Доступные перекодировки

QHB допускает преобразования между любыми двумя кодировками, для которых в системном каталоге pg_conversion имеется функция перекодирования. QHB поставляется с несколькими предопределенными перекодировками, перечисленными в Таблице 2 и подробнее описанными в Таблице 3. Можно создать и новую перекодировку, воспользовавшись командой SQL CREATE CONVERSION. (Чтобы применяться для автоматических клиент-серверных перекодировок, она должна быть помечена как перекодировка «по умолчанию» для своей пары кодировок.)

Таблица 2. Встроенные клиент-серверные перекодировки

Серверная кодировка | Доступные клиентские кодировки
BIG5 | не поддерживается в качестве серверной кодировки
EUC_CN | EUC_CN, MULE_INTERNAL, UTF8
EUC_JP | EUC_JP, MULE_INTERNAL, SJIS, UTF8
EUC_JIS_2004 | EUC_JIS_2004, SHIFT_JIS_2004, UTF8
EUC_KR | EUC_KR, MULE_INTERNAL, UTF8
EUC_TW | EUC_TW, BIG5, MULE_INTERNAL, UTF8
GB18030 | не поддерживается в качестве серверной кодировки
GBK | не поддерживается в качестве серверной кодировки
ISO_8859_5 | ISO_8859_5, KOI8R, MULE_INTERNAL, UTF8, WIN866, WIN1251
ISO_8859_6 | ISO_8859_6, UTF8
ISO_8859_7 | ISO_8859_7, UTF8
ISO_8859_8 | ISO_8859_8, UTF8
JOHAB | не поддерживается в качестве серверной кодировки
KOI8R | KOI8R, ISO_8859_5, MULE_INTERNAL, UTF8, WIN866, WIN1251
KOI8U | KOI8U, UTF8
LATIN1 | LATIN1, MULE_INTERNAL, UTF8
LATIN2 | LATIN2, MULE_INTERNAL, UTF8, WIN1250
LATIN3 | LATIN3, MULE_INTERNAL, UTF8
LATIN4 | LATIN4, MULE_INTERNAL, UTF8
LATIN5 | LATIN5, UTF8
LATIN6 | LATIN6, UTF8
LATIN7 | LATIN7, UTF8
LATIN8 | LATIN8, UTF8
LATIN9 | LATIN9, UTF8
LATIN10 | LATIN10, UTF8
MULE_INTERNAL | MULE_INTERNAL, BIG5, EUC_CN, EUC_JP, EUC_KR, EUC_TW, ISO_8859_5, KOI8R, LATIN1 - LATIN4, SJIS, WIN866, WIN1250, WIN1251
SJIS | не поддерживается в качестве серверной кодировки
SHIFT_JIS_2004 | не поддерживается в качестве серверной кодировки
SQL_ASCII | любая (преобразование не будет выполнено)
UHC | не поддерживается в качестве серверной кодировки
UTF8 | все поддерживаемые кодировки
WIN866 | WIN866, ISO_8859_5, KOI8R, MULE_INTERNAL, UTF8, WIN1251
WIN874 | WIN874, UTF8
WIN1250 | WIN1250, LATIN2, MULE_INTERNAL, UTF8
WIN1251 | WIN1251, ISO_8859_5, KOI8R, MULE_INTERNAL, UTF8, WIN866
WIN1252 | WIN1252, UTF8
WIN1253 | WIN1253, UTF8
WIN1254 | WIN1254, UTF8
WIN1255 | WIN1255, UTF8
WIN1256 | WIN1256, UTF8
WIN1257 | WIN1257, UTF8
WIN1258 | WIN1258, UTF8

Таблица 3. Все встроенные перекодировки

Имя преобразования ¹ | Исходная кодировка | Целевая кодировка
big5_to_euc_tw | BIG5 | EUC_TW
big5_to_mic | BIG5 | MULE_INTERNAL
big5_to_utf8 | BIG5 | UTF8
euc_cn_to_mic | EUC_CN | MULE_INTERNAL
euc_cn_to_utf8 | EUC_CN | UTF8
euc_jp_to_mic | EUC_JP | MULE_INTERNAL
euc_jp_to_sjis | EUC_JP | SJIS
euc_jp_to_utf8 | EUC_JP | UTF8
euc_kr_to_mic | EUC_KR | MULE_INTERNAL
euc_kr_to_utf8 | EUC_KR | UTF8
euc_tw_to_big5 | EUC_TW | BIG5
euc_tw_to_mic | EUC_TW | MULE_INTERNAL
euc_tw_to_utf8 | EUC_TW | UTF8
gb18030_to_utf8 | GB18030 | UTF8
gbk_to_utf8 | GBK | UTF8
iso_8859_10_to_utf8 | LATIN6 | UTF8
iso_8859_13_to_utf8 | LATIN7 | UTF8
iso_8859_14_to_utf8 | LATIN8 | UTF8
iso_8859_15_to_utf8 | LATIN9 | UTF8
iso_8859_16_to_utf8 | LATIN10 | UTF8
iso_8859_1_to_mic | LATIN1 | MULE_INTERNAL
iso_8859_1_to_utf8 | LATIN1 | UTF8
iso_8859_2_to_mic | LATIN2 | MULE_INTERNAL
iso_8859_2_to_utf8 | LATIN2 | UTF8
iso_8859_2_to_windows_1250 | LATIN2 | WIN1250
iso_8859_3_to_mic | LATIN3 | MULE_INTERNAL
iso_8859_3_to_utf8 | LATIN3 | UTF8
iso_8859_4_to_mic | LATIN4 | MULE_INTERNAL
iso_8859_4_to_utf8 | LATIN4 | UTF8
iso_8859_5_to_koi8_r | ISO_8859_5 | KOI8R
iso_8859_5_to_mic | ISO_8859_5 | MULE_INTERNAL
iso_8859_5_to_utf8 | ISO_8859_5 | UTF8
iso_8859_5_to_windows_1251 | ISO_8859_5 | WIN1251
iso_8859_5_to_windows_866 | ISO_8859_5 | WIN866
iso_8859_6_to_utf8 | ISO_8859_6 | UTF8
iso_8859_7_to_utf8 | ISO_8859_7 | UTF8
iso_8859_8_to_utf8 | ISO_8859_8 | UTF8
iso_8859_9_to_utf8 | LATIN5 | UTF8
johab_to_utf8 | JOHAB | UTF8
koi8_r_to_iso_8859_5 | KOI8R | ISO_8859_5
koi8_r_to_mic | KOI8R | MULE_INTERNAL
koi8_r_to_utf8 | KOI8R | UTF8
koi8_r_to_windows_1251 | KOI8R | WIN1251
koi8_r_to_windows_866 | KOI8R | WIN866
koi8_u_to_utf8 | KOI8U | UTF8
mic_to_big5 | MULE_INTERNAL | BIG5
mic_to_euc_cn | MULE_INTERNAL | EUC_CN
mic_to_euc_jp | MULE_INTERNAL | EUC_JP
mic_to_euc_kr | MULE_INTERNAL | EUC_KR
mic_to_euc_tw | MULE_INTERNAL | EUC_TW
mic_to_iso_8859_1 | MULE_INTERNAL | LATIN1
mic_to_iso_8859_2 | MULE_INTERNAL | LATIN2
mic_to_iso_8859_3 | MULE_INTERNAL | LATIN3
mic_to_iso_8859_4 | MULE_INTERNAL | LATIN4
mic_to_iso_8859_5 | MULE_INTERNAL | ISO_8859_5
mic_to_koi8_r | MULE_INTERNAL | KOI8R
mic_to_sjis | MULE_INTERNAL | SJIS
mic_to_windows_1250 | MULE_INTERNAL | WIN1250
mic_to_windows_1251 | MULE_INTERNAL | WIN1251
mic_to_windows_866 | MULE_INTERNAL | WIN866
sjis_to_euc_jp | SJIS | EUC_JP
sjis_to_mic | SJIS | MULE_INTERNAL
sjis_to_utf8 | SJIS | UTF8
windows_1258_to_utf8 | WIN1258 | UTF8
uhc_to_utf8 | UHC | UTF8
utf8_to_big5 | UTF8 | BIG5
utf8_to_euc_cn | UTF8 | EUC_CN
utf8_to_euc_jp | UTF8 | EUC_JP
utf8_to_euc_kr | UTF8 | EUC_KR
utf8_to_euc_tw | UTF8 | EUC_TW
utf8_to_gb18030 | UTF8 | GB18030
utf8_to_gbk | UTF8 | GBK
utf8_to_iso_8859_1 | UTF8 | LATIN1
utf8_to_iso_8859_10 | UTF8 | LATIN6
utf8_to_iso_8859_13 | UTF8 | LATIN7
utf8_to_iso_8859_14 | UTF8 | LATIN8
utf8_to_iso_8859_15 | UTF8 | LATIN9
utf8_to_iso_8859_16 | UTF8 | LATIN10
utf8_to_iso_8859_2 | UTF8 | LATIN2
utf8_to_iso_8859_3 | UTF8 | LATIN3
utf8_to_iso_8859_4 | UTF8 | LATIN4
utf8_to_iso_8859_5 | UTF8 | ISO_8859_5
utf8_to_iso_8859_6 | UTF8 | ISO_8859_6
utf8_to_iso_8859_7 | UTF8 | ISO_8859_7
utf8_to_iso_8859_8 | UTF8 | ISO_8859_8
utf8_to_iso_8859_9 | UTF8 | LATIN5
utf8_to_johab | UTF8 | JOHAB
utf8_to_koi8_r | UTF8 | KOI8R
utf8_to_koi8_u | UTF8 | KOI8U
utf8_to_sjis | UTF8 | SJIS
utf8_to_windows_1258 | UTF8 | WIN1258
utf8_to_uhc | UTF8 | UHC
utf8_to_windows_1250 | UTF8 | WIN1250
utf8_to_windows_1251 | UTF8 | WIN1251
utf8_to_windows_1252 | UTF8 | WIN1252
utf8_to_windows_1253 | UTF8 | WIN1253
utf8_to_windows_1254 | UTF8 | WIN1254
utf8_to_windows_1255 | UTF8 | WIN1255
utf8_to_windows_1256 | UTF8 | WIN1256
utf8_to_windows_1257 | UTF8 | WIN1257
utf8_to_windows_866 | UTF8 | WIN866
utf8_to_windows_874 | UTF8 | WIN874
windows_1250_to_iso_8859_2 | WIN1250 | LATIN2
windows_1250_to_mic | WIN1250 | MULE_INTERNAL
windows_1250_to_utf8 | WIN1250 | UTF8
windows_1251_to_iso_8859_5 | WIN1251 | ISO_8859_5
windows_1251_to_koi8_r | WIN1251 | KOI8R
windows_1251_to_mic | WIN1251 | MULE_INTERNAL
windows_1251_to_utf8 | WIN1251 | UTF8
windows_1251_to_windows_866 | WIN1251 | WIN866
windows_1252_to_utf8 | WIN1252 | UTF8
windows_1256_to_utf8 | WIN1256 | UTF8
windows_866_to_iso_8859_5 | WIN866 | ISO_8859_5
windows_866_to_koi8_r | WIN866 | KOI8R
windows_866_to_mic | WIN866 | MULE_INTERNAL
windows_866_to_utf8 | WIN866 | UTF8
windows_866_to_windows_1251 | WIN866 | WIN
windows_874_to_utf8 | WIN874 | UTF8
euc_jis_2004_to_utf8 | EUC_JIS_2004 | UTF8
utf8_to_euc_jis_2004 | UTF8 | EUC_JIS_2004
shift_jis_2004_to_utf8 | SHIFT_JIS_2004 | UTF8
utf8_to_shift_jis_2004 | UTF8 | SHIFT_JIS_2004
euc_jis_2004_to_shift_jis_2004 | EUC_JIS_2004 | SHIFT_JIS_2004
shift_jis_2004_to_euc_jis_2004 | SHIFT_JIS_2004 | EUC_JIS_2004

¹

Имена перекодировок следуют стандартной схеме именования: к официальному названию исходной кодировки, в котором все не буквенно-цифровые символы заменяются подчеркиваниями, добавляется _to_, за которым следует аналогично обработанное имя целевой кодировки. Таким образом, эти имена иногда отличаются от общепринятых названий, показанных в Таблице 1.

Дополнительные источники информации

Рекомендуемые источники для начала изучения различных видов систем кодирования.

CJKV Information Processing: Chinese, Japanese, Korean & Vietnamese Computing
Содержит подробные объяснения по EUC_JP, EUC_CN, EUC_KR, EUC_TW.

http://www.unicode.org/
Сайт Консорциума Unicode.

RFC 3629
UTF-8 (8-битный формат преобразования UCS/Unicode) определен здесь.

Регулярные задачи обслуживания базы данных

QHB, как и любому другому программному обеспечению для баз данных, для достижения оптимальной производительности требуется, чтобы определенные задачи выполнялись регулярно. Разбираемые здесь задачи являются обязательными, но они по своей сути периодически повторяющиеся и их можно легко автоматизировать с помощью стандартных инструментов, например скриптов cron. Ответственность за установку соответствующих скриптов и проверку их успешного выполнения лежит на администраторе баз данных.

Одной из очевидных задач обслуживания является создание на регулярной основе резервных копий данных. Без свежей резервной копии у вас нет шансов на восстановление системы после катастрофы (сбой диска, пожар, ошибочное удаление критической таблицы и т. д.). Механизмы резервного копирования и восстановления, доступные в QHB, подробно рассматриваются в главе Резервное копирование и восстановление.

Другой основной категорией задачи обслуживания является периодическая «очистка» базы данных. Эта задача рассматривается в разделе Регулярная очистка. С ней тесно связана задача обновления статистики, которая будет использоваться планировщиком запросов, как описано в подразделе Обновление статистики планировщика.

Другая задача, которая может потребовать периодического внимания, — это управление файлами журналов. Она рассматривается в разделе Обслуживание файлов журнала.

QHB нетребовательна в обслуживании по сравнению с другими системами управления базами данных. Тем не менее надлежащее внимание к данным задачам позволит обеспечить комфортный и продуктивный опыт работы с этой системой.

Регулярная очистка

Базы данных QHB требуют периодического обслуживания, известного как очистка. Для многих установок достаточно, чтобы очистку выполнял процесс «Автовакуум», описываемый в подразделе Процесс «Автовакуум». Возможно, для получения наилучших результатов в вашей системе вам придется настроить описанные там параметры автоматической очистки. Некоторые администраторы баз данных захотят дополнить или заменить операции автоочистки управляемыми вручную командами VACUUM, которые обычно выполняются по расписанию с помощью скриптов cron. Чтобы правильно настроить очистку вручную, важно понимать вопросы, рассматриваемые в следующих нескольких подразделах. Администраторам, которые полагаются на автоочистку, все равно рекомендуется ознакомится с этим материалом, чтобы лучше понять как настроить процесс «Автовакуум».

Основы очистки

Команда QHB VACUUM должна регулярно обрабатывать каждую таблицу по нескольким причинам:

Для восстановления или повторного использования дискового пространства, занятого измененными или удаленными строками.

Для обновления статистики данных, используемой планировщиком запросов QHB.

Для обновления карты видимости, которая ускоряет сканирование только по индексу.

Для защиты от потери очень старых данных из-за зацикливания идентификаторов транзакций или мультитранзакций.

Каждая из этих причин требует выполнение операций VACUUM с различной частотой и в разном объеме, что будет рассмотрено в следующих подразделах.

Существует два варианта VACUUM: стандартная команда VACUUM и VACUUM FULL. Команда VACUUM FULL может освободить больше места на диске, но работает намного медленнее. Кроме того, стандартная форма VACUUM может работать параллельно с другими операциями базы данных. (Команды вроде SELECT, INSERT, UPDATE и DELETE продолжат нормально функционировать, но вот изменить определение таблицы с помощью команд вроде ALTER TABLE во время очистки нельзя.) VACUUM FULL требует для таблиц, с которыми работает, блокировки ACCESS EXCLUSIVE и поэтому не может выполняться параллельно с другим использованием таблицы. Поэтому, как правило, администраторам стоит в основном использовать стандартную команду VACUUM и избегать VACUUM FULL.

Команда VACUUM создает значительный объем трафика ввода/вывода, что может привести к снижению производительности других активных сеансов. Существуют параметры конфигурации, которые можно регулировать с целью снижения влияния фоновой очистки на производительность — см. подраздел Задержка очистки по стоимости.

Восстановление дискового пространства

В QHB команды UPDATE или DELETE не сразу удаляют старую версию обрабатываемой строки. Этот подход необходим для получения преимуществ многоверсионного управления параллельным доступом (MVCC; см. главу Управление параллельным доступом): нельзя удалять версию строки, пока она потенциально может быть видна другим транзакциям. Но в конце концов устаревшая или удаленная версия строки перестает интересовать все транзакции. Занимаемое ей пространство должно стать доступным для повторного использования новыми строками, чтобы избежать неограниченного роста потребности в дисковом пространстве. Это достигается с помощью команды VACUUM.

Стандартная форма VACUUM удаляет неиспользуемые версии строк в таблицах и индексах и помечает пространство, доступное для повторного использования в будущем. Однако она не вернет пространство операционной системе, за исключением особого случая, когда одна или несколько страниц в конце таблицы становятся полностью свободными, и можно легко получить эксклюзивную блокировку таблицы. Напротив, VACUUM FULL активно уплотняет таблицы, записывая полную новую версию файла таблицы без неиспользуемого пространства. Это значительно уменьшает размер таблицы, но может занять очень много времени. Кроме того, вплоть до завершения операции для новой копии таблицы требуется дополнительное дисковое пространство.

Обычно цель регулярной очистки состоит в том, чтобы выполнять стандартную команду VACUUM достаточно часто, во избежание необходимости запуска VACUUM FULL. Фоновый процесс «Автовакуум» пытается работать таким же образом и на самом деле никогда не задействует VACUUM FULL. Идея такого подхода состоит не в том, чтобы поддерживать минимальный размер таблиц, а в том, чтобы поддерживать стабильно равномерное использование дискового пространства: каждая таблица занимает пространство, эквивалентное ее минимальному размеру, плюс тот объем, который был занят между очистками. Хотя VACUUM FULL и можно использовать для уменьшения таблицы до минимального размера и возврата дискового пространства в операционную систему, в этом нет особого смысла, если в будущем таблица снова будет расти. Таким образом, для обработки активно изменяемых таблиц стандартные прогоны VACUUM с умеренной частотой предпочтительнее редких прогонов VACUUM FULL.

Некоторые администраторы предпочитают планировать очистку самостоятельно, например, выполняя всю работу ночью при низкой нагрузке. Сложность выполнения очистки по фиксированному расписанию состоит в том, что если в таблице неожиданно происходит всплеск активных изменений, она может раздуться до такой степени, что для освобождения места действительно понадобится VACUUM FULL. Использование процесса «Автовакуум» облегчает эту задачу, поскольку данный процесс планирует очистку динамически, основываясь на интенсивности изменений. Неразумно полностью выключать этот процесс, если у вас нет стопроцентно предсказуемой рабочей нагрузки. Одним из возможных компромиссов является установка в параметрах процесса «Автовакуум» таких значений, чтобы он реагировал только на необычайно интенсивные изменения, тем самым не давая системе выйти из-под контроля, тогда как команды VACUUM, запускаемые по расписанию, должны выполнять основную часть работы при обычной нагрузке.

Для тех, кто не использует автоочистку, типичный подход — запланировать запуск VACUUM для всей базы данных один раз в сутки в течение периода наименьшего использования, по мере необходимости дополняя его более частой очисткой интенсивно изменяемых таблиц. (В некоторых установках с чрезвычайно высокой частотой изменений самые загруженные таблицы очищаются каждые несколько минут.) Если у вас в кластере несколько баз данных, не забудьте настроить VACUUM для каждой; здесь может пригодиться программа vacuumdb.

Совет
Результаты простой команды VACUUM могут быть неудовлетворительными, если в результате массового изменения или удаления в таблице содержится большое количество неиспользуемых версий строк. Если у вас есть такая таблица и вам нужно освободить занимаемое ею лишнее дисковое пространство, вам нужно будет использовать VACUUM FULL, или, как вариант, CLUSTER или один из вариантов ALTER TABLE с перезаписью таблицы. Эти команды переписывают совершенно новую копию таблицы и строят для нее новые индексы. Все эти варианты требуют блокировку ACCESS EXCLUSIVE. Обратите внимание, что они также временно используют дополнительное дисковое пространство, приблизительно равное размеру таблицы, поскольку старые копии таблицы и индексов невозможно освободить, пока не будут созданы новые.

Совет
Если у вас есть таблица, все содержимое которой периодически удаляется, попробуйте сделать это с помощью TRUNCATE, а не с помощью команд DELETE и последующей VACUUM. Команда TRUNCATE немедленно удаляет все содержимое таблицы, не требуя последующего выполнения VACUUM или VACUUM FULL для восстановления неиспользуемого дискового пространства. Недостатком этого способа является нарушение строгой семантики MVCC.

Обновление статистики планировщика

При создании хороших планов запросов планировщик запросов QHB полагается на статистическую информацию о содержимом таблиц. Эта статистика собирается с помощью команды ANALYZE, которую можно вызвать отдельно или в качестве необязательного шага в VACUUM. Важно иметь достаточно точную статистику, иначе неверный выбор планов выполнения может значительно снизить производительность базы данных.

Фоновый процесс «Автовакуум», если он включен, будет автоматически запускать команды ANALYZE всякий раз, когда содержимое таблицы изменяется в достаточной степени. Однако администраторы могут предпочесть прибегнуть к операциям ANALYZE, запланированным вручную, особенно если известно, что интенсивность изменения таблицы не повлияет на статистику по «интересным» столбцам. Процесс «Автовакуум» же планирует запуск ANALYZE строго в зависимости от количества добавленных или измененных строк; он не знает, приведет ли это к значимым статистическим изменениям.

Изменение кортежей в партициях и дочерних таблицах не запускает анализ родительской таблицы. Если родительская таблица пуста или редко изменяется, автоочистка может вообще никогда не обрабатывать ее, и статистика по иерархии наследования в целом собираться не будет. Чтобы поддерживать эту статистику актуальной, необходимо запускать ANALYZE для родительской таблицы вручную.

Как и в случае с очисткой для восстановления пространства, частые обновления статистики более полезны для интенсивно изменяемых таблиц, чем для изменяемых редко. Но даже для интенсивно изменяемой таблицы обновление статистики может не понадобиться, если статистическое распределение данных не особо меняется. Как правило, достаточно просто прикинуть, насколько изменяются минимальное и максимальное значения столбцов в таблице. Например, столбец timestamp, в котором содержится время изменения строки, будет иметь постоянно увеличивающееся максимальное значение при добавлении и изменении строк; для такого столбца, вероятно, потребуются более частые обновления статистики, чем, скажем, для столбца, содержащего URL-адреса страниц, к которым обращались на сайте. Столбец URL может изменяться так же часто, но статистическое распределение его значений, скорее всего, изменяется слабо.

Можно запустить ANALYZE для определенных таблиц и даже только для определенных столбцов таблицы, поэтому существует возможность обновлять некоторые статистические данные чаще, чем другие, если этого требует ваше приложение. На практике, однако, обычно лучше всего проанализировать всю базу данных, потому что это быстрая операция. Команда ANALYZE использует статистически случайную выборку строк таблицы, а не читает каждую строку.

Совет
Несмотря на то, что подстройка частоты ANALYZE для каждого столбца, возможно, не очень продуктивна, может оказаться целесообразным выполнить для каждого столбца настройку уровня детализации статистики, собираемой ANALYZE. Столбцы, которые интенсивно используются в WHERE и имеют очень нерегулярное распределение данных, могут потребовать более детальной гистограммы данных, чем остальные столбцы. Для этого можно выполнить команду ALTER TABLE SET STATISTICS или изменить значение по умолчанию для всей базы данных, используя параметр конфигурации default_statistics_target.

Также по умолчанию доступная информация об избирательности функций ограничена. Однако если вы создадите объект статистики или индекс по выражению, использующий вызов функции, о ней будет собрана полезная статистика, которая может значительно улучшить планы запросов, использующие индекс по выражению.

Совет
Фоновый процесс «Автовакуум» не запускает команды ANALYZE для сторонних таблиц, так как не имеет возможности определить, как часто это следует делать. Если для правильного планирования вашим запросам требуется статистика по сторонним таблицам, рекомендуется запускать для них команды ANALYZE вручную по подходящему расписанию.

Совет
Фоновый процесс «Автовакуум» не запускает команды ANALYZE для партиционированных таблиц. Родительские таблицы в иерархии наследования будут проанализированы, только если меняются данные в самом родителе — изменения в дочерних таблицах не запускают автоанализ в родительской таблице. Если для правильного планирования вашим запросам требуется статистика по родительским таблицам, необходимо периодически запускать для них команды ANALYZE вручную, чтобы поддерживать эту статистику актуальной.

Обновление карты видимости

Операция очистки поддерживает карты видимости для всех таблиц, чтобы отслеживать, какие страницы содержат только кортежи, заведомо видимые всем активным транзакциям (и всем будущим транзакциям, пока страница не будет снова изменена). Это преследует две цели. Во-первых, сама очистка может пропустить такие страницы при следующем запуске, так как там нечего убирать.

Во-вторых, карта видимости позволяет QHB отвечать на некоторые запросы, используя только индекс и не обращаясь к нижележащей таблице. Поскольку индексы QHB не содержат информации о видимости строки, при обычном сканировании по индексу для каждой соответствующей записи индекса извлекается кортеж кучи, чтобы проверить, должен ли он быть видимым для текущей транзакции. С другой стороны, сканирование только по индексу сначала проверяет карту видимости. Если известно, что все кортежи на странице видимы, выборку из кучи можно пропустить. Это особенно полезно при работе с большими наборами данных, где благодаря карте видимости можно избежать лишних обращений к диску. Карта видимости значительно меньше кучи, поэтому ее можно легко кэшировать, даже если объем кучи очень велик.

Предотвращение ошибок зацикливания идентификаторов транзакций

Семантика транзакций MVCC в QHB зависит от возможности сравнения номеров идентификаторов транзакций (XID): версия строки с XID добавления, превышающим XID текущей транзакции, находится «в будущем» и не должна быть видимой для текущей транзакции. Но поскольку идентификаторы транзакций имеют ограниченный размер (32 бита), долго работающий (более 4 миллиардов транзакций) кластер столкнется с зацикливанием идентификаторов транзакций: счетчик XID обнуляется, и все транзакции, которые были в прошлом, внезапно оказываются в будущем — то есть их выходные данные становятся невидимыми. Другими словами, произойдет катастрофическая потеря данных. (На самом деле данные все еще там, но это слабое утешение, если нет возможности их получить). Чтобы избежать этого, необходимо очищать каждую таблицу в каждой базе данных не реже одного раза на каждые два миллиарда транзакций.

Причина, по которой периодическая очистка решает проблему, состоит в том, что VACUUM помечает строки как замороженные, указывая на то, что они были добавлены транзакцией, зафиксированной достаточно давно, поэтому эффект добавляющей транзакции будет видим для всех текущих и будущих транзакций. Нормальные XID сравниваются с использованием арифметики модуля 2³². Это означает, что для каждого нормального XID существует два миллиарда «более старых» и два миллиарда «более новых» XID; иными словами, пространство нормальных XID циклично и не имеет конечной точки. Таким образом, после создания версии строки с определенным нормальным XID эта версия окажется «в прошлом» для следующих двух миллиардов транзакций, независимо от того, о каком нормальном XID идет речь. Если после более двух миллиардов транзакций эта версия строки еще существует, она внезапно оказывается в будущем. Чтобы предотвратить это, QHB резервирует специальный XID, FrozenTransactionId, который не следует правилам сравнения обычных XID и всегда считается старше любого обычного XID. Версии замороженных строк воспринимаются как получившие в качестве XID FrozenTransactionId, так что для всех обычных транзакций они будут относиться к «прошлому», независимо от проблем с зацикливанием, и поэтому такие версии строк будут действительны до момента удаления, когда бы это ни произошло.

Примечание
На самом деле XID добавления строки не заменяется на FrozenTransactionId. Вместо этого просто устанавливается битовый флаг, сохраняя исходное значение системного столбца xmin строки для возможной экспертизы в будущем.

Кроме того, системные каталоги могут содержать строки со значением xmin, равным BootstrapTransactionId (1), показывающим, что они были добавлены во время первой фазы qhb_bootstrap. Как и FrozenTransactionId, этот специальный XID считается более старым, чем любой обычный XID.

Параметр vacuum_freeze_min_age указывает, насколько старым должно быть значение XID, чтобы строки с этим XID были заморожены. Увеличение этого параметра поможет избежать ненужной работы, если строки, которые могли бы быть заморожены, вскоре будут снова изменены, но уменьшение этого параметра увеличивает число транзакций, которые могут совершиться, прежде чем таблицу необходимо будет снова очистить.

VACUUM использует карту видимости, чтобы определить, какие страницы таблицы необходимо сканировать. Обычно он пропускает страницы, которые не имеют неиспользуемых версий строк, даже если на этих страницах все еще могут быть версии строк со старыми значениями XID. Поэтому обычные операции VACUUM не всегда замораживают каждую старую версию строки в таблице. Когда это происходит, в итоге VACUUM приходится проводить агрессивную очистку, пропуская только те страницы, которые не содержат ни пустых строк, ни незамороженных значений XID или MXID. Параметр vacuum_freeze_table_age определяет, когда VACUUM выполнит следующие действия: все видимые, но не полностью замороженные страницы сканируются, если число транзакций, прошедших с момента последнего такого сканирования, больше чем vacuum_freeze_table_age минус vacuum_freeze_min_age. Установка в vacuum_freeze_table_age значения 0 заставляет VACUUM всегда использовать эту более агрессивную стратегию.

Максимальное время, в течение которого таблица может оставаться неочищенной, составляет два миллиарда транзакций минус значение vacuum_freeze_min_age во время последней агрессивной очистки. Если бы она оставалась неочищенной дольше, это могло бы привести к потере данных. Чтобы этого не происходило, автоочистка вызывается для любой таблицы, которая может содержать незамороженные строки с XID старше возраста, указанного в параметре конфигурации autovacuum_freeze_max_age. (Это произойдет, даже если автоочистка выключена).

Подразумевается, что если таблица не очищается иным способом, то она будет подвергаться автоочистке примерно через каждые autovacuum_freeze_max_age минус vacuum_freeze_min_age транзакции. Для таблиц, которые регулярно очищаются в целях освобождения пространства, это не имеет большого значения. Однако статические таблицы (включая те, для которых вызываются операции добавления, но не изменения или удаления) нет необходимости очищать для освобождения пространства, поэтому для очень больших статических таблиц может иметь смысл попытаться максимально увеличить интервал между принудительными сеансами автоочистки. Очевидно, что это можно сделать, либо увеличив autovacuum_freeze_max_age, либо уменьшив vacuum_freeze_min_age.

Сколько-нибудь эффективным максимальным значением vacuum_freeze_table_age является 0.95 * autovacuum_freeze_max_age; большее значение будет упираться в возможный максимум. Выставлять значение выше autovacuum_freeze_max_age вообще нет смысла, потому что к этому моменту процедура автоочистки, предотвращающая зацикливание, запустится сама. Таким образом, множитель 0.95 оставляет некоторое пространство для маневра, позволяющее запустить VACUUM вручную до того, как это произойдет. Как правило, в параметре vacuum_freeze_table_age следует устанавливать значение несколько меньшее, чем в autovacuum_freeze_max_age, оставляя промежуток для запуска обычной команды VACUUM по расписанию или автоочистки, вызванной обычными операциями удаления и изменения. Установка слишком близкого значения может повлечь запуск автоочистки для устранения зацикливания, даже если таблица была недавно очищена для освобождения пространства, тогда как при более низких значениях будет чаще проводиться агрессивная очистка.

Единственный недостаток увеличения autovacuum_freeze_max_age (вместе с vacuum_freeze_table_age) заключается в том, что подкаталоги pg_xact и pg_commit_ts кластера баз данных будут занимать больше места, поскольку в них нужно будет сохранять состояние фиксации и (если включен параметр track_commit_timestamp) временную метку всех транзакций возрастом вплоть до предела из autovacuum_freeze_max_age. На статус фиксации тратится по два бита на транзакцию, поэтому если для autovacuum_freeze_max_age установлено максимально допустимое значение в два миллиарда, можно ожидать, что размер pg_xact достигнет примерно половины гигабайта, а pg_commit_ts — около 20 ГБ. Если этот объем малозначим по сравнению с общим объемом базы данных, рекомендуется установить для autovacuum_freeze_max_age максимально допустимое значение. В противном случае установите его в зависимости от объема, который вы готовы отдать под хранилище pg_xact и pg_commit_ts. (Значение по умолчанию, 200 миллионов транзакций, соответствует примерно 50 МБ объема хранилища pg_xact и примерно 2 ГБ объема хранилища pg_commit_ts.)

Единственный недостаток уменьшения vacuum_freeze_min_age заключается в том, что из-за этого VACUUM может делать бесполезную работу: замораживание версии строки — пустая трата времени, если строка вскоре будет изменена (что приведет к получению ей нового XID). Поэтому значение этого параметра должно быть достаточно большим, чтобы строки не замораживались до тех пор, пока их дальнейшее изменение не станет маловероятным.

Чтобы отследить возраст самых старых незамороженных XID в базе данных, VACUUM сохраняет статистику XID в системных таблицах pg_class и pg_database. В частности, столбец relfrozenxid в строке для определенной таблицы в pg_class содержит самый старый XID, оставшийся незамороженным в конце последней операции VACUUM, которая успешно увеличила значение relfrozenxid (обычно это последний агрессивный VACUUM). Аналогично столбец datfrozenxid в строке для определенной базы данных в pg_database является нижней границей незамороженных XID, встречающихся в этой базе данных, — это просто минимальное из значений relfrozenxid каждой таблицы этой базы. Удобный способ проверить эту информацию — выполнить подобные запросы:

SELECT c.oid::regclass as table_name,
       greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');

SELECT datname, age(datfrozenxid) FROM pg_database;

Столбец age показывает количество транзакций от граничного XID до XID текущей транзакции.

Совет
Когда в команде VACUUM задается параметр VERBOSE, VACUUM выводит различную статистику по таблице. В числе прочего сюда входит информация об увеличении значений relfrozenxid и relminmxid. Та же информация вносится в журнал сервера при протоколировании (которое управляется параметром log_autovacuum_min_duration) сообщений об операции VACUUM, выполненной процессом «Автовакуум».

Обычно команда VACUUM сканирует только те страницы, которые были изменены с момента последней очистки, но значение relfrozenxid может увеличиться только после сканирования в таблице каждой страницы, которая может содержать незамороженные XID. Это происходит, когда значение relfrozenxid превышает vacuum_freeze_table_age транзакций, когда используется параметр FREEZE команды VACUUM или когда все страницы, которые еще не полностью заморожены, требуют очистки для удаления неиспользуемых версий строк. Когда VACUUM сканирует в таблице каждую страницу, которая еще не полностью заморожена, она должна задать для age(relfrozenxid) значение, чуть превышающее установленное в vacuum_freeze_min_age (больше на количество транзакций, начатых с момента запуска VACUUM). VACUUM установит для relfrozenxid значение, равное самому старому XID, остающемуся в таблице, поэтому возможно, что итоговое значение будет гораздо новее, чем требовалось. Если ни одна команда VACUUM, увеличивающая значение relfrozenxid, не обработает таблицу до достижения значения autovacuum_freeze_max_age, то вскоре для этой таблицы будет принудительно запущена автоочистка.

Если по какой-то причине автоочистке не удается удалить из таблицы старые XID, то когда самым старым XID базы данных останется сорок миллионов транзакций до точки зацикливания, система начнет выдавать предупреждающие сообщения вроде этого:

WARNING:  database "mydb" must be vacuumed within 39985967 transactions
HINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.
-- ПРЕДУПРЕЖДЕНИЕ: база данных «mydb» должна быть очищена в течение следующих 39985967 транзакций
-- СОВЕТ: Во избежание отключения базы данных выполните VACUUM для всей этой базы.

(Запуск VACUUM вручную должен решить эту проблему, как предлагает совет, но учтите, что VACUUM должен выполнять суперпользователь, иначе она не сможет обработать системные каталоги и, следовательно, не сможет повысить значение datfrozenxid базы данных). Если игнорировать эти предупреждения, система завершит работу и откажется начинать какие-либо новые транзакции, если до зацикливания останется менее 1 миллиона транзакций:

ERROR:  database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT:  Stop the qhbmaster and vacuum that database in single-user mode.
-- ОШИБКА: база данных «mydb» не принимает команды во избежание потери данных вследствие зацикливания
-- СОВЕТ: Остановите qhbmaster и очистите эту базу данных в однопользовательском режиме.

В этом состоянии любые уже выполняемые транзакции могут продолжаться, но запущены могут быть лишь транзакции только для чтения. Операции, модифицирующие записи базы данных или усекающие отношения, завершаться ошибкой. Команда VACUUM по-прежнему может запускаться обычным образом. Вопреки тому, что утверждается в совете, для восстановления нормального функционирования нет необходимости или даже нежелательно останавливать qhbmaster или входить в однопользовательский режим. Вместо этого выполните следующие шаги:

Разрешите старые подготовленные транзакции. Их можно найти, проверив представление pg_prepared_xacts на наличие строк с большим значением age(transactionid). Такие транзакции следует зафиксировать или откатить.

Завершите длительно выполняющиеся открытые транзакции. Их можно найти, проверив представление pg_stat_activity на наличие строк с большим значением age(backend_xid) или age(backend_xmin). Такие транзакции следует зафиксировать или откатить, или можно отменить сеанс с помощью функции pg_terminate_backend.

Удалите все старые слоты репликации. Воспользуйтесь представлением pg_stat_replication, чтобы найти слоты с большим значением age(xmin) или age(catalog_xmin). Во многих случаях такие слоты были созданы для репликации на серверы, которые уже не существуют или уже давно выключены. Если вы удалите слот для сервера, который еще существует и может по-прежнему пытаться подключиться к этому слоту, возможно, эту реплику придется пересобрать.

Выполните VACUUM в целевой базе данных. Самой простой операцией является VACUUM на уровне базы данных; чтобы сократить необходимые для этого время, также можно вручную выполнить команды VACUUM для таблиц с самым старым relminxid. Не используйте VACUUM FULL в этом сценарии, поскольку ему требуется XID, и поэтому он завершится ошибкой, за исключение режима суперпользователя, в котором он вместо этого будет расходовать XID, тем самым повышая риск зацикливания идентификаторов транзакций. Не используйте и VACUUM FREEZE, поскольку он выполнит больше минимального объема работы, необходимого для восстановления нормальной функционирования.

После восстановления нормального функционирования проверьте, что автоочистка правильно сконфигурирована в целевой базе данных, чтобы избежать проблем в будущем.

Примечание
В предыдущих версиях иногда приходилось останавливать qhbmaster и выполнять VACUUM для базы данных в однопользовательском режиме. В типичных сценариях в этом больше нет необходимости, и по возможности этой операции следует избегать, так как она предполагает выключение системы. Кроме того, это более рискованно, поскольку отключает защиту от зацикливания идентификаторов транзакций, предназначенную предотвращать потерю данных. Единственным оправданием использования однопользовательского режима в этом сценарии может служить желание выполнить TRUNCATE или DROP для ненужных таблиц, чтобы избежать необходимости выполнять для них VACUUM. Запас прочности в три миллиона транзакций предусмотрен, чтобы позволить администратору это сделать. Подробную информацию об использовании однопользовательского режима см. на справочной странице qhb.

Мультитранзакции и зацикливание

Идентификаторы мультитранзакций используются для поддержки блокировки строк несколькими транзакциями. Поскольку в заголовке кортежа имеется только ограниченное пространство для хранения информации о блокировках, эта информация кодируется как «идентификатор множественной транзакции», или сокращенно идентификатор мультитранзакции, когда существует несколько транзакций, одновременно блокирующих строку. Информация о том, какие идентификаторы транзакций содержатся в любом конкретном идентификаторе мультитранзакции, хранится отдельно в подкаталоге pg_multixact, а в поле xmax в заголовке кортежа отображается только идентификатор мультитранзакции. Как и идентификаторы транзакций, идентификаторы мультитранзакций реализованы в виде 32-битного счетчика и соответствующего хранилища, которые требуют тщательного управления их возрастом, очистки хранилища и решения проблемы зацикливания. Существует отдельная область хранения, содержащая список членов каждой мультитранзакции, которая тоже использует 32-битный счетчик и которой также необходимо управлять.

Всякий раз, когда VACUUM сканирует какую-либо часть таблицы, она заменяет любой обнаруженный ей идентификатор мультитранзакции старше возраста, заданного в vacuum_multixact_freeze_min_age, на другое значение, которое может быть нулевым значением, идентификатором отдельной транзакции или новым идентификатором мультитранзакции. Для каждой таблицы в pg_class.relminmxid сохраняется самый старый из возможных идентификаторов мультитранзакций, который все еще присутствует в каком-либо кортеже этой таблицы. Если это значение старше возраста, заданного в vacuum_multixact_freeze_table_age, принудительно запускается агрессивная очистка. Как описывалось в предыдущем разделе, агрессивная очистка означает, что будут пропущены только заведомо полностью замороженные страницы. Для определения возраста pg_class.relminmxid можно использовать функцию mxid_age().

Агрессивные операции VACUUM, независимо от того, что их вызывает, гарантированно способны повысить значение relminmxid для таблицы. В конце концов, поскольку сканируются все таблицы во всех базах данных, а самые старые из их значений мультитранзакций увеличиваются, пространство на диске, занятое информацией о старых мультитранзакциях, может освободиться.

В качестве способа защиты агрессивное сканирование при очистке будет выполняться для любой таблицы, возраст мультитранзакций которой больше значения параметра autovacuum_multixact_freeze_max_age. Кроме того, если объем хранилища, занятого членами мультитранзакций, превышает 2 ГБ, агрессивное сканирование будет происходить чаще для всех таблиц, начиная с имеющих самые старые мультитранзакции. Оба этих вида агрессивного сканирования будут выполняться, даже если автоочистка номинально выключена.

Как и в случае с XID, если автоочистке не удается удалить из таблицы старые MXID, система начнет выдавать предупреждающие сообщения когда самым старым MXID базы данных останется сорок миллионов транзакций до точки зацикливания. И опять же, как в случае с XID, если эти предупреждения игнорируются, система откажется генерировать новые MXID, когда до зацикливания останется менее трех миллионов.

Когда заканчиваются MXID, нормальное функционирование можно восстановить практически тем же способом, что и когда заканчиваются XID. Выполните те же шаги, что и в предыдущем подразделе, но со следующими отличиями:

Запущенные и подготовленные транзакции можно игнорировать, если они ни при каких обстоятельствах не могут появиться в мультитранзакции.

Информация о MXID не видима напрямую в системных представлениях, таких как pg_stat_activity; однако поиск старых XID по-прежнему остается хорошим методом определения того, какие транзакции вызывают проблемы с зацикливанием MXID.

Когда заканчиваются XID, блокируются все пишущие транзакции, а когда заканчиваются MXID, блокируется только подмножество пишущих транзакций, а именно те, которые включают блокировки строк, требующие MXID.

Процесс «Автовакуум»

QHB имеет необязательную, но настоятельно рекомендуемую функциональность, называемую автоочисткой, цель которой — автоматизировать выполнение команд VACUUM и ANALYZE. Включенная автоочистка проверяет таблицы, где было добавлено, изменено или удалено большое количество кортежей. В этих проверках используются средства сбора статистики; поэтому автоочистку можно использовать, только если для track_counts установлено значение true. В стандартной конфигурации автоочистка включена, и соответствующие параметры конфигурации установлены надлежащим образом.

Процесс «Автовакуум» на самом деле состоит из нескольких процессов. Существует постоянный фоновый процесс, называемый процессом запуска автовакуума, который отвечает за запуск рабочих процессов автовакуума для всех баз данных. Процесс запуска будет распределять работу по времени, пытаясь запускать по одному рабочему процессу в каждой базе данных каждые autovacuum_naptime секунд. (Следовательно, если в установке имеется N баз данных, новый рабочий процесс будет запускаться каждые autovacuum_naptime/N секунд). Одновременно разрешено запускать не более autovacuum_max_workers рабочих процессов. Если баз данных, требующих обработки, больше значения autovacuum_max_workers, то следующая база данных будет обработана, как только закончится первый рабочий процесс. Каждый рабочий процесс будет проверять каждую таблицу в своей базе данных и по мере надобности выполнять VACUUM и/или ANALYZE. Для мониторинга активности рабочих процессов автовакуума можно установить параметр log_autovacuum_min_duration.

Если за короткий промежуток времени возникает потребность в очистке нескольких больших таблиц, все рабочие процессы автовакуума, могут оказаться надолго заняты очисткой этих таблиц. Это приведет к тому, что другие таблицы и базы данных не будут очищаться, пока какой-либо рабочий процесс не станет доступным. На количество рабочих процессов в одной базе данных ограничений нет, но эти процессы стараются не повторять работу, уже выполненную другими. Обратите внимание, что в рамках ограничений max_connections или superuser_reserved_connections количество выполняющихся рабочих процессов не учитывается.

Таблицы, у которых значение relfrozenxid превышает autovacuum_freeze_max_age транзакций, очищаются всегда (это также относится к тем таблицам, чей максимальный возрастной порог замораживания был изменен с помощью параметров хранения; см. ниже). В противном случае, если количество кортежей, устаревших с момента последнего запуска VACUUM, превышает «порог очистки», таблица очищается. Порог очистки определяется как:

порог очистки = базовый порог очистки + коэффициент масштабирования очистки * число кортежей

где базовый порог очистки равен autovacuum_vacuum_threshold, коэффициент масштабирования очистки — autovacuum_vacuum_scale_factor, а число кортежей — pg_class.reltuples.

Также таблица очищается, если число кортежей, добавленных с момента последней очистки, превышает порог добавления, который определяется как:

порог очистки при добавлении = базовый порог очистки при добавлении + коэффициент масштабирования очистки при добавлении * число кортежей

где базовый порог очистки при добавлении равен autovacuum_vacuum_insert_threshold, а коэффициент масштабирования очистки при добавлении — autovacuum_vacuum_insert_scale_factor. При таких очистках допускается помечать части таблицы как полностью видимые и замораживать кортежи, что может уменьшить объем работы, которую надо будет сделать при последующих очистках. Для таблиц, в которые проводились операции INSERT, но не было или почти не было операций UPDATE/DELETE, может быть выгодно уменьшить параметр autovacuum_freeze_min_age такой таблицы, так как это позволит замораживать кортежи в более ранних очистках. Число устаревших и добавленных кортежей поступает от системы накопительной статистики; оно является приблизительным и обновляется после каждой операции UPDATE, DELETE и INSERT. (Оно приблизительное из-за того, что при высокой нагрузке часть информации может быть потеряна.) Если значение relfrozenxid для таблицы превышает vacuum_freeze_table_age транзакций, выполняется агрессивная очистка для замораживания старых кортежей и увеличения relfrozenxid; в противном случае сканируются только страницы, измененные с момента последней очистки.

Для анализа используется аналогичное условие: порог, определяемый как:

порог анализа = базовый порог анализа + коэффициент масштабирования анализа * число кортежей

сравнивается с общим числом добавленных, измененных или удаленных кортежей с момента последнего запуска ANALYZE.

В партиционированных таблицах кортежи не хранятся напрямую, поэтому такие таблицы не обрабатываются автоочисткой. (Автоочистка обрабатывает партиции таблицы точно так же, как другие таблицы.) К сожалению, это означает, что процесс «Автовакуум» не запускает ANALYZE для партиционированных таблиц, и это может привести к созданию неоптимальных планов для запросов, ссылающихся на статистику партиционированных таблиц. Это затруднение можно обойти, выполняя ANALYZE для партиционированных таблиц вручную после их первого наполнения, а потом всякий раз при значительном изменении распределения данных в их партициях.

Временные таблицы недоступны для автоочистки. Поэтому соответствующие операции очистки и анализа для них должны выполняться в сеансе с помощью команд SQL.

Пороговые значения по умолчанию и коэффициенты масштабирования берутся из файла qhb.conf, но их можно переопределить (как и многие другие параметры управления автоочисткой) для каждой таблицы; дополнительную информацию см. в параграфе Параметры хранения. Если какой-либо параметр был изменен с помощью параметров хранения таблицы, его значение используется при обработке этой таблицы; в противном случае используются глобальные параметры. Более подробную информацию о глобальных параметрах см. в разделе Автоматическая очистка.

Когда запущены несколько рабочих процессов автовакуума, параметры задержки автоочистки по стоимости (см. подраздел Задержка очистки по стоимости) «выравниваются» между всеми выполняющимися процессами, так что общее влияние операций ввода/вывода на систему остается тем же, независимо от количества фактически выполняющихся процессов, Однако любые обрабатываемые ими таблицы, для которых были установлены собственные значения параметров хранения autovacuum_vacuum_cost_delay или autovacuum_vacuum_cost_limit, не учитываются в алгоритме выравнивания нагрузки.

Рабочие процессы автовакуума обычно не блокируют другие команды. Если процесс попытается получить блокировку, которая конфликтует с блокировкой SHARE UPDATE EXCLUSIVE, удерживаемой автоочисткой, ее получение прервет автоочистку. Конфликтующие режимы блокировки перечислены в таблице Конфликтующие режимы блокировки. Однако если автоочистка выполняется для предотвращения зацикливания идентификаторов транзакций (т. е. имя запроса автоочистки в представлении pg_stat_activity заканчивается текстом (to prevent wraparound), то автоматически она не прерывается.

ПРЕДУПРЕЖДЕНИЕ
Регулярно выполняемые команды, которые получают блокировки, конфликтующие с блокировкой SHARE UPDATE EXCLUSIVE (например, ANALYZE), могут вообще не давать автоочистке завершаться.

Регулярная переиндексация

В некоторых ситуациях может быть целесообразно периодически перестраивать индексы с помощью команды REINDEX или ряда отдельных шагов перестройки.

Страницы индекса B-дерева, которые стали полностью пустыми, восстанавливаются для повторного использования. Однако при этом все равно существует вероятность неэффективного использования пространства: если почти все индексные ключи на странице были удалены, но несколько осталось, страница остается зарезервированной. Поэтому шаблон использования, при котором в итоге удаляется большинство, но не все ключи в каждом диапазоне, приведет к неэффективному использованию пространства. Для таких шаблонов использования рекомендуется периодическая переиндексация.

Потенциал раздувания в индексах, отличных от B-деревьев, изучался неглубоко. При использовании любого типа индекса, отличного от B-дерева, рекомендуется периодически отслеживать его физический размер.

Кроме того, для индексов B-деревьев доступ по недавно сконструированному индексу происходит немного быстрее, чем по индексу, который много раз изменялся, потому что логически смежные страницы обычно также физически соседствуют во вновь созданном индексе. (Это соображение не относится к индексам, отличным от B-деревьев). Возможно, периодически переиндексировать таблицы имеет смысл хотя бы для того, чтобы повысить скорость доступа.

Во всех случаях можно легко и безопасно использовать команду REINDEX. Эта команда по умолчанию требует блокировку ACCESS EXCLUSIVE, поэтому зачастую лучше выполнять ее с параметром CONCURRENTLY, который требует только блокировку SHARE UPDATE EXCLUSIVE.

Обслуживание файлов журнала

Никогда не помешает сохранять куда-нибудь вывод журнала сервера баз данных, а не просто сбрасывать его в /dev/null. Вывод журнала бесценен при диагностике проблем.

Примечание
Журнал сервера может содержать конфиденциальную информацию, поэтому его следует защищать, как или где бы он ни хранился и куда бы не передавался. К примеру, некоторые операторы DDL могут содержать незашифрованные пароли или другие сведения аутентификации. В операторах, протоколируемых на уровне ERROR может выводиться исходный код SQL для приложений, а также могут содержаться некоторые фрагменты строк данных. Целевым предназначением журнала как раз и является запись данных, событий и связанной с ними информации, так что это не утечка и не дефект. Поэтому обязательно следует позаботиться о том, чтобы журналы сервера были видимыми только для лиц с соответствующими полномочиями.

Вывод журнала зачастую весьма объемен (особенно на более высоких уровнях отладки), поэтому хранить его бесконечно долго нежелательно. Вам понадобится настроить ротацию файлов журнала так, чтобы новые файлы создавались, а старые удалялись через разумный промежуток времени.

Если вы просто направите в файл stderr утилиты qhb, то получите в нем вывод журнала, но единственный способ опустошить этот файл — это остановить и перезапустить сервер. Это может быть приемлемо, если вы используете QHB в среде разработки, но мало какой производственный сервер сочтет такое поведение приемлемым.

Лучшим подходом является отправка вывода stderr сервера в какую-нибудь программу ротации журналов. Существует встроенное средство ротации журналов, которое можно использовать, установив в файле qhb.conf для параметра конфигурации logging_collector значение true . Параметры управления для этой программы описаны в подразделе Куда протоколировать. Этот подход также можно использовать для получения данных журнала в машиночитаемом формате CSV (comma-separated values, значения, разделенные запятыми).

Как вариант, можно использовать внешнюю программу ротации журналов, если вы уже применяете такую с другим серверным программным обеспечением. Например, с QHB можно использовать утилиту rotatelogs, включенную в дистрибутив Apache. Один из способов сделать это — направить вывод stderr сервера в нужную программу. Если вы запускаете сервер с помощью команды qhb_ctl, то stderr уже будет перенаправлен на stdout, поэтому вам просто нужна команда с вертикальной чертой, например:

qhb_ctl start | rotatelogs /var/log/pgsql_log 86400

Вы можете объединить эти подходы, настроив программу logrotate для сбора файлов журналов, создаваемых встроенным сборщиком журналов QHB. В этом случае сборщик журналов определяет имена и расположение файлов журналов, а logrotate периодически архивирует эти файлы. Запуская ротацию журналов, logrotate нужно, чтобы приложение отправляло дальнейшие выходные данные в новый файл. Обычно это делается с помощью скрипта postrotate, отправляющего сигнал SIGHUP приложению, которое затем повторно открывает файл журнала. В QHB можно вместо этого запустить qhb_ctl с параметром logrotate. Когда сервер получает эту команду, он либо переключается на новый файл журнала, либо повторно открывает существующий файл, в зависимости от конфигурации протоколирования (см. подраздел Куда протоколировать).

Примечание
При использовании статических имен файлов журнала сервер может не открыть файл журнала, если достигнут максимальный лимит открытых файлов или переполнилась таблица файлов. В этом случае сообщения журнала отправляются в старый файл, пока не произойдет успешная ротация журнала. Если logrotate настроена на сжатие файла журнала с последующим удалением, сервер может потерять сообщения, запротоколированные в этот период времени. Чтобы избежать этой проблемы, можно настроить сборщик журналов на динамическое назначение имен файлов журналов и использовать скрипт prerotate, чтобы игнорировать открытые файлы журналов.

Другой производственный подход к управлению выводом журнала — отправить его в syslog и позволить этой службе заниматься ротацией файлов. Для этого установите в qhb.conf в параметре конфигурации log_destination значение syslog (для вывода только в syslog). Затем можно отправлять сигнал SIGHUP демону syslog всякий раз, когда вы хотите заставить его начать запись нового файла журнала. Если вы хотите автоматизировать ротацию журналов, программу logrotate можно настроить и для работы с файлами журналов из syslog.

Однако во многих системах служба syslog не очень надежна, особенно с большими сообщениями журнала; оно может обрезать или удалить сообщения именно тогда, когда они нужны вам больше всего. Кроме того, в Linux syslog будет сбрасывать все сообщения на диск, вызывая снижение производительности. (Для выключения синхронизации можно указать «-» в начале имени файла в файле конфигурации syslog.)

Обратите внимание, что все вышеописанные решения предусматривают запуск новых файлов журнала через настраиваемые интервалы, но не удаление старых, ставших бесполезными файлов. Возможно, вы захотите настроить пакетное задание для периодического удаления старых файлов журнала. Другая возможность — настроить программу ротации так, чтобы старые файлы журналов циклически перезаписывались.

Резервное копирование и восстановление

Как и для всего, что содержит ценные данные, для баз данных QHB следует регулярно проводить резервное копирование. Хотя по сути это простая процедура, важно четко понимать лежащие в ее основе методики и предположения.

Существует три принципиально разных подхода к резервному копированию данных в QHB:

SQL-дамп

Резервное копирование на уровне файловой системы

Непрерывное архивирование

У каждого из них есть свои сильные и слабые стороны, которые рассматриваются в следующих разделах.

SQL-дамп

Идея этого метода дампа состоит в том, чтобы создать файл с командами SQL, которые при передаче обратно на сервер воссоздают базу данных в том же состоянии, в котором она находилась во время создания дампа. QHB предоставляет для этой цели программу-утилиту qhb_dump. Простейшее использование этой программы выглядит следующим образом:

qhb_dump имя_бд > файл_дампа

Как видите, qhb_dump записывает свой результат в стандартный вывод. Ниже мы рассмотрим, чем это может быть полезно. Хотя вышеуказанная команда создает текстовый файл, qhb_dump может создавать файлы и в других форматах, которые допускают параллельную обработку и более точное управление восстановлением объектов.

qhb_dump — обычное клиентское приложение QHB. Это означает, что процедуру резервного копирования можно выполнить с любого удаленного хоста, имеющего доступ к базе данных. Но помните, что qhb_dump работает, не используя специальные разрешения. В частности, ему требуется доступ на чтение ко всем таблицам, для которых выполняется резервное копирование, поэтому для резервного копирования всей базы данных почти всегда требуется запускать ее с правами суперпользователя. (Если у вас недостаточно прав для резервного копирования всей базы данных, вы все равно можете создавать резервные копии тех ее частей, к которым у вас есть доступ, используя такие параметры, как -n схема или -t таблица).

Чтобы указать, к какому серверу баз данных должно обращаться qhb_dump, можно воспользоваться параметрами командной строки -h хост и -p порт. По умолчанию в качестве хоста используется локальный хост или значение, указанное в переменной среды PGHOST. Аналогичным образом порт по умолчанию указывается в переменной среды PGPORT или, если она не задана, значением, принятым по умолчанию. (Для удобства при компиляции серверу обычно по умолчанию назначается то же значение.)

Как и любое другое клиентское приложение QHB, qhb_dump по умолчанию соединяется с базой данных под именем пользователя, совпадающим с именем текущего пользователя операционной системы. Чтобы переопределить это, нужно либо указать параметр -U, либо установить переменную среды PGUSER. Помните, что соединения qhb_dump подчиняются обычным механизмам аутентификации клиента (эти механизмы описаны в главе Аутентификация клиентского приложения).

Важное преимущество qhb_dump перед другими методами резервного копирования, описанными ниже, заключается в том, что выходные данные qhb_dump, как правило, можно загружать в более новые версии QHB, тогда как резервные копии на уровне файлов и непрерывное архивирование жестко привязаны к версии сервера. Кроме того, qhb_dump является единственным методом, который будет работать при переносе базы данных на другую машинную архитектуру, например при переходе с 32-разрядного на 64-разрядный сервер.

Дампы, созданные qhb_dump, внутренне непротиворечивы, то есть дамп представляет собой снимок состояния базы данных на момент запуска qhb_dump. Приложение qhb_dump не блокирует другие операции с базой данных во время своей работы. (Исключение составляют операции, которым требуется эксклюзивная блокировка, как например, большинство форм команды ALTER TABLE.)

Восстановление дампа

Текстовые файлы, созданные qhb_dump, предназначены для чтения программой psql. Общая форма команды для восстановления дампа:

psql имя_бд < файл_дампа

где файл_дампа — это файл, содержащий вывод команды qhb_dump. Эта команда не будет создавать базу данных имя_бд, поэтому ее нужно создать самостоятельно из шаблона базы template0 перед выполнением psql (например, с помощью команды createdb -T template0 имя_бд). psql поддерживает аналогичные qhb_dump параметры для указания сервера баз данных, к которому нужно подключиться, и имени пользователя. Подробную информацию см. на справочной странице утилиты psql. Дампы нетекстовых файлов восстанавливаются с помощью утилиты qhb_restore.

Перед восстановлением SQL-дампа все пользователи, которым в выгруженной базе данных принадлежали объекты или права на них, уже должны существовать. Если их нет, при восстановлении не удастся воссоздать объекты с первоначальными владельцами и/или правами. (Иногда это желаемое поведение, но обычно это не так).

По умолчанию скрипт psql будет продолжать выполняться даже после возникновения ошибки SQL. Если запустить psql с переменной ON_ERROR_STOP, установленной для изменения этого поведения, то при ошибке SQL psql завершится кодом 3:

psql --set ON_ERROR_STOP=on имя_бд < файл_дампа

В любом случае у вас будет только частично восстановленная база данных. В качестве альтернативы можно указать, что весь дамп должен быть восстановлен за одну транзакцию, чтобы восстановление либо полностью завершилось, либо полностью отменилось. Этот режим можно указать, передав в psql параметры командной строки -1 или --single-transaction. При использовании этого режима помните, что даже небольшая ошибка может послужить причиной отката восстановления, выполняющегося уже в течение многих часов. Однако, возможно, это все же предпочтительнее, чем вручную чистить сложную базу данных после частично восстановленного дампа.

Способность qhb_dump и psql использовать каналы ввода/вывода позволяет скопировать базу данных напрямую с одного сервера на другой, например:

qhb_dump -h хост1 имя_бд | psql -h хост2 имя_бд

ВАЖНО!
Дампы, создаваемые qhb_dump, связаны с базой template0. Это означает, что любые языки, процедуры и т. д., добавленные через template1, также будут выгружены qhb_dump. В результате при восстановлении, если вы используете настроенный шаблон template1, следует создать пустую базу данных из template0, как показано в примере выше.

После восстановления резервной копии целесообразно запустить ANALYZE для каждой базы данных, чтобы оптимизатор запросов имел полезную статистику. Дополнительную информацию см. в подразделах Обновление статистики планировщика и Процесс «Автовакуум». Дополнительные советы о том, как эффективно загружать большие объемы данных в QHB, см. в разделе Заполнение базы данных.

Использование qhb_dumpall

qhb_dump одновременно создает дамп только одной базы данных и не включает в него информацию о ролях или табличных пространствах (потому что она относится ко всему кластеру, а не к самой базе данных). Для удобства создания дампа всего содержимого кластера баз данных предусмотрена программа qhb_dumpall. qhb_dumpall выполняет резервное копирование каждой базы данных в заданном кластере, а также сохраняет данные уровня кластера, например определения ролей и табличных пространств. Простой вариант использования этой программы:

qhb_dumpall > файл_дампа

Полученный дамп можно восстановить с помощью psql:

psql -f файл_дампа qhb

(На самом деле в качестве начальной базы можно указать имя любой существующей базы данных, но если вы загружаете в пустой кластер, обычно следует использовать qhb.) Восстановление дампа qhb_dumpall необходимо проводить с правами суперпользователя, поскольку это требуется для восстановления информации о ролях и табличных пространствах. Если вы используете табличные пространства, убедитесь, что их пути в дампе соответствуют новой среде.

qhb_dumpall работает, выполняя команды для воссоздания ролей, табличных пространств и пустых баз данных, а затем вызывая qhb_dump для каждой базы данных. Это означает, что хотя каждая база данных будет внутренне согласованной, снимки разных баз данных не синхронизируются.

Данные уровня кластера можно выгрузить отдельно, используя параметр qhb_dumpall --globals-only. Это необходимо для полного резервного копирования кластера при выполнении команды qhb_dump в отдельных базах данных.

Обработка больших баз данных

Некоторые операционные системы имеют ограничения максимального размера файла, которые вызывают проблемы при создании больших выходных файлов qhb_dump. К счастью, qhb_dump может записывать в стандартный вывод, поэтому для решения этой потенциальной проблемы можно воспользоваться стандартными инструментами Unix . Есть несколько возможных методов:

Используйте сжатые дампы. Вы можете использовать предпочитаемую программу сжатия, например, gzip:

qhb_dump имя_бд | gzip > имя_файла.gz

Восстановить дамп можно так:

gunzip -c имя_файла.gz | psql имя_бд

или так:

cat имя_файла.gz | gunzip | psql имя_бд

Используйте split. Команда split позволяет разбить вывод на более мелкие файлы, приемлемые по размеру для нижележащей файловой системы. Например, чтобы получить блоки по 2 гигабайта, выполните:

qhb_dump имя_бд | split -b 2G - имя_файла

Восстановить дамп можно так:

cat имя_файла* | psql имя_бд

Разбивку GNU можно использовать вместе с gzip:

qhb_dump имя_бд | split -b 2G --filter='gzip > $FILE.gz'

Восстановить такой дамп можно командой zcat.

Используйте специальный формат дампа qhb_dump. Если QHB была собрана в системе с установленной библиотекой сжатия zlib, специальный формат дампа будет сжимать данные по мере их записи в выходной файл. Вследствие этого размер файла дампа будет такой же, как при использовании gzip, но у него есть дополнительное преимущество: таблицы можно восстанавливать выборочно. Следующая команда создает дамп базы данных в специальном формате:

qhb_dump -Fc имя_бд > имя_файла

Дамп в специальном формате не является скриптом для psql, его следует восстанавливать с помощью qhb_restore, например:

qhb_restore -d имя_бд имя_файла

Подробную информацию см. на справочных страницах утилит qhb_dump и qhb_restore.

Для очень больших баз данных может потребоваться объединить split с одним из двух других подходов.

Используйте функцию параллельной выгрузки qhb_dump. Чтобы ускорить выгрузку большой базы данных, можно воспользоваться параллельным режимом qhb_dump. Этот режим позволит сбросить несколько таблиц одновременно. С помощью параметра -j можно управлять количеством потоков. Параллельные выгрузки поддерживаются только для архива в формате каталога.

qhb_dump -j число -F d -f выходной.каталог имя_бд

Для восстановления дампа в параллельном режиме можно использовать qhb_restore -j. Это будет работать для любого архива, созданного в специальном формате или в формате каталога, независимо от того, был ли он создан командой qhb_dump -j.

Резервное копирование на уровне файловой системы

Альтернативная стратегия резервного копирования заключается в прямом копировании файлов, которые QHB использует для хранения данных в базе; в разделе Создание кластера баз данных объясняется, где расположены эти файлы. Для резервного копирования файловой системы можно использовать любой метод, например:

tar -cf backup.tar /var/lib/qhb/data

Однако есть два ограничения, которые делают этот метод непрактичным или, по крайней мере, уступающим методу qhb_dump:

Сервер баз данных должен быть выключен, чтобы получить рабочую резервную копию. Промежуточные меры, такие как запрещение всех подключений, не сработают (отчасти потому, что tar и подобные ему инструменты не делают атомарный снимок состояния файловой системы, но еще и из-за буферизации внутри сервера). Информацию об остановке сервера можно найти в разделе Завершение работы сервера. Излишне говорить, что перед восстановлением данных сервер также необходимо выключить.

Если вы подробно ознакомились со структурой файловой системы базы данных, у вас может возникнуть соблазн попытаться выполнить резервное копирование или восстановление только отдельных таблиц или баз данных из соответствующих файлов или каталогов. Это не получится, потому что информацию, содержащуюся в этих файлах, нельзя использовать без файлов журнала транзакций pg_xact/*, которые содержат статус фиксации всех транзакций. Файл таблицы можно использовать только с этой информацией. Конечно, также невозможно восстановить только таблицу и связанные данные pg_xact, потому что это сделает все остальные таблицы в кластере баз данных бесполезными. Таким образом, резервные копии файловой системы работают только для полного резервного копирования и восстановления всего кластера баз данных.

Альтернативный подход к резервному копированию файловой системы заключается в создании «согласованного снимка» каталога данных, если файловая система поддерживает эту функциональность (и вы уверены, что она реализована правильно). Типичная процедура — сделать «замороженный снимок» тома, содержащего базу данных, затем скопировать весь каталог данных (а не только его части; см. выше) из моментального снимка на устройство резервного копирования, а затем освободить замороженный снимок. Эта операция выполнима даже при работающем сервере баз данных. Однако созданная таким образом резервная копия сохраняет файлы базы данных в таком состоянии, как если бы сервер баз данных был неправильно остановлен; поэтому, когда вы запустите сервер баз данных с резервной копией данных, он будет считать, что предыдущий экземпляр сервера завершился аварийно, и воспроизведет данные журнала WAL. Это не проблема, однако это стоит иметь в виду (и обязательно включить файлы WAL в свою резервную копию). Чтобы сократить время восстановления, перед созданием снимка можно выполнить команду CHECKPOINT.

Если ваша база данных распределена по нескольким файловым системам, то способа получить строго одновременно замороженные снимки всех томов может не оказаться. Например, если файлы данных и журнал WAL находятся на разных дисках или если табличные пространства находятся в разных файловых системах, использование резервного копирования со снимками может оказаться невозможным, поскольку снимки должны быть одновременными. Внимательно прочитайте документацию по файловой системе, прежде чем доверять технологии согласованных снимков в таких ситуациях.

Если одновременные снимки невозможны, один из вариантов — отключить сервер баз данных на достаточно длительное время, чтобы скопировать все замороженные снимки. Другим вариантом является выполнение непрерывного архивирования (см. подраздел Создание базовой резервной копии), поскольку такие резервные копии не пострадают от изменений файловой системы во время резервного копирования. Это требует включения непрерывного архивирования только на время процесса резервного копирования; восстановление выполняется с помощью процедуры восстановления из непрерывного архива (см. подраздел Восстановление из непрерывной архивной копии).

Другой вариант — использовать для резервного копирования файловой системы rsync. Для этого нужно сначала запустить rsync во время работы сервера баз данных, а затем завершить работу сервера баз данных на время, достаточное, чтобы выполнить rsync --checksum. (Ключ --checksum необходим, потому что rsync различает время с точностью только до секунд). Второй запуск rsync отработает быстрее, чем первый, потому что ему останется относительно мало данных для передачи, и конечный результат будет согласованным, поскольку сервер был выключен. Этот метод позволяет выполнять резервное копирование файловой системы с минимальным временем простоя.

Обратите внимание, что резервная копия файловой системы обычно больше, чем SQL-дамп. (Например, qhb_dump не нужно записывать содержимое индексов, только команды для их восстановления). Однако создание резервной копии файловой системы может выполняться быстрее.

Непрерывное архивирование и восстановление на момент времени (PITR)

Во время работы QHB неукоснительно эксплуатирует журнал упреждающей записи (WAL) в подкаталоге pg_wal/ каталога данных кластера. В журнал записываются все изменения, внесенные в файлы данных базы. Этот журнал существует главным образом в целях обеспечения безопасности при сбоях: в случае сбоя системы базы данных можно восстановить до состояния согласованности путем «воспроизведения» записей журнала, созданных с момента последней контрольной точки. Однако наличие журнала позволяет использовать третью стратегию резервного копирования баз данных: можно объединить резервное копирование на уровне файловой системы с резервным копированием файлов WAL. Если требуется восстановление, то чтобы привести систему к актуальному состоянию, сначала восстанавливается резервная копия файловой системы, а затем воспроизводятся изменения из резервных копий файлов WAL. Этот подход сложнее в управлении, чем любой из предыдущих подходов, но он имеет некоторые существенные преимущества:

Не требуется идеально согласованное резервное копирование файловой системы в качестве отправной точки. Любое внутреннее несоответствие в резервной копии будет исправлено при воспроизведения журнала (это не сильно отличается от того, что происходит во время восстановления после сбоя). Поэтому не нужна возможность создания снимков файловой системы, достаточно tar или аналогичного инструмента архивирования.

Поскольку для воспроизведения можно комбинировать неограниченно длинную последовательность файлов WAL, непрерывную резервную копию можно получить, просто продолжив архивирование файлов WAL. Это особенно ценно для больших баз данных, где не всегда удобно делать полное резервное копирование.

Нет необходимости воспроизводить записи WAL до самого конца. Можно в любой момент остановить воспроизведение и получить согласованный снимок базы данных на заданный момент времени. Таким образом, этот метод поддерживает восстановление на определенный момент времени: можно восстановить состояние базы данных на любое время с момента создания резервной копии.

Если непрерывно передавать последовательность файлов WAL другой машине, в которую был загружен тот же файл базовой резервной копии, мы получим систему теплого резерва: в любой момент мы можем запустить вторую машину, и она будет иметь почти текущую копию базы данных.

Примечание
qhb_dump и qhb_dumpall не создают резервные копии на уровне файловой системы и не могут использоваться как часть решения для непрерывного архивирования. Это логические дампы, и они не содержат достаточно информации, необходимой для воспроизведения WAL.

Как и в случае простого метода резервного копирования файловой системы, этот метод может поддерживать только восстановление всего кластера баз данных, но не его части. Кроме того, для этого требуется большое архивное хранилище: базовая резервная копия может быть громоздкой, а занятая система будет генерировать много мегабайт трафика WAL, который необходимо архивировать. Тем не менее во многих ситуациях, когда требуется высокая надежность, этот метод резервного копирования предпочтителен.

Для успешного восстановления с использованием непрерывного архивирования (также называемого многими разработчиками баз данных «оперативным резервным копированием») нужна непрерывная последовательность архивированных файлов WAL, которая начинается как минимум с момента запуска резервного копирования. Поэтому для начала необходимо настроить и протестировать процедуру архивации файлов WAL до того, как сделать первую базовую резервную копию. Соответственно, сначала рассмотрим механизм архивирования файлов WAL.

Настройка архивирования WAL

В абстрактном смысле работающая система QHB создает бесконечно длинную последовательность записей WAL. Система физически делит эту последовательность на файлы сегментов WAL, которые обычно имеют размер 16 МБ (хотя размер сегмента можно изменить во время создания нового кластера базы данных с помощью qhb_bootstrap (или initdb)). Файлы сегментов получают числовые имена, которые отражают их положение в абстрактной последовательности WAL. Когда архивация WAL не используется, система обычно создает только несколько файлов сегментов, а затем «перерабатывает» их, меняя имена ставших ненужными файлов WAL на новые, с большими номерами. Предполагается, что файлы сегментов, содержимое которых предшествует последней контрольной точке, больше не представляют интереса и могут быть использованы повторно.

При архивировании данных WAL нужно собрать содержимое каждого файла сегмента после его заполнения и где-то сохранить эти данные, прежде чем файл сегмента будет переработан для повторного использования. В зависимости от приложения и доступного оборудования, существует много разных способов «сохранения данных где-то»: можно скопировать файлы сегментов в каталог, смонтированный NFS на другом компьютере, записать их на ленточный накопитель (убедившись, что есть способ идентифицировать исходное имя каждого файла), или собрать их вместе и записать на компакт-диски, или что-то совершенно другое. Чтобы обеспечить администратору баз данных гибкость в этом вопросе, QHB старается не делать никаких предположений о том, как будет осуществляться архивирование. Вместо этого QHB позволяет администратору указать команду оболочки или библиотеку архивирования, которая будет выполняться для копирования заполненного файла сегмента туда, куда он должен попасть. Это может быть просто команда оболочки, использующая cp, или она может вызывать сложную функцию на C — все зависит от администратора.

Чтобы включить архивирование WAL, установите для параметра конфигурации wal_level значение replica или выше, для параметра archive_mode значение on, укажите требуемую команду оболочки в параметре конфигурации archive_command или требуемую библиотеку в параметре конфигурации archive_library. На практике эти настройки всегда будут помещаться в файл qhb.conf.

В archive_command %p заменяется путем к файлу для архивирования, а %f — только именем файла. (Путь указывается относительно текущего рабочего каталога, т. е. каталога данных кластера). Если в команду нужно вставить сам символ %, используйте %%. Простейшая полезная команда выглядит примерно так:

archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'  # Unix

Она скопирует архивируемые сегменты WAL в каталог /mnt/server/archivedir. (Это пример, а не рекомендация, и он может работать не на всех платформах). После замены параметров %p и %f фактически запускаемая команда может выглядеть следующим образом:

test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp qhb_wal/00000001000000A900000065 /mnt/server/archivedir/00000001000000A900000065

Схожая команда будет сгенерирована для каждого нового архивируемого файла.

Команда архивирования будет выполняться от имени того же пользователя, под которым работает сервер QHB. Поскольку последовательность архивируемых файлов WAL содержит практически все, что есть в базе данных, следует защитить архивируемые данные от посторонних глаз; например, архивировать их в каталог, на чтение которого отсутствует групповой и глобальный доступ.

Важно, чтобы команда архивирования возвращала нулевое состояние выхода, только если копирование произошло успешно. Получив нулевой результат, QHB предположит, что файл был успешно заархивирован, и удалит или переработает его. Однако ненулевое состояние говорит QHB, что файл не был заархивирован, и будут периодически предприниматься попытки заархивировать его заново, пока это не удастся.

Другой способ архивирования — использовать пользовательский модуль архивирования в качестве archive_library. Поскольку такие модули пишутся на языке C, создание собственного модуля может потребовать значительно больше усилий, чем написание команды оболочки. Однако модули архивирования могут быть эффективнее, чем архивирование через оболочку, и они будут иметь доступ ко многим полезным ресурсам сервера. Дополнительную информацию о модулях архивирования см. в главе Модули архивирования.

Когда команда архивирования завершается сигналом (отличным от SIGTERM, применяющимся как часть штатного отключения сервера) или ошибкой, если состояние выхода оболочки превышает 125 (например, когда команда не найдена), или если функция архивирования выдает ошибку уровня ERROR или FATAL, процесс архиватора прерывается и перезапускается процессом qhbmaster. В таких случаях в pg_stat_archiver не сообщается об ошибке.

Обычно команды и библиотеки архивирования разрабатываются так, чтобы не допускать перезапись любого уже существующего архивного файла. Это важная защитная характеристика, позволяющая сохранить целостность архива в случае ошибки администратора (например отправки вывода двух разных серверов в один каталог архива).

Рекомендуется протестировать команду или библиотеку архивирования, которую вы намереваетесь использовать, чтобы убедиться, что она действительно не перезаписывает существующий файл, и что если это не так, она возвращает ненулевое состояние. Приведенный выше пример команды для Unix обеспечивает это путем включения отдельного шага test. На некоторых платформах Unix в cp есть такие ключи, как -i, которые можно использовать для выполнения тех же операций менее явно, но не стоит полагаться на них, не убедившись, что возвращен правильный код состояния. (В частности, GNU cp вернет нулевой код состояния, когда используется -i и целевой файл уже существует, а это не желаемое поведение).

При разработке вашей конфигурации архивации проанализируйте, что произойдет, если команда или библиотека архивирования будет постоянно завершаться ошибкой, потому что некий аспект требует вмешательства оператора или архиву не хватает места. Например, это может произойти, если вы записываете на ленточный накопитель без устройства автоматической смены носителя; когда лента заполняется, ничего больше не будет архивироваться, пока накопитель не будет заменен. Вы должны убедиться в том, что о любой ошибке или запросе к оператору-человеку сообщается надлежащим образом, чтобы ситуация разрешалась достаточно быстро. Пока этого не произойдет, каталог pg_wal/ будет продолжать заполняться файлами сегментов WAL. (Если файловая система, содержащая pg_wal/ заполнится до конца, QHB завершит работу аварийно (режим PANIC). Никакие зафиксированные транзакции не будут потеряны, но база данных останется в автономном режиме, пока вы не освободите место.)

Скорость команды или библиотеки архивирования не имеет значения, пока она не опускается ниже средней скорости, с которой ваш сервер генерирует данные WAL. Обычная работа продолжается, даже если процесс архивирования немного отстает. Если архивирование отстает значительно, это увеличит объем данных, которые могут быть потеряны в случае аварии. Это также будет означать, что pg_wal/ будет содержать большое количество еще не заархивированных файлов сегментов, которые в конечном счете могут занять все доступное дисковое пространство. Рекомендуется следить за процессом архивации, чтобы убедиться, что он работает так, как вы задумывали.

При написании команды или библиотеки архивирования следует исходить из того, что имена архивируемых файлов могут иметь длину до 64 символов и содержать любую комбинацию букв ASCII, цифр и точек. Нет необходимости сохранять исходный относительный путь (%p), но нужно сохранить имя файла (%f).

Обратите внимание, что несмотря на то, что архивация WAL позволит вам восстановить любые изменения, внесенные в данные в вашей базе данных QHB, она не восстановит изменения, внесенные в файлы конфигурации (то есть qhb.conf, qhb_hba.conf и qhb_ident.conf), так как они редактируются вручную, а не с помощью операций SQL. Вам может понадобиться сохранить файлы конфигурации в том же месте, где будут храниться результаты ваших обычных процедур резервного копирования файловой системы. Как перемещать файлы конфигурации, описывается в разделе Расположение файлов.

Команда или функция архивирования вызывается только для завершенных сегментов WAL. Поэтому если сервер генерирует небольшой трафик WAL (или имеет периоды простоя, когда это происходит), может возникнуть длительная задержка между завершением транзакции и ее безопасной записью в архивное хранилище. Чтобы установить ограничение на срок хранения неархивированных данных, можно установить параметр archive_timeout, чтобы заставить сервер переключаться на новый файл сегмента WAL как минимум с этой частотой. Обратите внимание, что архивные файлы, архивируемые раньше из-за принудительного переключения, имеют ту же длину, что и полностью заполненные файлы. Поэтому неразумно устанавливать очень маленькое значение archive_timeout — это приведет к переполнению архивного хранилища. Обычно имеет смысл установить в archive_timeout интервал в минуту или около того.

Кроме того, если нужно, чтобы только что завершенная транзакция была заархивирована как можно скорее, можно вручную переключить сегмент с помощью pg_switch_wal. Другие функции-утилиты, относящиеся к управлению WAL, перечислены в таблице Функции для управления резервным копированием.

Когда wal_level имеет значение minimal, некоторые команды SQL оптимизируются, чтобы не протоколироваться в журнал WAL, как описано в подразделе Выключите архивацию WAL и потоковую репликацию. Если во время выполнения одного из этих операторов были включены архивация или потоковая репликация, WAL не будет содержать достаточно информации для восстановления архива. (На восстановление после аварийного завершения это не распространяется). По этой причине wal_level можно изменять только при запуске сервера. Тем не менее параметры archive_command и archive_library можно изменить, перезагрузив файл конфигурации. Если нужно на время остановить архивирование, один из способов сделать это — установить в качестве значения archive_command пустую строку (' '). Это приведет к тому, что файлы WAL будут накапливаться в pg_wal/, пока не будет восстановлена рабочая команда archive_command.

Создание базовой резервной копии

Самый простой способ выполнить базовое резервное копирование — использовать инструмент qhb_basebackup. Он может создавать базовую резервную копию в виде обычных файлов или в виде архива tar. Альтернативой этому инструменту служит qbackup. Эта программа позволяет сохранять ваши резервные копии в структурированный каталог, при этом поддерживается инкрементальное копирование и сжатие, а также параллельный режим работы.

Если требуется больше гибкости, чем могут обеспечить эти программы, можно создать базовую резервную копию, используя низкоуровневый API (см. подраздел Создание базовой резервной копии с использованием API низкого уровня).

Нет необходимости беспокоиться о количестве времени, которое занимает создание базовой резервной копии. Однако если обычно вы запускаете сервер с выключенным параметром full_page_writes, вы можете заметить падение производительности во время резервного копирования, так как full_page_writes в этом режиме включается принудительно.

Чтобы резервной копией можно было пользоваться, необходимо сохранить все файлы сегментов WAL, сгенерированные во время и после резервного копирования файловой системы. Чтобы помочь в этом, процесс базового резервного копирования создает файл истории резервного копирования, который немедленно сохраняется в области архивации WAL. Этот файл получает то же имя, что и первый файл сегмента WAL, требующийся для резервного копирования файловой системы. Например, если начальный файл WAL называется 0000000100001234000055CD, то файл истории резервного копирования будет иметь имя вида 0000000100001234000055CD.007C9330.backup. (Вторая часть имени файла обозначает его точную позицию в файле WAL и обычно может игнорироваться). После того, как вы безопасно заархивировали резервную копию файловой системы и файлы сегментов WAL, использованные во время резервного копирования (указанные в файле истории резервного копирования), все архивные сегменты WAL с меньшими по номеру именами становятся не нужны для восстановления резервной копии файловой системы и могут быть удалены. Однако стоит подумать о сохранении несколько наборов резервных копий, чтобы быть абсолютно уверенным в возможности восстановить нужные данные.

Файл истории резервного копирования представляет собой небольшой текстовый файл. Он содержит строку метки, которую вы передали qhb_basebackup, а также время и имена сегментов WAL на момент начала и завершения резервного копирования. Если вы использовали эту метку для идентификации связанного файла дампа, то заархивированного файла истории достаточно, чтобы указать, какой файл дампа нужно восстановить.

Поскольку приходится хранить все архивные файлы WAL с момента последней базовой резервной копии, интервал между повторными резервными копированиями обычно следует выбирать исходя из того, сколько памяти вы хотите выделить на архивные файлы WAL. Также следует учитывать, сколько времени вы готовы потратить на восстановление, ведь система должна будет воспроизвести все эти сегменты WAL, а это может занять некоторое время, если с момента последнего резервного копирования базы прошло много времени.

Создание базовой резервной копии с использованием API низкого уровня

Процедура создания базовой резервной копии с использованием низкоуровневых API содержит на несколько этапов больше, чем методы qhb_basebackup и qbackup, но является относительно простой. Очень важно, чтобы эти этапы выполнялись последовательно, и чтобы каждый следующий этап выполнялся только после успешного завершения предыдущего.

Несколько процессов резервного копирования можно запускать параллельно (через API этого процесса или qhb_basebackup).

Убедитесь, что архивация WAL включена и работает.

Подключитесь к серверу (не имеет значения, к какой базе данных) как пользователь с правами на запуск pg_start_backup (суперпользователь или пользователь, наделенный правом EXECUTE для этой функции) и выполните команду:

SELECT pg_backup_start(label => 'label', fast => false);

где label — это любая строка, которую вы хотите использовать в качестве идентификатора этой операции резервного копирования. Соединение, через которое вызывается pg_start_backup, должно поддерживаться до конца резервного копирования, иначе оно будет автоматически прервано.

Резервное копирование в режиме реального времени всегда запускается в начале контрольной точки. По умолчанию pg_start_backup будет ждать завершения очередной контрольной точки, что может занять долгое время (см. параметры конфигурации checkpoint_timeout и checkpoint_completion_target). Обычно именно это и требуется, поскольку это минимизирует влияние на работу системы. Если вы хотите начать резервное копирование как можно скорее, следует передать функции pg_backup_start в качесте второго параметра true, тогда она запросит немедленное выполнение контрольной точки, которая завершится максимально быстро, используя максимально возможное количество операций ввода/вывода.

Выполните резервное копирование, используя любой удобный инструмент для резервного копирования файловой системы, например tar или cpio (не qhb_dump или qhb_dumpall). Останавливать при этом нормальную работу базы данных необязательно и нежелательно. Нюансы, которые следует учитывать при выполнении этого резервного копирования, описаны в подразделе Резервное копирование каталога данных.

В том же соединении, что и раньше, введите команду:

SELECT * FROM pg_backup_stop(wait_for_archive => true);

Это завершает режим резервного копирования. На основном сервере также выполняется автоматическое переключение на следующий сегмент WAL. На резервном сервере невозможно автоматическое переключение сегментов WAL, поэтому может понадобиться запустить pg_switch_wal, чтобы выполнить ручное переключение. Цель этого переключения заключается в том, чтобы последний файл сегмента WAL, записанный во время резервного копирования, был готов к архивированию.

Функция pg_stop_backup вернет одну строку с тремя значениями. Второе из этих полей должно быть записано в файл с именем backup_label в корневом каталоге резервной копии. Третье поле должно быть записано в файл с именем tablespace_map, если это поле не пустое. Эти файлы крайне важны для резервного копирования и должны быть записаны побайтово без изменений, для чего может понадобиться открыть такой файл в двоичном режиме.

Как только файлы сегментов WAL, активные во время резервного копирования, будут заархивированы, все готово. Файл, определяемый первым возвращенным значением pg_stop_backup, является последним сегментом, необходимым для формирования полного набора файлов резервных копий. Если на основном сервере включен параметр archive_mode, а параметр wait_for_archive равен true, то функция pg_stop_backup не выполнится до тех пор, пока не будет заархивирован последний сегмент. При этом на резервном сервере параметр archive_mode должен иметь значение always, чтобы pg_stop_backup ожидала архивации. Архивирование этих файлов происходит автоматически, так как archive_command или archive_library уже сконфигурирована. В большинстве случаев это происходит быстро, но рекомендуется следить за системой архивирования, чтобы убедиться в отсутствии задержек. Если процесс архивации отстал из-за сбоев команды или библиотеки архивирования, он будет повторять попытки до успешного завершения архивации, и только тогда резервное копирование закончится. Если вы хотите установить ограничение по времени выполнения pg_stop_backup, задайте соответствующее значение в statement_timeout, но учтите, что если pg_stop_backup завершит работу по времени, резервная копия может оказаться непригодной.

Если процесс резервного копирования отслеживает и обеспечивает успешную архивацию всех файлов сегментов WAL, необходимых для резервного копирования, то для параметра wait_for_archive (который по умолчанию равен true) можно установить значение false, чтобы функция pg_stop_backup завершалась, как только запись остановки резервного копирования протоколируется в WAL. По умолчанию pg_stop_backup будет ждать окончания архивации всех файлов WAL, что может занять некоторое время. Этот параметр следует использовать с осторожностью: если архивирование WAL не контролируется должным образом, резервная копия может включать не все файлы WAL и, следовательно, окажется неполной и не сможет быть восстановлена.

Резервное копирование каталога данных

Некоторые инструменты резервного копирования файловой системы выдают предупреждения или ошибки, если файлы, которые они пытаются скопировать, в процессе копирования изменяются. При создании базовой резервной копии активной базы данных это нормальная ситуация, а не ошибка. Однако следует убедиться, что вы можете отличить сообщения такого рода от реальных ошибок. Например, некоторые версии rsync возвращают отдельный код завершения для «исчезнувших исходных файлов», и можно написать управляющий скрипт, чтобы принять этот код завершения как не ошибочный. Кроме того, некоторые версии GNU tar возвращают код ошибки, неотличимый от фатальной ошибки, если файл был усечен во время его копирования через tar. К счастью, GNU tar версии 1.16 и выше завершается с кодом 1, если файл был изменен во время резервного копирования, и с кодом 2 для других ошибок. В GNU tar версии 1.23 и выше можно использовать параметры предупреждений --warning=no-file-changed и --warning=no-file-removed, чтобы скрыть соответствующие предупреждающие сообщения.

Убедитесь, что ваша резервная копия содержит все файлы из каталога кластера баз данных (например, /var/lib/qhb/data). Если вы используете табличные пространства, которые находятся не внутри этого каталога, не забудьте включить их в копию (убедитесь, что резервная копия архивирует символические ссылки в виде ссылок, иначе восстановление повредит табличные пространства).

Однако следует исключить из резервной копии файлы в подкаталоге кластера pg_wal/. Эта небольшая корректировка имеет смысл, поскольку снижает риск ошибок при восстановлении. Это легко организовать, если pg_wal/ представляет собой символическую ссылку, указывающую за пределы каталога кластера — в любом случае такую настройку часто делают из соображений производительности. Также можно исключить qhbmaster.pid и qhbmaster.opts, которые записывают информацию о работающем qhbmaster, а не о том qhbmaster, который в конечном итоге будет использовать эту резервную копию. (Эти файлы могут запутать утилиту qhb_ctl).

Также зачастую бывает полезно также исключить из резервной копии каталог кластера pg_replslot/, чтобы слоты репликации, существующие на основном сервере, не попадали в копию. В противном случае последующее использование резервной копии для создания резервного сервера может привести к неограниченно долгому хранению файлов WAL в резервной системе и, возможно, переполнению основного сервера, если включено получение данных о горячем резерве, поскольку клиенты, использующие эти слоты репликации, все равно будут подключаться и изменять слоты на основном, а не на резервном сервере. Даже если резервная копия предназначена только для создания нового основного сервера, копирование слотов репликации не будет особенно полезным, так как к тому времени, когда новый основной сервер войдет в сеть, содержимое этих слотов, скорее всего, станет совершенно неактуальным.

Содержимое каталогов pg_dynshmem/, pg_notify/, pg_serial/, pg_snapshots/, pg_stat_tmp/ и pg_subtrans/ (но не сами эти каталоги) можно исключить из резервной копии, поскольку оно будет инициализировано при запуске qhbmaster.

Также из резервной копии можно исключить любой файл или каталог, начинающийся с pgsql_tmp. Эти файлы удаляются при запуске qhbmaster, а каталоги будут воссоздаваться по мере надобности.

Файлы pg_internal.init можно исключать из резервной копии всякий раз, когда найден файл с таким именем. Эти файлы содержат данные кэша отношений, которые всегда воссоздаются при восстановлении.

Файл метки резервной копии содержит строку метки, заданную при вызове pg_start_backup, а также время запуска этой функции и имя начального файла WAL. Поэтому в случае затруднения можно заглянуть в файл резервной копии и определить, в каком именно сеансе резервного копирования был получен файл дампа. Файл карты табличных пространств содержит имена символических ссылок в том виде, в каком они находятся в каталоге pg_tblspc/, и полный путь каждой символической ссылки. Эти файлы имеют не только справочное значение; их наличие и содержимое имеют решающее значение для правильной работы процесса восстановления системы.

Также возможно сделать резервную копию, когда сервер остановлен, например, с помощью qbackup. В этом случае, разумеется, нельзя вызывать pg_start_backup или pg_stop_backup, и поэтому придется своими силами разбираться в резервных копиях и отслеживать актуальность связанных файлов WAL (qbackup делает это автоматически). В целом, все же лучше следовать описанной выше процедуре непрерывного архивирования.

Восстановление из непрерывной архивной копии

Предположим, что случилось худшее, и вам нужно восстановить базу данных из резервной копии. Процедура восстановления состоит из следующих этапов:

Остановите сервер, если он работает.

Если у вас есть для этого место, скопируйте весь каталог данных кластера и все табличные пространства во временную папку на случай, если позже они вам понадобятся. Обратите внимание, что эта мера предосторожности потребует, чтобы свободного места в системе было достаточно для хранения двух копий существующей базы данных. Если места недостаточно, следует сохранить хотя бы содержимое подкаталога кластера pg_wal, так как он может содержать журналы, которые не были заархивированы до отказа системы.

Удалите все существующие файлы и подкаталоги в каталоге данных кластера и в корневых каталогах всех используемых табличных пространств.

Восстановите файлы базы данных из резервной копии файловой системы. Проследите, чтобы они были восстановлены с правильным владельцем (пользователем СУБД, а не root!) и с правильными разрешениями. Если вы используете табличные пространства, следует убедиться, что символические ссылки в pg_tblspc/ были правильно восстановлены.

Удалите все файлы, находящиеся в pg_wal/; они были получены из резервной копии и поэтому наверняка устарели и неактуальны. Если вы не архивировали pg_wal/, воссоздайте его с надлежащими разрешениями, а если ранее установили его как символическую ссылку, обязательно не забудьте ее восстановить.

Если у вас есть разархивированные файлы сегментов WAL, сохраненные на этапе 2, скопируйте их в pg_wal/. (Лучше всего их копировать, а не перемещать, поскольку так в случае возникновения проблем и необходимости начать заново у вас останутся неизмененные файлы.)

Задайте параметры конфигурации восстановления в qhb.conf (см. подраздел Восстановление из архива) и создайте файл recovery.signal в каталоге данных кластера. Кроме того, можно временно изменить qhb_hba.conf, чтобы обычные пользователи не могли подключаться, пока вы не убедитесь, что восстановление прошло успешно.

Запустите сервер. Он перейдет в режим восстановления и продолжит чтение нужных ему архивированных файлов WAL. Если восстановление прервется из-за внешней ошибки, сервер можно просто перезапустить, и он продолжит восстановление. По завершении процесса восстановления сервер удалит recovery.signal (для предотвращения случайного повторного входа в режим восстановления), а затем начнет обычные операции с базой данных.

Проверьте содержимое базы данных, чтобы убедиться, что она восстановилась до нужного состояния. Если это не так, вернитесь к этапу 1. Если все в порядке, разрешите пользователям подключаться, восстановив qhb_hba.conf до обычного состояния.

Ключевой частью всего этого является настройка конфигурации восстановления, описывающей, как вы хотите восстановить базу данных и как далеко должно зайти восстановление. Единственное, что обязательно нужно указать, это restore_command, которая сообщает QHB, как извлекать заархивированные сегменты файла WAL. Как и archive_command, это командная строка оболочки. Она может содержать ключ %f, который заменяется именем нужного файла журнала, и ключ %p, который заменяется путем, по которому нужно скопировать файл журнала. (Путь указывается относительно текущего рабочего каталога, т. е. каталога данных кластера). Если в команду нужно вставить сам символ %, напишите %%. Простейшая полезная команда выглядит примерно так:

restore_command = 'cp /mnt/server/archivedir/%f %p'

Она скопирует ранее заархивированные сегменты WAL из каталога /mnt/server/archivedir. Конечно, можно использовать что-то гораздо более сложное, возможно, даже скрипт оболочки, который укажет оператору вставить соответствующую ленту. Например, qbackup делает это автоматически, используя встроенную подкоманду.

Важно, чтобы команда возвращала ненулевой статус выхода при ошибке. Эта команда будет вызываться и для запроса файлов, которых нет в архиве; в этом случае она должна вернуть ненулевое значение. Однако это не должно считаться ошибкой. Исключением является случай прерывания команды сигналом (отличным от SIGTERM, используемым для отключения сервера баз данных) или ошибки оболочки (например, если команда не найдена) — тогда восстановление прервется, и сервер не запустится.

Не все запрошенные файлы будут файлами сегментов WAL; также следует ожидать запросов на файлы с суффиксом .history. Кроме того, не забывайте, что базовое имя пути %p будет отличаться от %f; не ждите, что они будут взаимозаменяемыми.

Сегменты WAL, которые нельзя найти в архиве, система будет искать в pg_wal/; это позволяет использовать последние неархивированные сегменты. Однако у сегментов, доступных из архива, будет приоритет перед файлами из pg_wal/.

Обычно восстановление проходит через все доступные сегменты WAL, тем самым восстанавливая базу данных до текущего момента времени (или максимально близко, учитывая доступные сегменты WAL). Поэтому нормальное восстановление заканчивается сообщением «файл не найден» (точный текст сообщения об ошибке зависит от выбора restore_command). Также сообщение об ошибке может появиться в начале восстановления для файла с именем вроде 00000001.history. Это тоже нормально и не говорит о проблеме в простых ситуациях восстановления; это описывается в подразделе Временная шкала.

Если вы хотите восстановить данные до некоторого предыдущего момента времени (скажем, непосредственно перед тем, как неопытный администратор баз данных удалил вашу основную таблицу транзакций), просто укажите необходимую точку остановки. Вы можете указать точку остановки, называемую также «целевая точка восстановления», по дате/времени, именованной точке восстановления или завершению транзакции c определенным идентификатором. На данный момент полезным может быть только написание даты/времени и именованной точки восстановления, поскольку инструменты, помогающие точно определить, какой идентификатор транзакции использовать, отсутствуют.

Примечание
Точка остановки должна быть указана после времени окончания процесса базового резервного копирования, т. е. после времени завершения pg_stop_backup. Нельзя использовать базовую резервную копию для восстановления до того времени, когда эта резервная копия еще создавалась. (Чтобы восстановиться до этого времени, следует вернуться к предыдущей базовой резервной копии и начать восстановление оттуда).

Примечание
В команде restore в qbackup существуют параметры, позволяющие выбрать эти настройки. Ими можно будет воспользоваться, только если резервная копия была создана при помощи backup.

Если восстановление обнаружит поврежденные данные WAL, оно остановится в этом месте, и сервер не запустится. В таком случае процесс восстановления можно перезапустить с самого начала, указав «целевую точку восстановления» до точки повреждения, чтобы восстановление могло завершиться нормально. Если восстановление завершится неудачно по внешней причине, например, при сбое системы или недоступности архива WAL, то восстановление можно просто перезапустить, и оно возобновится почти с того места, где произошел сбой. Перезапуск восстановления действует подобно контрольной точке в обычной работе: сервер периодически сохраняет все свое состояние на диск, а затем обновляет файл pg_control, тем самым указывая, что уже обработанные данные WAL не нужно сканировать заново.

Временная шкала

Возможность восстановить базу данных до предыдущего момента времени создает некоторые сложности, сродни научно-фантастическим рассказам о путешествиях во времени и параллельных вселенных. Например, предположим, что в исходной истории базы данных вы удалили критическую таблицу в 17:15 во вторник вечером, но не осознавали свою ошибку до полудня среды. Вы невозмутимо достаете свою резервную копию, восстанавливаете базу на момент 17:14 во вторник и запускаете сервер. В этой истории базы данных вы никогда не удаляли таблицу. Но, предположим, позднее вы понимаете, что это не такая уж хорошая идея, и хотели бы вернуться к утру среды в исходной истории. Вы не сможете этого сделать, если ваша база данных в процессе работы перезаписала некоторые файлы сегментов WAL, ведущих к тому времени, к которому вы хотите вернуться теперь. Таким образом, чтобы избежать этого, необходимо отличать последовательности записей WAL, сгенерированные после восстановления на определенный момент времени, от тех, что были сгенерированы в исходной истории базы данных.

Чтобы справиться с этой проблемой, в QHB есть понятие временная шкала. Всякий раз по завершении восстановления архива создается новая временная шкала для определения последовательности записей WAL, сгенерированной после этого восстановления. Идентификационный номер временной шкалы входит в состав имен файлов сегментов WAL, поэтому новая временная шкала не перезаписывает данные WAL, созданные предыдущими временными шкалами. На самом деле можно архивировать множество различных временных шкал. Хотя это может показаться бесполезным свойством, зачастую оно оказывается спасительным. Рассмотрим ситуацию, когда вы не совсем уверены, к какому моменту времени необходимо восстановить базу данных, и поэтому вам приходится методом проб и ошибок делать несколько попыток восстановления на определенный момент времени, пока не найдется лучшее место для ветвления из старой истории. Без временных шкал этот процесс быстро приведет к неуправляемому беспорядку. С помощью временных шкал можно восстановить любое предыдущее состояние, в том числе состояния в ветвях временных шкал, от которых вы отказались ранее.

Каждый раз, когда создается новая временная шкала, QHB создает файл «истории временной шкалы», который показывает, от какой временной шкалы она ответвилась и когда. Эти файлы истории необходимы для того, чтобы система могла выбрать правильные файлы сегментов WAL при восстановлении из архива, содержащего несколько временных шкал. Поэтому они архивируются в область архивирования WAL наравне с файлами сегментов WAL. Файлы истории — это просто небольшие текстовые файлы, которые не занимают много места, поэтому их целесообразно хранить неограниченно долгое время (в отличие от больших файлов сегментов). При желании в файлы истории можно добавлять комментарии, чтобы записывать свои заметки о том, как и почему была создана эта конкретная временная шкала. Такие комментарии будут особенно полезны, когда в результате экспериментов у вас образуются «заросли» из различных временных шкал.

По умолчанию восстановление происходит до последней временной шкалы, найденной в архиве. Если вы хотите восстановить базу до той временной шкалы, которая была текущей на момент создания базовой резервной копии, или до какой-то дочерней временной шкалы (то есть хотите вернуться к некоторому состоянию, которое само было получено после попытки восстановления), нужно указать current или идентификатор целевой временной шкалы в параметре recovery_target_timeline. Невозможно восстановить состояние до временной шкалы, ответвившейся раньше создания базовой резервной копии.

Советы и примеры

Здесь приведены некоторые советы по настройке непрерывного архивирования.

Автономные горячие резервные копии

Для создания автономных горячих резервных копий можно использовать средства резервного копирования QHB. Это резервные копии, которые нельзя использовать для восстановления на определенный момент времени, но, как правило, ими гораздо быстрее выполнять резервное копирование и восстановление, чем дампами qhb_dump. (Они также гораздо больше дампов qhb_dump, поэтому в некоторых случаях преимущество в скорости может быть сведено на нет.)

Как и в случае базовых резервных копий, самый простой способ создать автономную горячую резервную копию — использовать инструмент qhb_basebackup. Если при вызове этой утилиты задать параметр -X, в резервную копию будет автоматически включен весь журнал упреждающей записи, необходимый для ее использования, и никаких специальных действий для ее восстановления не потребуется.

Сжатие архивных журналов

Если размер архивного хранилища вызывает обеспокоенность, можно сжать архивные файлы с помощью gzip:

archive_command = 'gzip < %p > /var/lib/qhb/archive/%f'

Затем во время восстановления нужно будет использовать gunzip:

restore_command = 'gunzip < /mnt/server/archivedir/%f > %p'

Скрипты archive_command

Многие люди предпочитают использовать для определения команды archive_command скрипты, поэтому их запись в qhb.conf выглядит очень просто:

archive_command = 'local_backup_script.sh "%p" "%f"'

Использование отдельного файла скрипта целесообразно всегда, когда вы хотите использовать в процессе архивирования более одной команды. Это позволяет управлять всей сложностью этого процесса внутри скрипта, который можно написать на популярном языке скриптов, например, на bash или perl.

Примеры задач, которые могут быть решены в скрипте:

Копирование данных в безопасное внешнее хранилище

Пакетное копирование WAL, чтобы они передавались каждые три часа, а не по одному

Взаимодействие с другим программным обеспечением для резервного копирования и восстановления

Взаимодействие с программами мониторинга для регистрации ошибок

Совет
При использовании в archive_command скрипта желательно включить logging_collector. Любые сообщения, записанные в stderr из скрипта, затем появятся в журнале сервера баз данных, позволяя легко диагностировать сбои в сложных конфигурациях.

Примечание
Также для этих целей можно использовать команду backup-wal утилиты qbackup.

Ограничения применимости

На момент написания документации методика непрерывного архивирования имеет несколько ограничений. Вероятно, это будет исправлено в следующих версиях:

Если во время создания базовой резервной копии выполняется команда CREATE DATABASE, а затем шаблонная база данных, которую скопировала CREATE DATABASE, изменяется, пока базовое резервное копирование еще выполняется, есть вероятность, что восстановление приведет к распространению этих изменений и на созданную базу данных. Это, конечно, нежелательно. Чтобы избежать этого риска, лучше не изменять шаблонные базы данных во время создания базовой резервной копии.

Команды CREATE TABLESPACE протоколируются в WAL с буквальным абсолютным путем и поэтому будут воспроизведены как детища табличного пространства с тем же абсолютным путем. Это может быть нежелательно, если журнал воспроизводится на другом компьютере. Опасность остается, даже если журнал воспроизводится на том же компьютере, но в новом каталоге данных: воспроизведение все равно перезапишет содержимое исходного табличного пространства. Чтобы избежать потенциальных ошибок такого рода, рекомендуется создавать новую базовую резервную копию уже после создания или удаления табличных пространств.

Следует также отметить, что по умолчанию формат WAL довольно громоздкий, так как включает в себя множество снимков страниц диска. Эти снимки страниц предназначены для поддержки восстановления после сбоя, поскольку может потребоваться исправить частично записанные страницы диска. В зависимости от аппаратного и программного обеспечения системы, риск частичной записи может быть достаточно мал, чтобы его можно было игнорировать, и в этом случае можно значительно уменьшить общий объем архивированных журналов, выключив снимки страниц с помощью параметра full_page_writes. (Перед тем, как сделать это, прочтите примечания и предупреждения в главе WAL.) Выключение снимков страницы не мешает использованию журналов для операций PITR. Одно из направлений дальнейшей разработки — сжатие архивных данных WAL путем удаления ненужных копий страниц, даже если включен режим full_page_writes. В то же время администраторам может понадобиться уменьшить количество снимков страниц, включенных в WAL, в допустимых пределах увеличив параметры интервала контрольных точек.

Инструмент резервного копирования qbackup

qbackup — инструмент для управления резервным копированием и восстановлением кластеров баз данных QHB.

Синтаксис

qbackup help [команда]

qbackup list -B каталог_копий [параметр...]

qbackup backup -B каталог_копий [-D каталог_данных] [--incremental]
        [--compress] [--direct] [параметр...]

qbackup replica -D каталог_данных [параметр...]

qbackup restore -B каталог_копий -D каталог_данных
        -i идентификатор_копии [--connection] [параметр...]

qbackup remove -B каталог_копий -i идентификатор_копии ...
        [--single] [параметр...]

qbackup validate -B каталог_копий -i идентификатор_копии
        [-D каталог_данных] [-t тип_проверки] [параметр...]

qbackup backup-wal -B каталог_копий -D каталог_данных [--compress] -p путь_файла_wal
        -f имя_файла_wal [параметр...]

qbackup restore-wal -p путь_файла_wal
        -f путь_для_файла_wal [параметр...]

Обзор

По сравнению с другими средствами резервного копирования qbackup имеет следующие преимущества, полезные для реализации различных стратегий резервного копирования и работы с базами данных большого объема:

Постраничное инкрементальное копирование: позволяет сэкономить место на диске и создавать копии быстрее, чем при полном копировании. Восстановление инкрементальных копий также осуществляется быстрее, чем воспроизведение файлов WAL.

Реализация создания кумулятивной инкрементальной резервной копии с возможностью выбора родителя для инкрементального бэкапа.

Параллельное выполнение: выполнение внутренних процессов команд backup и restore в несколько параллельных потоков.

Сжатие: хранение копируемых данных в сжатом состоянии для экономии дискового пространства.

Каталогизация резервных копий: получение списка резервных копий и соответствующей метаинформации в виде простого текста или JSON.

Расширенные возможности работы с удаленным резервным копированием.

Для потокового (stream) резервного копирования используется протокол репликации.

Возможность удаленного восстановления из резервной копии.

Расширенные возможности валидации резервных копий и восстановленных кластеров.

Для управления резервными копиями qbackup создает каталог резервных копий. В этом каталоге сохраняются все файлы резервных копий с дополнительной метаинформацией, а также архивы WAL, необходимые для восстановления на момент времени.

Используя qbackup в режиме потокового (stream) резервного копирования, вы можете выполнять полное или инкрементальное резервное копирование с локального кластера, а также с удаленного:

Полные резервные копии содержат все файлы, необходимые для восстановления кластера баз данных с нуля.

Инкрементальные копии создаются на уровне страниц и включают только те данные, которые изменились со времени последнего копирования. Это позволяет сэкономить место на диске и создавать копии быстрее, чем при полном копировании. Восстановление инкрементальных копий также осуществляется быстрее, чем воспроизведение файлов WAL.

qbackup в режиме прямого (direct) резервного копирования может производить полное копирование как работающего, так и остановленного экземпляра. Для копирования WAL используются внутренние механизмы QHB (см. подраздел скрипты archive_command).

Ограничения применимости

В настоящее время qbackup версии 1.5.2 имеет следующие ограничения:

qbackup работает с серверами QHB версии 1.5.1 и новее.

На сервере QHB, где была сделана копия, и на сервере, где она будет восстанавливаться, должны быть одинаковые значения параметров block_size и wal_block_size и одинаковая основная версия. В зависимости от конфигурации кластера, QHB может накладывать дополнительные ограничения, например, по архитектуре процессора и версии libc/libicu.

Прямое (direct) резервное копирование предусмотрено только для создания локальных полных резервных копий. Для инкрементального бэкапирования используется потоковое (stream) резервное копирование как для локального, так и для удаленного хоста.

Невозможно сделать потоковую (stream) резервную копию на остановленном сервере. Для этого предусмотрено резервное копирование в режиме direct, которое не требует обязательного подключения к базе.

Инкрементальные резервные копии невозможно базировать на резервных копиях, созданных на остановленном сервере.

После включении CDC для инкрементального бэкапирования необходимо предварительно сделать полную резервную копию для корректного последующего захвата изменений данных.

Термины

Каталог копий — директория с резервными копиями. Внутри нее могут содержаться директории с резервными копиями, информация о них в файле каталога catalog.json и директория wal. Директорию каталога нужно создать до начала резервного копирования.

Каталог данных — директория с кластером баз данных.

Следующие поля в файле каталога копий являются служебными, но могут быть полезны. Они меняются с каждой дополнительной версией QHB:

Версия кластера (cluster_version) — бинарная версия QHB. Это номер сборки базы данных.

Версия qbackup (qbackup_version).

Установка и подготовка

Установив qbackup, выполните следующие действия:

Создайте каталог копий.

Настройте кластер баз данных для использования qbackup.

Настройте SSH для выполнения удаленного восстановления из резервных копий, если вы планируете использовать данную функциональность.

Создание каталога копий

qbackup хранит резервные копии в каталоге копий, путь к которому указывается в переменной среды BACKUPS_DIR или в аргументе командной строки -B (--backups-dir). Каталог копий должен быть создан пользователем перед началом работы с утилитой и не должен содержать никаких файлов. Ручное редактирование файлов в каталоге копий не допускается. Пользователь, запускающий qbackup, должен иметь полный доступ к каталогу_копий и как минимум доступ на чтение всего содержимого каталога_данных.

Настройка кластера баз данных

Хотя qbackup можно использовать от имени суперпользователя, рекомендуется создать отдельную роль с минимальными правами, необходимыми для выбранной стратегии копирования. В этих инструкциях по настройке такой ролью служит роль backup.

Программа qbackup должна читать непосредственно файлы кластера, поэтому запускать qbackup (или подключаться к нему удаленно) нужно от имени пользователя ОС, который имеет доступ на чтение всех файлов и каталогов внутри каталога данных кластера (PGDATA), подлежащего копированию.

Настройки для использования qbackup в режиме прямого (direct) резервного копирования

Если планируется использоваться бэкап в режиме stream для потокового резервного копирования, то данный раздел можно пропустить и сразу настраивать кластер с учетом требований к потоковому резервному копированию.

Для выполнения физического резервного копирования на активной базе данных роль backup должна иметь следующие разрешения на сервере QHB (только в базе данных, к которой производится подключение):

BEGIN;
CREATE ROLE backup WITH LOGIN;
GRANT USAGE ON SCHEMA pg_catalog TO backup;
GRANT EXECUTE ON FUNCTION pg_catalog.qhb_start_backup(text, boolean, boolean) TO backup;
GRANT EXECUTE ON FUNCTION pg_catalog.qhb_stop_backup(boolean, boolean) TO backup;
COMMIT;

В файле qhb_hba.conf разрешите подключение к кластеру баз данных пользователю с именем backup.

Для возможности прямого резервного копирования рабочего экземпляра QHB необходимо дополнительно настроить архивацию WAL, как указано в подразделе Настройка непрерывного архивирования WAL.

Настройки для использования qbackup в режиме потокового (stream) резервного копирования

Программа qbackup в режиме stream позволяет получить копию кластера базы данных с удаленного или локального сервера по сети, используя протокол репликации. Если вы используете qbackup через отдельного пользователя (например, backup), не забудьте дать ему роль REPLICATION в базе данных и соответствующие права в qhb_hba.conf.

Пример:

CREATE USER backup REPLICATION PASSWORD 'backup';

В файле qhb_hba.conf разрешите выполнение репликации для роли backup:

# qhb_hba.conf
host replication backup <master_ip> md5
host replication backup <standby_ip> md5
host all backup <master_ip> md5
host all backup <standby_ip> md5

В файле qhb.conf установите в параметре wal_level значение выше minimal.

Пример:

# qhb.conf
wal_level='replica'

Для потокового резервного копирования предусмотрены полное и инкрементальное бэкапирование (в том числе удаленное) на включенной СУБД. Для инкрементальных резервных копий реализован механизм CDC — захват изменения данных (Change Data Capture). Для использования CDC необходимо настроить параметр qcdc_registry_size. Он принимает размер в мегабайтах. После перезапуска базы данных в ее корне появится файл qhb-cdc.map с указанным размером. Размер может быть любым в указанных далее пределах; для оптимальной работы следует выбирать его исходя из правила: предполагаемый размер кластера / 1000. Максимальное значение этого параметра — 10 Гб (10240 Мб), минимальное — 1 Мб. При значении 0 CDC считается выключенным.

Пример:

# qhb.conf
# Предполагаемый размер кластера — 30GB
qcdc_registry_size=30MB

После включении CDC необходимо сделать полную резервную копию для корректного последующего захвата изменений данных при инкрементальном создании резервной копии.

В режиме потокового резервного копирования все необходимые файлы WAL будут автоматически архивированы (см. параграф варианты архивации).

ВНИМАНИЕ!
При наличии шифрованных таблиц в базе данных не рекомендуется использовать потоковое резервное копирование с указанием wal-method=stream. Для формирования зашифрованной потоковой резервной копии требуется наличие непрерывной архивации WAL и потокового резервного копирования в режиме wal-method=fetch или wal-method=none.

Если вы планируете восстановление на момент времени, то необходимо дополнительно настроить архивацию WAL, как указано в подразделе «Настройка непрерывного архивирования WAL» ниже.

Настройка непрерывного архивирования WAL

Для выполнения локального резервного копирования, а также для восстановления на момент времени должно осуществляться непрерывное архивирование WAL.

Чтобы настроить непрерывное архивирование через программу qbackup, модифицируйте файл конфигурации qhb.conf:

Установите в параметре wal_level значение выше minimal.

Установите в параметре archive_mode значение on или always.

Установите параметр archive_command:

archive_command='путь_установки/qbackup backup-wal
  -B каталог_копий -f %f -p %p'

Здесь путь_установки — путь к каталогу установленной версии qbackup, которую вы хотите использовать.

В результате команды qbackup backup-wal все сегменты WAL будут располагаться в директории каталог_копий/wal. Подкаталог /wal будет сформирован автоматически после начала работы.

Примечание
Вместо команды qbackup backup-wal можно воспользоваться любым другим средством при условии, что в процессе непрерывного архивирования сегменты WAL будут попадать в каталог каталог_копий/wal.

Пример возможной конфигурации для копирования работающего кластера:

# qhb.conf
wal_level='replica'
archive_mode='on'
archive_command='/usr/bin/qbackup backup-wal -B /opt/backup_dir -f %f -p %p'

Установка archive_command необходима для прямого (direct) резервного копирования работающего кластера. Для копирования остановленного кластера этот параметр устанавливать не требуется. Для потокового резервного копирования или при использовании подкоманды replica параметры archive_mode и archive_command можно не настраивать. Все необходимые файлы WAL будут автоматически архивированы (возможны варианты архивации). Подробнее о параметрах для репликации, указываемых в qhb.conf, можно прочитать в подразделе Репликация.

Настройка копирования с резервного сервера

Утилита qbackup может копировать данные с резервного сервера. Для этого требуется дополнительная настройка:

на основном сервере в qhb.conf установите для параметра full_page_writes значение on и ненулевое значение для параметра archive_timeout;

на резервном сервере в qhb.conf установите для параметра archive_mode значение always, а для параметра hot_standby — on.

Пример:

# qhb.conf
full_page_writes = on
hot_standby = on
# Следующий параметр обязателен для резервного сервера, если предполагается архивация WAL
archive_mode = always

Для получения самодостаточных копий с резервного сервера выполните действия, описанные в подразделе Настройки для использования qbackup в режиме потокового (stream) резервного копирования.

Для получения архивных копий с резервного сервера выполните действия, описанные в подразделе Настройка непрерывного архивирования WAL.

После этих подготовительных действий вы можете делать резервные копии с резервного сервера в режимах stream и direct.

Копирование с резервного сервера имеет следующие особенности настройки и ограничения:

Если резервный сервер повышается до ведущего во время копирования, копирование прерывается с ошибкой.

На основном сервере необходимо установить ненулевой параметр archive_timeout. Это связано с тем, что утилита qbackup на резервном сервере ожидает переключения сегмента WAL на основном сервере для возможности завершить архивацию на резервном сервере. В противном случае выводится уведомление:

Warning! Still waiting for all required wal segments to be archived.
# Предупреждение! Все еще ожидаю завершения архивации всех требуемых сегментов WAL.

Все необходимые для резервной копии записи WAL должны содержать полные страницы. Для этого требуется включить режим full_page_writes на основном сервере и отказаться от использования в archive_command утилит, удаляющих полные страницы из файлов WAL.

Чтобы qbackup на резервном сервере смогла отправить команду переключения сегмента WAL на основной сервер, необходимо предоставить права на переключение сегмента пользователю, который отвечает за репликацию, например, пользователю repluser. Какой пользователь отвечает за репликацию, можно увидеть в qhb.auto.conf.

GRANT execute ON function pg_switch_wal TO repluser

Настройка SSH для выполнения удаленного восстановления

Программа qbackup поддерживает удаленный режим восстановления. В этом режиме каталог резервных копий находится в локальной системе, а целевой экземпляр QHB работает на удаленном компьютере. В настоящее время удаленное восстановление поддерживается с использованием только одного сетевого протокола — SSH.

Если вы хотите использовать qbackup для восстановления резервной копии в удаленном режиме (применяя SSH), выполните следующие действия:

Установите qbackup в обеих системах: backup_host (сервер резервного копирования) и db_host (сервер баз данных). На удаленном компьютере qbackup должна находиться в таком месте, чтобы она могла запускаться без указания пути к программе

Для организации связи между узлами настройте SSH-соединение без пароля для подключения пользователя (например, backup) на компьютере backup_host к серверу db_host под именем qhb:

[backup@backup_host] ssh-keygen -t rsa
[backup@backup_host] ssh-copy-id -i ~/.ssh/id_rsa.pub qhb@db_host

При необходимости может потребоваться запустить ssh-агент вручную:

[backup@backup_host] eval `ssh-agent`
[backup@backup_host] ssh-add

Проверьте верность настроек с помощью попытки удаленного запуска на backup_host команды:

[backup@backup_host] ssh qhb@db_host qbackup apply-inc --help

Здесь:

backup_host — система, в которой находится каталог копий.

db_host — система, в которой функционирует кластер QHB.

backup — пользователь в системе backup_host, от имени которого запускается qbackup.

qhb — пользователь в системе db_host, от имени которого запускается кластер QHB.

В удаленном режиме восстановления qbackup работает следующим образом:

Основной процесс запускается в системе backup_host и подключается к системе db_host.

В процессе работы на db_host копируются файлы резервной копии, а файлы инкрементальных изменений накладываются путем вызова на db_host программы qbackup.

Если qbackup на db_host сталкивается с ошибкой, основной процесс qbackup выдает информацию о возникшей ошибке и завершает работу.

Распаковка сжатой резервной копии всегда осуществляется в системе backup_host.

Использование

Создание резервной копии

Создание прямой (direct) резервной копии

Для создания прямой резервной копии с локального хоста выполните следующую команду:

qbackup backup -B каталог_копий -D каталог_данных --direct [--compress]
    [параметры подключения...] [параметр...]

Флаг --compress значительно уменьшает размер резервной копии, но может привести к замедлению процесса копирования.

Создание потоковой (stream) резервной копии

Для создания потоковой резервной копии выполните следующую команду:

qbackup backup -B каталог_копий [--compress] [--incremental] [--from идентификатор_копии]
    [параметры подключения...] [параметр...]

qbackup в режиме stream (по умолчанию) поддерживает два основных типа создания резервных копий:

Полные копии. Содержимое каталога_данных копируется в каталог_копий, за исключением необязательных файлов. В каталоге_копий может содержаться любое количество независимых резервных копий.

Инкрементальные копии. При передаче флага --incremental qbackup запрашивает у сервера данные об изменившихся файлах и на основе этих данных создает резервную копию. В инкрементальной копии хранятся только те файлы (или части файлов), которые изменились с момента создания полной копии. Это значительно сокращает размер резервной копии, но может потребовать больше времени для выполнения операций сравнения файлов. (Это экспериментальная функция, но, несмотря на это, резервные копии всегда будут согласованными.) Обратите внимание, что инкрементальные резервные копии могут создаваться продолжительное время и иметь большой размер.

При восстановлении кластера из инкрементальной копии qbackup использует родительскую полную копию и все инкрементальные копии между ними, в совокупности образующие «цепочку копий». Таким образом, прежде чем делать инкрементальные копии, необходимо сделать как минимум одну полную, причем после включения CDC.

Флаг --compress значительно уменьшает размер резервной копии, но может привести к замедлению процесса копирования.

Флаг --from применяется для создания дополнительной цепи резервных копий. Применяется только для инкрементальных резервных копий для выбора, на основе какой из копий (любых полных или инкрементальных) будет сделан инкремент. Также этот флаг необходим, если восстановлению подлежала не последняя резервная копия из цепочки; в таком случае для продолжения инкрементального бэкапирования необходимо указать --from id_восстановленного_бэкапа. Последующие инкрементальные резервные копии могут быть созданы без дополнительного параметра --from. Подробнее можно ознакомиться в подразделе Дополнительные параметры для команды backup.

При указании удаленного хоста можно получить копию кластера базы данных с удаленного сервера по сети, используя протокол репликации. Подробнее о параметрах репликации можно прочитать ниже в подразделе Дополнительные параметры копирования.

qbackup поддерживает протокол репликации для получения резервных копий с удаленного сервера.

Создание реплики кластера

Для создания реплики выполните следующую команду на хосте предполагаемой реплики, используя параметры подключения к основному серверу:

qbackup replica -D каталог_данных [--compress] [параметры подключения...]
    [параметры репликации...] [параметр...]

Эта подкоманда полезна для создания резервного сервера.

Флаг --compress значительно уменьшает размер резервной копии, но может привести к замедлению процесса копирования.

Восстановление кластера

Чтобы восстановить кластер баз данных из резервной копии, выполните команду restore:

qbackup restore -B каталог_копий -D каталог_данных
  -i идентификатор_копии [--tablespace-mapping старый_путь=новый_путь] [--connection] [--wal-directory] [параметры...]

Здесь:

каталог_копий — каталог, в котором хранятся все файлы резервных копий и метаданные.

каталог_данных — каталог, в котором будут храниться данные восстановленного кластера. Этот каталог должен быть создан перед выполнением команды, быть пустым и доступным для записи.

идентификатор_копии определяет, из какой резервной копии будет восстановлен кластер. Если вы выбираете для восстановления инкрементальную копию, qbackup автоматически восстанавливает нижележащую полную копию и затем последовательно применяет все необходимые добавления.

Параметр --connection используется при восстановлении на удаленный хост. См. описание параметра в Параметры подключения для удаленного восстановления.

Параметр --wal-directory используется для переопределения директории расположения архива WAL файлов, если она отличалась от каталога_копий. См. описание параметра --wal-directory ниже.

См. описание параметра --tablespace-mapping ниже.

По окончании работы команды restore запустите службу базы данных, используя qhb_ctl.

ВАЖНО
Восстановление по умолчанию осуществляется до последнего LSN в бэкапе. При наличии непрерывного архива wal можно восстановить до последнего существующего в архиве LSN через указание --target-lsn=latest.

Выполнение восстановления на момент времени (PITR)

Вы можете восстановить состояние кластера на любой момент времени (до заданной точки восстановления), используя с командой restore параметры точки восстановления.

Чтобы восстановить состояние кластера на определенный момент времени, укажите это время в параметре --target-time, в формате timestamp. Например:

qbackup restore -B каталог_копий -D каталог_данных
  -i идентификатор_копии --target-time='2017-05-18 14:18:11+03'

Чтобы восстановить состояние кластера до определенной транзакции, воспользуйтесь параметром --target-xid. Например:

qbackup restore -B каталог_копий -D каталог_данных
  -i идентификатор_копии --target-xid=687

Чтобы восстановить состояние кластера до определенной позиции в журнале (LSN), воспользуйтесь параметром --target-lsn. Например:

qbackup restore -B каталог_копий -D каталог_данных
  -i идентификатор_копии --target-lsn=16/B374D848

Примечание
При указании --target-lsn=latest восстановление будет осуществлено до последнего LSN в непрерывном архиве wal.

Чтобы восстановить состояние кластера до заданной именованной точки восстановления, воспользуйтесь параметром --target-name. Например:

qbackup restore -B каталог_копий -D каталог_данных
  -i идентификатор_копии --target-name='before_app_upgrade'

Чтобы восстановить самое раннее из возможных согласованное состояние кластера, укажите флаг --immediate:

qbackup restore -B каталог_копий -D каталог_данных
  -i идентификатор_копии --immediate

В дополнение к параметрам, устанавливающим точку восстановления, можно также указать параметры --action, --timeline и --inclusive.

Проверка резервных копий

Команда validate позволяет проверить (и в некоторых случая восстановить) целостность резервной копии, а также восстановленного кластера. Существует три вида проверки:

checksum (режим по умолчанию)

По умолчанию по окончании резервного копирования qbackup подсчитывает контрольную сумму всех файлов внутри директории резервной копии, что позволяет впоследствии проверять их согласованность. Этот вариант позволяет сравнить эту контрольную сумму с той, которая будет вычислена в процессе проверки.

Если контрольные суммы не совпадают, то следует перейти к режиму qhb-checksum для выявления поврежденного файла таблицы. Если проверка прошла успешна, это означает, что повреждены второстепенные файлы базы.

qhb-checksum

Позволяет проверить контрольные суммы файлов таблиц баз данных. Эта функция аналогична qhb_checksums за исключением того, что данный режим работает с каталогизированными qbackup резервными копиями. Если резервная копия была сделана с включенными контрольными суммами, то данный вариант позволяет сверить их. Если контрольные суммы были выключены в кластере на момент резервного копирования, то флаг -e --enable позволяет включить их. Может использоваться исключительно на полных несжатых резервных копиях в режиме direct.

database

Этот режим позволяет двоично сравнить файлы внутри резервной копии с файлами кластера. Проверка должна производится на выключенном сервере. Если будут найдены поврежденные файлы, то будет предложено их восстановить. Этот вариант полезен для проверки только что восстановленной базы данных, а также тех файлов таблиц, которые не должны были измениться с момента восстановления. Кроме того, в этом режиме можно проверить только что созданную/восстановленную не архивированную полную резервную копию.

Пример:

$ qbackup backup -B /tmp/backup -h localhost -u user1 -fc # сетевая полная сжатая резервная копия ID: QZWGEI
$ qbackup backup -B /tmp/backup -h localhost -u user1 -fi # сетевая инкрементальная резервная копия ID: QZWGFJ

$ qbackup validate -B /tmp/cat -t checksum -i QZWGFJ # Проверка типа checksum
$ qbackup validate -B /tmp/cat -t qhbchecksum -i QZWGEI # Проверка типа qhb-checksum

$ qbackup restore -B /tmp/backup -i QZWGFJ -D /tmp/qhb-data/ # Восстановление

# Предположительное повреждение файла `/tmp/qhb-data/base/12677/175`

$ qbackup validate -B /tmp/backup -i QZWGFJ -D /tmp/qhb-data/ -t database # Проверка типа database
You are going to restore
/tmp/cat/QZWH7I/base/12677/175 into /tmp/qhb-data/base/12677/175
You are going to restore
/tmp/cat/QZWH7I/base/12677/175 into /tmp/qhb-data/base/12677/175
 yes

$ qbackup validate -B /tmp/backup -i QZWGFJ -D /tmp/qhb-data/ -t database # Проверка типа database (успешная)

При использовании режима проверки database можно использовать флаг -y --yes, позволяющий автоматически согласиться на восстановление

Примечание
В каких ситуациях восстановить файл не получится:

Повреждены одинаковые файлы внутри резервной копии и внутри кластера

Файл были изменен с помощью СУБД

Файл был полностью удален

В файле поврежден заголовок (информация о последнем изменении)

Примечание
Если вы видите предупреждение о том, что файл не найден, то, скорее всего, за время работы СУБД были созданы новые файлы таблиц. Если это не так, значит, был потерян файл в резервной копии.

Запуск qbackup в параллельных потоках

Команды backup и restore могут выполняться в несколько параллельных потоков. Это может существенно ускорять работу qbackup при наличии достаточных ресурсов (ядер процессора, производительности дисковой подсистемы и сети).

Параллельным выполнением управляет ключ командной строки --threads. Например, чтобы запустить резервное копирование в четыре параллельных потока, выполните:

qbackup backup -B каталог_копий [-D каталог_данных] [--direct] [--compress] [--incremental] -j 4

По умолчанию или если заданное число потоков равно 0, qbackup автоматически выбирает число потоков, которое обеспечит максимальную производительность копирования (в ущерб другим операциям).

Управление каталогом резервных копий

С помощью qbackup можно управлять резервными копиями в командной строке:

Просматривать имеющиеся резервные копии

Удалять резервные копии

Просмотр информации о резервных копиях

Чтобы просмотреть список существующих копий для каждого экземпляра, выполните команду list:

qbackup list -B каталог_копий

qbackup выводит список всех имеющихся резервных копий. Например:

+----------+--------+----------+-----------------+----------------------+-----------+----------+----------------------------+----------------------------+------------+
|    ID    | status |   size   | compressed size |         kind         | start lsn |  parent  |         start time         |          end time          |  comment   |
+----------+--------+----------+-----------------+----------------------+-----------+----------+----------------------------+----------------------------+------------+
| LE4D8X48 |  Done  | 43.26MiB |     5.24MiB     |    Full (Stream)     | 0/2000028 |    —     | 2023-02-14 17:55:14 +03:00 | 2023-02-14 17:55:29 +03:00 |     —      |
+----------+--------+----------+-----------------+----------------------+-----------+----------+----------------------------+----------------------------+------------+
| LE4D99DU |  Done  | 25.49MiB |     1.82MiB     | Incremental (Stream) | 0/4000028 | LE4D8X48 | 2023-02-14 17:55:30 +03:00 | 2023-02-14 17:55:44 +03:00 |     —      |
+----------+--------+----------+-----------------+----------------------+-----------+----------+----------------------------+----------------------------+------------+
| LE4D9L7B |  Done  | 25.49MiB |     1.81MiB     | Incremental (Stream) | 0/6000028 | LE4D99DU | 2023-02-14 17:55:45 +03:00 | 2023-02-14 17:55:59 +03:00 |     —      |
+----------+--------+----------+-----------------+----------------------+-----------+----------+----------------------------+----------------------------+------------+
| LE4DB3RG |  Done  | 61.05MiB |     8.69MiB     |    Full (Direct)     | 0/8000028 |    —     | 2023-02-14 17:56:56 +03:00 | 2023-02-14 17:57:21 +03:00 | backup1402 |
+----------+--------+----------+-----------------+----------------------+-----------+----------+----------------------------+----------------------------+------------+

Для каждой копии выдаются следующие сведения:

ID — уникальный идентификатор копии.

status — состояние резервной копии. Возможные варианты:

Done — резервная копия выполнена и готова к использованию;

In Progress — резервное копирование еще выполняется;

Error — резервное копирование завершилось с ошибкой;

Cancelled — резервное копирование отменено.

size — размер резервной копии (в несжатом виде).

compressed size — размер сжатой резервной копии (только для сжатых копий).

kind — тип резервной копии. Возможные варианты:

Full (Stream) — полная потоковая резервная копия;

Incremental (Stream) — инкрементальная потоковая резервная копия;

Full (Direct) — полная прямая резервная копия;

is compressed — сжата ли копия.

start lsn — LSN (Log Sequence Number, последовательный номер в журнале WAL) в момент начала копирования. Отсутствует для копий остановленного кластера.

parent — идентификатор предыдущей копии для инкрементальных копий;

start time — системное время начала резервного копирования;

end time — системное время окончания резервного копирования.

Также можно получить информацию о резервных копиях в формате JSON:

qbackup list -B каталог_копий --json

Пример вывода:

[
  {
    "identifier": "LE8EH39A",
    "status": "Done",
    "size": 45364828,
    "compressed_size": 5498462,
    "kind": {
      "Full": "Stream"
    },
    "start_lsn": "0/2000028",
    "start_time": "2023-02-17T13:40:40+03:00",
    "end_time": "2023-02-17T13:40:55+03:00",
    "parent": null,
    "comment": null
  },
  {
    "identifier": "LE8EHFOO",
    "status": "Done",
    "size": 26723166,
    "compressed_size": 1911987,
    "kind": {
      "Incremental": "Stream"
    },
    "start_lsn": "0/4000028",
    "start_time": "2023-02-17T13:40:56+03:00",
    "end_time": "2023-02-17T13:41:10+03:00",
    "parent": "LE8EH39A",
    "comment": null
  },
  {
    "identifier": "LE8EHR4V",
    "status": "Done",
    "size": 26725616,
    "compressed_size": 1902556,
    "kind": {
      "Incremental": "Stream"
    },
    "start_lsn": "0/6000028",
    "start_time": "2023-02-17T13:41:11+03:00",
    "end_time": "2023-02-17T13:41:25+03:00",
    "parent": "LE8EHFOO",
    "comment": null
  }
]

Более подробную системную информацию можно получить в файле каталог_копий/catalog.json.

Удаление резервных копий

Для удаления ставшей ненужной резервной копии выполните команду remove:

qbackup remove -B каталог_копий -i идентификатор_копии

Эта команда удалит резервную копию с заданным идентификатором_копии вместе со всеми инкрементальными копиями, которые от нее зависят (если таковые имеются). Таким образом вы можете удалить некоторые последние инкрементальные копии из «цепочки копий». В параметре -i можно передавать несколько идентификаторов через пробел. Перед удалением программа выводит информацию об удаляемых резервных копиях и требует дополнительного подтверждения; это можно выключить с помощью флага -y.

Подробнее о расширенных параметрах этой команды можно прочитать в подразделе Дополнительные параметры для команды remove.

Справка по командной строке

Команды

В этом подразделе описываются команды qbackup. Необязательные параметры этих команд заключаются в квадратные скобки. Подробно все параметры описываются ниже в подразделе Параметры.

help

qbackup help [команда]

Выдает справку по командам qbackup. Если в параметрах задается одна из команд qbackup, выводит подробную информацию по параметрам, которые принимает эта команда.

Аналогичную роль выполняет флаг --help, доступный для любой из команд.

list

qbackup list -B каталог_копий [--json] [--help]

Показывает содержимое каталога копий.

По умолчанию содержимое каталога представляется в виде обычного текста. Можно передать флаг --json, чтобы получить результат в формате JSON.

Более подробно использование этой команды описывается в подразделе Управление каталогом резервных копий.

backup

qbackup backup -B каталог_копий [-D каталог_данных] [--direct]
[--compress] [--incremental]
[--help] [-j число_потоков] [--progress] [параметры репликации...]

Создает резервную копию экземпляра QHB.

Более подробно использование этой команды описывается в подразделе Создание резервной копии.

Подробнее о расширенных параметрах этой команды можно прочитать в подразделе Дополнительные параметры для команды backup.

restore

qbackup restore -B каталог_копий -D каталог_данных -i идентификатор_копии
[--help]
[-j число_потоков] [--progress] [--connection] [--wal-directory]
[параметры точки восстановления...]

Восстанавливает экземпляр QHB из резервной копии, расположенной в каталоге_копий, на локальный или удаленный хост.

Более подробно использование этой команды описывается в подразделе Восстановление кластера. Подробную информацию по параметрам точки восстановления см. в подразделе Выполнение восстановления на момент времени (PITR).

replica

qbackup replica -D каталог_данных
[--compress] [--help] [--progress] [параметры репликации...]

Создает реплику экземпляра QHB.

Более подробно использование этой команды описывается в подразделе Создание реплики кластера.

remove

qbackup remove -B каталог_копий -i идентификатор_копии [идентификатор_копии_2]... [--single]
[--help] [--progress]

Удаляет копию с заданным идентификатором_копии и все инкрементальные копии, которые от нее зависят. Если передан флаг --single, удаляет только указанную копию.

Подробнее о расширенных параметрах этой команды можно прочитать в подразделе Дополнительные параметры для команды remove.

Более подробно использование этой команды описывается в подразделе Удаление резервных копий.

validate

qbackup validate -B каталог_копий -i идентификатор_копии [-D каталог_данных] [-t тип_проверки]
[--help] [--progress] [параметр...]

Проверяет корректность резервной копии с заданным идентификатором_копии. Существует три типа проверки: checksum, qhb-checksum и database; подробную информацию см в подразделе Проверка резервных копий.

backup-wal

qbackup backup-wal -B каталог_копий [--compress] --wal-file-name=имя_файла_wal --wal-file-path=путь_к_файлу_wal
[--help]

Копирует файлы WAL в соответствующий подкаталог каталога копий. При необходимости для расположения WAL можно использовать любую директорию, отличную от каталога_копий, и учитывать это при выполнении restore-wal.

Флаг --compress значительно уменьшает размер архивированного WAL, но может привести к замедлению процесса копирования.

Команду backup-wal можно указать как значение параметра archive_command QHB при настройке непрерывного архивирования WAL. Подробную информацию см. в подразделе Настройка непрерывного архивирования WAL.

restore-wal

qbackup restore-wal --destination-wal-file-path=путь_и_имя_файла_wal --source-wal-file-path=путь_к_исходному_файлу_wal
[--help]

Копирует файлы WAL в соответствующий подкаталог с нужным именем.

Команда restore-wal автоматически используется как restore_command в qbackup.conf после восстановления.

Пример использования:

restore_command='qbackup restore-wal -f /tmp/catalog/wal/%f -p %p'

Параметры

Общие параметры

Общие параметры поддерживаются большей частью команд qbackup.

-j
--threads
Позволяет указать количество потоков для операций, которые поддерживают параллельное выполнение (backup и restore).
По умолчанию, если значение параметра не установлено или равно 0, используется оптимальное число потоков для данной системы.

-P
--progress
Выводит интерактивную информацию о процессе выполнения операции. Предназначено для отображения пользователям. При работе потокового резервного копирования также получает информацию о размере табличных пространств.

-v
--verbose
Выводит более подробную информацию о процессе выполнения операции.

Параметры подключения

Параметры подключения для резервного копирования

Для подключения к базе необходимо указать ряд параметров. Обязательными для успешного подключения являются только два из них: имя пользователя и имя хоста базы. Необходимость остальных параметров определяется настройками кластера.

Параметры подключения используются только в команде backup и только для резервного копирования работающего кластера.

-u
--user
Имя пользователя базы, от имени которого выполняется копирование. Значение по умолчанию qhb.
Если установлена переменная среды QBACKUP_USER, используется ее значение. Параметр, указанный в командной строке, имеет приоритет над переменной среды.

-h
--host
Указывает имя хоста компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix. Множественные имена хоста можно перечислить через запятую. Каждое из имен будет опробовано последовательно. Значение по умолчанию localhost.
Если установлена переменная среды QBACKUP_HOST, используется ее значение. Параметр, указанный в командной строке, имеет приоритет над переменной среды.

--db-name
Имя базы данных для подключения. По умолчанию соответствует имени пользователя.
Если установлена переменная среды QBACKUP_DBNAME, используется ее значение. Параметр, указанный в командной строке, имеет приоритет над переменной среды.

-p
--port
Порт для подключения к базе. Несколько портов можно перечислить через запятую.
Число указанных портов может быть равно 1, (в этом случае для всех хостов используется один и тот же порт), либо должно соответствовать указанному число хостов. Значение по умолчанию 5432.
Если установлена переменная среды QBACKUP_PORT, используется ее значение. Параметр, указанный в командной строке, имеет приоритет над переменной среды.

--password
Пароль пользователя для подключения к базе.
Если установлена переменная среды QBACKUP_PASSWORD, используется ее значение. Параметр, указанный в командной строке, имеет приоритет над переменной среды. Если пароль не указан, но он потребовался в процессе работы программы, он будет запрошен в диалоговом режиме. После первой неверной попытки будет выведено сообщение об ошибке аутентификации.

-w
--no-password
Никогда не входить в диалоговый режим для запроса пароля. Этот параметр может быть полезен в скриптах автоматизации.

Параметры подключения для удаленного восстановления

-C
--connection
Параметр подключения к удаленному хосту через ssh для восстановления из резервной копии.
Задается в формате -C user@ip:port при настроенном ssh-соединении между хостами. См. подраздел Настройка SSH для выполнения удаленного восстановления.

Параметры точки восстановления

Для команды restore можно указать дополнительные параметры, регулирующие восстановление до определенной точки (PITR).

Используются только в команде restore.

--target-time=момент_времени
Восстановление до определенного момента времени в формате timestamp.
Значение этого параметра задается в том же формате, что принимается типом данных timestamp with time zone, за исключением того, что в нем нельзя использовать сокращенное название часового пояса. Поэтому рекомендуется задавать числовое смещение от UTC или записывать название часового пояса полностью, например, Europe/Helsinki (но не EEST).

--target-xid=xid_транзакции
Восстановление на определенный идентификатор транзакции.
Имейте в виду, что числовое значение идентификатора отражает последовательность именно старта транзакций, а фиксироваться они могут в ином порядке. Восстановлению будут подлежать все транзакции, которые были зафиксированы до указанной (и, возможно, включая ее, в зависимости от значения параметра --inclusive).

--target-lsn=[ latest | LSN ]
Восстановление на определенный LSN. Этот параметр принимает значение системного типа данных pg_lsn. С ключевым словом latest восстанавливаться будет до последнего LSN в архиве.

--target-name=имя_точки_восстановления
Восстановление на определенную именованную точку восстановления, созданную с помощью функции pg_create_restore_point().

--immediate
Данный параметр указывает, что процесс восстановления должен завершиться, как только будет достигнуто целостное состояние, т. е. как можно раньше. При восстановлении из оперативной резервной копии, это будет точкой, в которой завершился процесс резервного копирования.

--action=[ pause | promote | shutdown ]
Указывает, какое действие должен предпринять сервер после достижения цели восстановления. Вариант по умолчанию — pause, что означает приостановку восстановления. Второй вариант, promote, означает, что процесс восстановления завершится, и сервер начнет принимать подключения. Наконец, с вариантом shutdown сервер остановится, как только целевая точка восстановления будет достигнута.
Вариант pause позволяет выполнить запросы к базе данных и убедиться в том, что достигнутая цель оказалась желаемой точкой восстановления. Для снятия с паузы нужно вызвать функцию pg_wal_replay_resume(), что в итоге приведет к завершению восстановления. Если окажется, что желаемая точка восстановления еще не достигнута, нужно остановить сервер, установить более позднюю цель и перезапустить сервер для продолжения восстановления.
Вариант shutdown полезен для получения готового экземпляра сервера в желаемой точке. При этом данный экземпляр сможет воспроизводить дополнительные записи WAL (а при перезапуске ему придется воспроизводить записи WAL после последней контрольной точки).
Обратите внимание, что так как recovery.signal не переименовывается, когда в --action выбран вариант shutdown, при последующем запуске будет происходить немедленная остановка, пока вы не измените конфигурацию или не удалите файл recovery.signal вручную.
Этот параметр не действует, если целевая точка восстановления не установлена.

--timeline=[ current | latest | id_временной_шкалы ]
Указывает временную шкалу для восстановления. Значение может задаваться числовым идентификатором временной шкалы (id_временной_шкалы) или ключевым словом. С ключевым словом current восстанавливается та временная шкала, которая была активной при создании базовой резервной копии. С ключевым словом latest восстанавливаться будет последняя временная шкала, найденная в архиве, что полезно для резервного сервера. По умолчанию подразумевается latest.

--inclusive=[ on | off ]
Прекратить восстановление в момент достижения заданной точки (on) или непосредственно перед ее достижением (off). По умолчанию установлено on.

--wal-directory=каталог_архива_wal
Переопределить директорию с архивом WAL. Требуется при указании директории в archive_command, отличной от каталога_копий. По умолчанию параметр соответствует -B каталог_копий.

Дополнительные параметры копирования

-R
--write-recovery-conf
Записать строку подключения к основному серверу в qhb.auto.conf. Записывает параметры primary_slot_name и primary_conninfo. Это параметр по умолчанию; при ошибке подключения к основному серверу нужно отредактировать эти параметры вручную.

-r
--max-rate=максимальная_скорость
Ограничить скорость передачи файлов. Принимает суффиксы k — КБ и M - МБ. Принимает значения в пределах от 32 КБ/с до 1024 МБ/с.

-X
--wal-method=[ stream | fetch | none ]
Способ архивирования WAL-файлов во время репликации.
Здесь:

stream — архивировать WAL в отдельном потоке параллельно. Это значение по умолчанию. Файлы архивируются в $PGDATA/pg_wal/.

fetch — архивировать WAL вместе с файлами кластера. Они будут расположены внутри директории $PGDATA/pg_wal/.

none — не архивировать файлы WAL.

ВНИМАНИЕ!
При выборе метода none файлы WAL не будут архивироваться! Вам следует архивировать файлы WAL вручную, иначе резервная копия будет недействительна!

ВНИМАНИЕ!
При наличии шифрованных таблиц потоковый метод (wal-method=stream) использовать не рекомендуется.

-S
--slotname=имя_слота
Создать слот репликации для архивирования WAL-файлов. По умолчанию создается временный слот репликации, а данный параметр создает постоянный слот репликации. В случае, если слот имя_слота уже существует, то он будет использоваться для репликации, если же такого слота нет, то он будет создан.

Примечание
Имя слота репликации может включать в себя только английские буквы нижнего регистра (a-z), цифры (0-9) и символ нижнего подчеркивания (_).

-T
--tablespace-mapping старый_путь=новый_путь
Выполняет адресацию табличных пространств. Адресация — изменение оригинального пути директории табличного пространства на новый. Этот параметр можно указывать несколько раз.
Здесь:

старый_путь — оригинальный путь табличного пространства.

новый_путь — новый путь (путь адресации) табличного пространства.

Прочие параметры

Дополнительные параметры для команды backup

-i
--incremental
Позволяет создавать инкрементальные резервные копии.

-c
--compress
Позволяет уменьшить размер резервной копии.

-f
--fast-checkpoint
Запрашивает выполнение быстрой контрольной точки вместо обычной. Применяется при резервном копировании с горячего сервера или при репликации.

--skip-broken
Позволяет базировать новые инкрементальные копии на последней успешной копии, пропуская ошибочные без их непосредственного удаления.

-C
--comment
Позволяет добавить комментарий для резервной копии.

--no-checksum
Позволяет выключить подсчет контрольной суммы резервной копии. Применяйте, только если уверены, что осознаете все последствия.

--from
Позволяет выбрать родительскую резервную копию для создания от него инкрементальной копии. Применяется для создания дополнительной цепи резервных копий. Для указания используется идентификатор резервной копии.

Дополнительные параметры для команды remove

-y --yes
Позволяет удалить резервные копии без дополнительного предостережения.

--prune
Позволяет удалить все резервные копии.

--single
Позволяет удалить одну копию без удаления цепочки.

ВНИМАНИЕ!
После удаления резервной копии из цепочки все копии, сделанные позже нее, становятся недействительными!

Версионирование

При разработке qbackup используется семантическое версионирование semver.

Высокая доступность, балансировка нагрузки и репликация

Серверы баз данных могут работать вместе, что позволяет быстро переключаться на второй сервер в случае отказа первого (высокая доступность), или позволяет нескольким компьютерам обрабатывать одни и те же данные (балансировка нагрузки). В идеале серверы баз данных могут работать вместе незаметно для пользователя. Веб-серверы, обрабатывающие статические страницы, можно достаточно легко объединить, просто распределив веб-запросы на несколько машин. Собственно, серверы баз данных только для чтения тоже можно относительно легко объединить. К сожалению, большинство серверов баз данных получают смешанные запросы на чтение/запись, а серверы чтения/записи объединить гораздо сложнее. Это связано с тем, что данные только для чтения нужно разместить на каждом сервере только один раз, а запись на любой из серверов должна распространяться на все серверы, чтобы будущие запросы на чтение к этим серверам возвращали согласованные результаты.

Эта проблема синхронизации является фундаментальным препятствием для совместной работы серверов. Поскольку универсального решения, ликвидирующего влияние проблемы синхронизации во всех случаях, не существует, имеется несколько решений. Каждое из них подходит к этой проблеме по-своему и минимизирует ее влияние для конкретной рабочей нагрузки.

Некоторые решения справляются с задачей синхронизации, позволяя модифицировать данные только одному серверу. Серверы, способные модифицировать данные, называются серверами чтения/записи, главными или основными серверами. Серверы, отслеживающие изменения на основном, называются резервными или вторичными серверами. Резервный сервер, к которому нельзя подключаться, пока он не будет повышен до основного, называется сервером теплого резерва, а тот, который может принимать подключения и обрабатывать запросы только на чтение, называется сервером горячего резерва.

Некоторые решения являются синхронными, то есть транзакция, модифицирующая данные, не считается зафиксированной, пока все серверы ее не зафиксируют. Это гарантирует, что при сбое не потеряются никакие данные и что все серверы с балансировкой нагрузки вернуть согласованные результаты независимо от того, к какому серверу был запрос. Асинхронное решение, наоборот, допускает некоторую задержку между временем фиксации и передачей транзакции другим серверам, допуская возможность, что некоторые транзакции могут быть потеряны при переключении на резервный сервер и что серверы с балансировкой нагрузки могут вернуть слегка устаревшие результаты. Асинхронная передача используется, когда синхронная будет чересчур медленной.

Также решения можно классифицировать по степени их детализации. Некоторые решения работают только со всем сервером баз данных, тогда как другие допускают управление на уровне таблиц или баз данных.

В любом случае следует учитывать производительность. Обычно выбирается компромисс между функциональностью и производительностью. Например, полностью синхронное решение в медленной сети может снизить производительность более чем наполовину, тогда как асинхронное будет оказывать лишь минимальное влияние.

Далее в этой главе кратко обрисовываются различные решения по реализации отказоустойчивости, репликации и балансировки нагрузки.

Сравнение различных решений

Отказоустойчивость на разделяемых дисках

Отказоустойчивость на разделяемых дисках позволяет избежать издержек синхронизации, поскольку имеется только одна копия базы данных. Она применяет единственный дисковый массив, который совместно используется несколькими серверами. Если основной сервер баз данных отказывает, резервный сервер может подключиться и запустить базу данных, как будто та восстановилась после сбоя. Это обеспечивает быстрое переключение при отказе без потери данных.

Функциональность разделяемого оборудования распространена в сетевых устройствах хранения. Также возможно использование сетевой файловой системы, хотя следует позаботиться о том, чтобы поведение системы полностью соответствовало POSIX (см. подраздел NFS). Единственное значительное ограничение этого метода заключается в том, что при отказе или повреждении разделяемого дискового массива и основной, и резервный серверы перестанут функционировать. Другая сложность состоит в том, что резервный сервер никогда не должен обращаться к разделяемому хранилищу, пока работает основной.

Репликация на уровне файловой системы (блочного устройства)

Модифицированной версией функциональности разделяемого оборудования является репликация на уровне файловой системы, где все изменения в файловой системе отражаются в файловой системе, находящейся на другом компьютере. Единственное ограничение заключается в том, что отражение должно происходить способом, гарантирующим, что на резервном сервере имеется согласованная копия файловой системы — в частности, записями на резервном сервере должны происходить в том же порядке, что и на основном. DRBD является популярным решением с репликацией файловой системы для Linux.

Доставка журналов упреждающей записи

Серверы теплого и горячего резерва могут поддерживаться в актуальном состоянии путем чтения потока записей журнала упреждающей записи (WAL). При сбое на главном сервере резервный содержит почти все данные с него и может быстро быть преобразован в новый основной сервер баз данных. Этот процесс может быть синхронным или асинхронным и может быть выполнен только на уровне всего сервера баз данных.

Резервный сервер можно реализовать путем использования передачи файлов журналов (см. раздел Доставка журналов на резервные серверы), или потоковой репликации (см. подраздел Потоковая репликация), или их комбинацией. Информацию о горячем резерве см. в разделе Горячий резерв.

Логическая репликация

Логическая репликация позволяет серверу баз данных передавать поток модификаций данных другому серверу. Механизм логической репликации QHB формирует поток модификаций данных из WAL. Логическая репликация позволяет воспроизводить изменения данных на уровне таблиц. Кроме того, сервер, публикующий свои изменения, может также подписываться на изменения от другого сервера, позволяя данным передаваться в нескольких направлениях. Более подробную информацию о логической репликации см. в главе Логическая репликация. Через интерфейс логического декодирования (глава Логическое декодирование) подобную функциональность могут предоставлять также и сторонние расширения.

Репликация основной-резервный на основе триггеров

Обычно с установкой репликации на основе триггеров запросы, модифицирующие данные, отправляются на выбранный основной сервер. Действуя на уровне таблиц, основной сервер асинхронно (как правило) передает изменения данных резервным серверам. Резервные сервера могут выполнять запросы одновременно с основным и допускать некоторые изменения локальных данных или операции записи. Эта форма репликации часто применяется для снижения нагрузки, связанной с крупными аналитическими запросами или запросами к хранилищу данных.

Примером такого типа репликации является Slony-I, с детализацией на уровне таблиц и поддержкой нескольких резервных серверов. Поскольку она обновляет резервный сервер асинхронно (пакетами), существует вероятность потери данных во время переключения при отказе.

Репликация на основе SQL в ПО промежуточного слоя

При репликации на основе SQL в промежуточном слоя программа перехватывает каждый запрос SQL и передает его одному или всем серверам. Каждый сервер работает независимо. Запросы на чтение-запись должны передаваться всем серверам, чтобы каждый из них получил все изменения. Но запросы только на чтение можно передавать только одному серверу, позволяя распределить нагрузку чтения между всеми серверами.

Если запросы просто рассылаются без модификаций, функции вроде random(), CURRENT_TIMESTAMP и последовательности могут получить различные значения на разных серверах. Это связано с тем, что каждый сервер работает независимо, а вместо фактических изменений данных рассылаются запросы SQL. Если это неприемлемо, то ПО промежуточного слоя либо приложение должны получить подобные значения из одного источника, а затем использовать их в запросах на запись. Также следует позаботиться о том, чтобы все транзакции фиксировались или прерывались на всех серверах, возможно, с помощью двухфазной фиксации (см. PREPARE TRANSACTION и COMMIT PREPARED). Примерами репликации этого типа являются Pgpool-II и Continuent Tungsten.

Асинхронная репликация с несколькими главными серверами

Для серверов, которые подключаются нерегулярно или через медленные каналы связи, как, например, ноутбуки или удаленные серверы, поддержание согласованности данных между ними представляет сложную задачу. При использовании асинхронной репликации с несколькими главными серверами каждый сервер работает независимо и периодически связывается с другими серверами, чтобы определить конфликтующие транзакции. Эти конфликты могут разрешиться пользователями или правилами разрешения конфликтов. Примером репликации такого типа является Bucardo.

Синхронная репликация с несколькими главными серверами

При синхронной репликации с несколькими главными серверами каждый сервер может принимать запросы на запись, а модифицированные данные передаются с исходного сервера всем остальным, прежде чем каждая транзакция будет зафиксирована. Интенсивная запись может вызвать излишние блокировки, приводящие к снижению производительности. Запросы на чтение могут передаваться на любой сервер. Для снижения издержек при обмене данными некоторые реализации используют разделяемые диски. Синхронная репликация с несколькими главными серверами лучше всего подходит для сред, где преобладают операции чтения, хотя ее значительное преимущество состоит в том, что любой сервер может принимать запросы на запись — нет необходимости разделять рабочую нагрузку между основным и резервными серверами, а поскольку изменения данных передаются от одного сервера другому, отсутствует проблема с недетерминированными функциями вроде random().

QHB не предоставляет репликацию такого типа, хотя для ее реализации в коде приложения или в ПО промежуточного слоя можно использовать двухфазную фиксацию QHB (см. PREPARE TRANSACTION и COMMIT PREPARED).

В Таблице 1 кратко перечислены возможности различных решений.

Таблица 1. Матрица свойств высокой доступности, балансировки нагрузки и репликации

Свойство | Раздел. диск | Репл. файл. сист. | Доставка WAL | Логическ. репл. | Триггерн. репл. | Репл. SQL в пром. слое | Асинхр. репл. с н. гл. | Синхр. репл. с н. гл.
Изв. примеры | NAS | DRBD | встр. поток. репл. | встр. логич. репл., pglogical | Londiste, Slony | pgpool-II | Bucardo |
Метод коммуник. | раздел. диски | дисков. блоки | WAL | логич. декодир. | строки табл. | SQL | строки табл. | строки табл. и блокир. строк
Не треб. спец. оборуд. |  | • | • | • | • | • | • | •
Допуск. неск. осн. серверов |  |  |  | • |  | • | • | •
Нет издержек на осн. | • |  | • | • |  | • |  |
Нет ожидания при неск. серверах | • |  | без синхр. | без синхр. | • |  | • |
Отказ осн. не приводит к потере данных | • | • | с синхр. | с синхр. |  | • |  | •
Сервер реплики приним. запр. только на чтен. |  |  | с горячим резервом | • | • | • | • | •
Детал. репликац. на ур. таблиц |  |  |  | • | • |  | • | •
Не треб. разрешен. конфликтов | • | • | • |  | • | • |  | •

Существует также несколько решений, которые не подпадают под указанные выше категории:

Партиционирование данных

При партиционировании данных таблицы расщепляются на наборы данных. Каждый набор может быть модифицирован только на одном сервере. К примеру, данные могут быть партиционированы по офисам, например, Москва и Новосибирск, с сервером в каждом офисе. Если требуются запросы с комбинированными данными по Москве и Новосибирску, приложение может запросить оба сервера, или может быть применена репликация основной/резервный для хранения на каждом сервере данных из другого офиса в виде копии только для чтения.

Выполнение параллельных запросов на нескольких серверах

Многие из описанных выше решений допускают обработку нескольких запросов несколькими серверами, но ни одно из них не разрешает одному запросу использовать несколько серверов для ускорения вычислений. Это решение позволяет нескольким серверам параллельно обрабатывать один запрос. Обычно это достигается путем разделения данных между серверами, чтобы каждый сервер выполнил свою часть запроса и вернул результаты центральному серверу, где они объединяются и возвращаются пользователю. Это решение можно реализовать с помощью набора средств PL/Proxy.

Доставка журналов на резервные серверы

Для создания кластерной конфигурации высокой доступности (high availability, HA) с одним или несколькими резервными серверами, готовыми взять под контроль операции в случае краха основного сервера, может использоваться постоянное архивирование. Эта способность широко известна как теплый резерв или доставка журналов.

Основной и резервный серверы работают вместе для обеспечения этой возможности, хотя эти серверы связаны лишь косвенно. Основной сервер работает в режиме постоянного архивирования, тогда как каждый резервный сервер работает в режиме постоянного восстановления, читая файлы WAL от основного. Для включения этой возможности нет необходимости вносить изменения в таблицы базы данных, поэтому издержки на ее администрирование ниже, чем у ряда других решений репликации. Кроме того, эта конфигурация относительно слабо влияет на производительность основного сервера.

Прямая передача записей WAL с одного сервера баз данных на другой обычно описывается как доставка журналов. QHB реализует доставку журналов на уровне файлов путем передачи записей WAL по одному файлу (сегменту WAL) за раз. Файлы WAL (16 МБ) можно легко и незатратно доставить на любое расстояние, будь то соседняя системы, другая системы в местной сети или система на другом конце света. Пропускная способность, требуемая для этой методики зависит от скорости записи транзакций на основном сервере. Доставка журналов на уровне записей более фрагментарна и передает поток изменений WAL последовательно через сетевое соединение (см. подраздел Потоковая репликация).

Следует отметить, что доставка журналов асинхронна, т. е. записи WAL доставляются после фиксации транзакции. В результате образуется временное окно потенциальной потери данных при критическом отказе основного сервера; еще не доставленные транзакции будут утеряны. Размер этого окна при доставке журналов на уровне файлов можно ограничить с помощью параметра archive_timeout, в котором можно установить значение всего в несколько секунд. Однако такое низкое значение потребует значительно увеличить пропускную способность, необходимую для доставки файлов. Потоковая репликация (см. подраздел Потоковая репликация) позволяет сделать окно потенциальной потери данных гораздо меньше.

Производительность восстановления довольно высока — обычно резервный сервер становится полностью доступным всего через пару мгновений после активации. В результате эта конфигурация называется теплым резервом, который обеспечивает весьма высокую доступность. Восстановление сервера из архивной копии базы данных и восстановление изменений занимает значительно больше времени, так что эта методика предлагает решение только для восстановления после аварии, но не высокую доступность. Также резервный сервер может обрабатывать запросы только на чтение, и в этом случае он называется сервером горячего резерва. Подробную информацию см. в разделе Горячий резерв.

Планирование

Обычно разумно создавать основной и резервный серверы так, чтобы они были максимально похожи, по крайней мере в том, что касается сервера баз данных. В частности, пути, связанные с табличными пространствами, будут передаваться без изменений, поэтому при использовании этой функциональности и на основном, и на резервных серверах должны быть одинаковые пути монтирования для табличных пространств. Учитывайте, что если CREATE TABLESPACE выполняется на основном сервере, любая новая необходимая для нее точка монтирования должна быть создана на основном и всех резервных серверах еще до выполнения этой команды. Аппаратной части необязательно быть полностью одинаковой, но опыт показывает, что сопровождать на протяжении существования приложения и системы две идентичные системы легче, чем две разные. В любом случае архитектура аппаратной части должна быть одинаковой — доставка журналов, скажем, с 32-битной системы на 64-битную не будет работать.

В целом, доставка журналов между серверами с различными главными версиями QHB невозможна. Политика главной группы разработки QHB состоит в том, чтобы не вносить изменения в дисковые форматы при обновлениях дополнительной версии, так что, скорее всего, основной и резервные серверы с разными дополнительными версиями будут работать успешно. Однако формально это не поддерживается, поэтому рекомендуется, насколько это возможно, поддерживать на основном и резервных серверах одинаковую версию. При обновлении дополнительной версии безопаснее всего будет сначала обновить резервные серверы — новая дополнительная версия с большей вероятностью сможет прочитать файлы WAL из предыдущей дополнительной версии, чем наоборот.

Работа резервного сервера

Сервер переходит в режим резерва, если при его запуске в каталоге данных существует файл standby.signal.

В режиме резерва сервер непрерывно применяет файлы WAL, полученные от основного сервера. Резервный сервер может читать файлы WAL из архива WAL (см. restore_command) или напрямую с основного сервера через соединение TCP (потоковая репликация). Также резервный сервер попытается восстановить все файлы WAL, найденные в его кластере в каталоге pg_wal. Обычно это происходит после перезапуска резервного сервера, когда он снова воспроизводит файлы WAL, полученные от основного перед перезапуском, но можно в любой момент вручную скопировать файлы в pg_wal, чтобы они воспроизвелись.

При запуске резервный сервер начинает восстанавливать все файлы WAL, доступные в архивном хранилище, вызвав команду restore_command. По достижении конца доступных там файлов WAL или при сбое restore_command сервер пытается восстановить все файлы WAL, доступные в каталоге pg_wal. Если это не удается и была сконфигурирована потоковая репликация, резервный сервер пытается подключиться к основному, и начать закачивать поток WAL с последней рабочей записи, найденной в архиве или pg_wal. Если же и это не удается, или потоковая репликация не была сконфигурирована, или подключение позднее прервалось, резервный сервер возвращается к шагу 1 и снова пытается восстановить файл из архива. Этот цикл попыток получения файлов из архива, pg_wal и посредством потоковой репликации продолжается до остановки сервера или переключения его режима, вызванного триггерным файлом.

Режим резерва завершается и сервер переключается в обычный режим работы, когда выполняется команда qhb_ctl promote, вызывается функция pg_promote() или обнаруживается триггерный файл (параметр promote_trigger_file). Перед переключением будут восстановлены все файлы WAL, непосредственно доступные из архива или pg_wal, но попыток подключения к основному серверу больше не будет.

Подготовка основного сервера для работы с резервными

Настройка непрерывного архивирования на основном сервере в архивный каталог, доступный с резервного, описана в разделе Непрерывное архивирование и восстановление на момент времени (PITR). Местоположение архива должно быть доступно с резервного сервера даже при отключении основного, т. е. он должен быть размещен на самом резервном или другом доверенном сервере, но не на основном.

Если вы хотите использовать потоковую репликацию, настройте на основном сервере аутентификацию, чтобы разрешить подключения с резервных серверов; то есть создайте роль и обеспечьте подходящую запись или записи в qhb_hba.conf, а в поле базы данных установите значение replication. Также убедитесь, что в параметре max_wal_senders в файле конфигурации основного сервера установлено достаточно большое значение. Если будут применяться слоты репликации, убедитесь, что значение параметра max_replication_slots тоже достаточно большое.

Создайте базовую резервную копию, как описано в подразделе Создание базовой резервной копии, чтобы запускать резервный сервер.

Настройка резервного сервера

Для настройки резервного сервера восстановите базовую резервную копию, сделанную с основного сервера (см. подраздел Восстановление из непрерывной архивной копии). Затем создайте файл standby.signal в каталоге данных кластера резервного сервера. Задайте в параметре restore_command простую команду для копирования файлов из архива WAL. Если вы планируете иметь несколько резервных серверов в целях высокой доступности, убедитесь, что в параметре recovery_target_timeline установлено latest (значение по умолчанию), чтобы резервный сервер переходил на новую временную шкалу, возникающую при переключении на другой резервный сервер после отказа.

Примечание
Если файла не существует, команда из restore_command должна немедленно завершиться; при необходимости сервер ее повторит.

Если вы желаете использовать потоковую репликацию, задайте в параметре primary_conninfo строку подключения для libpq, включая имя хоста (или IP-адрес) и все дополнительные сведения, необходимые для подключения к основному серверу. Если основному серверу для аутентификации нужен пароль, его тоже следует задать в primary_conninfo.

Если вы настраиваете резервный сервер в целях высокой доступности, настройте на нем архивирование WAL, подключения и аутентификацию, как на основном сервере, поскольку после переключения из-за отказа этот резервный сервер будет работать как основной.

Если вы используете архив WAL, его размер можно уменьшить с помощью команды из archive_cleanup_command, которая будет удалять файлы, больше не нужные резервному серверу. Утилита qhb_archivecleanup разработана специально для использования с командой archive_cleanup_command в типичных конфигурациях с одним резервным сервером. Однако обратите внимание, что если вы используете для резервного копирования архив, следует сохранять файлы, необходимые для восстановления, как минимум с последней базовой резервной копии, даже если они больше не нужны резервному серверу.

Простой пример конфигурации:

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass options=''-c wal_sender_timeout=5000'''
restore_command = 'cp /путь/к/архиву/%f %p'
archive_cleanup_command = 'qhb_archivecleanup /путь/к/архиву %r'

Можно иметь сколько угодно резервных серверов, но если вы используете потоковую репликацию, убедитесь, что установили в max_wal_senders на основном сервере достаточно большое значение, чтобы позволить им подключаться одновременно.

Потоковая репликация

Потоковая репликация позволяет резервному серверу отставать меньше, чем это возможно при доставке журналов на уровне файлов. Резервный сервер подключается к основному, который передает ему поток записей WAL по мере их генерирования, не дожидаясь окончания заполнения файла WAL.

По умолчанию потоковая репликация асинхронна (см. подраздел Синхронная репликация), то есть имеется небольшая задержка между фиксацией транзакции на основном сервере и проявлением этих изменений на резервном. Тем не менее эта задержка гораздо меньше, чем при доставке файлов журналов, обычно менее одной секунды при условии, что резервный сервер достаточно мощный, чтобы справляться с нагрузкой. При потоковой репликации нет необходимости корректировать значение archive_timeout для уменьшения окна потенциальной потери данных.

Если вы выполняете потоковую репликацию без непрерывного архивирования файлов, сервер может переработать старые сегменты WAL до того, как их получит резервный сервер. Если это произойдет, резервному серверу потребуется повторная инициализация из новой базовой резервной копии. Этого можно избежать, установив в параметре wal_keep_size достаточно большое значение, чтобы сегменты WAL точно не перерабатывались слишком рано, или сконфигурировав слот репликации для резервного. Если вы установили архив WAL, который доступен с резервного сервера, в этих решениях нет необходимости, поскольку резервный сервер всегда может воспользоваться архивом, чтобы сократить отставание (при условии, что в том сохраняется достаточно сегментов).

Чтобы использовать потоковую репликацию, настройте резервный сервер для доставки журналов на уровне файлов, как описано в разделе Доставка журналов на резервные серверы. Чтобы переключить его в режим потоковой репликации, нужно установить в параметре primary_conninfo строку подключения, указывающую на основной сервер. Настройте listen_addresses и параметры аутентификации (см. qhb_hba.conf) на основном сервере так, чтобы резервный мог подключиться к его псевдобазе данных replication (см. подраздел Аутентификация).

В системах, поддерживающих параметр сокета keepalive, настройка параметров tcp_keepalives_idle, tcp_keepalives_interval и tcp_keepalives_count помогает основному серверу вовремя заметить прерванное соединение.

Установите максимальное число одновременных подключений резервных серверов (подробную информацию см. в описании параметра max_wal_senders).

При запуске резервного сервера с корректно установленным primary_conninfo он подключится к основному серверу после воспроизведения всех файлов WAL, доступных в архиве. При успешной установке соединения вы увидите на резервном сервере процесс walreceiver, а на основном — соответствующий процесс walsender.

Аутентификация

Крайне важно установить права доступа для репликации так, чтобы поток WAL могли читать только доверенные пользователи, поскольку из него легко извлечь конфиденциальную информацию. Резервные серверы должны аутентифицироваться на основном от имени пользователя с правом REPLICATION или от имени суперпользователя. Рекомендуется создавать для репликации выделенного пользователя с правами REPLICATION и LOGIN. Хотя право REPLICATION дает очень широкие полномочия, оно не позволяет пользователю модифицировать данные в основной системе, как это делает право SUPERUSER.

Аутентификация клиента для репликации управляется записью в qhb_hba.conf с заданным в поле database значением replication. Например, если резервный сервер запущен на хосте с IP-адресом 192.168.1.100 и учетная запись для репликации foo, администратор может добавить в файл qhb_hba.conf основного сервера следующую строку:

# Разрешить пользователю "foo" с хоста 192.168.1.100 подключаться к этому основному
# серверу в качестве резервного сервера репликации, если пользователем был передан
# корректный пароль.
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     foo             192.168.1.100/32        md5

Имя хоста и номер порта для основного сервера, имя подключающегося пользователя и пароль указываются в primary_conninfo. Кроме того, пароль можно задать в файле ~/.pgpass на резервном сервере (укажите в поле database значение replication). Например, если основной сервер запущен на хосте с IP-адресом 192.168.1.50, порт 5432, учетная запись для репликации foo с паролем foopass, администратор может добавить в файл qhb.conf резервного сервера следующую строку:

# Резервный сервер подключается к основному, запущенному на хосте 192.168.1.50
# и порте 5432, от имени пользователя "foo" с паролем "foopass".
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'

Мониторинг

Важным индикатором работоспособности потоковой репликации является количество записей WAL, сгенерированных на основном сервере, но еще не примененных на резервном. Эту задержку можно рассчитать путем сравнения позиции текущей записи WAL на основном сервере с последней позицией WAL, полученной на резервном сервере. Данные позиции можно получить с помощью функции pg_current_wal_lsn на основном и функции pg_last_wal_receive_lsn на резервном сервере соответственно (дополнительную информацию см. в таблицах Функции для управления резервным копированием и Функции для получения информации о восстановлении). Последняя позиция WAL, полученная на резервном сервере, также отображается в текущем статусе процесса- приемника WAL, который выводится с помощью команды ps (дополнительную информацию см. в разделе Стандартные инструменты Unix).

Получить список процессов-передатчиков WAL возможно посредством представления pg_stat_replication. Значительные различия между pg_current_wal_lsn и полем представления sent_lsn могут указывать на большую нагрузку на основном сервере, в то время как разница между sent_lsn и pg_last_wal_receive_lsn на резервном сервере может указывать на сетевую задержку или большую нагрузку на резервном сервере.

На сервере горячего резерва статус процесса-приемника WAL можно получить через представление pg_stat_wal_receiver. Значительные различия между pg_last_wal_replay_lsn и полем представления flushed_lsn указывают на то, что получение WAL происходит быстрее, чем его воспроизведение.

Слоты репликации

Слоты репликации предоставляют автоматический способ сделать так, чтобы основной сервер не удалял сегменты WAL, пока их не получат все резервные серверы, и чтобы основной сервер не удалял строки, которые могут вызвать конфликт восстановления даже при отключении резервного сервера.

Вместо применения слотов репликации для предотвращения удаления старых сегментов WAL можно воспользоваться параметром wal_keep_size либо сохранять сегменты в архиве с помощью команды из archive_command или библиотеки из archive_library. Однако эти методы зачастую приводят к тому, что хранится больше сегментов WAL, чем необходимо, тогда как в слотах репликации хранится лишь то количество сегментов, которое точно понадобится. С другой стороны, в слотах репликации может храниться столько сегментов WAL, что они заполнят все пространство, выделенное для каталога pg_wal; объем файлов WAL, сохраняемых слотами репликации, ограничивается параметром max_slot_wal_keep_size.

Схожим образом параметры hot_standby_feedback и vacuum_defer_cleanup_age обеспечивают защиту актуальных строк от удаления при очистке, но первый параметр не защищает в тот период времени, когда резервный сервер не подключен, а в последнем для получения адекватной защиты часто требуется задавать большое значение. Слоты репликации устраняют эти недостатки.

Запросы и манипуляции слотов репликации

Каждый слот репликации имеет имя, которое может содержать строчные буквы, цифры и символы подчеркивания.

Существующие слоты репликации и их статус можно просмотреть в представлении pg_replication_slots.

Слоты можно создавать и удалять либо через протокол потоковой репликации (см. раздел Протокол потоковой репликации), либо с помощью функций SQL (см. подраздел Функции для управления репликацией).

Пример конфигурации

Слот репликации можно создать следующим образом:

qhb=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
  slot_name  | lsn
-------------+-----
 node_a_slot |

qhb=# SELECT slot_name, slot_type, active FROM pg_replication_slots;
  slot_name  | slot_type | active
-------------+-----------+--------
 node_a_slot | physical  | f
(1 row)

Чтобы сконфигурировать резервный сервер на использование этого слота, следует указать его имя в параметре конфигурации primary_slot_name этого сервера. Вот простой пример:

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
primary_slot_name = 'node_a_slot'

Каскадная репликация

Функциональность каскадной репликации позволяет резервному серверу принимать подключения репликации и поточно передавать записи WAL другим резервным серверам, выступая в качестве посредника. Это можно использовать для снижения количества прямых подключений к основному серверу, а также для минимизации издержек при передаче данных между узлами.

Резервный сервер, действующий одновременно как получатель и отправитель, называется каскадным резервным сервером. Резервные серверы, находящиеся ближе к основному, называются вышестоящими серверами, тогда как более отдаленные резервные серверы называются нижестоящими. Каскадная репликация не ограничивает количество или организацию нижестоящих серверов, хотя каждый резервный сервер подключается только к одному вышестоящему серверу, который в итоге соединяется с единственным основным сервером.

Каскадный резервный сервер отправляет не только записи WAL, полученные от основного, но и те, что были восстановлены из архива. Так что даже если вышестоящее подключение разорвется, потоковая репликация на нижестоящих серверах будет продолжаться, пока доступны новые записи WAL.

В настоящее время каскадная репликация асинхронна. Параметры синхронной репликации (см. подраздел Синхронная репликация) на данный момент не влияют на каскадную репликацию.

Обратная связь горячего резерва распространяется от нижестоящих серверов к вышестоящим независимо от организации каскада.

Если вышестоящий резервный сервер становится новым основным, нижестоящие серверы продолжат получать поток от нового основного, если в параметре recovery_target_timeline установлено 'latest' (значение по умолчанию).

Для использования каскадной репликации настройте каскадный резервный сервер, чтобы он мог принимать подключения репликации (то есть установите параметры max_wal_senders и hot_standby и сконфигурируйте аутентификацию по узлу). Кроме того, вам может понадобиться установить на нижестоящем резервном сервере значение параметра primary_conninfo, указывающее на каскадный резервный сервер.

Синхронная репликация

По умолчанию потоковая репликация в QHB асинхронна. Если на основном сервере происходит сбой, то некоторые зафиксированные транзакции могут не скопироваться на резервный, приводя к потере данных. Объем потерянных данных пропорционален задержке репликации на момент переключения после отказа.

Синхронная репликация предоставляет возможность гарантировать, что все изменения, произведенные транзакцией, были переданы одному или нескольким синхронным резервным серверам. Это повышает стандартный уровень прочности, предлагаемый фиксацией транзакции. Такой уровень защиты соответствует второму уровню безопасности репликации в теории вычислительных систем или первому уровню групповой безопасности (безопасности групповой и первого уровня), когда в synchronous_commit установлено remote_write.

При запросе на синхронную репликацию каждая фиксация пишущей транзакции будет ожидать получения подтверждения, что эта фиксация была записана в журнал упреждающей записи на диске как на основном, так и на резервном сервере. При этом потеря данных может произойти только в том случае, если сбой произойдет одновременно на основном и резервном серверах. Это обеспечивает более высокий уровень прочности, но только если системный администратор благоразумно подходит к размещению и управлению этими двумя серверами. Ожидание подтверждения повышает уверенность пользователя в том, что изменения не будут потеряны во время отказа сервера, но также и увеличивает время отклика для запрашивающей транзакции. Минимальное время ожидания равно времени на передачу и подтверждение данных между основным и резервным серверами.

Транзакциям только для чтения и откатам транзакций не нужно ждать ответов от резервных серверов. Фиксации субтранзакций не ожидают ответов от резервных серверов, только фиксации верхнего уровня. Долгосрочные операции вроде загрузки данных или построения индекса не ожидают финального сообщения о фиксации. Все операции с двухфазной фиксацией требуют ожидания фиксации, включая подготовку и саму фиксацию.

Синхронным резервным сервером может быть резервный сервер при физической репликации или подписчик при логической репликации. Также это может быть любой другой потребитель потока физической или логической репликации WAL, который знает, как отправлять подходящие ответные сообщения. Помимо встроенных систем физической и логической репликации сюда входят специальные программы, например qhb_receivewal и qhb_recvlogical, а также некоторые сторонние системы репликации и настраиваемые программы. Подробную информацию о поддержке синхронной репликации с их использованием можно найти в соответствующей документации.

Базовая конфигурация

При сконфигурированной потоковой репликации для настройки синхронной репликации требуется всего один дополнительный шаг: в параметре synchronous_standby_names необходимо установить непустое значение. Также в параметре synchronous_commit нужно установить on, но поскольку это значение по умолчанию, обычно никаких изменений не требуется. (См. подразделы Параметры и Главный сервер.) При такой конфигурации каждая фиксация будет ожидать подтверждения, что резервный сервер сохранил запись о фиксации в надежное хранилище. Значение synchronous_commit могут устанавливать для себя отдельные пользователи, поэтому оно может быть настроено в файле конфигурации для конкретных пользователей или баз данных либо динамически изменено приложениями для управления гарантией прочности на уровне отдельных транзакций.

После сохранения записи о фиксации на диск на основном сервере эта запись WAL передается на резервный сервер. Тот передает подтверждающие сообщения каждый раз, как очередной блок данных WAL сохраняется на диск, если только в его параметре wal_receiver_status_interval не установлен ноль. В случае, когда в synchronous_commit установлен режим remote_apply, резервный сервер передает подтверждающие сообщения после воспроизведения записи фиксации, делающего транзакцию видимой. Если резервный сервер выбран в качестве синхронного, то в соответствии со значением параметра synchronous_standby_names на основном сервере сообщения от этого резервного сервера вместе с сообщениями от других синхронных резервных серверов будут рассматриваться для определения момента освобождения транзакций, ожидающих подтверждения получения записи фиксации. Эти параметры позволяют администратору указывать, какие резервные серверы должны быть синхронными. Обратите внимание, что конфигурация синхронной репликации производится главным образом на основном сервере. Именованные резервные серверы должны быть напрямую подключены к основному; тот ничего не знает о нижестоящих резервных серверах, использующих каскадную репликацию.

При установке в synchronous_commit значения remote_write каждая фиксация будет ожидать подтверждения, что резервный сервер получил запись фиксации и сохранил ее в своей операционной системе, но не самого сброса данных на его диск. Это значение дает более слабую гарантию прочности, чем значение on: резервный сервер может потерять данные в случае сбоя операционной системы, но не в случае сбоя QHB. Однако на практике такой вариант полезен, поскольку он снижает время отклика для транзакции. Потеря данных может произойти только при одновременном отказе основного и резервного серверов с повреждением базы данных на основном сервере.

При установке в synchronous_commit значения remote_apply каждая фиксация будет ожидать, пока текущие синхронные резервные серверы не сообщат, что они воспроизвели транзакцию, сделав ее видимой для пользовательских запросов. В простых случаях это позволяет обеспечить распределение нагрузки и причинную согласованность.

Пользователи прекратят ожидание при запросе на быструю остановку сервера. Однако, как и при использовании асинхронной репликации, сервер не остановится полностью, пока все ожидающие обработки записи WAL не переместятся на текущие подключенные резервные серверы.

Несколько синхронных резервных серверов

Синхронная репликация поддерживает участие одного или нескольких синхронных резервных серверов; транзакции будут ожидать, пока все резервные серверы, считающиеся синхронными, не подтвердят получение своих данных. Количество синхронных резервных сервером, от которых транзакции должны ожидать ответ, задается в synchronous_standby_names. Также в этом параметре задается список имен резервных серверов и метод выбора синхронных резервных серверов из перечисленных в этом списке (FIRST или ANY).

При методе FIRST задается синхронная репликация на основе приоритетов, а фиксации транзакций вынуждены дожидаться, пока их записи WAL реплицируются на запрошенное количество синхронных резервных серверов, выбранных на основе их приоритетов. Резервные серверы, имена которых находятся в начале списка, имеют более высокий приоритет и будут считаться синхронными. Другие резервные серверы, находящиеся в этом списке после них, представляют потенциальные синхронные резервные серверы. Если любой из текущих синхронных резервных серверов по какой-либо причине отключается, он будет сразу же заменен следующим по приоритетности резервным сервером.

Пример значения synchronous_standby_names для нескольких синхронных резервных серверов, выбираемых на основе приоритетов:

synchronous_standby_names = 'FIRST 2 (s1, s2, s3)'

В этом примере, если запущены четыре резервных сервера s1, s2, s3 и s4, два из них, s1 и s2, будут выбраны как синхронные резервные, поскольку их имена находятся в начале списка имен резервных серверов. Сервер s3 является потенциальным синхронным и возьмет на себя роль синхронного резервного в случае отказа s1 или s2. Сервер s4 является асинхронным резервным, так как его имени в списке нет.

При методе ANY задается синхронная репликация на основе кворума, а фиксации транзакций вынуждены дожидаться, пока их записи WAL реплицируются как минимум на запрошенное количество синхронных резервных серверов в списке.

Пример значения synchronous_standby_names для нескольких синхронных резервных серверов, выбираемых на основе кворума:

synchronous_standby_names = 'ANY 2 (s1, s2, s3)'

В этом примере, если запущены четыре резервных сервера s1, s2, s3 и s4, фиксации транзакций будут ожидать ответов как минимум от любых двух резервных серверов из s1, s2 и s3. Сервер s4 является асинхронным резервным, так как его имени в списке нет.

Статусы синхронности резервных серверов можно увидеть в представлении pg_stat_replication.

Планирование производительности

Обычно синхронная репликация требует тщательного планирования и размещения резервных серверов для обеспечения приемлемой производительности приложений. Ожидание не потребляет системные ресурсы, но блокировки транзакций будут удерживаться до подтверждения передачи. Как следствие, неосмотрительное применение синхронной репликации снизит производительность приложений базы данных из-за увеличения времени отклика и возросшего числа конфликтов.

QHB позволяет разработчикам приложений задавать требуемый уровень прочности посредством репликации. Его можно задать не только для системы в целом, но также и для конкретных пользователей или подключений или даже для отдельных транзакций.

Например, в рабочей нагрузке приложения 10% изменений могут относиться к важным данным клиентов, тогда как 90% — к менее важным данным, потерю которых бизнес сможет пережить гораздо легче (например, сообщения, которыми обмениваются пользователи).

Если параметры синхронной репликации задаются на уровне приложения (на основном сервере), мы можем предложить синхронную репликацию для большинства важных изменений без замедления основной массы общей рабочей нагрузки. Настройка на уровне приложения является важным и практичным инструментом получения выгод синхронной репликации для приложений с высокой производительностью.

Следует учитывать, что пропускная способность сети должна быть выше скорости генерирования данных WAL.

Планирование высокой доступности

В параметре synchronous_standby_names задаются количество и имена синхронных резервных серверов, от которых фиксации транзакций будут ожидать ответов, когда в параметре synchronous_commit установлено значение on, remote_apply или remote_write. Такие фиксации транзакций могут никогда не завершиться, если один из синхронных резервных серверов выйдет из строя.

Лучшим решением для высокой доступности будет обеспечить требуемое количество синхронных резервных серверов. Этого можно добиться, указав в параметре synchronous_standby_names несколько потенциальных синхронных резервных серверов.

При синхронной репликации на основе приоритетов в качестве синхронных будут использоваться резервные серверы, имена которых идут в списке первыми. Серверы, перечисленные после них, возьмут на себя роль синхронных при отказе одного из текущих.

При синхронной репликации на основе кворума кандидатами на роль синхронных будут все резервные серверы, указанные в списке. Даже при отказе одного из них остальные резервные серверы будут продолжать исполнять роль кандидатов.

Когда резервный сервер впервые присоединяется к основному, он еще не будет полностью синхронизированным. Это описывается как режим catchup (наверстывание). Как только отставание резервного сервера от основного впервые сокращается до нуля, система переходит в состояние streaming (потоковая передача) в реальном времени. Сразу после создания резервного сервера наверстывание может быть длительным. При выключении резервного сервера период наверстывания увеличится в соответствии с продолжительностью его нахождения в отключенном состоянии. Резервный сервер может стать синхронным только по достижении им состояния streaming. Это состояние можно увидеть в представлении pg_stat_replication.

Если основной сервер перезапускается, пока фиксации ожидают подтверждения, эти ожидающие транзакции будут помечены как полностью зафиксированные после восстановления базы данных на основном сервере. При этом нет никакой возможности убедиться, что к моменту отказа основного сервера все резервные серверы получили все ожидающие обработки данные WAL. Некоторые транзакции могут не отображаться на резервном сервере как зафиксированные, даже если они отображаются как зафиксированные на основном сервере. Гарантия, которую мы может предложить, заключается в том, что приложение не получит явное подтверждение успешной фиксации транзакции, пока не станет известно, что данные WAL беспрепятственно получены всеми синхронными резервными серверами.

Если вы действительно не можете обеспечить требуемое количество синхронных резервных серверов, следует уменьшить в параметре synchronous_standby_names количество синхронных серверов, от которых фиксации транзакций должны ожидать ответов, (или вовсе выключить его) и перезагрузить файл конфигурации на основном сервере.

Если основной сервер отделен от оставшихся резервных, следует переключиться на наилучшего кандидата из этих оставшихся.

Если вам нужно повторно создать резервный сервер при наличии ожидающих транзакций, убедитесь, что команды pg_start_backup() и pg_stop_backup() запускаются в сеансе с synchronous_commit = off, иначе для вновь возникшего резервного сервера эти запросы будут бесконечно ожидающими.

Непрерывное архивирование на резервном сервере

Когда на резервном сервере используется непрерывное архивирование WAL, возможно два различных сценария: архив WAL может быть совместно используемым основным и резервным серверами, или резервный сервер может иметь собственный архив WAL. Когда у резервного сервера есть собственный архив WAL, установите в archive_mode значение always, и этот сервер будет вызывать команду архивации для каждого сегмента WAL, полученного им при восстановлении из архива или потоковой репликации. С разделяемым архивом можно поступить схожим образом, но archive_command или archive_library должна проверять, не существует ли уже архивируемый файл, и не имеет ли этот существующий файл идентичное содержимое. Это требует от archive_command или archive_library большей осторожности, поскольку она должна позаботиться, чтобы существующий файл с другим содержимым не был перезаписан, но сообщать об успешном выполнении при попытке повторного архивирования идентичного файла. И все это необходимо делать независимо от условий гонки, если два сервера попытаются одновременно заархивировать один и тот же файл.

Если в archive_mode установлено on, то в режиме восстановления или резерва архиватор не включается. При повышении резервного сервера он начнет архивацию после повышения, но не будет архивировать файлы WAL или истории временной шкалы, которые генерировал не он сам. Чтобы заполучить в архив полный набор файлов WAL, следует обеспечить архивацию всех файлов WAL до того, как они достигнут резервного сервера. Это по умолчанию происходит при доставке журналов на уровне файлов, поскольку резервный сервер может восстановить только файлы, которые обнаруживаются в архиве, но при включении потоковой репликации этого не происходит. Когда сервер находится не в режиме восстановления, разницы между режимами on и always нет.

Переключение после отказа

При отказе основного сервера резервный должен начать процедуры переключения после отказа.

При отказе резервного сервера никакого переключения после него не требуется. Если резервный сервер можно перезапустить, даже через некоторое время, то процесс восстановления тоже можно запустить немедленно, воспользовавшись преимуществом возобновляемого восстановления. Если резервный сервер нельзя восстановить, то следует создать полностью новый экземпляр резервного сервера.

Если основной сервер отказывает и резервный сервер становится новый основным, а затем старый основной сервер перезапускается, вам нужно иметь механизм, информирующий старый основной сервер, что тот больше не основной. Иногда его называют STONITH (Shoot The Other Node In The Head, «выстрели в голову другому узлу»), и он необходим, чтобы избежать ситуаций, когда обе системы считают себя основными, что приводит к путанице и в итоге к потере данных.

Во многих системах переключения после отказа используются всего две системы: основная и резервная, соединенные неким контрольным механизмом, который постоянно проверяет соединение между ними и работоспособность основной. Также возможно использование третьей системы (называемой следящим сервером) для предотвращения некоторых вариантов недопустимого переключения после отказа, но эта дополнительная сложность может быть оправдана, только если она установлена с достаточной тщательностью и интенсивным тестированием.

QHB не предоставляет системного программного обеспечения, необходимого для определения сбоя на основном сервере и уведомления резервного сервера баз данных. Существует множество подобных инструментов, которые хорошо интегрируются со средствами операционной системы, требуемыми для успешного переключения после отказа, например, для миграции IP-адреса.

После переключения на резервный сервер только один сервер продолжает работу. Это называется ущербным состоянием. Бывший резервный сервер теперь является основным, но бывший основной сервер отключен и может оставаться отключенным. Для возвращения к нормальной работе нужно воссоздать резервный сервер, либо в бывшей ведущей системе, когда она восстановится, либо в третьей, возможно, новой системе. Для ускорения этого процесса в больших кластерах можно воспользоваться утилитой qhb_rewind. После завершения этого процесса можно считать, что основной и резервный серверы поменялись ролями. Некоторые предпочитают использовать третий сервер в качестве запасного для нового основного, пока не будет воссоздан новый резервный сервер, хотя это явно усложняет конфигурацию системы и рабочие процессы.

Таким образом, переключение с основного сервера на резервный может быть быстрым, но требует некоторого времени на повторную подготовку отказоустойчивого кластера. Регулярное переключение с основного сервера на резервный полезно, поскольку позволяет выделять на каждой системе плановое время на отключение для проведения обслуживания. Кроме того, это позволяет проверить механизм переключения после отказа, чтобы убедиться, что он действительно будет работать, когда потребуется. Рекомендуется документировать эти процедуры администрирования.

Чтобы запустить переключение резервного сервера, принимающего журналы, выполните команду qhb_ctl promote, вызовите функцию pg_promote() или создайте триггерный файл с именем и путем, заданным в параметре promote_trigger_file. Если для переключения вы планируете использовать qhb_ctl promote или вызвать pg_promote(), файл из promote_trigger_file не требуется. Если вы устанавливаете резервный сервер как сервер отчетов, который используется только чтобы разгрузить основной сервер, выполняя запросы на чтение, а не для обеспечения высокой доступности, повышать его до основного нет необходимости.

Горячий резерв

Термин «горячий резерв» используется, чтобы описать возможность подключаться к серверу и выполнять запросы только на чтение, пока сервер находится в режиме резерва или восстановления архива. Это полезно как для целей репликации, так и для восстановления из резервной копии до желаемого состояния с высокой точностью. Кроме того, термин «горячий резерв» относится к возможности сервера переходить из режима восстановления к обычной работе, пока пользователи продолжают выполнять запросы и/или держат свои соединения открытыми.

Выполнение запросов в режиме горячего резерва схоже с обычной операцией с запросами, хотя и с некоторыми отличиями в использовании и администрировании, описанными ниже.

Обзор на уровне пользователя

Когда на резервном сервере параметр hot_standby установлен в true, тот начнет принимать подключения, как только восстановление приведет систему в согласованное состояние. Все подобные подключения открываются строго только для чтения; записывать нельзя даже во временные таблицы.

Получение данных резервным сервером с основного занимает некоторое время, так что между основным и резервным серверами будет измеряемая задержка. Поэтому одновременное выполнение одного и того же запроса на основном и резервном серверах может вернуть разные результаты. Можно сказать, что данные на резервном сервере в конце концов согласуются с основным. После того как запись о фиксации транзакции воспроизводится на резервном сервере, изменения, совершенные этой транзакцией, становятся видимыми во всех новых снимках состояния, сделанных на резервном сервере. Снимки могут быть сделаны в начале каждого запроса или в начале каждой транзакции в зависимости от текущего уровня изоляции транзакции. Более подробную информацию см. в разделе Изоляция транзакций.

Транзакции, запущенные во время горячего резерва, могут выполнять следующие команды:

Доступ к данным: SELECT, COPY TO

Команды для курсора: DECLARE, FETCH, CLOSE

Параметры: SHOW, SET, RESET

Команды управления транзакциями:

BEGIN, END, ABORT, START TRANSACTION

SAVEPOINT, RELEASE, ROLLBACK TO SAVEPOINT

Блоки EXCEPTION и другие внутренние субтранзакции

LOCK TABLE, но только когда выполняется явно в одном из следующих режимов: ACCESS SHARE, ROW SHARE или ROW EXCLUSIVE.

Планы и ресурсы: PREPARE, EXECUTE, DEALLOCATE, DISCARD

Плагины и расширения: LOAD

UNLISTEN

Транзакции, запущенные во время горячего резерва, никогда не получат идентификатор транзакции и не смогут быть записаны в системный журнал упреждающей записи. Поэтому при попытках выполнить следующие действия будут выдаваться сообщения об ошибке:

Язык манипулирования данными (DML): INSERT, UPDATE, DELETE, MERGE, COPY FROM, TRUNCATE. Обратите внимание, что нет разрешенных действий, которые приводили бы к выполнению триггера во время восстановления. Это ограничение применяется даже к временным таблицам, поскольку строки таблиц нельзя прочитать или записать без присвоения идентификатора транзакции, что в среде горячего резерва в настоящее время невозможно.

Язык определения данных (DDL): CREATE, DROP, ALTER, COMMENT. Это ограничение применяется даже к временным таблицам, поскольку выполнение этих операций потребовало бы обновления таблиц системных каталогов.

SELECT ... FOR SHARE | UPDATE, поскольку блокировки строк нельзя провести без обновления нижележащих фалов данных.

Правила для операторов SELECT, которые генерируют команды DML.

LOCK, которая явно требует режим более строгий, чем ROW EXCLUSIVE MODE.

LOCK в короткой форме по умолчанию, поскольку она требует ACCESS EXCLUSIVE MODE.

Команды управления транзакциями, которые явно устанавливают состояние не только для чтения:

BEGIN READ WRITE, START TRANSACTION READ WRITE

SET TRANSACTION READ WRITE, SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE

SET transaction_read_only = off

Команды двухфазной фиксации: PREPARE TRANSACTION, COMMIT PREPARED, ROLLBACK PREPARED, поскольку даже транзакции только для чтения нужно записывать в WAL в фазе подготовки (первая фаза двухфазной фиксации).

Обновления последовательностей: nextval(), setval()

LISTEN, NOTIFY

При обычной работе транзакциям «только для чтения» разрешено использовать LISTEN и NOTIFY, следовательно, сеансы горячего резерва работают с чуть более строгими ограничениями, чем обычные сеансы с доступом только для чтения. Возможно, в будущих выпусках некоторые из этих ограничений будут ослаблены.

Во время горячего резерва параметр transaction_read_only всегда равен true и не может быть изменен. Но если не пытаться модифицировать базу данных, подключения при горячем резерве работают почти так же, как и любое другое подключение к базе данных. Если происходит переключение после отказа или смена ролей, база данных переходит в обычный режим обработки. Когда сервер меняет режим работы, сеансы остаются подключенными. После выхода из режима горячего резерва можно будет запускать транзакции на чтение и запись (даже в сеансах, начатых во время горячего резерва).

Пользователи могут узнать, активен ли в настоящее время режим горячего резерва для их сеанса, выполнив команду SHOW in_hot_standby. (В ранних версиях сервера параметра in_hot_standby не существует; вместо него работает вариант SHOW transaction_read_only.) Кроме того, ряд функций (таблица Функции для получения информации о восстановлении) позволяет пользователям получить информацию о резервном сервере. Эти инструменты позволяют писать программы, учитывающие текущее состояние базы данных. Также их можно использовать для мониторинга прогресса восстановления или писать сложные программы, восстанавливающие базу данных до конкретного состояния.

Обработка конфликтов запросов

Основной и резервный серверы слабо связаны между собой самым различным образом. Действия на основном сервере отразятся на резервном. В результате существует потенциальная возможность отрицательного взаимодействия или конфликтов между ними. Самый простой для понимания конфликт — производительность: если на основном сервере происходит загрузка значительного объема данных, это создаст соответствующий поток записей WAL на резервном сервере, поэтому запросы на последнем могут начать конкурировать за системные ресурсы, например, за ввод/вывод.

Также существуют дополнительные типы конфликта, которые могут произойти на сервере горячего резерва. Эти конфликты называются жесткими конфликтами в том смысле, что для их разрешения может понадобиться отменить запросы, а в некоторых случаях даже прерывать сеансы. Пользователю предоставляется несколько способов обработки таких конфликтов. Сценарии конфликтов включают:

Эксклюзивные блокировки на основном сервере, установленные как с помощью явных команд LOCK, так и при различных операциях DDL, конфликтуют с доступом к таблицам в запросах на резервном сервере.

Удаление табличного пространства на основном сервере конфликтует с запросами на резервном, использующими это табличное пространство для хранения временных рабочих файлов.

Удаление базы данных на основном сервере конфликтует с сеансами, подключенными к этой базе данных на резервном сервере.

Приложение очистки старых записей из WAL конфликтует с транзакциями на резервном сервере, снимки которых по-прежнему могут «видеть» какие-то из удаляемых строк.

Приложение очистки старых записей из WAL конфликтует с запросами, обращающимися к целевой странице на резервном сервере независимо от того, являются ли удаляемые данные видимыми.

На основном сервере эти сценарии просто приводят к ожиданию; пользователь может выбрать, какое из конфликтующих действий отменить. Однако на резервном сервере выбора нет: действие, запротоколированное в WAL, уже произошло на основном сервере, поэтому резервный должен успешно его применить. Более того, позволять приложению для WAL ожидать неограниченное время может быть крайне нежелательно, поскольку состояние резервного сервере значительно отстанет от состояния основного. По этой причине предоставляется механизм принудительной отмены на резервном сервере запросов, конфликтующих с применяемыми записями WAL.

Пример проблемной ситуации: администратор на основном сервере выполнил команду DROP TABLE для таблицы, которая в данный момент фигурирует в запросе на резервном сервере. Очевидно, что такой запрос не может выполняться дальше, если на резервном сервере применится DROP TABLE. Если бы эта ситуация возникла на основном сервере, DROP TABLE ждала бы завершения другого запроса. Но когда DROP TABLE выполняется на основном сервере, у того отсутствует информация о том, какие запросы выполняются на резервном, поэтому основной сервер не будет ожидать завершения подобных запросов. Записи WAL с изменениями поступят на резервный сервер, пока запрос на нем еще выполняется, вызвав конфликт. Резервный сервер должен либо задержать применение этих записей WAL (и всех, что идут за ними), либо отменить конфликтующий запрос, чтобы можно было применить DROP TABLE.

Если конфликтующий запрос короткий, обычно желательно разрешить ему завершиться, ненадолго задержав применение WAL, но длительная задержка применения WAL, как правило, нежелательна. Поэтому у механизма отмены имеются параметры max_standby_archive_delay и max_standby_streaming_delay, определяющие максимально допустимую задержку применения WAL. Конфликтующие запросы будут отменены, если длятся дольше соответствующего времени задержки применения очередных полученных данных WAL. Два параметра существуют, чтобы можно было задать разные значения задержки для чтения данных WAL из архива (т. е. при изначальном восстановлении из базовой резервной копии или при «наверстывании» резервного сервера в случае значительного отставания) и для чтения данных WAL при потоковой репликации.

На резервном сервере, существующем главным образом для обеспечения высокой доступности, лучше всего установить параметры задержки относительно короткими, чтобы этот сервер не слишком отстал от основного из-за задержек, вызванных запросами резервных серверов. Однако если резервный сервер предназначен для выполнения длительных запросов, то может быть предпочтительнее установить значительную или даже бесконечную задержку. Тем не менее учтите, что если длительный запрос задерживает применение записей WAL, другие сеансы на резервном сервере могут не увидеть последние изменения на основном.

После превышения времени задержки, указанного в max_standby_archive_delay или max_standby_streaming_delay, конфликтующие запросы будут отменены. Обычно это приводит только к ошибке отмены, хотя в случае воспроизведения команды DROP DATABASE прервется весь конфликтующий сеанс. Также, если конфликт произошел из-за блокировки, удерживаемой фиктивной транзакцией, конфликтующий сеанс прерывается (это поведение может измениться в будущем).

Отмененные запросы можно тут же повторить (разумеется, после начала новой транзакции). Поскольку причина отмены запроса зависит от природы воспроизводимых записей WAL, отмененный запрос вполне может быть успешен при повторном выполнении.

Учтите, что параметры задержки сравниваются с временем, прошедшим с момента получения резервным сервером данных WAL. Таким образом, допустимый период отсрочки для любого запроса на резервном сервере никогда не превышает значение параметра задержки и может быть значительно короче, если резервный сервер уже отстал от основного в результате ожидания завершения предыдущих запросов или из-за загрузки большого объема обновлений.

Наиболее частой причиной конфликтов между запросами на резервном сервере и воспроизведением WAL является преждевременная очистка. Обычно QHB допускает очистку старых версий строк, когда ни одной транзакции нет необходимости их видеть, в целях обеспечения корректной видимости данных согласно правилам MVCC. Однако это правило могут применяться только к транзакциям, выполняемым на основном сервере. Поэтому возможен сценарий, когда очистка на основном сервере удалит версии строк, которые все еще видимы для транзакции на резервном сервере.

Опытные пользователи должны обратить внимание, что не только очистка, но и замораживание версий строк может потенциально вызвать конфликт с запросами на резервном сервере. Выполнение команды VACUUM FREEZE вручную, скорее всего, вызовет конфликт даже в таблицах, где нет измененных или удаленных строк.

Пользователи должны ясно понимать, что регулярно и значительно изменяемые таблицы на основном сервере быстро станут причиной отмены более долгих запросов на резервном сервере. В таких случаях можно считать, что установка конечного значения в max_standby_archive_delay или max_standby_streaming_delay схожа с установкой параметра statement_timeout.

Если количество отмененных запросов на резервном сервере оказывается неприемлемым, существует ряд возможностей это исправить. Первый вариант — установить параметр hot_standby_feedback, не дающий команде VACUUM удалять строки, ставшие неактивными недавно, и тем самым предотвратить конфликты очистки. Если вы это сделаете, то следует учесть, что это вызовет задержку очистки неактивных строк на основном сервере, что может привести к нежелательному разбуханию таблицы. Тем не менее ситуация с очисткой будет не хуже, чем если бы запросы с резервного сервера выполнялись непосредственно на основном, и при этом вы все равно получали бы преимущества, как при перекладывании этой нагрузки на резервный сервер. Если соединение с резервными серверами часто прерывается и восстанавливается, возможно, стоит скорректировать период, когда обратная связь через hot_standby_feedback не обеспечивается. Например, подумайте об увеличении значения max_standby_archive_delay, чтобы в периоды разъединения при конфликтах в файлах архива WAL запросы отменялись не сразу. Также следует подумать об увеличении значения max_standby_streaming_delay, чтобы избежать быстрой отмены запросов из-за очередных полученных в потоке записей WAL после восстановления подключения.

Другой вариант — увеличить значение параметра vacuum_defer_cleanup_age на основном сервере, чтобы неактивные строки очищались не так быстро, как обычно. Это даст запросам на резервном сервере больше времени на выполнение, прежде чем они будут отменены, без необходимости устанавливать высокое значение в max_standby_streaming_delay. Однако с таким подходом трудно гарантировать какое-то определенное временное окно для выполнения запросов, поскольку vacuum_defer_cleanup_age измеряется в транзакциях, выполняемых на основном сервере.

Количество отмен запросов и их причины можно посмотреть с помощью системного представления pg_stat_database_conflicts на резервном сервере. Краткую информацию также содержит системное представление pg_stat_database.

Пользователи могут управлять тем, создается ли в журнале сообщение, когда воспроизведение WAL ожидает разрешения конфликта дольше заданного в параметре deadlock_timeout времени. Это делается с помощью параметра log_recovery_conflict_waits.

Обзор на уровне администратора

Если в файле qhb.conf параметр hot_standby равен on (это значение по умолчанию) и присутствует файл standby.signal, сервер будет запущен в режиме горячего резерва. Однако может пройти некоторое время, прежде чем к нему будут разрешены подключения, поскольку этот сервер не будет их принимать, пока не произведет восстановление, достаточное для обеспечения состояния согласованности, с которым могут выполняться запросы. В течение этого периода клиентам, пытающимся к нему подключиться, будет отказано, и они получат сообщение об ошибке. Чтобы убедиться, что сервер готов к работе, можно либо повторять попытки подключения из приложения, либо поискать в журналах сервера эти сообщения:

LOG:  entering standby mode
-- вошел в режим резерва

... потом, еще через некоторое время ...

LOG:  consistent recovery state reached
LOG:  database system is ready to accept read-only connections
-- при восстановлении достигнуто согласованное состояние
-- система баз данных готова принимать подключение только на чтение

На основном сервере информация о согласованности записывается один раз во время каждой контрольной точки. Нельзя включить сервер горячего резерва при прочтении данных WAL, записанных в период, когда на основном сервере параметр wal_level не был установлен в значение replica или logical. Достижение согласованного состояния также может быть отложено, если выполняются оба следующих условия:

Пишущая транзакция имеет более 64 субтранзакций

Очень долгосрочные пишущие транзакции

Если вы выполняете доставку журналов на уровне файлов («теплый резерв»), возможно, вам придется подождать получения следующего файла WAL, причем время ожидания может достигать значения параметра archive_timeout на основном сервере.

Значения некоторых параметров определяют объем разделяемой памяти для отслеживания идентификаторов транзакций, блокировок и подготовленных транзакций. Эти структуры разделяемой памяти на резервном сервере должны быть не меньше, чем на основном, чтобы гарантировать, что во время восстановления на резервном сервере не закончится разделяемая память. Например, если на основном сервере выполнялась подготовленная транзакция, но резервный сервер не выделил разделяемую память для отслеживания подготовленных транзакций, то без изменения конфигурации резервного сервера восстановление не сможет продолжаться. Это касается следующих параметров:

max_connections

max_prepared_transactions

max_locks_per_transaction

max_wal_senders

max_worker_processes

Самый простой способ сделать так, чтобы это не стало проблемой, — установить на резервных серверах значения этих параметров равными или превышающими их значения на основном сервере. Поэтому если вы хотите увеличить эти значения, прежде чем вносить изменения на основном сервере, нужно сначала сделать это на всех резервных. И наоборот, если вы хотите уменьшить эти значения, нужно сначала сделать это на основном сервере, а уже потом вносить изменения на всех резервных. Учтите, что когда резервный сервер повышается до основного, он становится новым источником требуемых значений параметров для идущих за ним резервных серверов. Таким образом, чтобы избежать этой проблемы во время переключения узлов или переключения после отказа, рекомендуется устанавливать одинаковые значения этих параметров на всех резервных серверах.

Изменения этих параметров на основном сервере отслеживаются в WAL. Если сервер горячего резерва обрабатывает WAL, указывающий, что текущее значение на основном сервере превышает его собственное, он протоколирует предупреждение и приостанавливает восстановление. Например:

WARNING:  hot standby is not possible because of insufficient parameter settings
-- ПРЕДУПРЕЖДЕНИЕ: горячий резерв невозможен вследствие несоответствующих значений параметров
DETAIL:  max_connections = 80 is a lower setting than on the primary server, where its value was 100.
-- ДЕТАЛИЗАЦИЯ: Значение max_connections = 80 ниже, чем на основном сервере, где это значение равно 100
LOG:  recovery has paused
-- ЖУРНАЛ: восстановление было приостановлено
DETAIL:  If recovery is unpaused, the server will shut down.
-- ДЕТАЛИЗАЦИЯ: Если продолжить восстановление, сервер отключится.
HINT:  You can then restart the server after making the necessary configuration changes.
-- СОВЕТ: После внесения в конфигурацию необходимых изменений вы можете перезапустить сервер.

На этом этапе прежде чем можно будет продолжить восстановление, нужно изменить значения на резервном сервере и перезапустить этот экземпляр. Если резервный сервер не находится в режиме горячего резерва, то когда он обнаруживает несовместимое изменение параметра, то отключается немедленно, без приостановки, поскольку в этом случае нет смысла поддерживать его в рабочем состоянии.

Важно, чтобы администратор выбрал подходящие значения для параметров max_standby_archive_delay и max_standby_streaming_delay. Оптимальные значения зависят от приоритетных направлений деятельности. Например, если главным назначением сервера является обеспечение высокой доступности, то желательно установить низкие значения задержки, возможно даже нулевые, хотя это очень агрессивный вариант. Если резервный сервер служит дополнительным сервером для запросов в системе поддержки принятия решений, то может оказаться приемлемой установка значений максимальной задержки в несколько часов или даже -1, что означает бесконечное ожидание завершения запросов.

Вспомогательные биты статуса транзакций, записанные на основном сервере, не протоколируются в WAL, поэтому данные на резервном сервере, скорее всего, снова перезапишут на нем эти биты. Таким образом, резервный сервер все равно будет производить запись на диск, даже если все пользователи только читают данные; сами значения данных не меняются. Пользователь по-прежнему будут записывать временные файлы при сортировке больших объемов и обновлять информационные файлы кэша отношений, поэтому в режиме горячего резерва ни одна часть базы данных на самом деле не работает в режиме «только чтение». Также обратите внимание, что записи в удаленные базы данных с помощью модуля dblink и другие операции вне базы данных с использованием функций PL все равно будут возможны, даже несмотря на то, что локально транзакции будут только читающими.

В режиме восстановления недоступны следующие типы административных команд:

Язык определения данных (DDL): например, CREATE INDEX

Управление правами и владением: GRANT, REVOKE, REASSIGN

Команды обслуживания: ANALYZE, VACUUM, CLUSTER, REINDEX

Опять же, обратите внимание, что некоторые из этих команд в действительности разрешены для транзакций в режиме «только чтение» на основном сервере.

В результате нельзя создать дополнительные индексы и статистику, существующие исключительно на резервном сервере. Если эти административные команды нужны, их следует выполнить на основном сервере, и в итоге эти изменения распространятся на резервный.

На сервере пользователя будут работать функции pg_cancel_backend() и pg_terminate_backend(), но не процесс запуска, осуществляющий восстановление. Представление pg_stat_activity не отображает восстанавливаемые транзакции как активные. В результате представление pg_prepared_xacts всегда пустует во время восстановления. Если вы желаете разобрать сомнительные подготовленные транзакции, просмотрите pg_prepared_xacts на основном сервере и выполните команды для разбора транзакций там либо по окончание восстановления.

В представлении pg_locks, как обычно, отобразятся блокировки, удерживаемые серверной частью. Также pg_locks показывает виртуальные транзакции, обработанные процессом запуска, который владеет всеми блокировками AccessExclusiveLock, удерживаемыми транзакциями, воспроизведенными при восстановлении. Обратите внимание, что процесс запуска не запрашивает блокировки, чтобы внести изменения в базу данных, поэтому блокировки, отличные от AccessExclusiveLock, для процесса запуска в pg_locks не отображаются; их существование просто подразумевается.

Команды управления файлами WAL, например, pg_start_backup, pg_switch_wal и т. п., во время восстановления работать не будут.

Динамически загружаемые модули, включая pg_stat_statements, работают.

Рекомендательные блокировки при восстановлении работают как обычно, включая выявление взаимоблокировок. Обратите внимание, что рекомендательные блокировки никогда не протоколируются в WAL, поэтому для рекомендательной блокировки как на основном, так и на резервном сервере невозможен конфликт с воспроизведением WAL. Также невозможно получить рекомендательную блокировку на основном сервере и инициировать похожую рекомендательную блокировку на резервном. Рекомендательные блокировки относятся только к тому серверу, на котором они получены.

Системы репликации на основе триггеров, например Slony, Londiste и Bucardo, вообще не будут запускаться на резервном сервере, хотя будут замечательно выполняться на основном сервере, пока изменения не передаются для применения на резервный сервер. Воспроизведение WAL не основано на триггерах, поэтому WAL нельзя переслать с резервного сервера в любую систему, требующую дополнительных записей в базу данных или полагающуюся на использование триггеров.

Новые OID не могут быть присвоены, хотя некоторые генераторы UUID могут по-прежнему работать, если они не прибегают к записи нового статуса в базу данных.

В настоящее время создание временных таблиц во время читающих транзакций не допускается, поэтому в некоторых случаях существующие скрипты будут выполняться некорректно. В будущих выпусках это ограничение может быть ослаблено. Это требует решения как технической проблемы, так и проблемы с соответствием стандарту SQL.

Команда DROP TABLESPACE может выполниться успешно, только если табличное пространство пустое. Некоторые пользователи резервного сервера могут активно использовать табличное пространство посредством своего параметра temp_tablespaces. Если в табличном пространстве имеются временные файлы, все активные запросы отменяются для обеспечения удаления временных файлов, чтобы можно было удалить табличное пространство и продолжить воспроизведение WAL.

Выполнение команды DROP DATABASE или ALTER DATABASE ... SET TABLESPACE на основном сервере сгенерирует запись WAL, которая вызовет принудительное отключение всех пользователей, подключенных к этой базе данных на резервном сервере. Это действие происходит немедленно независимо от значения параметра max_standby_streaming_delay. Обратите внимание, что команда ALTER DATABASE ... RENAME не отключает пользователей, так что обычно проходит незамеченной, хотя в некоторых случаях может вызвать сбой программы, если та каким-то образом зависит от имени базы данных.

Если вы в обычном режиме (не восстановления) выполните DROP USER или DROP ROLE для роли с возможностью подключения, пока этот пользователь еще подключен, то с ним ничего не произойдет — он останется подключенным. Однако переподключиться этот пользователь уже не сможет. Такое же поведение действует в режиме восстановления, так что выполнение DROP USER на основном сервере не отключит этого пользователя от резервного сервера.

Система накопительной статистики во время восстановления активна. Все операции сканирования, чтения, блоки, использование индексов и т. д. будут записываться на резервном сервере обычным образом. Однако воспроизведение WAL не увеличит счетчики, специфичные для отношения или базы данных, т. е. воспроизведение не увеличит значения столбцов (например, n_tup_ins) в представлении pg_stat_all_tables, операции чтения или записи, выполняемые процессом запуска, не будут отслеживаться в представлениях pg_statio_, и не будут увеличиваться значения связанных столбцов pg_stat_database.

Автоочистка во время восстановления неактивна. Она запустится как обычно в конце восстановления.

Процесс контрольных точек и процесс фоновой записи во время восстановления активны. Процесс контрольных точек будет создавать точки перезапуска (схожие с контрольными точками на основном сервере), а процесс фоновой записи будет выполнять обычные операции по очистке блоков. Сюда могут входить обновления информации о вспомогательных битах, сохраненных на резервном сервере. Команда CHECKPOINT во время восстановления принимается, но создает точку перезапуска, а не новую контрольную точку.

Ссылки на параметры горячего резерва

Различные параметры были упомянуты выше в подразделах Обработка конфликтов запросов и Обзор на уровне администратора.

На основном сервере можно использовать параметры wal_level и vacuum_defer_cleanup_age. Параметры max_standby_archive_delay и max_standby_streaming_delay, установленные на основном сервере, не действуют.

На резервном сервере можно использовать параметры hot_standby, max_standby_archive_delay и max_standby_streaming_delay. Параметр vacuum_defer_cleanup_age не действует, пока сервер остается в режиме резерва, но станет значимым, если резервный сервер станет основным.

Ограничения применимости

В режиме горячего резерва имеется несколько ограничений. Они могут и скорее всего будут исправлены в будущих выпусках:

Перед созданием снимка данных требуется полная информация о запущенных транзакциях. Транзакции, использующие большое количество субтранзакций (в настоящее время больше 64) будут задерживать начало подключений только для чтения до завершения самой длительной пишущей транзакции. При возникновении такой ситуации в журнал сервера будут переданы поясняющие сообщения.

Допустимые стартовые точки для запросов на резервном сервере генерируются при каждой контрольной точке на основном. Если резервный сервер отключается, пока основной находится в отключенном состоянии, для него может оказаться невозможным вновь войти в режим горячего резерва, пока не запустится основной сервер, чтобы резервный сервер продолжил генерировать стартовые точки в журналах WAL. В большинстве случаев, когда эта ситуация может произойти, он не является проблемой. Обычно если основной сервер отключен и больше не доступен, это является следствием серьезного сбоя, который в любом случае требует, чтобы резервный сервер был преобразован в новый основной. А в ситуациях, когда основной сервер был отключен намеренно, процедура согласования для гарантии благополучного преобразования резервного сервера в новый основной тоже является стандартной.

В конце восстановления блокировки AccessExclusiveLock, удерживаемые подготовленными транзакциями, потребуют удвоенное по сравнению с обычным количество блокировок записей таблицы. Если вы планируете выполнить либо большое количество параллельных транзакций, обычно запрашивающих AccessExclusiveLock, либо одну большую транзакцию, запрашивающую много AccessExclusiveLock, рекомендуется установить большое значение max_locks_per_transaction, возможно, в два раза больше значения этого параметра на основном сервере. Это необязательно делать, если вы установили в max_prepared_transactions значение 0.

В настоящее время в режиме горячего резерва недоступен уровень изоляции транзакции Serializable. (Подробную информацию см. в подразделах Уровень изоляции Serializable и Обеспечение согласованности с помощью сериализуемых транзакций.) Попытка установить для транзакции уровень сериализуемой изоляции в режиме горячего резерва вызовет ошибку.

Мониторинг активности базы данных

Администратор базы данных часто задается вопросом: «Что сейчас делает система?». В этой главе рассказывается, как это выяснить.

Для мониторинга активности базы данных и анализа производительности доступно несколько инструментов. Большая часть этой главы посвящена описанию сборщика статистики QHB, но не стоит пренебрегать и обычными программами мониторинга Unix, такими как ps, top, iostat и vmstat. Кроме того, после выявления неэффективного запроса может потребоваться дополнительное исследование с помощью команды QHB EXPLAIN. В разделе Использование EXPLAIN рассматривается EXPLAIN и другие методы для изучения поведения отдельного запроса.

Стандартные инструменты Unix

В большинстве платформ Unix QHB изменяет заголовок своей команды, выводимый на экран программой ps, поэтому отдельные процессы сервера можно легко идентифицировать. Пример вывода:

$ ps auxww | grep ^qhb
qhb  15551  0.0  0.1  57536  7132 pts/0    S    18:02   0:00 qhb -i
qhb  15554  0.0  0.0  57536  1184 ?        Ss   18:02   0:00 qhb: background writer
qhb  15555  0.0  0.0  57536   916 ?        Ss   18:02   0:00 qhb: checkpointer
qhb  15556  0.0  0.0  57536   916 ?        Ss   18:02   0:00 qhb: walwriter
qhb  15557  0.0  0.0  58504  2244 ?        Ss   18:02   0:00 qhb: autovacuum launcher
qhb  15582  0.0  0.0  58772  3080 ?        Ss   18:04   0:00 qhb: joe runbug 127.0.0.1 idle
qhb  15606  0.0  0.0  58772  3052 ?        Ss   18:07   0:00 qhb: tgl regression [local] SELECT waiting
qhb  15610  0.0  0.0  58772  3056 ?        Ss   18:07   0:00 qhb: tgl regression [local] idle in transaction

(Допустимый формат вызова ps, как и детали отображаемой информации, отличаются на разных платформах. Этот пример приведен для одной из последних версий системы Linux.) Первым из перечисленных здесь процессов является процесс главного сервера. Выведенные для него аргументы команды те же, что использовались при запуске. Следующие четыре процесса являются фоновыми рабочими процессами, автоматически запускаемыми главным процессом. (Процесса «autovacuum launcher» (фоновый процесс «Автовакуум») не будет в этом списке, если в системе выключена автоочистка.) Каждый из оставшихся процессов — это серверный процесс, обрабатывающий одно клиентское подключение. Для каждого такого процесса устанавливается отображение командной строки в виде

qhb: пользователь база_данных хост активность

Элементы пользователь, база данных и хост остаются неизменными на протяжении всего жизненного цикла клиентского соединения, а индикатор активности меняется. Он может принимать значение idle (т. е. ожидание команды клиента), idle in transaction (ожидание клиента внутри блока BEGIN) или имя типа команды, например SELECT. Кроме того, если в настоящее время серверный процесс ожидает освобождения блокировки, которую удерживает другой сеанс, то к индикатору активности добавляется waiting. В приведенном выше примере мы можем сделать вывод, что процесс 15606 ожидает, пока процесс 15610 завершит свою транзакцию и тем самым освободит некоторую блокировку. (Процесс 15610 должен быть блокирующим, потому что нет другого активного сеанса. В более сложных случаях для определения, кто кого блокирует, понадобилось бы обратиться к системному представлению pg_locks.)

Если установлен параметр cluster_name, имя кластера также будет показано в выводе ps:

$ psql -c 'SHOW cluster_name'
 cluster_name
--------------
 server1
(1 row)

$ ps aux|grep server1
qhb   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 qhb: server1: background writer
...

Если выключить параметр update_process_title, то индикатор активности не меняется; заголовок процесса устанавливается только один раз при запуске нового процесса. На некоторых платформах это существенно снижает затраты на выполнение команды, на других это влияние незначительно.

Совет
Solaris требует особого обращения. Вместо /bin/ps нужно использовать /usr/ucb/ps. Также нужно использовать не один, а два флага w. Кроме того, у исходного вызова команды qhb отображение статуса в ps должно быть короче, чем у предоставляемого каждым серверным процессом. Если не сделать все эти три вещи, выводом ps для каждого серверного процесса будет исходная командная строка qhb.

Система накопительной статистики

Система накопительной статистики QHB поддерживает сбор и передачу информации о работе сервера. В настоящее время она учитывает обращения к таблицам и индексам как по дисковым блокам, так и по отдельным строкам. Также она отслеживает общее количество строк в каждой таблице и информацию о выполнении очистки и анализа для каждой таблицы. Кроме того, она может подсчитывать вызовы пользовательских функций и общее время, потраченное на каждую из них.

QHB также поддерживает выдачу динамической информации о том, что именно происходит в системе в данный момент, например, о том, какая именно команда выполняется в настоящее время другими серверными процессами, и о том, какие другие соединения существуют в системе. Эта возможность не зависит от системы накопительной статистики.

Конфигурация сбора статистики

Поскольку сбор статистики несколько увеличивает затраты на выполнение запроса, систему можно настроить так, чтобы она собирала или не собирала информацию. Это контролируется параметрами конфигурации, которые обычно устанавливаются в qhb.conf. (Подробную информацию об установке параметров конфигурации см. в главе Конфигурация сервера).

Параметр track_activities включает мониторинг текущих команд, выполняемых любым серверным процессом.

Параметр track_counts определяет, будет ли собираться статистика по обращениям к таблицам и индексам.

Параметр track_functions включает отслеживание использования пользовательских функций.

Параметр track_io_timing включает мониторинг времени чтения и записи блоков.

Параметр track_wal_io_timing включает мониторинг времени записи WAL.

Обычно эти параметры устанавливаются в qhb.conf так, чтобы они применялись ко всем процессам сервера, но с помощью команды SET их можно включить или выключить в отдельных сеансах. (Чтобы обычные пользователи не могли скрывать свою активность от администратора, изменять эти параметры с помощью SET могут только суперпользователи.)

Кумулятивная статистика собирается в разделяемой памяти. Каждый процесс QHB собирает статистику локально, а затем обновляет разделяемые данные с оптимальными интервалами. Когда сервер, включая физическую реплику, завершает работу в штатном режиме, постоянная копия статистических данных сохраняется в подкаталоге pg_stat, так что статистику можно хранить при перезапусках сервера. И наоборот, когда сервер запускается после аварийного выключения (например, после немедленного отключения, сбоя сервера, запуска из базовой резервной копии и восстановления на определенный момент времени), все счетчики статистики сбрасываются.

Просмотр статистики

Для отображения текущего состояния системы имеется несколько предопределенных представлений, перечисленных в Таблице 1. Кроме того, имеется несколько других представлений, перечисленных в Таблице 2, отображающих накопленную статистику. В качестве альтернативы с помощью нижележащих функций накопительной статистики можно создавать собственные представления, как описано в подразделе Функции статистики.

При использовании представлений и функций накопительной статистики для мониторинга собранных данных важно понимать, что информация обновляется не мгновенно. Каждый отдельный процесс сервера сбрасывает накопленную статистику в разделяемую память непосредственно перед тем, как перейти в режим ожидания, но не чаще одного раза в PGSTAT_MIN_INTERVAL миллисекунд (1 секунда, если значение переменной не было изменено при сборке сервера); поэтому запрос или транзакция, которые все еще находятся в процессе выполнения, не влияют на отображаемые статистические данные, а отображаемая информация отстает от фактической активности. Однако информация о текущем запросе, собираемая с параметром track_activities, всегда актуальна.

Другим важным моментом является то, что когда у серверного процесса запрашивают отображение накопленной статистики, в конфигурации по умолчанию запрашиваемые значения кэшируются до окончания текущей транзакции. Таким образом, статистическая информация не изменится, пока выполняется текущая транзакция. Точно так же собирается информация о текущих запросах всех сеансов, когда она запрашивается в рамках транзакции, и эта же информация будет отображаться в течение всей транзакции. Это особенность, а не дефект, поскольку она позволяет выполнять несколько запросов статистических данных и сопоставлять результаты, не беспокоясь о том, что цифры за кадром меняются. При интерактивном анализе статистики или выполнении высокозатратных запросов задержка между обращениями к отдельным статистическим данным может привести к значительной погрешности в кэшируемой статистике. Установив в параметре stats_fetch_consistency значение snapshot, можно минимизировать эту погрешность ценой увеличения использования памяти для кэширования ненужных статистических данных. И наоборот, если известно, что обращения к статистике единичны, в кэшировании статистики, к которой уже обращались, нет необходимости, и его можно избежать, установив в stats_fetch_consistency значение none. Также можно вызвать функцию pg_stat_clear_snapshot(), которая сбросит снимок статистики или кэшируемые значения (если таковые имеются) текущей транзакции. При следующем обращении к статистической информации будет получен новый снимок (в режиме снимка) или запрошенная статистика сохранится в кэше (в режиме кэширования).

Кроме того, транзакция может видеть свою собственную статистику (пока еще не сброшенную в разделяемую память) в представлениях pg_stat_xact_all_tables, pg_stat_xact_sys_tables, pg_stat_xact_user_tables и pg_stat_xact_user_functions. Эти данные ведут себя не так, как описано выше; напротив, они постоянно обновляются на протяжении всей транзакции.

Некоторая информация в представлениях динамической статистики, показанных в Таблице 1, имеет ограничения по безопасности. Обычные пользователи могут видеть только всю информацию о своих собственных сеансах (сеансах, принадлежащих роли, членами которой они являются). В строках о других сеансах многие столбцы будут содержать NULL. Однако обратите внимание, что существование сеанса и его общие свойства, такие как пользователь сеанса и база данных, видны всем пользователям. Суперпользователи и члены встроенной роли pg_read_all_stats (см. также раздел Предопределенные роли) могут видеть всю информацию обо всех сеансах.

Таблица 1. Представления динамической статистики

Имя представления | Описание
pg_stat_activity | По одной строке для каждого серверного процесса, отображающей информацию, связанную с текущей активностью этого процесса, например состояние и текущий запрос. Подробную информацию см. в справке по pg_stat_activity.
pg_stat_replication | По одной строке для каждого процесса отправителя WAL, отображающей статистику по репликации на подключенном резервном сервере этого отправителя. Подробную информацию см. в справке по pg_stat_replication.
pg_stat_wal_receiver | Только одна строка, отображающая статистику о получателе WAL с подключенного сервера этого получателя. Подробную информацию см. в справке по pg_stat_wal_receiver.
pg_stat_recovery_prefetch | Только одна строка, отображающая статистику о блоках, предварительно извлеченных во время восстановления Подробную информацию см. в справке по pg_stat_recovery_prefetch.
pg_stat_subscription | Как минимум по одной строке для каждой подписки, отображающей информацию о рабочих процессах подписки. Подробную информацию см. в справке по pg_stat_subscription.
pg_stat_ssl | По одной строке для каждого подключения (обычного и реплицирующего), отображающей информацию о SSL, используемом для этого соединения. Подробную информацию см. в справке по pg_stat_ssl.
pg_stat_gssapi | По одной строке для каждого подключения (обычного и реплицирующего), отображающей информацию об аутентификации и шифровании GSSAPI, используемых в этом соединении. Подробную информацию см. в справке по pg_stat_gssapi.
pg_stat_progress_analyze | По одной строке, отображающей текущий прогресс, для каждого обслуживающего процесса (включая рабочие процессы «Автовакуум»), в котором выполняется ANALYZE. См. подраздел Отчет о ходе выполнения ANALYZE.
pg_stat_progress_create_index | По одной строке, отображающей текущий прогресс, для каждого обслуживающего процесса, в котором выполняется CREATE INDEX или REINDEX. См. подраздел Отчет о ходе выполнения CREATE INDEX.
pg_stat_progress_vacuum | По одной строке, отображающей текущий прогресс, для каждого обслуживающего процесса (включая рабочие процессы «Автовакуум»), в котором выполняется VACUUM. См. подраздел Отчет о ходе выполнения VACUUM.
pg_stat_progress_cluster | По одной строке, отображающей текущий прогресс, для каждого обслуживающего процесса, в котором выполняется CLUSTER или VACUUM FULL. См. подраздел Отчет о ходе выполнения CLUSTER.
pg_stat_progress_basebackup | По одной строке, отображающей текущий прогресс, для каждого процесса отправителя WAL, поточно передающего базовую копию. См. подраздел Отчет о ходе выполнения базового резервного копирования.
pg_stat_progress_copy | По одной строке, отображающей текущий прогресс, для каждого обслуживающего процесса, в котором выполняется COPY. См. подраздел Отчет о ходе выполнения COPY.

Таблица 2. Представления собранной статистики

Имя представления
Описание

pg_stat_archiver
Только одна строка, отображающая статистику о работе процесса архиватора WAL. Подробную информацию см. в справке по pg_stat_archiver.

pg_stat_bgwriter
Только одна строка, отображающая статистику о работе фонового процесса записи. Подробную информацию см. в справке по pg_stat_bgwriter.

pg_stat_wal
Только одна строка, отображающая статистику о работе WAL. Подробную информацию см. в справке по pg_stat_wal.

pg_stat_database
По одной строке на каждую базу данных, отображающей статистику на уровне базы данных. Подробную информацию см. в справке по pg_stat_database.

pg_stat_database_conflicts
По одной строке на каждую базу данных, отображающей статистику на уровне базы данных по отменам запросов из-за конфликта с восстановлением на резервных серверах. Подробную информацию см. в справке по pg_stat_database_conflicts.

pg_stat_all_tables
По одной строке для каждой таблицы в текущей базе данных, отображающей статистику обращений к этой конкретной таблице. Подробную информацию см. в справке по pg_stat_all_tables.

pg_stat_sys_tables
То же, что и pg_stat_all_tables, за исключением того, что отображаются только системные таблицы.

pg_stat_user_tables
То же, что и pg_stat_all_tables, за исключением того, что отображаются только пользовательские таблицы.

pg_stat_xact_all_tables
Похоже на pg_stat_all_tables, но подсчитывает действия, совершенные к настоящему моменту в текущей транзакции (которые еще не включены в pg_stat_all_tables и связанные представления). Столбцы с количеством активных и неиспользуемых строк, а также с данными по операциям очистки и анализа в этом представлении отсутствуют.

pg_stat_xact_sys_tables
То же, что и pg_stat_xact_all_tables, за исключением того, что отображаются только системные таблицы.

pg_stat_xact_user_tables
То же, что и pg_stat_xact_all_tables, за исключением того, что отображаются только пользовательские таблицы.

pg_stat_all_indexes
По одной строке для каждого индекса в текущей базе данных, отображающей статистику обращений к этому конкретному индексу. Подробную информацию см. в справке по pg_stat_all_indexes.

pg_stat_sys_indexes
То же, что и pg_stat_all_indexes, за исключением того, что отображаются только индексы по системным таблицам.

pg_stat_user_indexes
То же, что и pg_stat_all_indexes, за исключением того, что отображаются только индексы по пользовательским таблицам.

pg_statio_all_tables
По одной строке для каждой таблицы в текущей базе данных, отображающей статистику операций ввода/вывода для этой конкретной таблицы. Подробную информацию см. в справке по pg_statio_all_tables.

pg_statio_sys_tables
То же, что и pg_statio_all_tables, за исключением того, что отображаются только системные таблицы.

pg_statio_user_tables
То же, что и pg_statio_all_tables, за исключением того, что отображаются только пользовательские таблицы.

pg_statio_all_indexes
По одной строке для каждого индекса в текущей базе данных, отображающей статистику операций ввода/вывода для этого конкретного индекса. Подробную информацию см. в справке по pg_statio_all_indexes.

pg_statio_sys_indexes
То же, что и pg_statio_all_indexes, за исключением того, что отображаются только индексы по системным таблицам.

pg_statio_user_indexes
То же, что и pg_statio_all_indexes, за исключением того, что отображаются только индексы по пользовательским таблицам.

pg_statio_all_sequences
По одной строке для каждой последовательности в текущей базе данных, отображающей статистику операций ввода/вывода для этой конкретной последовательности. Подробную информацию см. в справке по pg_statio_all_sequences.

pg_statio_sys_sequences
То же, что и pg_statio_all_sequences, за исключением того, что отображаются только системные последовательности. (В настоящее время системные последовательности не определены, поэтому это представление всегда пусто.)

pg_statio_user_sequences
То же, что и pg_statio_all_sequences, за исключением того, что отображаются только пользовательские последовательности.

pg_stat_user_functions
По одной строке для каждой отслеживаемой функции, отображающей статистику выполнения этой функции. Подробную информацию см. в справке по pg_stat_user_functions.

pg_stat_xact_user_functions
Похоже на pg_stat_user_functions, но подсчитывает только вызовы во время текущей транзакции (которые еще не включены в pg_stat_user_functions).

pg_stat_slru
По одной строке для каждого SLRU-кэша, отображающей статистику операций. Подробную информацию см. в справке по pg_stat_slru.

pg_stat_replication_slots
По одной строке для каждого слота репликации, отображающей статистику по использованию этого слота. Подробную информацию см. в справке по pg_stat_replication_slots.

pg_stat_subscription_stats
По одной строке для каждой подписки, отображающей статистику по ошибкам. Подробную информацию см. в справке по pg_stat_subscription_stats.

Статистика по каждому индексу особенно полезна для определения того, какие индексы используются и насколько они эффективны.

Представления pg_statio_ в первую очередь полезны для определения эффективности буферного кэша. Когда количество фактических операций чтения с диска гораздо меньше числа обращений к буферу, кэш удовлетворяет большинству запросов на чтение без вызова ядра. Однако эти статистические данные не дают полной картины: по причине способа, которым QHB обрабатывает дисковый ввод/вывод, данные, которых нет в буферном кэше QHB, могут по-прежнему находиться в кэше ввода/вывода ядра и поэтому все еще могут извлекаться без необходимости физического чтения. Пользователям, заинтересованным в получении более подробной информации о поведении ввода/вывода QHB, рекомендуется использовать сборщик статистики QHB в сочетании с утилитами операционной системы, позволяющими лучше понять, как ядро обрабатывает ввод/вывод.

pg_stat_activity

В представлении pg_stat_activity для каждого серверного процесса будет находиться по одной строке, отображающей информацию, касающуюся текущей активности этого процесса.

Таблица 3. Представление pg_stat_activity

Столбец Тип

Описание

datid oid

OID базы данных, к которой подключен этот обслуживающий процесс

datname name

Имя базы данных, к которой подключен этот обслуживающий процесс

pid integer

PID этого обслуживающего процесса

leader_pid integer

PID ведущего процесса параллельной группы, если этот процесс является рабочим процессом параллельного запроса. NULL, если этот процесс является ведущим процессом параллельной группы или не участвует в параллельном запросе.

usesysid oid

OID пользователя, подсоединившегося к этому обслуживающему процессу

usename name

Имя пользователя, подсоединившегося к этому обслуживающему процессу

application_name text

Имя приложения, подключенного к этому обслуживающему процессу

client_addr inet

IP-адрес клиента, подключенного к этому обслуживающему процессу. Если данное поле равно NULL, это показывает, что клиент подключен через сокет домена Unix на компьютере сервера или что это внутренний процесс, например процесс «Автовакуум».

client_hostname text

Имя хоста подключенного клиента, получаемое обратным поиском в DNS по client_addr. Это поле будет отлично от NULL только для IP-соединений и только когда включен параметр log_hostname.

client_port integer

Номер TCP-порта, который клиент использует для взаимодействия с этим обслуживающим процессом, или -1, если используется сокет домена Unix. Если данное поле равно NULL, это показывает, что это внутренний серверный процесс.

backend_start timestamp with time zone

Время запуска этого процесса. Для обслуживающих процессов клиента это время подключения клиента к серверу.

xact_start timestamp with time zone

Время начала текущей транзакции этого процесса или NULL, если активная транзакция отсутствует. Если текущий запрос является первым в своей транзакции, значение в этом столбце равно значению столбца query_start.

query_start timestamp with time zone

Время запуска текущего активного запроса или, если значение state не active, то время запуска последнего запроса

state_change timestamp with time zone

Время последнего изменения поля state

wait_event_type text

Тип события, которого ждет обслуживающий процесс, если таковое ожидание имеется; в противном случае NULL. См. Таблицу 4.

wait_event text

Имя ожидаемого события, если обслуживающий процесс в настоящий момент находится в ожидании, в противном случае NULL. См. с Таблицы 5 по Таблицу 13.

state text

Общее текущее состояние этого обслуживающего процесса. Возможные значения:

active: Обслуживающий процесс выполняет запрос.

idle: Обслуживающий процесс ожидает новой команды от клиента.

idle in transaction: Обслуживающий процесс находится внутри транзакции, но в настоящее время не выполняет запрос.

idle in transaction (aborted): Этот состояние схоже с idle in transaction, за исключением того, что один из операторов в транзакции вызвал ошибку.

fastpath function call: Обслуживающий процесс выполняет функцию по быстрому пути (fast-path).

disabled: Это состояние отображается, если в этом обслуживающем процессе отключен параметр track_activities.

backend_xid xid

Идентификатор транзакции верхнего уровня этого серверного процесса, если таковая имеется.

backend_xmin xid

Граница xmin текущего обслуживающего процесса.

query_id bigint

Идентификатор последнего запроса этого обслуживающего процесса. Если state имеет значение active, в этом поле отображается идентификатор запроса, выполняемого в настоящий момент. При всех остальных состояниях в нем отображается идентификатор последнего выполненного запроса. По умолчанию идентификаторы запросов не вычисляются, поэтому если не включен параметр compute_query_id или не сконфигурирован сторонний модуль, вычисляющий идентификаторы запросов, данное поле будет равно NULL.

query text

Текст последнего запроса этого обслуживающего процесса. Если state имеет значение active, в этом поле отображается запрос, выполняемый в настоящий момент. При всех остальных состояниях в нем отображается последний выполненный запрос. По умолчанию текст запроса усекается до 1024 байт; это значение можно изменить с помощью параметра track_activity_query_size.

backend_type text

Тип текущего обслуживающего процесса. Возможные типы: autovacuum launcher, autovacuum worker, logical replication launcher, logical replication worker, parallel worker, background writer, client backend, checkpointer, archiver, startup, walreceiver, walsender и walwriter. Кроме того, у фоновых рабочих процессов, регистрируемых расширениями, могут быть дополнительные типы.

Примечание
Столбцы wait_event и state независимы друг от друга. Если обслуживающий процесс находится в активном состоянии (active), он может ожидать (waiting) какое-нибудь событие или не ожидать никакое. Если состояние active и поле wait_event не равно NULL, это означает, что запрос выполняется, но заблокирован где-то в системе.

Таблица 4. Типы событий ожидания

Тип события ожидания
Описание

Activity
Серверный процесс бездействует. Этот тип события показывает, что процесс ожидает активности в своем основном цикле обработки. В wait_event будет указана конкретная точка ожидания; см. Таблицу 5.

BufferPin
Серверный процесс ожидает эксклюзивного доступа к буферу данных. Ожидание закрепления буфера может затянуться, если другой процесс удерживает открытый курсор, который последним читал данные из указанного буфера. См. Таблицу 6.

Client
Серверный процесс ожидает активность в сокете, подключенном к пользовательскому приложению. Таким образом, сервер ожидает, что произойдет событие, не зависящее от его внутренних процессов. В wait_event будет указана конкретная точка ожидания; см. Таблицу 7.

Extension
Серверный процесс ожидает некоторого условия, определяемого модулем расширения. См. Таблицу 8.

IO
Серверный процесс ожидает завершения операции ввода/вывода. В wait_event будет указана конкретная точка ожидания; см. Таблицу 9.

IPC
Серверный процесс ожидает некоторого взаимодействия с другим серверным процессом. В wait_event будет указана конкретная точка ожидания; см. Таблицу 10.

Lock
Серверный процесс ожидает тяжелую блокировку. Тяжелые блокировки, также известные как блокировки менеджера блокировок или просто блокировки, в основном защищают видимые SQL объекта, например, таблицы. Однако они также используются для обеспечения взаимного исключения некоторых внутренних операций, например, расширения отношений. В wait_event будет указан тип ожидаемой блокировки; см. Таблицу 11.

LWLock
Серверный процесс ожидает легкую блокировку. Большинство таких блокировок защищают конкретные структуры данных в разделяемой памяти. В wait_event будет содержаться имя, указывающее на цель этой легкой блокировки. (Некоторые блокировки имеют особые имена; другие являются частью группы блокировок с похожим предназначением.) См. Таблицу 12.

Timeout
Серверный процесс ожидает истечения тайм-аута. В wait_event будет указана конкретная точка ожидания; см. Таблицу 13.

Таблица 5. События ожидания типа Activity

Событие ожидания Activity | Описание
ArchiverMain | Ожидание в основном цикле процесса архиватора.
AutoVacuumMain | Ожидание в основном цикле процесса запуска автовакуума.
BgWriterHibernate | Ожидание в фоновом процессе записи, переход в спящий режим.
BgWriterMain | Ожидание в основном цикле фонового процесса записи.
CheckpointerMain | Ожидание в основном цикле процесса контрольной точки.
LogicalApplyMain | Ожидание в основном цикле процесса применения логической репликации.
LogicalLauncherMain | Ожидание в основном цикле процесса запуска логической репликации.
RecoveryWalStream | Ожидание поступления WAL в основном цикле процесса запуска во время потокового восстановления.
SysLoggerMain | Ожидание в основном цикле процесса системного журнала (syslogger).
WalReceiverMain | Ожидание в основном цикле процесса-получателя WAL.
WalSenderMain | Ожидание в основном цикле процесса-отправителя WAL.
WalWriterMain | Ожидание в основном цикле процесса записи WAL.

Таблица 6. События ожидания типа BufferPin

Событие ожидания BufferPin | Описание
BufferPin | Ожидание при получении эксклюзивного закрепления буфера.

Таблица 7. События ожидания типа Client

Событие ожидания Client | Описание
ClientRead | Ожидание при чтении данных, получаемых от клиента.
ClientWrite | Ожидание при записи данных, передаваемых клиенту.
GSSOpenServer | Ожидание при чтении данных, получаемых от клиента, при установлении сеанса GSSAPI.
LibPQWalReceiverConnect | Ожидание в получателе WAL при установлении подключения к удаленному серверу.
LibPQWalReceiverReceive | Ожидание в получателе WAL при получении данных от удаленного сервера.
SSLOpenServer | Ожидание SSL при попытке подключения.
WalSenderWaitForWAL | Ожидание сброса WAL на диск в процессе-отправителе WAL.
WalSenderWriteData | Ожидание какой-либо активности при обработке ответов от получателя WAL в процессе-отправителе WAL.

Таблица 8. События ожидания типа Extension

Событие ожидания Extension | Описание
Extension | Ожидание в расширении.

Таблица 9. События ожидания типа IO

Событие ожидания IO | Описание
BaseBackupRead | Ожидание чтения базовой резервной копии из файла.
BaseBackupSync | Ожидание помещения данных, записанных процессом базового резервного копирования, в надежное хранилище.
BaseBackupWrite | Ожидание записи базовой резервной копии в файл.
BufFileRead | Ожидание чтения из буферизованного файла.
BufFileWrite | Ожидание записи в буферизованный файл.
BufFileTruncate | Ожидание усечения буферизованного файла.
ControlFileRead | Ожидание чтения из файла pg_control.
ControlFileSync | Ожидание помещения файла pg_control в долговременное хранилище.
ControlFileSyncUpdate | Ожидание помещения изменений файла pg_control в долговременное хранилище.
ControlFileWrite | Ожидание записи в файл pg_control.
ControlFileWriteUpdate | Ожидание записи изменения файла pg_control.
CopyFileRead | Ожидание чтения во время операции копирования файла.
CopyFileWrite | Ожидание записи во время операции копирования файла.
DSMFillZeroWrite | Ожидание заполнения нулями поддерживающего файла динамической разделяемой памяти.
DataFileExtend | Ожидание расширения файла данных отношения.
DataFileFlush | Ожидание помещения файла данных отношения в долговременное хранилище.
DataFileImmediateSync | Ожидание немедленной синхронизации файла данных отношения с долговременным хранилищем.
DataFilePrefetch | Ожидание асинхронной предварительной выборки из файла данных отношения.
DataFileRead | Ожидание чтения из файла данных отношения.
DataFileSync | Ожидание помещения изменений файла данных отношения в долговременное хранилище.
DataFileTruncate | Ожидание усечения файла данных отношения.
DataFileWrite | Ожидание записи в файл данных отношения.
LockFileAddToDataDirRead | Ожидание чтения при добавлении строки в файл блокировки каталога данных.
LockFileAddToDataDirSync | Ожидание помещения данных в долговременное хранилище при добавлении строки в файл блокировки каталога данных.
LockFileAddToDataDirWrite | Ожидание записи при добавлении строки в файл блокировки каталога данных.
LockFileCreateRead | Ожидание чтения при создании файла блокировки каталога данных.
LockFileCreateSync | Ожидание помещения данных в долговременное хранилище при создании файла блокировки каталога данных.
LockFileCreateWrite | Ожидание записи при создании файла блокировки каталога данных.
LockFileReCheckDataDirRead | Ожидание чтения во время повторной проверки файла блокировки каталога данных.
LogicalRewriteCheckpointSync | Ожидание помещения сопоставлений логической перезаписи в долговременное хранилище во время контрольной точки.
LogicalRewriteMappingSync | Ожидание помещения данных сопоставлений в долговременное хранилище во время логической перезаписи.
LogicalRewriteMappingWrite | Ожидание записи данных сопоставлений во время логической перезаписи.
LogicalRewriteSync | Ожидание помещения сопоставлений логической перезаписи в долговременное хранилище.
LogicalRewriteTruncate | Ожидание усечения данных сопоставлений во время логической перезаписи.
LogicalRewriteWrite | Ожидание записи сопоставлений логической перезаписи.
RelationMapRead | Ожидание чтения из файла карты отношений.
RelationMapSync | Ожидание помещения файла карты отношений в долговременное хранилище.
RelationMapWrite | Ожидание записи в файл карты отношений.
ReorderBufferRead | Ожидание чтения во время работы с буфером переупорядочивания.
ReorderBufferWrite | Ожидание записи во время работы с буфером переупорядочивания.
ReorderLogicalMappingRead | Ожидание чтения логического сопоставления во время работы с буфером переупорядочивания.
ReplicationSlotRead | Ожидание чтения из управляющего файла слота репликации.
ReplicationSlotRestoreSync | Ожидание помещения управляющего файла слота репликации в долговременное хранилище при восстановлении его в памяти.
ReplicationSlotSync | Ожидание помещения управляющего файла слота репликации в долговременное хранилище.
ReplicationSlotWrite | Ожидание записи в управляющий файл слота репликации.
SLRUFlushSync | Ожидание помещения данных SLRU в долговременное хранилище во время контрольной точки или выключения базы данных.
SLRURead | Ожидание чтения из страницы SLRU.
SLRUSync | Ожидание помещения данных SLRU в долговременное хранилище после записи страницы.
SLRUWrite | Ожидание записи на страницу SLRU.
SnapbuildRead | Ожидание чтения сериализованного исторического снимка каталога.
SnapbuildSync | Ожидание помещения сериализованного исторического снимка каталога в долговременное хранилище.
SnapbuildWrite | Ожидание записи сериализованного исторического снимка каталога.
TimelineHistoryFileSync | Ожидание помещения файла истории временной шкалы, полученного посредством потоковой репликации, в долговременное хранилище.
TimelineHistoryFileWrite | Ожидание записи в файл истории временной шкалы, полученного посредством потоковой репликации.
TimelineHistoryRead | Ожидание чтения из файла истории временной шкалы.
TimelineHistorySync | Ожидание помещения только что созданного файла истории временной шкалы в долговременное хранилище.
TimelineHistoryWrite | Ожидание записи в только что созданный файл истории временной шкалы.
TwophaseFileRead | Ожидание чтения из файла двухфазного состояния.
TwophaseFileSync | Ожидание помещения файла двухфазного состояния в долговременное хранилище.
TwophaseFileWrite | Ожидание записи в файл двухфазного состояния.
VersionFileWrite | Ожидание записи файла версии во время создания базы данных.
WALBootstrapSync | Ожидание помещения WAL в долговременное хранилище во время начальной загрузки.
WALBootstrapWrite | Ожидание записи в WAL во время начальной загрузки.
WALCopyRead | Ожидание чтения при создании нового сегмента WAL путем копирования существующего.
WALCopySync | Ожидание помещения нового сегмента WAL, созданного путем копирования существующего, в долговременное хранилище.
WALCopyWrite | Ожидание записи при создании нового сегмента WAL путем копирования существующего.
WALInitSync | Ожидание помещения только что инициализированного файла WAL в долговременное хранилище.
WALInitWrite | Ожидание записи при инициализации нового файла WAL.
WALRead | Ожидание чтения из файла WAL.
WALSenderTimelineHistoryRead | Ожидание чтения из файла истории временной шкалы во время обработки команды временной шкалы процессом-отправителем WAL.
WALSync | Ожидание помещения файла WAL в долговременное хранилище.
WALSyncMethodAssign | Ожидание помещения данных в долговременное хранилище при назначении нового метода синхронизации WAL.
WALWrite | Ожидание записи в файл WAL.

Таблица 10. События ожидания типа IPC

Событие ожидания IPC | Описание
AppendReady | Ожидание готовности вложенных узлов планового узла Append.
ArchiveCleanupCommand | Ожидание завершения archive_cleanup_command.
ArchiveCommand | Ожидание завершения archive_command.
BackendTermination | Ожидание завершения другого обслуживающего процесса.
BackupWaitWalArchive | Ожидание файлов WAL, необходимых для успешного архивирования резервной копии.
BgWorkerShutdown | Ожидание завершения фонового рабочего процесса.
BgWorkerStartup | Ожидание запуска фонового рабочего процесса.
BtreePage | Ожидание доступности номера страницы, необходимого для продолжения параллельного сканирования B-дерева.
BufferIO | Ожидание завершения буферного ввода/вывода.
CheckpointDone | Ожидание завершения контрольной точки.
CheckpointStart | Ожидание запуска контрольной точки.
ExecuteGather | Ожидание активности от дочернего процесса при выполнении планового узла Gather.
HashBatchAllocate | Ожидание выделения хеш-таблицы выбранным участником параллельного хеширования.
HashBatchElect | Ожидание при выборе участника параллельного хеширования для выделения хеш-таблицы.
HashBatchLoad | Ожидание завершения загрузки хеш-таблицы другими участниками параллельного хеширования.
HashBuildAllocate | Ожидание выделения начальной хеш-таблицы выбранным участником параллельного хеширования.
HashBuildElect | Ожидание при выборе участника параллельного хеширования для выделения начальной хеш-таблицы.
HashBuildHashInner | Ожидание завершения хеширования внутреннего отношения другими участниками параллельного хеширования.
HashBuildHashOuter | Ожидание завершения хеширования внешнего отношения другими участниками параллельного хеширования.
HashGrowBatchesAllocate | Ожидание выделения дополнительных пакетов выбранным участником параллельного хеширования.
HashGrowBatchesDecide | Ожидание при выборе участника параллельного хеширования для принятия решения о предстоящем увеличении числа пакетов.
HashGrowBatchesElect | Ожидание при выборе участника параллельного хеширования для выделения дополнительных пакетов.
HashGrowBatchesFinish | Ожидание принятия решения о предстоящем увеличении числа пакетов выбранным участником параллельного хеширования.
HashGrowBatchesRepartition | Ожидание завершения повторного партиционирования другими участниками параллельного хеширования.
HashGrowBucketsAllocate | Ожидание завершения выделения дополнительных блоков выбранным участником параллельного хеширования.
HashGrowBucketsElect | Ожидание при выборе участника параллельного хеширования для выделения дополнительных блоков.
HashGrowBucketsReinsert | Ожидание завершения добавления кортежей в новые блоки другими участниками параллельного хеширования.
LogicalSyncData | Ожидание передачи данных для начальной синхронизации таблиц удаленным сервером логической репликации.
LogicalSyncStateChange | Ожидание изменения состояния удаленного сервера логической репликации.
MessageQueueInternal | Ожидание присоединения другого процесса к общей очереди сообщений.
MessageQueuePutMessage | Ожидание при записи сообщения протокола в общую очередь сообщений.
MessageQueueReceive | Ожидание при получении байтов из общей очереди сообщений.
MessageQueueSend | Ожидание при передаче байтов в общую очередь сообщений.
ParallelBitmapScan | Ожидание инициализации параллельного сканирования по битовой карте.
ParallelCreateIndexScan | Ожидание завершения сканирования кучи параллельными рабочими процессами CREATE INDEX.
ParallelFinish | Ожидание завершения вычислений параллельными рабочими процессами.
ProcArrayGroupUpdate | Ожидание обнуления идентификатора транзакции ведущим процессом группы в конце параллельной операции.
ProcSignalBarrier | Ожидание обработки события барьера всеми обслуживающими процессами.
Promote | Ожидание повышения резервного сервера.
RecoveryConflictSnapshot | Ожидание разрешения конфликта восстановления для проведения очистки.
RecoveryConflictTablespace | Ожидание разрешения конфликта восстановления для удаления табличного пространства.
RecoveryEndCommand | Ожидание завершения recovery_end_command.
RecoveryPause | Ожидание возобновления восстановления.
ReplicationOriginDrop | Ожидание перехода источника репликации в неактивное состояние, чтобы его можно было удалить.
ReplicationSlotDrop | Ожидание перехода слота репликации в неактивное состояние, чтобы его можно было удалить.
RestoreCommand | Ожидание завершения restore_command.
SafeSnapshot | Ожидание при получении допустимого снимка для транзакции READ ONLY DEFERRABLE.
SyncRep | Ожидание подтверждения от удаленного сервера во время синхронной репликации.
WalReceiverExit | Ожидание завершения процесса-получателя WAL.
WalReceiverWaitStart | Ожидание передачи процессом запуска начальных данных для потоковой репликации.
XactGroupUpdate | Ожидание изменения состояния транзакции ведущим процессом группы в конце параллельной операции.

Таблица 11. События ожидания типа Lock

Событие ожидания Lock | Описание
advisory | Ожидание при получении рекомендательной пользовательской блокировки.
extend | Ожидание при расширении отношения.
frozenid | Ожидание при изменении полей pg_database.datfrozenxid и pg_database.datminmxid.
object | Ожидание при получении блокировки для нереляционного объекта базы данных.
page | Ожидание при получении блокировки для страницы отношения.
relation | Ожидание при получении блокировки для отношения.
spectoken | Ожидание при получении блокировки спекулятивного добавления.
transactionid | Ожидание завершения транзакции.
tuple | Ожидание при получении блокировки для кортежа.
userlock | Ожидание при получении пользовательской блокировки.
virtualxid | Ожидание при получении блокировки виртуального идентификатора транзакции.

Таблица 12. События ожидания типа LWLock

Событие ожидания LWLock | Описание
AddinShmemInit | Ожидание при обработке выделения области в разделяемой памяти для расширений.
AutoFile | Ожидание при изменении файла qhb.auto.conf.
Autovacuum | Ожидание при чтении или изменении текущего состояния рабочих процессов автовакуума.
AutovacuumSchedule | Ожидание при подтверждении, что таблице, выбранной для автоочистки, все еще необходима очистка.
BackgroundWorker | Ожидание при чтении или изменении состояния фонового рабочего процесса.
BtreeVacuum | Ожидание при чтении или изменении информации, связанной с очисткой, для индекса B-дерева.
BufferContent | Ожидание при обращении к странице данных в памяти.
BufferMapping | Ожидание при связывании блока данных с буфером в пуле буферов.
CheckpointerComm | Ожидание при обработке запросов fsync.
CommitTs | Ожидание при чтении или записи последнего значения, установленного для времени фиксирования транзакции.
CommitTsBuffer | Ожидание ввода/вывода с SLRU-буфером данных о времени фиксирования.
CommitTsSLRU | Ожидание при обращении к SLRU-кэшу данных о времени фиксирования.
ControlFile | Ожидание при чтении или изменении файла pg_control или создании нового файла WAL.
DynamicSharedMemoryControl | Ожидание при чтении или изменении информации о выделении динамической разделяемой памяти.
LockFastPath | Ожидание при чтении или изменении информации процесса о блокировке по быстрому пути.
LockManager | Ожидание при чтении или изменении информации о «тяжелых» блокировках.
LogicalRepWorker | Ожидание при чтении или изменении состояния рабочих процессов логической репликации.
MultiXactGen | Ожидание при чтении или изменении общего состояния мультитранзакций.
MultiXactMemberBuffer | Ожидание ввода/вывода с SLRU-буфером данных о членах мультитранзакций.
MultiXactMemberSLRU | Ожидание при обращении к SLRU-кэшу данных о членах мультитранзакций.
MultiXactOffsetBuffer | Ожидание ввода/вывода с SLRU-буфером данных о смещениях мультитранзакций.
MultiXactOffsetSLRU | Ожидание при обращении к SLRU-кэшу данных о смещениях мультитранзакций.
MultiXactTruncation | Ожидание при чтении или усечении информации мультитранзакции.
NotifyBuffer | Ожидание ввода/вывода с SLRU-буфером сообщений NOTIFY.
NotifyQueue | Ожидание при чтении или изменении сообщений NOTIFY.
NotifyQueueTail | Ожидание изменения границы хранилища сообщений NOTIFY.
NotifySLRU | Ожидание при обращении к SLRU-кэшу сообщений NOTIFY.
OidGen | Ожидание при выделении нового OID.
OldSnapshotTimeMap | Ожидание при чтении или изменении информации по управлению старым снимком.
ParallelAppend | Ожидание при выборе следующего подплана во время выполнения плана параллельного добавления (Parallel Append).
ParallelHashJoin | Ожидание при синхронизации рабочих процессов во время выполнения плана параллельного соединения по хешу (Parallel Hash Join).
ParallelQueryDSA | Ожидание выделения динамической разделяемой памяти для параллельного запроса.
PerSessionDSA | Ожидание выделения динамической разделяемой памяти для параллельного запроса.
PerSessionRecordType | Ожидание при обращении к информации параллельного запроса о составных типах.
PerSessionRecordTypmod | Ожидание при обращении к информации параллельного запроса о модификаторах типа, определяющих типы анонимных записей.
PerXactPredicateList | Ожидание при обращении к списку предикатных блокировок, удерживаемых текущей сериализуемой транзакцией, во время параллельного запроса.
PredicateLockManager | Ожидание при обращении к информации о предикатных блокировках, используемых сериализуемыми транзакциями.
ProcArray | Ожидание при обращении к общим структурам данных в рамках процесса (обычно при получении снимка или отображении идентификатора транзакции в сеансе).
RelationMapping | Ожидание при чтении или изменении файла pg_filenode.map (используемого для отслеживания назначений файловых узлов определенных системных каталогов).
RelCacheInit | Ожидание при чтении или изменении файла инициализации кэша отношения (pg_internal.init).
ReplicationOrigin | Ожидание при создании, удалении или использовании источника репликации.
ReplicationOriginState | Ожидание при чтении или изменении состояния одного источника репликации.
ReplicationSlotAllocation | Ожидание при выделении или освобождении слота репликации.
ReplicationSlotControl | Ожидание при чтении или изменении состояния слота репликации.
ReplicationSlotIO | Ожидание ввода/вывода со слотом репликации.
SerialBuffer | Ожидание ввода/вывода с SLRU-буфером данных о конфликтах сериализуемых транзакций.
SerializableFinishedList | Ожидание при обращении к списку завершенных сериализуемых транзакций.
SerializablePredicateList | Ожидание при обращении к списку предикатных блокировок, удерживаемых сериализуемыми транзакциями.
PgStatsDSA | Ожидание выделения динамической разделяемой памяти для статистики.
PgStatsHash | Ожидание доступа к хеш-таблице в разделяемой памяти.
PgStatsData | Ожидание доступа к данным статистики в разделяемой памяти.
SerializableXactHash | Ожидание при чтении или изменении информации о сериализуемых транзакциях.
SerialSLRU | Ожидание при обращении к SLRU-кэшу данных о конфликтах сериализуемых транзакций.
SharedTidBitmap | Ожидание при обращении к разделяемой битовой карте TID во время параллельного сканирования индекса по битовой карте.
SharedTupleStore | Ожидание при обращении к разделяемому хранилищу кортежей во время параллельного запроса.
ShmemIndex | Ожидание при поиске или выделении области в разделяемой памяти.
SInvalRead | Ожидание при получении сообщений из общей очереди сообщений аннулирования каталогов.
SInvalWrite | Ожидание при добавлении сообщения в общую очередь сообщений аннулирования каталогов.
SubtransBuffer | Ожидание ввода-вывода с SLRU-буфером данных о субтранзакциях.
SubtransSLRU | Ожидание при обращении к к SLRU-кэшу данных о субтранзакциях.
SyncRep | Ожидание при чтении или записи информации о состоянии синхронной репликации.
SyncScan | Ожидание при выборе начального положения для синхронизированного сканирования таблицы.
TablespaceCreate | Ожидание при создании или удалении табличного пространства.
TwoPhaseState | Ожидание при чтении или записи состояния подготовленных транзакций.
WALBufMapping | Ожидание при замене страницы в буферах WAL.
WALInsert | Ожидание при добавлении данных WAL в буфер памяти.
WALWrite | Ожидание записи буферов WAL на диск.
WrapLimitsVacuum | Ожидание при изменении пределов потребления идентификаторов транзакций и мультитранзакций.
XactBuffer | Ожидание ввода/вывода с SLRU-буфером данных о статусе транзакций.
XactSLRU | Ожидание при обращении к SLRU-кэшу данных о статусе транзакций.
XactTruncation | Ожидание при выполнении pg_xact_status или изменении самого старого доступного ей идентификатора транзакции.
XidGen | Ожидание при выделении нового идентификатора транзакции.

Примечание
Расширения могут добавлять типы LWLock в список, приведенный в Таблице 12. В некоторых случаях имя, назначенное расширением, станет невидимым во всех серверных процессах; поэтому событие ожидания LWLock может отображаться с именем «extension», а не тем, что было назначено расширением.

Таблица 13. События ожидания типа Timeout

Событие ожидания Timeout | Описание
BaseBackupThrottle | Ожидание во время базового резервного копирования, когда ограничена активность.
CheckpointWriteDelay | Ожидание между операциями записи при выполнении контрольной точки.
PgSleep | Ожидание в результате вызова pg_sleep или родственной ей функции.
RecoveryApplyDelay | Ожидание при применении WAL во время восстановления вследствие установленной задержки.
RecoveryRetrieveRetryInterval | Ожидание во время восстановления, когда данные WAL нельзя получить ни из какого источника (каталога pg_wal, архива или потока).
RegisterSyncRequest | Ожидание при передаче запросов синхронизации процессу контрольной точки вследствие переполнения очереди запросов.
VacuumDelay | Ожидание в точке задержки очистки по стоимости.
VacuumTruncate | Ожидание при получении эксклюзивной блокировки для усечения пустых страниц в конце очищаемой таблицы.

Вот пример того, как можно просмотреть события ожидания:

SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event is NOT NULL;
 pid  | wait_event_type | wait_event
------+-----------------+------------
 2540 | Lock            | relation
 6644 | LWLock          | ProcArray
(2 rows)

pg_stat_replication

Представление pg_stat_replication будет содержать для каждого процесса- отправителя WAL по одной строке, отображающей статистику по репликации на резервный сервер, к которому подключен этот отправитель. Перечисляются только резервные сервера, подключенные напрямую; информация о резервных серверах, подключенных опосредованно, не представлена.

Таблица 14. Представление pg_stat_replication

Столбец Тип

Описание

pid integer

PID процесса-отправителя WAL

usesysid oid

OID пользователя, подсоединившегося к этому процессу-отправителю WAL

usename name

Имя пользователя, подсоединившегося к этому процессу-отправителю WAL

application_name text

Имя приложения, подключенного к этому отправителю WAL

client_addr inet

IP-адрес клиента, подключенного к этому отправителю WAL. Если данное поле равно NULL, это показывает, что клиент подключен через сокет домена Unix на компьютере сервера.

client_hostname text

Имя хоста подключенного клиента, получаемое обратным поиском в DNS по client_addr. Это поле будет отлично от NULL только для IP-соединений и только когда включен параметр log_hostname.

client_port integer

Номер TCP-порта, который клиент использует для взаимодействия с этим отправителем WAL, или -1, если используется сокет домена Unix

backend_start timestamp with time zone

Время запуска этого процесса, т. е. время подключения клиента к этому отправителю WAL

backend_xmin xid

Граница xmin этого резервного сервера, отображаемая при включенном параметре hot_standby_feedback.

state text

Текущее состояние этого отправителя WAL. Возможные значения:

startup: Этот отправитель WAL запускается.

catchup: Резервный сервер, к которому подключен этот отправитель WAL, догоняет основной сервер.

streaming: Этот отправитель WAL передает в потоке изменения после того, как резервный сервер, к которому он подключен, догнал основной сервер.

backup: Этот отправитель WAL передает резервную копию.

stopping: Этот отправитель WAL останавливается.

sent_lsn pg_lsn

Последняя позиция в журнале упреждающей записи, переданная через это соединение

write_lsn pg_lsn

Последняя позиция в журнале упреждающей записи, записанная на диск этим резервным сервером

flush_lsn pg_lsn

Последняя позиция в журнале упреждающей записи, сброшенная на диск этим резервным сервером

replay_lsn pg_lsn

Последняя позиция в журнале упреждающей записи, воспроизведенная в базу данных с этого резервного сервера

write_lag interval

Время, прошедшее с момента локального сброса последних данных WAL до получения уведомления о том, что этот резервный сервер их записал (но еще не сбросил на диск и не применил). Это можно использовать для оценки задержки при фиксации, вызываемой установкой в параметре synchronous_commit уровня remote_write, если этот сервер был сконфигурирован как синхронный резервный.

flush_lag interval

Время, прошедшее с момента локального сброса последних данных WAL до получения уведомления о том, что этот резервный сервер их записал и сбросил на диск (но еще не применил). Это можно использовать для оценки задержки при фиксации, вызываемой установкой в параметре synchronous_commit уровня on, если этот сервер был сконфигурирован как синхронный резервный.

replay_lag interval

Время, прошедшее с момента локального сброса последних данных WAL до получения уведомления о том, что этот резервный сервер их записал, сбросил на диск и применил. Это можно использовать для оценки задержки при фиксации, вызываемой установкой в параметре synchronous_commit уровня remote_apply, если этот сервер был сконфигурирован как синхронный резервный.

sync_priority integer

Приоритет этого резервного сервера для выбора в качестве синхронного резервного сервера при синхронной репликации на основе приоритетов. Не влияет на синхронную репликацию на основе кворума.

sync_state text

Состояние синхронизации этого резервного сервера. Возможные значения:

async: Этот резервный сервер является асинхронным.

potential: Сейчас этот резервный сервер является асинхронным, но потенциально может стать синхронным, если откажет один из текущих синхронных серверов.

sync: Этот резервный сервер является синхронным.

quorum: Этот резервный сервер считается кандидатом в кворум резервных серверов.

reply_time timestamp with time zone

Время передачи последнего ответного сообщения, полученного от резервного сервера

Время задержки, отображаемое в представлении pg_stat_replication, включает в себя суммарное время, затраченное на запись, сброс и воспроизведение последних данных WAL, а также на то, чтобы отправитель узнал об этом. Эта длительность представляет задержку фиксирования, которая была (или должна была быть) добавлена каждым уровнем синхронного фиксирования, если удаленный сервер был сконфигурирован как синхронный резервный сервер. Для асинхронного резервного сервера столбец replay_lag приблизительно определяет задержку перед тем, как последние транзакции стали видимыми для запросов. Если резервный сервер полностью догнал передающий и активность WAL больше не наблюдается, последнее измеренное значение времени задержки будет отображаться еще в течение короткого времени, а затем сменится на NULL.

Время задержки для физической репликации определяется автоматически. Плагины логического декодирования могут не отправлять сообщения отслеживания; в этом случае механизм отслеживания просто отобразит задержку, равную NULL.

Примечание
Выводимые длительности задержки не предсказывают, сколько времени потребуется резервному серверу, чтобы догнать передающий сервер, исходя из текущей скорости воспроизведения. Такая система будет показывать схожие значения времени при генерировании новых данных WAL, но не при переходе передающего сервера в режим ожидания. В частности, когда резервный сервер полностью догоняет основной, pg_stat_replication показывает время, затраченное на запись, сброс на диск и воспроизведение самой последней переданной позиции WAL, а не ноль, как могут ожидать некоторые пользователи. Это соответствует целям измерения задержек синхронного фиксирования и видимости транзакций для недавних записанных транзакций. Чтобы меньше сбивать с толку пользователей, ожидающих другую модель задержек, значения столбцов задержек вскоре возвращаются в NULL в воспроизведшей все изменения простаивающей системе. Системы мониторинга должны выбрать, следует ли представлять это как отсутствующие данные или ноль или продолжать отображать последнее известное значение.

pg_stat_replication_slots

Представление pg_stat_replication_slots будет содержать для каждого слота логической репликации по одной строке, отображающей статистику его использования.

Таблица 15. Представление pg_stat_replication_slots

Столбец Тип

Описание

slot_name text

Уникальный на уровне кластера идентификатор для слота репликации

spill_txns bigint

Количество транзакций, вытесненных на диск после того, как объем памяти, используемый при логическом декодировании изменений из WAL, превысил logical_decoding_work_mem. Этот счетчик прирастает с учетом как транзакций верхнего уровня, так и субтранзакций.

spill_count bigint

Сколько раз транзакции были вытеснены на диск при декодировании изменений из WAL для этого слота. Этот счетчик прирастает каждый раз, когда транзакция вытесняется, и одна и та же транзакция может вытесняться несколько раз.

spill_bytes bigint

Объем декодированных транзакционных данных, вытесненных на диск при проведении декодирования изменений из WAL для этого слота. Этот и другие счетчики вытеснения можно использовать для оценки ввода/вывода, происходящего во время логического декодирования и настройки параметра logical_decoding_work_mem.

stream_txns bigint

Количество текущих транзакций, передаваемых в потоке в плагин вывода декодирования после того, как объем памяти, используемый для логического декодирования изменений из WAL для этого слота, превысил logical_decoding_work_mem. Потоковая передача работает только для транзакций верхнего уровня (субтранзакции не могут передаваться в потоке независимо), поэтому счетчик прирастает без учета субтранзакций.

stream_count bigint

Сколько раз текущие транзакции передавались в потоке плагину вывода декодирования при декодировании изменений из WAL для этого слота. Этот счетчик прирастает каждый раз, когда транзакция передается в потоке, и одна и та же транзакция может передаваться в потоке несколько раз.

stream_bytes bigint

Объем транзакционных данных, декодированных для передачи текущих транзакций в потоке в плагин вывода декодирования при декодировании изменений из WAL для этого слота. Этот и другие счетчики потоковой передачи для этого слота можно использовать для настройки параметра logical_decoding_work_mem.

total_txns bigint

Количество декодированных транзакций, отправленных в плагин вывода декодирования для этого слота. Считаются только транзакции верхнего уровня, но не субтранзакции. Обратите внимание, что сюда входят передаваемые в потоке и/или вытесняемые транзакции.

total_bytes bigint

Объем транзакционных данных, декодированных для отправки транзакций в плагин вывода декодирования при декодировании изменений из WAL для этого слота. Обратите внимание, что сюда входят передаваемые в потоке и/или вытесняемые данные.

stats_reset timestamp with time zone

Время последнего сброса этих статистических данных

pg_stat_wal_receiver

Представление pg_stat_wal_receiver будет содержать только одну строку, отображающую статистику по процессу-получателю WAL от сервера, к которому подключен этот получатель.

Таблица 16. Представление pg_stat_wal_receiver

Столбец Тип

Описание

pid integer

PID процесса-получателя WAL

status text

Статус активности процесса-получателя WAL

receive_start_lsn pg_lsn

Первая позиция в журнале упреждающей записи, использованная в момент запуска получателя WAL

receive_start_tli integer

Первый номер временной шкалы, использованный в момент запуска получателя WAL

written_lsn pg_lsn

Последняя позиция в журнале упреждающей записи, уже полученная и записанная на диск, но еще не сброшенная. Эту позицию не следует использовать для проверки целостности данных.

flushed_lsn pg_lsn

Последняя позиция в журнале упреждающей записи, уже полученная и сброшенная на диск; начальным значением этого поля будет первая позиция в журнале, использованная в момент запуска получателя WAL

received_tli integer

Номер временной шкалы последней позиции в журнале упреждающей записи, уже полученной и сброшенной на диск; начальным значением этого поля будет номер временной шкалы первой позиции в журнале, использованной в момент запуска получателя WAL

last_msg_send_time timestamp with time zone

Время передачи последнего сообщения, полученного от исходного отправителя WAL

last_msg_receipt_time timestamp with time zone

Время приема последнего сообщения, полученного от исходного отправителя WAL

latest_end_lsn pg_lsn

Последняя позиция в журнале упреждающей записи, сообщенная исходному отправителю WAL

latest_end_time timestamp with time zone

Время последней позиции в журнале упреждающей записи, сообщенной исходному отправителю WAL

slot_name text

Имя слота репликации, используемого этим получателем WAL

sender_host text

Хост экземпляра QHB, к которому подключен этот получатель WAL. Это может быть имя хоста, IP-адрес или путь каталога (если подключение установлено через сокет домена Unix). (Вариант с путем можно распознать, поскольку это всегда будет абсолютный путь, начинающийся с /.)

sender_port integer

Номер порта экземпляра QHB, к которому подключен этот получатель WAL.

conninfo text

Строка подключения, используемая этим получателем WAL, со скрытыми полями повышенной секретности.

pg_stat_recovery_prefetch

Представление pg_stat_recovery_prefetch будет содержать только одну строку. В столбцах wal_distance, block_distance и io_depth выводятся текущие значения, а в остальных столбцах — накопительные счетчики, которые можно сбросить с помощью функции pg_stat_reset_shared.

Таблица 17. Представление pg_stat_recovery_prefetch

Столбец Тип

Описание

stats_reset timestamp with time zone

Время последнего сброса этих статистических данных

prefetch bigint

Количество блоков, извлеченных предварительно из-за того, что их не было в пуле буферов

hit bigint

Количество блоков, не извлеченных предварительно из-за того, что они уже были в пуле буферов

skip_init bigint

Количество блоков, не извлеченных предварительно из-за того, что они должны инициализироваться нулями

skip_new bigint

Количество блоков, не извлеченных предварительно из-за того, что они еще не существовали

skip_fpw bigint

Количество блоков, не извлеченных предварительно из-за того, что в WAL были включены образы полных страниц

skip_rep bigint

Количество блоков, не извлеченных предварительно из-за того, что недавно они уже извлекались предварительно

wal_distance int

Количество байтов, просматриваемых процессом предварительной выборки

block_distance int

Количество блоков, просматриваемых процессом предварительной выборки

io_depth int

Количество операций предварительной выборки, которые были инициированы, но еще не считаются завершенными

pg_stat_subscription

Таблица 18. Представление pg_stat_subscription

Столбец Тип

Описание

subid oid

Идентификатор подписки

subname name

Имя подписки

pid integer

PID рабочего процесса этой подписки

relid oid

Идентификатор отношения, которое синхронизирует этот рабочий процесс; NULL для основного задействованного рабочего процесса

received_lsn pg_lsn

Последняя полученная позиция в журнале упреждающей записи; начальным значением этого поля будет 0

last_msg_send_time timestamp with time zone

Время передачи последнего сообщения, полученного от исходного отправителя WAL

last_msg_receipt_time timestamp with time zone

Время приема последнего сообщения, полученного от исходного отправителя WAL

latest_end_lsn pg_lsn

Последняя позиция в журнале упреждающей записи, сообщенная исходному отправителю WAL

latest_end_time timestamp with time zone

Время последней позиции в журнале упреждающей записи, сообщенной исходному отправителю WAL

pg_stat_subscription_stats

Представление pg_stat_subscription_stats будет содержать по одной строке для каждой подписки.

Таблица 19. Представление pg_stat_subscription_stats

Столбец Тип

Описание

subid oid

Идентификатор подписки

subname name

Имя подписки

apply_error_count bigint

Сколько раз происходили ошибки при применении изменений

sync_error_count bigint

Сколько раз происходили ошибки во время начальной синхронизации таблиц

stats_reset timestamp with time zone

Время последнего сброса этих статистических данных

pg_stat_ssl

Представление pg_stat_ssl будет содержать для каждого обслуживающего процесса или процесса-отправителя WAL по одной строке, отображающей статистику использования SSL для этого подключения. Его можно объединить с pg_stat_activity или pg_stat_replication по столбцу pid, чтобы получить более подробную информацию о подключении.

Таблица 20. Представление pg_stat_ssl

Столбец Тип

Описание

pid integer

PID обслуживающего процесса или процесса-отправителя WAL

ssl boolean

True, если для этого подключения используется SSL

version text

Версия используемого SSL или NULL, если для этого подключения не используется SSL

cipher text

Имя используемого шифра SSL или NULL, если для этого подключения не используется SSL

bits integer

Количество битов в используемом алгоритме шифрования или NULL, если для этого подключения не используется SSL

client_dn text

Поле DN (Distinguished Name, уникальное имя) из используемого клиентского сертификата или NULL, если клиентский сертификат не предоставлен или если для этого подключения не используется SSL. Это поле усекается, если поле DN длиннее NAMEDATALEN (64 символа в стандартной сборке).

client_serial numeric

Серийный номер клиентского сертификата или NULL, если клиентский сертификат не предоставлен или если для этого подключения не используется SSL. Сочетание серийного номера сертификата и имени центра сертификации однозначно идентифицирует сертификат (если только центр сертификации по ошибке не использует серийные номера повторно).

issuer_dn text

DN центра сертификации, выдавшего клиентский сертификат, или NULL, если клиентский сертификат не предоставлен или если для этого подключения не используется SSL. Это поле усекается аналогично полю client_dn.

pg_stat_gssapi

Представление pg_stat_gssapi будет для каждого обслуживающего процесса содержать по одной строке, отображающей информацию об использовании GSSAPI для этого подключения. Его можно объединить с pg_stat_activity или pg_stat_replication по столбцу pid, чтобы получить более подробную информацию о подключении.

Таблица 21. Представление pg_stat_gssapi

Столбец Тип

Описание

pid integer

PID обслуживающего процесса

gss_authenticated boolean

True, если для этого подключения используется аутентификация GSSAPI

principal text

Администратор доступа, используемый для аутентификации этого подключения, или NULL, если для аутентификации этого подключения не использовалось GSSAPI. Это поле усекается, если имя администратора доступа длиннее NAMEDATALEN (64 символа в стандартной сборке).

encrypted boolean

True, если для этого подключения используется шифрование GSSAPI

pg_stat_archiver

Представление pg_stat_archiver всегда будет включать только одну строку, содержащую данные о процессе архивации кластера.

Таблица 22. Представление pg_stat_archiver

Столбец Тип

Описание

archived_count bigint

Количество файлов WAL, которые уже были успешно заархивированы

last_archived_wal text

Имя последнего успешно заархивированного файла WAL

last_archived_time timestamp with time zone

Время последней успешной операции архивирования

failed_count bigint

Число неудачных попыток архивирования файлов WAL

last_failed_wal text

Имя файла WAL последней неудачной операции архивирования

last_failed_time timestamp with time zone

Время последней неудачной операции архивирования

stats_reset timestamp with time zone

Время последнего сброса этих статистических данных

Обычно файлы WAL архивируются в порядке от самых старых к самым но это не гарантируется и не выполняется в особых обстоятельствах, например, при повышении резервного сервера или после восстановления после сбоя. Таким образом, небезопасно предполагать, что все файл старше last_archived_wal тоже были успешно заархивированы.

pg_stat_bgwriter

Представление pg_stat_bgwriter всегда будет включать только одну строку, содержащую глобальные данные по всему кластеру.

Таблица 23. Представление pg_stat_bgwriter

Столбец Тип

Описание

checkpoints_timed bigint

Количество запланированных контрольных точек, которые уже были выполнены

checkpoints_req bigint

Количество запрошенных контрольных точек, которые уже были выполнены

checkpoint_write_time double precision

Общее время, которое было затрачено на том этапе обработки контрольной точки, где файлы записываются на диск, в миллисекундах

checkpoint_sync_time double precision

Общее время, которое было затрачено на том этапе обработки контрольной точки, где файлы синхронизируются с диском, в миллисекундах

buffers_checkpoint bigint

Количество буферов, записанных во время контрольных точек

buffers_clean bigint

Количество буферов, записанных фоновым процессом записи

maxwritten_clean bigint

Сколько раз фоновый процесс записи останавливал сканирование с записью грязных данных из-за того, что записал слишком много буферов

buffers_backend bigint

Количество буферов, записанных непосредственно обслуживающим процессом

buffers_backend_fsync bigint

Сколько раз обслуживающему процессу пришлось выполнить вызов fsync самостоятельно (обычно эти вызовы обрабатывает фоновый процесс записи, даже когда обслуживающий процесс выполняет запись самостоятельно)

buffers_alloc bigint

Количество выделенных буферов

stats_reset timestamp with time zone

Время последнего сброса этих статистических данных

pg_stat_wal

Представление pg_stat_wal всегда будет включать только одну строку, содержащую данные об активности WAL в кластере.

Таблица 24. Представление pg_stat_wal

Столбец Тип

Описание

wal_records bigint

Общее количество сгенерированных записей WAL

wal_fpi bigint

Общее количество сгенерированных образов полных страниц в WAL

wal_bytes numeric

Общий сгенерированный объем WAL в байтах

wal_buffers_full bigint

Сколько раз данные WAL записывались на диск из-за переполнения буферов WAL

wal_write bigint

Сколько раз буферы WAL записывались на диск по запросу функции XLogWrite. Подробную информацию о внутренней функции WAL XLogWrite см. в разделе Конфигурация WAL.

wal_sync bigint

Сколько раз файлы WAL синхронизировались с диском по запросу функции issue_xlog_fsync (если параметр fsync равен on (включен), а параметр wal_sync_method равен fdatasync, fsync или fsync_writethrough, иначе будет выводиться ноль). Подробную информацию о внутренней функции WAL issue_xlog_fsync см. в разделе Конфигурация WAL.

wal_write_time double precision

Общее время, затраченное на запись буферов WAL на диск по запросу функции XLogWrite, в миллисекундах (если включен параметр track_wal_io_timing, иначе будет выводиться ноль). Когда параметр wal_sync_method равен open_datasync или open_sync, сюда же включается время синхронизации.

wal_sync_time double precision

Общее время, затраченное на синхронизацию файлов WAL с диском по запросу функции issue_xlog_fsync, в миллисекундах (если включен параметр track_wal_io_timing, параметр fsync равен on, а параметр wal_sync_method — fdatasync, fsync или fsync_writethrough, иначе будет выводиться ноль).

stats_reset timestamp with time zone

Время последнего сброса этих статистических данных

pg_stat_database

Представление pg_stat_database будет содержать для каждой базы данных в кластере по одной строке, отображающей общую статистику на уровне базы данных, плюс еще одну строку для общих объектов.

Таблица 25. Представление pg_stat_database

Столбец Тип

Описание

datid oid

OID этой базы данных или 0, если объекты принадлежат разделяемому отношению

datname name

Имя этой базы данных или NULL для разделяемых объектов

numbackends integer

Количество обслуживающих процессов, в настоящее время подключенных к этой базе данных, или NULL для разделяемых объектов. Это единственный столбец в данном представлении, который возвращает значение, отражающее текущее состояние; все остальные столбцы возвращают накопленные значения с момента последнего сброса статистических данных.

xact_commit bigint

Количество зафиксированных транзакций в этой базе данных

xact_rollback bigint

Количество откатившихся транзакций в этой базе данных

blks_read bigint

Количество прочитанных дисковых блоков в этой базе данных

blks_hit bigint

Сколько раз дисковые блоки были обнаружены уже в буферном кэше, благодаря чему в чтении не было необходимости (сюда включаются только случаи обнаружения в буферном кэше QHB, а не в кэше файловой системы операционной системы)

tup_returned bigint

Количество активных строк, извлеченных при последовательных сканированиях, и записей индекса, возвращенных при сканированиях индексов, в этой базе данных

tup_fetched bigint

Количество активных строк, извлеченных при сканированиях индексов, в этой базе данных

tup_inserted bigint

Количество строк, добавленных запросами в этой базе данных

tup_updated bigint

Количество строк, измененных запросами в этой базе данных

tup_deleted bigint

Количество строк, удаленных запросами в этой базе данных

conflicts bigint

Количество запросов, отмененных вследствие конфликтов с восстановлением в этой базе данных. (Конфликты случаются только на резервных серверах; подробную информацию см. в описании представления pg_stat_database_conflicts.)

temp_files bigint

Количество временных файлов, созданных запросами в этой базе данных. Учитываются все временные файлы, независимо от причины их создания (например, для сортировки или хеширования) и значения параметра log_temp_files.

temp_bytes bigint

Общий объем данных, записанных во временные файлы запросами в этой базе данных. Учитываются все временные файлы, независимо от причины их создания и значения параметра log_temp_files.

deadlocks bigint

Количество взаимных блокировок, выявленных в этой базе данных

checksum_failures bigint

Количество ошибок контрольных сумм в страницах данных в этой базе данных (или в разделяемом объекте) или NULL, если проверка контрольных сумм данных не включена.

checksum_last_failure timestamp with time zone

Время выявления последней ошибки контрольных сумм в страницах данных в этой базе данных (или в разделяемом объекте) или NULL, если проверка контрольных сумм данных не включена.

blk_read_time double precision

Время, затраченное обслуживающими процессами на чтение блоков из файлов данных в этой базе данных, в миллисекундах (если включен параметр track_io_timing; в противном случае ноль)

blk_write_time double precision

Время, затраченное обслуживающими процессами на запись блоков в файлы данных в этой базе данных, в миллисекундах (если включен параметр track_io_timing; в противном случае ноль)

session_time double precision

Продолжительность сеансов в этой базе данных, в миллисекундах (обратите внимание, что статистика обновляется только при изменении состояния сеанса, поэтому если сеансы долгое время простаивали, это время простоя сюда включено не будет)

active_time double precision

Время, затраченное на выполнение операторов SQL в этой базе данных, в миллисекундах (это соответствует состояниям active и fastpath function call в pg_stat_activity)

idle_in_transaction_time double precision

Время простоя в транзакциях в этой базе данных, в миллисекундах (это соответствует состояниям idle in transaction и idle in transaction (aborted) в pg_stat_activity)

sessions bigint

Общее количество сеансов, установленных в этой базе данных

sessions_abandoned bigint

Количество сеансов в этой базе данных, прерванных вследствие потери соединения с клиентом

sessions_fatal bigint

Количество сеансов в этой базе данных, прерванных вследствие критических ошибок

sessions_killed bigint

Количество сеансов в этой базе данных, прерванных вследствие вмешательства оператора

stats_reset timestamp with time zone

Время последнего сброса этих статистических данных

pg_stat_database_conflicts

Представление pg_stat_database_conflicts будет содержать для каждой базы данных по одной строке, отображающей статистику на уровне базы данных по отменам запросов, возникающим из-за конфликтов с восстановлением на резервных серверах. Это представление будет содержать информацию только по резервным серверам, поскольку на главных серверах конфликты не возникают.

Таблица 26. Представление pg_stat_database_conflicts

Столбец Тип

Описание

datid oid

OID базы данных

datname name

Имя этой базы данных

confl_tablespace bigint

Количество запросов в этой базе данных, отмененных из-за удаленного табличного пространства

confl_lock bigint

Количество запросов в этой базе данных, отмененных из-за тайм-аутов блокировок

confl_snapshot bigint

Количество запросов в этой базе данных, отмененных из-за устаревших снимков состояния

confl_bufferpin bigint

Количество запросов в этой базе данных, отмененных из-за закрепленных буферов

confl_deadlock bigint

Количество запросов в этой базе данных, отмененных из-за взаимоблокировок

pg_stat_all_tables

Представление pg_stat_all_tables будет содержать для каждой таблицы в текущей базе данных (включая таблицы TOAST) по одной строке, отображающей статистику по обращениям к этой конкретной таблице. Представления pg_stat_user_tables и pg_stat_sys_tables содержат ту же информацию, но отфильтрованную так, чтобы отображать только пользовательские и системные таблицы соответственно.

Таблица 27. Представление pg_stat_all_tables

Столбец Тип

Описание

relid oid

OID таблицы

schemaname name

Имя схемы, в которой находится эта таблица

relname name

Имя этой таблицы

seq_scan bigint

Количество последовательных сканирований, инициированных для этой таблицы

seq_tup_read bigint

Количество активных строк, извлеченных при последовательных сканированиях

idx_scan bigint

Количество сканирований по индексу, инициированных для этой таблицы

idx_tup_fetch bigint

Количество активных строк, извлеченных при сканированиях по индексу

n_tup_ins bigint

Количество добавленных строк

n_tup_upd bigint

Количество измененных строк (включая строки, измененные в режиме HOT)

n_tup_del bigint

Количество удаленных строк

n_tup_hot_upd bigint

Количество строк, измененных в режиме HOT (т. е. без необходимости в отдельном изменении индекса)

n_live_tup bigint

Расчетное количество активных строк

n_dead_tup bigint

Расчетное количество неиспользуемых строк

n_mod_since_analyze bigint

Расчетное количество строк, измененных в этой таблице с момента последнего анализа

n_ins_since_vacuum bigint

Расчетное количество строк, добавленных в эту таблицу с момента последней очистки

last_vacuum timestamp with time zone

Время последней очистки этой таблицы вручную (не учитывая VACUUM FULL)

last_autovacuum timestamp with time zone

Время последней очистки этой таблицы фоновым процессом «Автовакуум»

last_analyze timestamp with time zone

Время последнего анализа этой таблицы вручную

last_autoanalyze timestamp with time zone

Время последнего анализа этой таблицы фоновым процессом «Автовакуум»

vacuum_count bigint

Сколько раз эта таблица очищалась вручную (не учитывая VACUUM FULL)

autovacuum_count bigint

Сколько раз эта таблица очищалась фоновым процессом «Автовакуум»

analyze_count bigint

Сколько раз эта таблица анализировалась вручную

autoanalyze_count bigint

Сколько раз эта таблица анализировалась фоновым процессом «Автовакуум»

pg_stat_all_indexes

Представление pg_stat_all_indexes будет содержать для каждого индекса в текущей базе данных по одной строке, отображающей статистику по обращениям к этому конкретному индексу. Представления pg_stat_user_indexes и pg_stat_sys_indexes содержат ту же информацию, но отфильтрованную так, чтобы отображать только пользовательские и системные индексы соответственно.

Таблица 28. Представление pg_stat_all_indexes

Столбец Тип

Описание

relid oid

OID таблицы для этого индекса

indexrelid oid

OID этого индекса

schemaname name

Имя схемы, в которой находится этот индекс

relname name

Имя таблицы для этого индекса

indexrelname name

Имя этого индекса

idx_scan bigint

Количество сканирований, инициированных по этому индексу

idx_tup_read bigint

Количество записей индекса, возвращенных при сканировании по этому индексу

idx_tup_fetch bigint

Количество активных строк таблицы, извлеченных при простых сканированиях по этому индексу

Индексы могут использоваться при простых сканированиях по индексу, сканированиях «битовой карты» индекса и в работе оптимизатора. При сканировании битовых карт выходные данные нескольких индексов можно комбинировать с помощью правил И или ИЛИ, поэтому при использовании битовых карт сложно связать выборки отдельных строк кучи с конкретными индексами. Поэтому при сканировании битовых карт увеличиваются счетчики pg_stat_all_indexes.idx_tup_read для индексов, которые оно использует, и счетчик pg_stat_all_tables.idx_tup_fetch для таблицы, но при этом оно не влияет на pg_stat_all_indexes.idx_tup_fetch. Оптимизатор также обращается к индексам для проверки предоставленных констант, значения которых находятся за пределами записанного диапазона статистики оптимизатора, поскольку статистика оптимизатора может быть устаревшей.

Примечание
Значения счетчиков idx_tup_read и idx_tup_fetch могут отличаться даже без использования сканирований битовых карт, поскольку idx_tup_read подсчитывает записи индекса, извлеченные из индекса, тогда как idx_tup_fetch подсчитывает количество активных строк, извлеченных из таблицы. Последнее значение будет меньше, если какие-либо неиспользуемые или еще не зафиксированные строки извлекаются с использованием индекса или если какие-либо выборки из кучи не производятся посредством сканирования только по индексу.

pg_statio_all_tables

Представление pg_statio_all_tables будет содержать для каждой таблицы в текущей базе данных (включая таблицы TOAST) по одной строке, отображающей статистику по вводу/выводу для этой конкретной таблицы. Представления pg_statio_user_tables и pg_statio_sys_tables содержат ту же информацию, но отфильтрованную так, чтобы отображать только пользовательские и системные таблицы соответственно.

Таблица 29. Представление pg_statio_all_tables

Столбец Тип

Описание

relid oid

OID таблицы

schemaname name

Имя схемы, в которой находится эта таблица

relname name

Имя этой таблицы

heap_blks_read bigint

Количество дисковых блоков, прочитанных из этой таблицы

heap_blks_hit bigint

Количество попаданий в буфер в этой таблице

idx_blks_read bigint

Количество дисковых блоков, прочитанных из всех индексов этой таблицы

idx_blks_hit bigint

Количество попаданий в буфер во всех индексах этой таблицы

toast_blks_read bigint

Количество дисковых блоков, прочитанных из таблицы TOAST (если таковая имеется) для этой таблицы

toast_blks_hit bigint

Количество попаданий в буфер в таблице TOAST (если таковая имеется) для этой таблицы

tidx_blks_read bigint

Количество дисковых блоков, прочитанных из индексов таблицы TOAST (если таковая имеется) для этой таблицы

tidx_blks_hit bigint

Количество попаданий в буфер в индексах таблицы TOAST (если таковая имеется) для этой таблицы

pg_statio_all_indexes

Представление pg_statio_all_indexes будет содержать для каждого индекса в текущей базе данных по одной строке, отображающей статистику по вводу/выводу для этого конкретного индекса. Представления pg_statio_user_indexes и pg_statio_sys_indexes содержат ту же информацию, но отфильтрованы так, чтобы отображать только пользовательские и системные индексы соответственно.

Таблица 30. Представление pg_statio_all_indexes

Столбец Тип

Описание

relid oid

OID таблицы для этого индекса

indexrelid oid

OID этого индекса

schemaname name

Имя схемы, в которой находится этот индекс

relname name

Имя таблицы для этого индекса

indexrelname name

Имя этого индекса

idx_blks_read bigint

Количество дисковых блоков, прочитанных из этого индекса

idx_blks_hit bigint

Количество попаданий в буфер в этом индексе

pg_statio_all_sequences

Представление pg_statio_all_sequences будет содержать для каждой последовательности в текущей базе данных по одной строке, отображающей статистику по вводу/выводу для этой конкретной последовательности.

Таблица 31. Представление pg_statio_all_sequences

Столбец Тип

Описание

relid oid

OID последовательности

schemaname name

Имя схемы, в которой находится эта последовательность

relname name

Имя этой последовательности

blks_read bigint

Количество дисковых блоков, прочитанных из этой последовательности

blks_hit bigint

Количество попаданий в буфер в этой последовательности

pg_stat_user_functions

Представление pg_stat_user_functions будет содержать для каждой отслеживаемой функции по одной строке, отображающей статистику по выполнениям этой функции. Параметр track_functions определяет, какие именно функции отслеживаются.

Таблица 32. Представление pg_stat_user_functions

Столбец Тип

Описание

funcid oid

OID функции

schemaname name

Имя схемы, в которой находится эта функция

funcname name

Имя этой функции

calls bigint

Сколько раз вызывалась эта функция

total_time double precision

Общее время, затраченное на выполнение этой функции и всех других функций, вызванных ею, в миллисекундах

self_time double precision

Общее время, затраченное на выполнение самой этой функции, без учета других функций, вызванных ею, в миллисекундах

pg_stat_slru

QHB обращается к некоторой информации на диске посредством кэшей с алгоритмом SLRU (simple least-recently-used, простое вытеснение по давности использования). Представление pg_stat_slru будет содержать для каждого отслеживаемого кэша SLRU по одной строке, отображающей статистику по обращениям к кэшированным страницам.

Таблица 31. Представление pg_stat_slru

Столбец Тип

Описание

name text

Имя кэша SLRU

blks_zeroed bigint

Количество блоков, обнуленных во время инициализации

blks_hit bigint

Сколько раз дисковые блоки были обнаружены уже в кэше SLRU, благодаря чему в чтении не было необходимости (сюда включаются только попадания в кэш SLRU, а не в кэш файловой системы операционной системы)

blks_read bigint

Количество прочитанных дисковых блоков для этого кэша SLRU

blks_written bigint

Количество записанных дисковых блоков для этого кэша SLRU

blks_exists bigint

Количество проверенных на существование дисковых блоков для этого кэша SLRU

flushes bigint

Количество операций сброса грязных данных для этого кэша SLRU

truncates bigint

Количество операций усечения для этого кэша SLRU

stats_reset timestamp with time zone

Время последнего сброса этих статистических данных

Функции статистики

Другие способы просмотра статистики можно настроить, написав запросы, использующие те же лежащие в основе функции доступа к статистике, которые используются стандартными представлениями, приведенными выше. Подробную информацию, например, имена этих функций, см. в определениях этих стандартных представлений (например, в psql можно выполнить \d+ pg_stat_activity). Функции доступа к статистике на уровне базы данных принимают в качестве аргумента OID базы данных, чтобы определить, по какой именно базе собирать информацию. Функции на уровне таблиц и индексов принимают OID таблицы или индекса. Функции, выдающие статистику на уровне функций принимают OID функции. Обратите внимание, что с помощью этих функций можно видеть таблицы, индексы и функции только в текущей базе данных.

Дополнительные функции, связанные со сбором статистики, перечислены в Таблице 34.

Таблица 34. Дополнительные статистические функции

Функция

Описание

pg_backend_pid () → integer

Возвращает PID серверного процесса, закрепленного за текущим сеансом.

pg_stat_get_activity ( integer ) → setof record

Возвращает запись с информацией об обслуживающем процессе с заданным идентификатором процесса или по одной записи для каждого активного обслуживающего процесса в системе, если указан NULL. Возвращаемые поля являются подмножеством полей представления pg_stat_activity.

pg_stat_get_snapshot_timestamp () → timestamp with time zone

Возвращает временную метку текущего снимка статистики или NULL, если снимок статистики не делался. Снимок создается при первом обращении к накопительной статистике в транзакции, если параметр stats_fetch_consistency имеет значение snapshot.

pg_stat_get_xact_blocks_fetched ( oid ) → bigint

Возвращает количество запросов на чтение блоков для таблицы или индекса в текущей транзакции. Это число минус pg_stat_get_xact_blocks_hit дает количество вызовов функции read() для ядра; количество фактических физических операций чтения обычно меньше вследствие буферизации на уровне ядра.

pg_stat_get_xact_blocks_hit ( oid ) → bigint

Возвращает количество запросов на чтение блоков для таблицы или индекса в текущей транзакции, обнаруженных в кэше (не провоцирует вызовы функции read() для ядра).

pg_stat_clear_snapshot () → void

Сбрасывает текущий снимок статистики или кэшированную информацию.

pg_stat_reset () → void

Обнуляет все статистические счетчики для текущей базы данных.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_stat_reset_shared ( text ) → void

Обнуляет некоторые статистические счетчики на уровне кластера, в зависимости от аргумента. Значением аргумента может быть bgwriter для обнуления всех счетчиков, отображаемых в представлении pg_stat_bgwriter, archiver для обнуления всех счетчиков, отображаемых в представлении pg_stat_archiver, wal для обнуления всех счетчиков, отображаемых в представлении pg_stat_wal, или recovery_prefetch для обнуления всех счетчиков, отображаемых в представлении pg_stat_recovery_prefetch.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_stat_reset_single_table_counters ( oid ) → void

Обнуляет статистику для отдельной таблицы или индекса в текущей базе данных или для совместно используемых всеми базами данных в кластере.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_stat_reset_single_function_counters ( oid ) → void

Обнуляет статистику для отдельной функции в текущей базе данных.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_stat_reset_slru ( text ) → void

Обнуляет статистику для отдельного кэша SLRU или для всех кэшей SLRU в кластере. Если аргумент имеет значение NULL, сбрасываются все счетчики, отображаемые в представлении pg_stat_slru, для всех кэшей SLRU. Аргументом также может быть CommitTs, MultiXactMember, MultiXactOffset, Notify, Serial, Subtrans или Xact для сброса счетчиков только для соответствующей записи. При значении other (или даже любом нераспознаваемом имени) сбрасываются счетчики для всех остальных кэшей SLRU, например для определенных расширением.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_stat_reset_replication_slot ( text ) → void

Сбрасывает статистику слота репликации, определенного в аргументе. Если аргумент имеет значение NULL, сбрасывает статистику для всех слотов репликации.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_stat_reset_subscription_stats ( oid ) → void

Обнуляет статистику для отдельной подписки, отображаемую в представлении pg_stat_subscription_stats. Если аргумент имеет значение NULL, обнуляет статистику для всех подписок.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

ПРЕДУПРЕЖДЕНИЕ
При вызове функции pg_stat_reset() также сбрасываются счетчики, которые процесс «Автовакуум» использует для определения времени запуска очистки или анализа. В результате сброса этих счетчиков процесс «Автовакуум» может не выполнить необходимую работу, что может привести к таким проблемам, как раздувание таблиц или потеря актуальности статистическими данными по таблицам. В связи с этим после сброса статистики рекомендуется выполнить ANALYZE на уровне базы данных.

Лежащая в основе представления pg_stat_activity функция pg_stat_get_activity возвращает набор записей, содержащих всю доступную информацию о каждом обслуживающем процессе. Иногда бывает удобнее получить только часть этой информации. В таких случаях можно воспользоваться более старым набором функций доступа к статистике на уровне обслуживающих процессов; они перечислены в Таблице 35. Эти функции доступа используют идентификатор обслуживающего процесса, значение которого варьирует от единицы до количества текущих активных обслуживающих процессов. Функция pg_stat_get_backend_idset предоставляет удобный способ генерации по одной строке для каждого активного обслуживающего процесса, что требуется для вызова этих функций. Например, чтобы вывести PID и текущие запросы всех обслуживающих процессов, можно выполнить:

SELECT pg_stat_get_backend_pid(s.backendid) AS pid,
       pg_stat_get_backend_activity(s.backendid) AS query
    FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS s;

Таблица 35. Статистические функции на уровне обслуживающих процессов

Функция

Описание

pg_stat_get_backend_idset () → setof integer

Возвращает набор идентификаторов активных на данный момент обслуживающих процессов (в диапазоне от 1 до количества активных обслуживающих процессов).

pg_stat_get_backend_activity ( integer ) → text

Возвращает текст последнего запроса этого обслуживающего процесса.

pg_stat_get_backend_activity_start ( integer ) → timestamp with time zone

Возвращает время начала выполнения последнего запроса этого обслуживающего процесса.

pg_stat_get_backend_client_addr ( integer ) → inet

Возвращает IP-адрес клиента, подключенного к этому обслуживающему процессу.

pg_stat_get_backend_client_port ( integer ) → integer

Возвращает номер TCP-порта, который клиент использует для взаимодействия.

pg_stat_get_backend_dbid ( integer ) → oid

Возвращает идентификатор базы данных, к которой подключен этот обслуживающий процесс.

pg_stat_get_backend_pid ( integer ) → integer

Возвращает PID этого обслуживающего процесса.

pg_stat_get_backend_start ( integer ) → timestamp with time zone

Возвращает время запуска этого процесса.

pg_stat_get_backend_userid ( integer ) → oid

Возвращает идентификатор пользователя, подсоединившегося к этому обслуживающему процессу.

pg_stat_get_backend_wait_event_type ( integer ) → text

Возвращает имя типа ожидаемого события, если этот обслуживающий процесс в данный момент находится в состоянии ожидания, или NULL в противном случае. Подробную информацию см. в Таблице 4.

pg_stat_get_backend_wait_event ( integer ) → text

Возвращает имя ожидаемого события, если этот обслуживающий процесс в данный момент находится в состоянии ожидания, или NULL в противном случае. См. с Таблицы 5 по Таблицу 13.

pg_stat_get_backend_xact_start ( integer ) → timestamp with time zone

Возвращает время начала текущей транзакции этого обслуживающего процесса.

Просмотр информации о блокировках

Еще одним полезным инструментом для мониторинга активности базы данных является системная таблица pg_locks. Она позволяет администратору базы данных просматривать информацию о блокировках в менеджере блокировок. Например, эту возможность можно использовать для того, чтобы:

Просмотреть все имеющиеся на данный момент блокировки, все блокировки на отношения в конкретной базе данных, все блокировки на определенном отношении или все блокировки, удерживаемые определенным сеансом QHB.

Определить отношение в текущей базе данных с наибольшим количеством неразрешенных блокировок (которые могут быть источником конкуренции среди клиентов базы данных).

Определить влияние конкуренции за блокировку на общую производительность базы данных, а также то, до каких пределов меняется конкуренция в зависимости от общего трафика базы данных.

Подробная информации о представлении pg_locks находится в разделе pg_locks. Дополнительную информации о блокировках и управлении параллельным доступом в QHB см. в главе Управление параллельным доступом.

Отчеты о ходе выполнения команд

QHB имеет возможность сообщать о ходе выполнения определенных команд во время их выполнения. В настоящее время командами, которые поддерживают отображение текущего состояния, являются ANALYZE, CLUSTER, CREATE INDEX, VACUUM, COPY и BASE_BACKUP (т. е. команда репликации, которую запускает qhb_basebackup для создания базовой резервной копии). В будущем этот список может быть расширен.

Отчет о ходе выполнения ANALYZE

При каждом выполнении ANALYZE представление pg_stat_progress_analyze будет содержать по одной строке для каждого обслуживающего процесса, который в данный момент выполняет эту команду. В таблицах ниже описываются предоставляемые сведения и поясняется, как их интерпретировать.

Таблица 36. Представление pg_stat_progress_analyze

Столбец Тип

Описание

pid integer

PID обслуживающего процесса.

datid oid

Идентификатор базы данных, к которой подключен этот обслуживающий процесс.

datname name

Имя базы данных, к которой подключен этот обслуживающий процесс.

relid oid

Идентификатор анализируемой таблицы.

phase text

Текущая фаза обработки. См. Таблицу 37.

sample_blks_total bigint

Общее количество блоков кучи, которые будут отобраны.

sample_blks_scanned bigint

Количество просканированных блоков кучи.

ext_stats_total bigint

Количество объектов расширенной статистики.

ext_stats_computed bigint

Количество вычисленных объектов расширенной статистики. Этот счетчик увеличивается только в фазе computing extended statistics.

child_tables_total bigint

Количество дочерних таблиц.

child_tables_done bigint

Количество просканированных дочерних таблиц. Этот счетчик увеличивается только в фазе acquiring inherited sample rows.

current_child_table_relid oid

Идентификатор дочерней таблицы, сканируемой в настоящий момент. Это поле актуально только в фазе acquiring inherited sample rows.

Таблица 37. Фазы ANALYZE

Фаза | Описание
initializing | Команда готовится начать сканирование кучи. Предполагается, что эта фаза будет очень короткой.
acquiring sample rows | В данный момент команда сканирует таблицу с указанным relid, чтобы получить строки выборки.
acquiring inherited sample rows | В данный момент команда сканирует дочерние таблицы, чтобы получить строки выборки. В столбцах child_tables_total, child_tables_done и current_child_table_relid содержится информация о ходе выполнения этой фазы.
computing statistics | Команда вычисляет статистику по строкам выборки, полученным во время сканирования таблицы.
computing extended statistics | Команда вычисляет объекты расширенной статистики по строкам выборки, полученным во время сканирования таблицы.
finalizing analyze | Команда обновляет каталог pg_class. По окончании этой фазы ANALYZE завершит работу.

Примечание
Обратите внимание, что когда ANALYZE обрабатывает партиционированную таблицу, все ее партиции тоже рекурсивно анализируются. В этом случае сначала выводится информация о ходе ANALYZE для родительской таблицы, во время выполнения которой собирается ее наследуемая статистика, затем выводится такая же информация по каждой партиции.

Отчет о ходе выполнения CREATE INDEX

При каждом выполнении CREATE INDEX или REINDEX представление pg_stat_progress_create_index будет содержать по одной строке для каждого обслуживающего процесса, который в данный момент создает индексы. В таблицах ниже описываются предоставляемые сведения и поясняется, как их интерпретировать.

Таблица 38. Представление pg_stat_progress_create_index

Столбец Тип

Описание

pid integer

PID обслуживающего процесса.

datid oid

Идентификатор базы данных, к которой подключен этот обслуживающий процесс.

datname name

Имя базы данных, к которой подключен этот обслуживающий процесс.

relid oid

Идентификатор таблицы, в которой создается индекс.

index_relid oid

Идентификатор создаваемого или перестраиваемого индекса. Во время выполнения CREATE INDEX в непараллельном режиме содержит 0.

command text

Выполняемая команда: CREATE INDEX, CREATE INDEX CONCURRENTLY, REINDEX или REINDEX CONCURRENTLY.

phase text

Текущая фаза создания индекса. См. Таблицу 39.

lockers_total bigint

Общее количество блокирующих процессов, потребовавших ожидания, если применимо.

lockers_done bigint

Количество блокирующих процессов, ожидание которых уже завершено.

current_locker_pid bigint

PID блокирующего процесса, ожидание которого происходит в данный момент.

blocks_total bigint

Общее количество блоков, которые будут обработаны в текущей фазе.

blocks_done bigint

Количество блоков, уже обработанных в текущей фазе.

tuples_total bigint

Общее количество кортежей, которые будут обработаны в текущей фазе.

tuples_done bigint

Количество кортежей, уже обработанных в текущей фазе.

partitions_total bigint

При создании индекса в партиционированной таблице в этом столбце выводится общее количество партиций, в которых будет создаваться индекс. Во время выполнения REINDEX это поле содержит 0.

partitions_done bigint

При создании индекса в партиционированной таблице в этом столбце выводится общее количество партиций, в которых уже был создан индекс. Во время выполнения REINDEX это поле содержит 0.

Таблица 39. Фазы CREATE INDEX

Фаза
Описание

initializing
CREATE INDEX или REINDEX готовится к созданию индекса. Предполагается, что эта фаза будет очень короткой.

waiting for writers before build
CREATE INDEX CONCURRENTLY или REINDEX CONCURRENTLY ожидает завершения транзакций, удерживающих блокировки записи, которые потенциально могут видеть таблицу. Эта фаза пропускается, когда команда выполняется не в параллельном режиме. В столбцах lockers_total, lockers_done и current_locker_pid содержится информация о ходе выполнения этой фазы.

building index
Индекс строится с помощью кода, реализующего метод доступа. На этом этапе методы доступа, поддерживающие отчеты о ходе выполнения, передают собственные данные о ходе выполнения, и в этом столбце указывается внутренняя фаза. Как правило, данные о ходе выполнения содержат столбцы blocks_total и blocks_done и потенциально столбцы tuples_total и tuples_done.

waiting for writers before validation
CREATE INDEX CONCURRENTLY или REINDEX CONCURRENTLY ожидает завершения транзакций, удерживающих блокировки записи, которые потенциально могут записывать данные в таблицу. Эта фаза пропускается, когда команда выполняется не в параллельном режиме. В столбцах lockers_total, lockers_done и current_locker_pid содержится информация о ходе выполнения этой фазы.

index validation: scanning index
CREATE INDEX CONCURRENTLY сканирует индекс в поисках кортежей, которые необходимо проверить. Эта фаза пропускается, когда команда выполняется не в параллельном режиме. В столбцах blocks_total (отображает общий размер индекса) и blocks_done содержится информация о ходе выполнения этой фазы.

index validation: sorting tuples
CREATE INDEX CONCURRENTLY сортирует выходные данные фазы сканирования индекса.

index validation: scanning table
CREATE INDEX CONCURRENTLY сканирует таблицу для проверки кортежей индексов, собранных в предыдущих двух фазах. Эта фаза пропускается, когда команда выполняется не в параллельном режиме. В столбцах blocks_total (отображает общий размер индекса) и blocks_done содержится информация о ходе выполнения этой фазы.

waiting for old snapshots
CREATE INDEX CONCURRENTLY или REINDEX CONCURRENTLY ожидает освобождения снимков транзакциями, которые потенциально могут видеть таблицу. Эта фаза пропускается, когда команда выполняется не в параллельном режиме. В столбцах lockers_total, lockers_done и current_locker_pid содержится информация о ходе выполнения этой фазы.

waiting for readers before marking dead
REINDEX CONCURRENTLY ожидает завершения транзакций, удерживающих блокировки чтения, прежде чем пометить старый индекс как неиспользуемый. Эта фаза пропускается, когда команда выполняется не в параллельном режиме. В столбцах lockers_total, lockers_done и current_locker_pid содержится информация о ходе выполнения этой фазы.

waiting for readers before dropping
REINDEX CONCURRENTLY ожидает завершения транзакций, удерживающих блокировки чтения, прежде чем удалить старый индекс. Эта фаза пропускается, когда команда выполняется не в параллельном режиме. В столбцах lockers_total, lockers_done и current_locker_pid содержится информация о ходе выполнения этой фазы.

Отчет о ходе выполнения VACUUM

При каждом выполнении VACUUM представление pg_stat_progress_vacuum будет содержать по одной строке для каждого обслуживающего процесса (включая рабочие процессы автовакуума), который в данный момент производит очистку. В таблицах ниже описываются предоставляемые сведения и поясняется, как их интерпретировать. Информация о ходе выполнения команд VACUUM FULL выводится в представлении pg_stat_progress_cluster поскольку VACUUM FULL и CLUSTER перезаписывают таблицу, в то время как простая команда VACUUM только модифицирует ее саму. См. подраздел Отчет о ходе выполнения CLUSTER.

Таблица 40. Представление pg_stat_progress_vacuum

Столбец Тип

Описание

pid integer

PID обслуживающего процесса.

datid oid

Идентификатор базы данных, к которой подключен этот обслуживающий процесс.

datname name

Имя базы данных, к которой подключен этот обслуживающий процесс.

relid oid

Идентификатор очищаемой таблицы.

phase text

Текущая фаза очистки. См. Таблицу 41.

heap_blks_total bigint

Общее количество блоков кучи в этой таблице. Это число отображает состояние на начало сканирования; блоки, добавленные позже, не будут (и не должны) обрабатываться этой командой VACUUM.

heap_blks_scanned bigint

Количество просканированных блоков кучи. Поскольку для оптимизации сканирования используется карта видимости, некоторые блоки будут пропущены без проверки; пропущенные блоки включаются в это общее количество, поэтому данное число в итоге по завершении очистки сравняется со значением heap_blks_total. Этот счетчик увеличивается только в фазе scanning heap.

heap_blks_vacuumed bigint

Количество очищенных блоков кучи. Если в таблице имеются индексы, этот счетчик увеличивается только в фазе vacuuming heap. Блоки, не содержащие неиспользуемые кортежи, пропускаются, поэтому иногда этот счетчик может резко увеличиваться с большими приращениями.

index_vacuum_count bigint

Количество завершенных циклов очистки индекса.

max_dead_tuples bigint

Количество неиспользуемых кортежей, которые мы можем хранить, прежде чем возникнет необходимость выполнить цикл очистки индекса, исходя из значения maintenance_work_mem.

num_dead_tuples bigint

Количество неиспользуемых кортежей, собранных с момента последнего цикла очистки индекса.

Таблица 41. Фазы VACUUM

Фаза
Описание

initializing
VACUUM готовится начать сканирование кучи. Предполагается, что эта фаза будет очень короткой.

scanning heap
В данный момент VACUUM сканирует кучу. Команда будет по мере необходимости обрезать и дефрагментировать каждую страницу и, возможно, замораживать активность. Для отслеживания хода сканирования можно использовать столбец heap_blks_scanned.

vacuuming indexes
В данный момент VACUUM очищает индексы. Если в таблице есть какие-либо индексы, это будет происходить как минимум один раз за очистку после полного сканирования кучи. Это может происходить и несколько раз за очистку, если значения maintenance_work_mem (или, в случае автоочистки, autovacuum_work_mem, если этот параметр установлен) оказывается недостаточно для хранения всех найденных неиспользуемых кортежей.

vacuuming heap
В данный момент VACUUM очищает кучу. Очистка кучи отличается от сканирования кучи и выполняется после каждого эпизода очистки индексов. Если heap_blks_scanned меньше, чем heap_blks_total, то после завершения этой фазы система вернется к сканированию кучи; в противном случае она начнет приводить в порядок индексы.

cleaning up indexes
В данный момент VACUUM приводит в порядок индексы. Это происходит после того, как была полностью отсканирована куча и завершена вся очистка индексов и кучи.

truncating heap
В данный момент VACUUM опустошает кучу, чтобы вернуть пустые страницы в конце отношения в операционную систему. Это происходит после приведения индексов в порядок.

performing final cleanup
VACUUM выполняет окончательную ликвидацию последствий. На этом этапе VACUUM будет очищать карту свободного пространства, обновлять статистику в каталоге pg_class и передавать статистические данные сборщику статистики. Когда эта фаза закончится, VACUUM завершит работу.

Отчет о ходе выполнения CLUSTER

При каждом выполнении CLUSTER или VACUUM FULL представление pg_stat_progress_cluster будет содержать по одной строке для каждого обслуживающего процесса, который в данный момент выполняет любую из этих команд. В таблицах ниже описываются предоставляемые сведения и поясняется, как их интерпретировать.

Таблица 42. Представление pg_stat_progress_cluster

Столбец Тип

Описание

pid integer

PID обслуживающего процесса.

datid oid

Идентификатор базы данных, к которой подключен этот обслуживающий процесс.

datname name

Имя базы данных, к которой подключен этот обслуживающий процесс.

relid oid

Идентификатор кластеризуемой таблицы.

command text

Выполняемая команда. Либо CLUSTER, либо VACUUM FULL.

phase text

Текущая фаза обработки. См. Таблицу 43.

cluster_index_relid oid

Если таблица сканируется с использованием индекса, здесь отображается идентификатор данного индекса; в противном случае ноль.

heap_tuples_scanned bigint

Количество просканированных кортежей кучи. Этот счетчик увеличивается только в фазе seq scanning heap, index scanning heap или writing new heap.

heap_tuples_written bigint

Количество записанных кортежей кучи. Этот счетчик увеличивается только в фазе seq scanning heap, index scanning heap или writing new heap.

heap_blks_total bigint

Общее количество блоков кучи в таблице. Это число отображает состояние на начало фазы seq scanning heap.

heap_blks_scanned bigint

Количество просканированных блоков кучи. Этот счетчик увеличивается только в фазе seq scanning heap.

index_rebuild_count bigint

Количество перестроенных индексов. Этот счетчик увеличивается только в фазе rebuilding index.

Таблица 43. Фазы CLUSTER и VACUUM FULL

Фаза | Описание
initializing | Команда готовится начать сканирование кучи. Предполагается, что эта фаза будет очень короткой.
seq scanning heap | В данный момент команда сканирует таблицу с помощью последовательного сканирования.
index scanning heap | В данный момент CLUSTER сканирует таблицу по индексу.
sorting tuples | В данный момент CLUSTER сортирует кортежи.
writing new heap | В данный момент CLUSTER записывает новую кучу.
swapping relation files | В данный момент команда переставляет вновь построенные файлы на место.
rebuilding index | В данный момент команда перестраивает индекс.
performing final cleanup | Команда выполняет окончательную ликвидацию последствий. Когда эта фаза заканчивается, CLUSTER или VACUUM FULL завершает работу.

Отчет о ходе выполнения базового резервного копирования

Каждый раз, когда приложение вроде qhb_basebackup создает базовую резервную копию, представление pg_stat_progress_basebackup будет содержать по одной строке для каждого процесса-отправителя WAL, который в настоящий момент выполняет команду репликации BASE_BACKUP и поточно передает копируемые данные. В таблицах ниже описываются предоставляемые сведения и поясняется, как их интерпретировать.

Таблица 44. Представление pg_stat_progress_basebackup

Столбец Тип

Описание

pid integer

PID процесса-отправителя WAL.

phase text

Текущая фаза обработки. См. Таблицу 45.

backup_total bigint

Общий объем данных, которые будут передаваться в потоке. Это значение является оценочным и отображает состояние на начало фазы streaming database files (потоковая передача файлов базы данных). Обратите внимание, что это только приблизительная оценка, поскольку база данных может измениться во время фазы streaming database files, и позже в резервную копию может быть добавлен журнал WAL. Это значение всегда становится равным backup_streamed, как только объем переданных данных превысит ожидаемое значение. Если предварительная оценка в qhb_basebackup отключена (т. е. задан параметр --no-estimate-size), это поле содержит NULL.

backup_streamed bigint

Объем переданных в потоке данных. Этот счетчик увеличивается только в фазе streaming database files или transferring wal files.

tablespaces_total bigint

Общее количество табличных пространств, которые будут передаваться в потоке.

tablespaces_streamed bigint

Количество переданных в потоке табличных пространств. Этот счетчик увеличивается только в фазе streaming database files.

Таблица 45. Фазы базового резервного копирования

Фаза
Описание

initializing
Процесс-отправитель WAL готовится начать резервное копирование. Предполагается, что эта фаза будет очень короткой.

waiting for checkpoint to finish
В данный момент процесс-отправитель WAL выполняет pg_start_backup, чтобы подготовиться к созданию базовой резервной копии, и ожидает завершения контрольной точки начала резервного копирования.

estimating backup size
В данный момент процесс-отправитель WAL оценивает общий объем файлов базы данных, которые будут переданы в потоке в качестве базовой резервной копии.

streaming database files
В данный момент процесс-отправитель WAL передает в потоке файлы базы данных в качестве базовой резервной копии.

waiting for wal archiving to finish
В данный момент процесс-отправитель WAL выполняет pg_stop_backup, чтобы завершить резервное копирование, и ожидает успешного архивирования всех файлов WAL, требуемых для базовой резервной копии. Если в qhb_basebackup задан параметр --wal-method=none или --wal-method=stream, резервное копирование завершится по окончании этой фазы.

transferring wal files
В данный момент процесс-отправитель WAL переносит все журналы WAL, сгенерированные во время резервного копирования. Эта фаза наступает после фазы waiting for wal archiving to finish, если в qhb\basebackup задан параметр --wal-method=fetch. Резервное копирование завершится по окончании этой фазы.

Отчет о ходе выполнения COPY

При каждом выполнении COPY представление pg_stat_progress_copy будет содержать по одной строке для каждого обслуживающего процесса, который в данный момент выполняет команду COPY. В таблице ниже описываются предоставляемые сведения и поясняется, как их интерпретировать.

Таблица 46. Представление pg_stat_progress_copy

Столбец Тип

Описание

pid integer

PID обслуживающего процесса.

datid oid

Идентификатор базы данных, к которой подключен этот обслуживающий процесс.

datname name

Имя базы данных, к которой подключен этот обслуживающий процесс.

relid oid

Идентификатор таблицы, в которой выполняется команда COPY. Если копирование производится из запроса SELECT, это поле содержит 0.

command text

Выполняемая команда: COPY FROM или COPY TO.

type text

Тип ввода/вывода, посредством которого считываются или записываются данные: FILE, PROGRAM, PIPE (для COPY FROM STDIN и COPY TO STDOUT) или CALLBACK (используется, например, во время начальной синхронизации таблицы при логической репликации).

bytes_processed bigint

Количество байтов, уже обработанных командой COPY.

bytes_total bigint

Размер исходного файла для команды COPY FROM в байтах. Если это неприменимо, данное поле содержит 0.

tuples_processed bigint

Количество кортежей, уже обработанных командой COPY.

tuples_excluded bigint

Количество кортежей, которые не были обработаны из-за того, что были исключены предложением WHERE команды COPY.

Динамическая трассировка

QHB предоставляет средства для поддержки динамической трассировки сервера баз данных. Это позволяет вызывать внешнюю утилиту в определенных точках кода и тем самым отслеживать его выполнение.

Несколько зондов или точек трассировки уже встроено в исходный код. Предполагается, что эти зонды будут использоваться разработчиками и администраторами базы данных. По умолчанию зонды не скомпилированы в QHB; пользователю необходимо явно указать скрипту конфигурации, чтобы тот обеспечил наличие этих зондов.

В настоящее время поддерживается утилита DTrace, которая на момент составления этой документации доступна для Solaris, macOS, FreeBSD, NetBSD и Linux. Проект SystemTap для Linux представляет собой эквивалент DTrace и также может быть использован. Теоретически возможна поддержка и других утилит динамической трассировки, для чего нужно изменить определения для макроса.

Компиляция для динамической трассировки

По умолчанию зонды недоступны, поэтому необходимо явно указать скрипту конфигурации, чтобы тот обеспечил их наличие в QHB. Чтобы добавить поддержку DTrace, укажите --enable-dtrace в файле конфигурации.

Встроенные зонды

В исходном коде имеется несколько встроенных зондов, представленных в Таблице 47; в Таблице 48 перечислены типы данных, используемые в этих зондах. Конечно, для улучшения наблюдаемости QHB можно добавить дополнительные зонды.

Таблица 47. Встроенные зонды DTrace

Имя
Параметры
Описание

transaction-start
(LocalTransactionId)
Зонд, который срабатывает в начале новой транзакции. В arg0 передается идентификатор этой транзакции.

transaction-commit
(LocalTransactionId)
Зонд, который срабатывает при успешном завершении транзакции. В arg0 передается идентификатор этой транзакции.

transaction-abort
(LocalTransactionId)
Зонд, который срабатывает при завершении транзакции с ошибкой. В arg0 передается идентификатор этой транзакции.

query-start
(const char *)
Зонд, который срабатывает в начале обработки запроса. В arg0 передается строка запроса.

query-done
(const char *)
Зонд, который срабатывает по завершении обработки запроса. В arg0 передается строка запроса.

query-parse-start
(const char *)
Зонд, который срабатывает в начале анализа запроса. В arg0 передается строка запроса.

query-parse-done
(const char *)
Зонд, который срабатывает по завершении анализа запроса. В arg0 передается строка запроса.

query-rewrite-start
(const char *)
Зонд, который срабатывает в начале перезаписи запроса. В arg0 передается строка запроса.

query-rewrite-done
(const char *)
Зонд, который срабатывает по завершении перезаписи запроса. В arg0 передается строка запроса.

query-plan-start
()
Зонд, который срабатывает в начале планирования запроса.

query-plan-done
()
Зонд, который срабатывает по завершении планирования запроса.

query-execute-start
()
Зонд, который срабатывает в начале выполнения запроса.

query-execute-done
()
Зонд, который срабатывает по завершении выполнения запроса.

statement-status
(const char *)
Зонд, который срабатывает каждый раз, когда серверный процесс изменяет свой pg_stat_activity.status. В arg0 передается новая строка состояния.

checkpoint-start
(int)
Зонд, который срабатывает в начале контрольной точки. arg0 содержит битовые флаги, используемые для дифференциации разных типов контрольных точек, например, shutdown, immediate или force.

checkpoint-done
(int, int, int, int, int)
Зонд, который срабатывает по завершении контрольной точки. (Зонды, перечисленные далее, срабатывают последовательно во время обработки контрольной точки.) В arg0 передается количество записанных буферов. В arg1 — общее количество буферов. arg2, arg3 и arg4 содержат количество добавленных, удаленных и переработанных файлов WAL соответственно.

clog-checkpoint-start
(bool)
Зонд, который срабатывает в начале этапа записи контрольной точки в CLOG. arg0 равен true для обычной контрольной точки и false для контрольной точки типа shutdown.

clog-checkpoint-done
(bool)
Зонд, который срабатывает по завершении этапа записи контрольной точки в CLOG. arg0 имеет то же значение, что и для clog-checkpoint-start.

subtrans-checkpoint-start
(bool)
Зонд, который срабатывает в начале этапа записи контрольной точки в SUBTRANS. arg0 равен true для обычной контрольной точки и false для контрольной точки типа shutdown.

subtrans-checkpoint-done
(bool)
Зонд, который срабатывает по завершении этапа записи контрольной точки в SUBTRANS. arg0 имеет то же значение, что и для subtrans-checkpoint-start.

multixact-checkpoint-start
(bool)
Зонд, который срабатывает в начале этапа записи контрольной точки в MultiXact. arg0 равен true для обычной контрольной точки и false для контрольной точки типа shutdown.

multixact-checkpoint-done
(bool)
Зонд, который срабатывает по завершении этапа записи контрольной точки в MultiXact. arg0 имеет то же значение, что и для multixact-checkpoint-start.

buffer-checkpoint-start
(int)
Зонд, который срабатывает в начале записи буферов контрольной точки. arg0 содержит битовые флаги, используемые для дифференциации разных типов контрольных точек, например, shutdown, immediate или force.

buffer-sync-start
(int, int)
Зонд, который срабатывает в начале записи грязных буферов во время контрольной точки (после определения, какие буферы должны быть записаны). В arg0 передается общее количество буферов. В arg1 — количество буферов, которые в данный момент являются грязными и должны быть записаны.

buffer-sync-written
(int)
Зонд, который срабатывает после записи каждого буфера во время контрольной точки. В arg0 передается идентификатор этого буфера.

buffer-sync-done
(int, int, int)
Зонд, который срабатывает после записи всех грязных буферов. В arg0 передается общее количество буферов. В arg1 — количество буферов, фактически записанных процессом контрольной точки. В arg2 — количество буферов, которое должно было быть записано (arg1 в buffer-sync-start); любое различие говорит о том, что во время контрольной точки другие процессы сбрасывали буферы на диск.

buffer-checkpoint-sync-start
()
Зонд, который срабатывает после записи грязных буферов в ядро и до начала выдачи запросов fsync.

buffer-checkpoint-done
()
Зонд, который срабатывает по завершении синхронизации буферов с диском.

twophase-checkpoint-start
()
Зонд, который срабатывает в начале двухфазного этапа контрольной точки.

twophase-checkpoint-done
()
Зонд, который срабатывает по завершении двухфазного этапа контрольной точки.

buffer-read-start
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool)
Зонд, который срабатывает в начале чтения из буфера. arg0 и arg1 содержат номера ветви и блока этой страницы (но arg1 будет равен -1, если это запрос на расширение отношения). arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. arg6 равен true для запроса на расширение отношения и false для обычного чтения.

buffer-read-done
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool, bool)
Зонд, который срабатывает по завершении чтения из буфера. arg0 и arg1 содержат номера ветви и блока этой страницы (если это запрос на расширение отношения, то arg1 будет содержать номер только что добавленного блока). arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. arg6 равен true для запроса на расширение отношения и false для обычного чтения. arg7 равен true, если буфер был обнаружен в пуле, и false, если нет.

buffer-flush-start
(ForkNumber, BlockNumber, Oid, Oid, Oid)
Зонд, который срабатывает перед выдачей любого запроса на запись в разделяемый буфер. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение.

buffer-flush-done
(ForkNumber, BlockNumber, Oid, Oid, Oid)
Зонд, который срабатывает по завершении запроса на запись. (Обратите внимание, что это отражает только время передачи данных ядру; обычно они еще не записаны на диск.) Аргументы те же, что и для buffer-flush-start.

buffer-write-dirty-start
(ForkNumber, BlockNumber, Oid, Oid, Oid)
Зонд, который срабатывает, когда серверный процесс начинает записывать в грязный буфер. (Если это происходит часто, значит, значение shared_buffers слишком мало или нужно настроить параметры, управляющие фоновым процессом записи.) arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение.

buffer-write-dirty-done
(ForkNumber, BlockNumber, Oid, Oid, Oid)
Зонд, который срабатывает по завершении записи в грязный буфер. Аргументы те же, что и для buffer-write-dirty-start.

wal-buffer-write-dirty-start
()
Зонд, который срабатывает, когда серверный процесс начинает запись в грязный буфер WAL, потому что в буферах WAL не осталось места. (Если это происходит часто, значит, значение wal_buffers слишком мало.)

wal-buffer-write-dirty-done
()
Зонд, который срабатывает по завершении записи в грязный буфер WAL.

wal-insert
(unsigned char, unsigned char)
Зонд, который срабатывает при добавлении записи в WAL. В arg0 передается идентификатор менеджера ресурсов (rmid) для записи. arg1 содержит информационные флаги.

wal-switch
()
Зонд, который срабатывает при запросе на переключение сегмента WAL.

smgr-md-read-start
(ForkNumber, BlockNumber, Oid, Oid, Oid, int)
Зонд, который срабатывает в начале чтения блока из отношения. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера.

smgr-md-read-done
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int)
Зонд, который срабатывает по завершении чтения блока. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. В arg6 передается количество фактически прочитанных байтов, тогда как в arg7 — количество запрошенных байтов (если эти значения различаются, это говорит о наличии проблем).

smgr-md-write-start
(ForkNumber, BlockNumber, Oid, Oid, Oid, int)
Зонд, который срабатывает в начале записи блока в отношение. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера.

smgr-md-write-done
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int)
Зонд, который срабатывает по завершении записи блока. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. В arg6 передается количество фактически записанных байтов, тогда как в arg7 — количество запрошенных байтов (если эти значения различаются, это говорит о наличии проблем).

sort-start
(int, bool, int, int, bool, int)
Зонд, который срабатывает в начале операции сортировки. В arg0 передается сортировка кучи, индекса или элемента данных. arg1 равен true для анализа уникальных данных. В arg2 передается количество ключевых столбцов. arg3 — количество килобайт доступной рабочей памяти. arg4 равен true, если требуется произвольный доступ к результату сортировки. В arg5 значение 0 указывает на последовательный рабочий процесс, 1 — на параллельный, а 2 — на ведущий процесс в параллельной группе.

sort-done
(bool, long)
Зонд, который срабатывает по завершении сортировки. arg0 равен true для внешней сортировки и false для внутренней. В arg1 передается количество дисковый блоков, использованных для внешней сортировки, или килобайтов памяти, использованных для внутренней сортировки.

lwlock-acquire
(char *, LWLockMode)
Зонд, который срабатывает при получении блокировки LWLock. В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная.

lwlock-release
(char *)
Зонд, который срабатывает при освобождении блокировки LWLock (но обратите внимание, что никакие освобожденные ждущие процессы еще не пробуждены). В arg0 передается транш LWLock.

lwlock-wait-start
(char *, LWLockMode)
Зонд, который срабатывает, когда блокировка LWLock не доступна моментально и серверный процесс начинает ждать ее освобождения. В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная.

lwlock-wait-done
(char *, LWLockMode)
Зонд, который срабатывает при освобождении серверного процесса от ожидания LWLock (но фактически он блокировку еще не получил). В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная.

lwlock-condacquire
(char *, LWLockMode)
Зонд, который срабатывает при успешном получении LWLock, когда запросивший ее процесс указал режим без ожидания. В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная.

lwlock-condacquire-fail
(char *, LWLockMode)
Зонд, который срабатывает, если LWLock не была успешно получена, когда запросивший ее процесс указал режим без ожидания. В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная.

lock-wait-start
(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE)
Зонд, который срабатывает, когда запрос на тяжелую блокировку (блокировку lmgr) переходит в состояние ожидания, потому что эта блокировка недоступна. В аргументах с arg0 по arg3 передаются поля тегов, идентифицирующие блокируемый объект. arg4 указывает тип блокируемого объекта. arg5 указывает тип запрошенной блокировки.

lock-wait-done
(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE)
Зонд, который срабатывает, когда запрос на тяжелую блокировку (lmgr lock) завершает ожидание (т. е. получил эту блокировку). Аргументы те же, что и для lock-wait-start.

deadlock-found
()
Зонд, который срабатывает при обнаружении взаимной блокировки детектором взаимоблокировок.

Таблица 48. Определенные типы, используемые в параметрах зондов

Тип | Определение
LocalTransactionId | unsigned int
LWLockMode | int
LOCKMODE | int
BlockNumber | unsigned int
Oid | unsigned int
ForkNumber | int
bool | unsigned int

Использование зондов

В приведенном ниже примере показан скрипт DTrace для анализа числа транзакций в системе, как альтернатива созданию снимка pg_stat_database до и после теста производительности:

#!/usr/sbin/dtrace -qs

qhb$1:::transaction-start
{
      @start["Start"] = count();
      self->ts  = timestamp;
}

qhb$1:::transaction-abort
{
      @abort["Abort"] = count();
}

qhb$1:::transaction-commit
/self->ts/
{
      @commit["Commit"] = count();
      @time["Total time (ns)"] = sum(timestamp - self->ts);
      self->ts=0;
}

После выполнения D-скрипт в этом примере выдает результат вида:

# ./txn_count.d `pgrep -n qhb` or ./txn_count.d <PID>
^C

Start                                          71
Commit                                         70
Total time (ns)                        2312105013

Примечание
В SystemTap используется отличная от DTrace запись для скриптов трассировки, хотя лежащие в их основе точки трассировки совместимы. Однако стоит отметить, что на момент написания этого раздела скрипты SystemTap должны обращаться к зондам, обрамляя их имена двойными подчеркиваниями, а не дефисами. Ожидается, что в будущих релизах SystemTap это будет исправлено.

Следует помнить, что скрипты DTrace должны быть аккуратно написаны и отлажены, иначе собранная трассировочная информация может оказаться бессмысленной. В большинстве случаев причиной обнаруженных проблем является инструментарий, а не лежащая в основе система. При обсуждении информации, полученной с использованием динамической трассировки, обязательно прилагайте скрипт, с помощью которого они были получены, чтобы его тоже можно было проверить и обсудить.

Определение новых зондов

Новые зонды можно определить в любом месте кода, где пожелает разработчик, однако это потребует перекомпиляции. Ниже приведены шаги, которые необходимо предпринять для добавления новых зондов:

Определиться с именами проб и данными, которые должны стать доступными благодаря этим зондам

Добавить описания зондов в /usr/local/qhb/backend/utils/probes.d

Включить pg_trace.h, если его еще нет в модуле (или модулях), содержащих точки зондов, и добавить зондовый макрос TRACE_QHB в желаемые места в исходном коде

Перекомпилировать и убедиться, что новые зонды доступны

Пример:

Ниже приведен пример того, как можно добавить зонд для трассировки всех новых транзакций по их идентификатору.

Решаем, что зонд будет называться transaction-start и принимать параметр типа LocalTransactionId

Добавляем определение зонда в /usr/local/qhb/backend/utils/probes.d:

probe transaction__start(LocalTransactionId);

Обратите внимание на использование двойного подчеркивания в имени зонда. В скрипте DTrace, использующем этот зонд, двойное подчеркивание следует заменить на дефис, поэтому в документации для пользователей именем этого зонда будет transaction-start.

Во время компиляции transaction__start преобразуется в макрос с именем TRACE_QHB_TRANSACTION_START (заметьте, что здесь подчеркивание одинарное), который доступен в результате включения pg_trace.h. Добавляет вызов этого макроса в подходящее место в исходном коде. В данном случае это будет выглядеть примерно так:

TRACE_QHB_TRANSACTION_START(vxid.localTransactionId);

После перекомпиляции и запуска нового двоичного файла выполняем следующую команду DTrace для проверки, что только что добавленный зонд доступен. Вывод будет выглядеть примерно так:

# dtrace -ln transaction-start
   ID    PROVIDER      MODULE           FUNCTION NAME
18705    qhb49878       qhb     StartTransactionCommand transaction-start
18755    qhb49877       qhb     StartTransactionCommand transaction-start
18805    qhb49876       qhb     StartTransactionCommand transaction-start
18855    qhb49875       qhb     StartTransactionCommand transaction-start
18986    qhb49873       qhb     StartTransactionCommand transaction-start

При добавлении макросов трассировки в код на языке C/RUST следует позаботиться о нескольких вещах:

Следует позаботиться о том, чтобы типы данных, указанные в параметрах зонда, совпадали с типами данных переменных, используемых в макросе. Иначе вы получите ошибки при компиляции.

На большинстве платформ, если QHB собрана с указанием --enable-dtrace, аргументы макроса трассировки будут вычисляться каждый раз, когда макрос получает управление, даже если трассировка не производится. Обычно об этом не нужно беспокоиться, если вы просто возвращаете значения нескольких локальных переменных. Но избегайте помещения в эти аргументы затратных вызовов функций. Если это все-таки необходимо, попробуйте защитить макрос проверкой, чтобы понять, действительно ли включена трассировка:

if (TRACE_QHB_TRANSACTION_START_ENABLED())
    TRACE_QHB_TRANSACTION_START(some_function(...));

У каждого макроса трассировки есть соответствующий макрос ENABLED.

Метрики QHB

Общие сведения

В настоящее время все метрики в QHB собираются на уровне всего кластера баз данных и относятся ко всему набору процессов QHB независимо от того, являются ли они фоновыми или поддерживающими пользовательские подключения. Также часть метрик собирается на уровне баз данных и на уровне бэкендов. Данные метрик отсылаются на коллектор и агрегатор метрик metricsd, краткое описание которого можно найти в главе Сервер метрик. Далее метрики агрегируются и записываются в Graphite, в качестве интерфейса к которому выступает Grafana.

Типы метрик

Gauge — неотрицательное целое значение. Может быть установлено, увеличено или уменьшено на заданное число.

Counter — неотрицательное целое значение. Может быть только увеличено на заданное число.

Timer — неотрицательное целое значение, длительность в наносекундах. Значение может быть только записано; во время агрегации вычисляется ряд статистических характеристик:

sum — сумма значений

count — количество записанных значений

max — максимальное из записанных значений

min — минимальное из записанных значений

mean — среднее арифметическое

median — медиана

std — стандартное квадратичное отклонение

при необходимости список перцентилей

Группы метрик

sys

К группе sys относятся различные системные метрики.

Сбор системных метрик регулируется двумя глобальными константами:

qhb_os_monitoring — логическая константа, по умолчанию значение off (выключена). При значении on выполняется периодический сбор метрик.

qhb_os_stat_period — период сбора системной статистики в секундах, по умолчанию 30 секунд.

Таблица 49. Метрики загрузки и ЦП

Имя | Описание | Тип
sys.load_average.1min | load average за последнюю минуту | gauge
sys.load_average.5min | То же, но за последние 5 минут | gauge
sys.load_average.15min | То же, но за последние 15 минут | gauge
sys.cpu.1min | Процент загрузки процессоров за последнюю минуту | gauge
sys.cpu.5min | То же, но за последние 5 минут | gauge
sys.cpu.15min | То же, но за последние 15 минут | gauge

Примечание
Значения метрик load average содержат значения с точностью до сотых, однако при передаче данных в силу технических особенностей исходные значения умножаются на 100 и передаются как целые числа. Поэтому при выводе данных следует учитывать эту особенность и делить значения на 100.

load average — усредненное количество исполняемых и ожидающих потоков за заданный интервал времени (1, 5 и 15 минут). Обычно соответствующие значения выводятся с помощью команд uptime, top или cat /proc/loadavg. Подробную информацию об особенностях, связанными с этим показателем, можно найти в переводе статьи Load Average в Linux.

Если значение этого показателя за последнюю минуту больше, чем за последние 5 и 15 минут, нагрузка растет, если меньше — падает. Однако этот показатель важен не сам по себе, а относительно общего числа процессоров. Дополнительные и производные от load average метрики по загрузке процессоров sys.cpu.Nmin показывают приблизительный процент загрузки процессоров с учетом их количества и рассчитываются по следующей упрощенной формуле:

sys.cpu.<N>min = sys.load_average.<N>min / cpu_count * 100

где cpu_count — количество процессоров в системе, а N принимает значения 1, 5 или 15.

Количество процессоров рассчитывается как произведение количества физических сокетов, ядер на сокет и нитей на ядро. Команда lscpu выводит все необходимые данные. Например:

Thread(s) per core:              2
Core(s) per socket:              4
Socket(s):                       1

В данном случае cpu_count = 2 * 4 * 1 = 8.

Альтернативным и более простым методом может быть получение этого значения через команду nproc.

Таким образом, в данном случае загрузка в 100% будет достигнута, если величина load average будет стремиться к 8. Однако эти расчеты и значения будут иметь довольно приблизительный и даже условный характер, что показывает приведенная выше по ссылке статья.

Таблица 50. Метрики использования памяти

Имя | Описание | Тип
sys.mem.total | Общий размер установленной памяти RAM | gauge
sys.mem.used | Используемая память | gauge
sys.mem.free | Неиспользуемая память | gauge
sys.mem.available | Память, доступная для запускаемых приложений (не включая swap, но учитывая потенциально освобождаемую память, занимаемую страничным кэшем) | gauge
sys.swap.total | Общий размер файла подкачки | gauge
sys.swap.free | Неиспользуемая память файла подкачки | gauge

Значения метрик соответствуют полям из вывода утилиты free (значения отображаются в килобайтах, т. е. соответствуют выводу free -k).

Таблица 51. Соответствие значений метрик полям вывода утилиты free

Метрика | Поле утилиты free
sys.mem.total | Mem:total
sys.mem.used | Mem:used
sys.mem.free | Mem:free
sys.mem.available | Mem:available
sys.swap.total | Swap:total
sys.swap.free | Swap:free

Величину, соответствующую выводимому в утилите free значению Mem:buff/cache, можно рассчитать по формуле:

Mem:buff/cache = Mem:total - Mem:used - Mem:free

Таким образом, в Grafana можно, используя функцию diffSeries, рассчитывать и выводить это значение на основании других имеющихся данных.

Значение Mem:shared (данные виртуальной файловой системы tmpfs) через метрики не выводится.

Значение Swap:used можно рассчитать по формуле:

Swap:used = Swap:total - Swap:free

Это значение также можно выводить в Grafana в виде рассчитываемой величины через функцию diffSeries.

Более подробное описание этих показателей можно получить через справочную систему операционной системы для утилиты free (Для этого вызовите man free).

Таблица 52. Метрики использования дискового пространства

Имя | Описание | Тип
sys.disk_space.total | Объем дисковой системы, на которой находится каталог с данными (в байтах) | gauge
sys.disk_space.free | Свободное пространство в дисковой системе, на которой находится каталог с данными (в байтах) | gauge

Метрики относятся к дисковой системе, на которой расположен каталог с файлами базы данных. Этот каталог определяется параметром командной строки -D при запуске базы данных либо переменной среды $PGDATA. Параметр data_directory в файле конфигурации qhb.conf может переопределять расположение каталога с данными.

Таблица 53. Другие системные метрики

Имя | Описание | Тип
sys.processes | Общее количество запущенных в системе процессов | gauge
sys.uptime | Количество секунд, прошедших с начала запуска системы | gauge

db_stat

К группе db_stat относятся метрики чтения и записи блоков на уровне экземпляра QHB.

Таблица 54. Метрики чтения и записи блоков

Имя | Описание | Тип
qhb.db_stat.numbackends | Количество активных серверных процессов | gauge
qhb.db_stat.blocks_fetched | Количество блоков, полученных при чтении | counter
qhb.db_stat.blocks_hit | Количество блоков, найденных в кэше при чтении | counter
qhb.db_stat.blocks_read_time | Время чтения блоков, в миллисекундах | counter
qhb.db_stat.blocks_write_time | Время записи блоков, в миллисекундах | counter
qhb.db_stat.conflicts.tablespace | Количество запросов, отмененных из-за удаленных табличных пространств | counter
qhb.db_stat.conflicts.lock | Количество запросов, отмененных из-за тайм-аутов блокировок | counter
qhb.db_stat.checksum_failures | Количество несовпадений контрольной суммы страницы данных | counter
qhb.db_stat.conflicts.snapshot | Количество запросов, отмененных из-за устаревших снимков состояния | counter
qhb.db_stat.conflicts.bufferpin | Количество запросов, отмененных из-за закрепленных буферов | counter
qhb.db_stat.conflicts.startup_deadlock | Количество запросов, отмененных из-за взаимных блокировок | counter
qhb.db_stat.tuples.returned | Количество строк, полученных при последовательном сканировании | counter
qhb.db_stat.tuples.fetched | Количество строк, полученных при сканировании по индексу | counter
qhb.db_stat.tuples.inserted | Количество строк, добавленных в базу данных | counter
qhb.db_stat.tuples.updated | Количество строк, измененных в базе данных | counter
qhb.db_stat.tuples.deleted | Количество строк, удаленных из базы данных | counter

Также генерируются версии этих метрик по базам данных и отдельным бэкендам. В имена метрик включается идентификатор базы данных, например, qhb.db_stat.db_16384.numbackends, или (для некоторых метрик) номер процесса, например: qhb.db_stat.34567.blocks_fetched. Данная группа метрик может генерировать большие объемы данных, поэтому администратор должен решить, действительно ли необходимо оставлять эту группу метрик включенной.

На основании метрик qhb.db_stat.blocks_fetched и qhb.db_stat.blocks_hit рассчитывается коэффициент попадания в кэш:

k = blocks_hit / blocks_fetched * 100%

Хорошим уровнем обычно считается значение более 90%. Если значение коэффициента существенно ниже этой отметки, желательно рассмотреть возможность увеличения объема буферного кэша.

bgwr

К группе bgwr относятся метрики фонового процесса записи.

Таблица 55. Метрики фонового процесса записи

Имя | Описание | Тип
qhb.bgwr.checkpoints_timed | Количество запланированных контрольных точек, которые были выполнены | counter
qhb.bgwr.checkpoints_req | Количество запрошенных контрольных точек, выполненных вне очереди запланированных | counter
qhb.bgwr.checkpoint_write_time | Время, потраченное на этап обработки контрольной точки, где файлы записываются на диск, в миллисекундах | counter
qhb.bgwr.checkpoint_sync_time | Время, потраченное на этап обработки контрольной точки, где файлы синхронизируются с диском, в миллисекундах | counter
qhb.bgwr.buffers_checkpoint | Количество буферов, записанных во время контрольных точек | counter
qhb.bgwr.buffers_clean | Количество буферов, записанных фоновым процессом записи | counter
qhb.bgwr.maxwritten_clean | Сколько раз фоновый процесс записи останавливал сброс грязных страниц, поскольку записывал слишком много буферов | counter
qhb.bgwr.buffers_backend | Количество буферов, записанных непосредственно обслуживающим процессом | counter
qhb.bgwr.buffers_backend_fsync | Сколько раз обслуживающий процесс вызывал fsync сам (обычно их обрабатывает фоновый процесс записи, даже когда обслуживающий процесс выполняет запись самостоятельно) | counter
qhb.bgwr.buffers_alloc | Количество выделенных буферов | counter

Данные по перечисленным метрикам отображаются в разделе «Контрольные точки и операции с буферами» информационной панели QHB. Обычно при выполнении запланированных контрольных точек сначала происходит запись информации о начале контрольной точки, затем в течение некоторого времени идет сброс блоков на диск и по окончании контрольной точки фиксируется информация о продолжительности записи и синхронизации данных. В случае обработки запланированной контрольной точки запись блоков равномерно распределяется во времени согласно параметрам настройки, чтобы снизить влияние этого процесса на общий ввод/вывод. При контрольных точках, запрошенных через команду, сброс блоков происходит сразу, без искусственной задержки.

Также генерируются версии этих метрик по базам данных. В имена метрик включается идентификатор базы данных, например: qhb.bgwr.db_16384.buffers_backend.

mem_stat

К группе mem_stat относятся метрики для отслеживания размера памяти work area.

Память work area может быть приблизительно описана как память, используемая внутренними операциями сортировки и хеш-таблицами. Размер этой памяти контролируется глобальным параметром конфигурации work_mem: при превышении лимита вместо оперативной памяти начинают использоваться временные файлы на диске. Параметр work_mem ограничивает память, используемую единичной операцией.

В группу mem_stat входит три разновидности метрик с общим именем work_area:

qhb.mem_stat.work_area — показывает общий размер work area в байтах всего кластера

qhb.mem_stat.db_<номер>.work_area — показывает размер work area в байтах для одной базы данных

qhb.mem_stat..work_area — показывает размер work area в байтах для одного рабочего процесса

Метрики второй и третьей разновидностей создаются динамически.

Все метрики типа gauge, то есть их значения могут уменьшаться по мере освобождения ранее занятой памяти.

temp_stat

К группе temp_stat относятся метрики по временным файлам и таблицам QHB.

Таблица 56. Метрики по временным файлам и таблицам

Имя | Описание | Тип
qhb.temp_stat.temp_files | Количество временных файлов, созданных за период агрегации | counter
qhb.temp_stat.temp_bytes | Общий объем временных файлов, созданных за период агрегации, в байтах | counter
qhb.temp_stat.temp_tables | Количество временных таблиц, созданных за период агрегации | counter
qhb.temp_stat.temp_table_bytes | Общий объем временных таблиц, созданных за период агрегации, в байтах | counter

Данные собираются за период агрегации коллектора метрик. Обновление данных происходит по окончании соответствующей операции, например, при создании временной таблицы с указанием DROP ON COMMIT обновление статистики произойдет после выполнения команды COMMIT при удалении временной таблицы. В некоторых случаях объем, занимаемый временными файлами и таблицами, может быть весьма существенным.

Также генерируются версии этих метрик по базам данных. В имена метрик включается идентификатор базы данных, например: qhb.temp_stat.db_16384.temp_bytes.

wal

К группе wal относятся метрики процесса архивации журнала упреждающей записи.

Таблица 57. Метрики процесса архивации WAL

Имя | Описание | Тип
qhb.wal.archived | Количество успешно выполненных операций архивации файлов журнала упреждающей записи | counter
qhb.wal.failed | Количество попыток архивации, завершившихся сбоем | counter
qhb.wal.archive_time | Время, потраченное на копирование файлов журналов, в наносекундах | counter

Эти метрики работают в том случае, если настроена архивация журнала упреждающей записи. Для этого необходимо установить параметр archive_mode в значение on (включен) и определить команду архивации в параметре archive_command.

transaction

К группе transaction относятся метрики по транзакциям.

Таблица 58. Метрики по транзакциям

Имя | Описание | Тип
qhb.transaction.commit | Количество фиксаций транзакций | counter
qhb.transaction.rollback | Количество откатов транзакций | counter
qhb.transaction.deadlocks | Количество взаимоблокировок | counter

Данные метрик собираются непосредственно при выполнении команд завершения и отмены транзакций на уровне всего кластера баз данных, на уровне отдельных баз данных и на уровне бэкендов.

Коэффициент подтверждения транзакций рассчитывается как процентное отношение фиксаций транзакций к сумме фиксаций и откатов транзакций:

k = commit/(commit + rollback)*100%

Обычно значение стремится к 100%, поскольку чаще всего транзакции завершаются успешно. Существенная доля откатившихся транзакций может говорить о том, что в системе существуют проблемы.

Взаимоблокировки возникают, когда различные сеансы взаимно ожидают освобождения заблокированных данных. В этом случае после автоматического определения взаимоблокировки происходит откат одной из транзакций.

wait

К группе wait относятся метрики событий ожидания.

Данный набор метрик полностью соответствует набору стандартных событий ожидания. Метрики имеют префикс qhb.wait. Далее в наименовании идет класс события ожидания и через точку название события ожидания. В текущем релизе имена метрик ограничены в размере и имеют в наименовании максимум 31 символ. Все метрики по событиям ожидания имеют тип counter, однако значение отражает промежуток времени в микросекундах, который эти ожидания заняли в совокупности во всех сеансах за период сбора.

Примечание
Если в течение периода наблюдения работало множество пользовательских подключений, которые находились в состоянии ожидания, суммарное значение времени ожидания может многократно превысить этот период. Например, если в течение 10 секунд 1000 сеансов провели в ожиданиях по одной секунде, суммарное время ожидания составит 1000 секунд.

Метрики с типами событий ожидания Lock, LWLock, IO и IPC отображаются в разделе «События ожидания» информационной панели QHB. Значения метрик выводятся в микросекундах (автоматически переводясь в другие единицы при увеличении значений). На существующих графиках выводятся не все события ожиданий, а только по пять самых значимых по величине на каждом графике. Разные события ожиданий могут иметь сильно отличающиеся по величине продолжительности. Значительные колебания значений могут отражать возникающие проблемы.

Таблица 59. Наиболее значимые события ожидания

Имя события ожидания | Описание
Lock.extend | Ожидание расширения отношения. Становится заметным при активном росте таблиц. Необходимость выделения новых блоков приводит к некоторым задержкам, которые отражаются в этой метрике.
Lock.transactionid | Ожидание завершения транзакции. Событие ожидания возникает в том случае, если транзакция вынуждена ждать окончания обработки предыдущих транзакций, которые также получают подтверждение своего окончания.
Lock.tuple | Ожидание получения блокировки для кортежа. Возникает в случае одновременной работы с теми же данными нескольких транзакций.
LWLock.WALWriteLock | Ожидание записи буферов WAL на диск. Часто является лидером среди событий ожиданий этого типа, так как операции с диском являются наиболее медленными в этой группе событий ожидания.
LWLock.wal_insert | Ожидание вставки WAL в буфер памяти.
LWLock.buffer_content | Ожидание чтения или записи страницы данных в памяти. Возникает и становится существенным при интенсивном вводе/выводе.
LWLock.buffer_mapping | Ожидание связывания блока данных с буфером в буферном пуле.
LWLock.lock_manager | Ожидание добавления или проверки блокировок для обслуживающих процессов. Событие становится значимым при частых транзакциях.

bufmgr

К группе bufmgr относятся метрики буферного менеджера, касающиеся механизмов управления памятью. Сбор метрик проводится при операциях чтения данных.

Таблица 60. Метрики буферного менеджера

Имя | Описание | Тип | Агрегат
qhb.bufmgr.BufferAlloc | Сколько раз проводился поиск буфера | timer | count
qhb.bufmgr.BufferAlloc | Общее время, потраченное на поиск буфера | timer | sum
qhb.bufmgr.happy_path | Сколько раз буфер нашелся сразу | timer | count
qhb.bufmgr.happy_path | Общее время, потраченное на поиск, когда буфер нашелся сразу | timer | sum
qhb.bufmgr.cache_miss | Количество промахов кэша буферов | timer | count
qhb.bufmgr.cache_miss | Общее время, потраченное на обработку промахов кэша буферов | timer | sum
qhb.bufmgr.disk_read | Количество чтений страницы с диска (асинхронно) | timer | count
qhb.bufmgr.flush_dirty | Количество выгрузок страницы на диск (асинхронно) | timer | count
qhb.bufmgr.retry_counter | Количество повторных обработок промаха | counter |
qhb.bufmgr.strategy_pop_cnt | Сколько раз срабатывала специальная стратегия получения или вытеснения буфера | counter |
qhb.bufmgr.strategy_reject_cnt | Количество забракованных буферов, предложенных специальной стратегией | counter |
tarq_cache.allocate | Сколько раз проводился поиск в TARQ | timer | count
tarq_cache.allocate | Общее время, потраченное на поиск в TARQ | timer | sum
tarq_cache.allocate_new | Сколько раз выбирался исключаемый блок в TARQ | timer | count
tarq_cache.rollback | Количество откатов вытеснения в TARQ | timer | count
tarq_cache.rollback | Общее время, потраченное на откаты вытеснения в TARQ | timer | sum
tarq_cache.touch | Общее время, потраченное на учет популярных страниц в TARQ | timer | sum

queryid

К группе queryid относятся метрики, предоставляющие возможность отслеживать статистику планирования и выполнения сервером всех (почти) операторов SQL.

Эта группа отличается тем, что входящие в нее метрики создаются динамически при начале обработки движком нового оператора. В имена новых метрик добавляется уникальное имя или ID запроса. Например, метрика qhb.queryid.c92e145f160e7b9e.exec_calls отражает количество исполнений некоторого оператора SQL. Текст самого оператора можно получить из новой системной таблицы qhb_queryid запросом вида SELECT * FROM qhb_queryid WHERE qid = 'c92e145f160e7b9e'.

Подробнее настройка метрик группы queryid описана в разделе Настройка метрик queryid.

Таблица 62. Метрики queryid

Имя | Описание | Тип
qhb.queryid.id.plan_calls | Число планирований оператора | counter
qhb.queryid.id.total_plan_time | Общее время, затраченное на планирование оператора, в наносекундах | counter
qhb.queryid.id.min_plan_time | Минимальное время, затраченное на планирование оператора, в наносекундах | gauge
qhb.queryid.id.max_plan_time | Максимальное время, затраченное на планирование оператора, в наносекундах | gauge
qhb.queryid.id.mean_plan_time | Среднее время, затраченное на планирование оператора, в наносекундах | gauge
qhb.queryid.id.stddev_plan_time | Стандартное отклонение времени, затраченного на планирование оператора, в наносекундах | gauge
qhb.queryid.id.exec_calls | Число выполнений оператора | counter
qhb.queryid.id.total_exec_time | Общее время, затраченное на выполнение оператора, в наносекундах | counter
qhb.queryid.id.min_exec_time | Минимальное время, затраченное на выполнение оператора, в наносекундах | gauge
qhb.queryid.id.max_exec_time | Максимальное время, затраченное на выполнение оператора, в наносекундах | gauge
qhb.queryid.id.mean_exec_time | Среднее время, затраченное на выполнение оператора, в наносекундах | gauge
qhb.queryid.id.stddev_exec_time | Стандартное отклонение времени, затраченного на выполнение оператора, в наносекундах | gauge
qhb.queryid.id.shared_blks_hit | Общее количество попаданий в разделяемый кэш блоков для этого оператора | counter
qhb.queryid.id.shared_blks_read | Общее количество прочтений разделяемых блоков для этого оператора | counter
qhb.queryid.id.shared_blks_dirtied | Общее количество разделяемых блоков, «загрязненных» этим оператором | counter
qhb.queryid.id.shared_blks_written | Общее количество разделяемых блоков, записанных этим оператором | counter
qhb.queryid.id.local_blks_hit | Общее количество попаданий в локальный кэш блоков для этого оператора | counter
qhb.queryid.id.local_blks_read | Общее количество прочтений локальных блоков для этого оператора | counter
qhb.queryid.id.local_blks_dirtied | Общее количество локальных блоков, «загрязненных» этим оператором | counter
qhb.queryid.id.local_blks_written | Общее количество локальных блоков, записанных этим оператором | counter
qhb.queryid.id.temp_blks_read | Общее количество прочтений временных блоков для этого оператора | counter
qhb.queryid.id.temp_blks_written | Общее количество временных блоков, записанных этим оператором | counter
qhb.queryid.id.exec_rows | Общее количество строк, полученных или затронутых этим оператором | counter
qhb.queryid.id.blk_read_time | Общее время, затраченное оператором на чтение блоков, в миллисекундах (если включен track_io_timing; иначе 0) | counter
qhb.queryid.id.blk_write_time | Общее время, затраченное оператором на запись блоков, в миллисекундах (если включен track_io_timing; иначе 0) | counter
qhb.queryid.id.wal_records | Общее количество записей WAL, сгенерированных этим оператором | counter
qhb.queryid.id.wal_fpi | Общее количество образов полных страниц WAL, сгенерированных этим оператором | counter
qhb.queryid.id.wal_bytes | Общий объем WAL, сгенерированный этим оператором, в байтах | counter

Метрики пула соединений QCP

Ниже представлены метрики пула соединений, характеризующие работу пула. Сбор метрик проводится при операциях выполнения запроса.

Таблица 63. Метрики пула соединений QCP

Имя | Описание | Тип
qcp.queue | Количество запросов в очереди на данный момент | gauge
qcp.obtain_backend | Время ожидания назначения обслуживающего процесса для исполнения запроса клиента | timer
qcp.obtain_backend_failed | Превышено максимальное время ожидания назначения обслуживающего процесса для исполнения запроса клиента | timer
<адрес субд>.in_use | Количество используемых подключений (обслуживающих процессов) к СУБД | gauge

Включение и выключение записи значений для групп метрик

В QHB имеется механизм, который позволяет включать и выключать отправку групп метрик. В каталоге данных расположен файл конфигурации metrics_config.toml, в котором каждая строка соответствует группе метрик:

[is_enabled]
default = true
sys = true
db_stat = true
bgwr = true
wal = true
transaction = true
wait = true
bufmgr = true
mem_stat = false
temp_stat = true
queryid = false

Переменные принимают значения true (отправка метрик группы разрешена) или false (отправка запрещена).

Таблица 64. Настройка записи значений для групп метрик

Группа метрик | Описание | Значение по умолчанию
default | Группа по умолчанию (различные метрики, не выделенные в отдельные группы) | true
sys | Метрики операционной системы | true
db_stat | Метрики чтения и записи блоков | true
bgwr | Метрики фонового процесса записи | true
mem_stat | Метрики размера памяти «work area» | true
temp_stat | Метрики по данным временных файлов и таблиц | true
wal | Метрики архивации файлов WAL | true
transaction | Метрики транзакций | true
wait | Метрики событий ожидания | true
bufmgr | Метрики механизмов управления памятью | true
queryid | Метрики планирования и выполнения сервером операторов SQL | false

Примечание
Для сбора метрик операционной системы необходимо также установить в параметре qhb_os_monitoring значение on (включен).

Для просмотра списка групп метрик и их текущего состояния можно воспользоваться SQL-функцией metrics_config_show. Пример вывода этой функции:

SELECT * FROM metrics_config_show();
 group_name  | enabled
-------------+---------
 bgwr        | t
 bufmgr      | t
 db_stat     | t
 default     | t
 mem_stat    | f
 queryid     | f
 sys         | t
 temp_stat   | t
 transaction | t
 wait        | t
 wal         | t
(11 rows)

Помимо задания значений настроек через файл параметров, имеется возможность менять эти значения через функции. Изменения сразу же отображаются в файле конфигурации.

Таблица 65. Функции включения и выключения отправки метрик

Функция

Описание

metrics_config_enable_group ( group_name: cstring, respect_transient_settings: boolean ) → void

Включает отправку значений для метрик заданной группы.

metrics_config_disable_group ( group_name: cstring, respect_transient_settings: boolean ) → void

Выключает отправку значений для метрик заданной группы.

metrics_config_transient_set ( backend_pid: int4, group_name: cstring, option_value: boolean ) → void

Устанавливает временную возможность отправки значений группы метрик для заданного обслуживающего процесса.

С помощью второго параметра respect_transient_settings в функциях metrics_config_enable_group и metrics_config_disable_group можно указать, отдавать ли приоритет параметрам отправки групп метрик, установленным на уровне отдельных обслуживающих процессов.

С помощью функции metrics_config_transient_set можно включить или выключить отправку значений заданной группы метрик для конкретного обслуживающего процесса. Настройка будет действовать в течение всего времени жизни обслуживающего процесса либо до вызова функции metrics_config_enable_group или metrics_config_disable_group со вторым параметром respect_transient_settings = false, который в данном случае распространит действие команды также на те обслуживающие процессы, в которых устанавливалось собственное значение параметров.

При запуске обслуживающего процесса с идентификатором, использовавшимся ранее, для данного идентификатора обнуляются все временные настройки.

Примечание
Все сказанное о выключении записи групп метрик не касается метрик, которые передаются через SQL-функции. При использовании этих функций метрики записываются всегда.

Информационные панели метрик QHB для Grafana

Информационные панели (dashboards) QHB для Grafana расположены в репозитории по следующей ссылке.

QHB поставляется совместно с сервером метрик, который записывает данные метрик в Graphite. Интерфейсом к этим метрикам служит Grafana. Текущий набор информационных панелей для Grafana поставляется в качестве самодокументируемых образцов, на основе которых пользователи при необходимости могут самостоятельно создать панели, более соответствующие их потребностям. Разумеется, поставляемые панели можно использовать и в исходном виде.

Импорт информационных панелей

Экспорт JSON-описания информационных панелей выполнен в Grafana 6.7.2.

Перед импортом JSON-описания необходимо решить, будут ли названия метрик содержать в качестве префикса имя хоста. Именно таким образом устроены наименования метрик внутри панелей, и этот вариант рекомендуется оставить. В начале имен метрик добавлена переменная $server_name, по умолчанию для нее выбрано значение your_host_name. Перед импортом можно заменить в JSON-файлах это значение на наименование одного из хостов. В дальнейшем в этой переменной через интерфейс Grafana можно будет добавить через запятую все имена хостов, с которых будут собираться метрики. Это позволит быстро переключаться при просмотре метрик с одного хоста на другой. Если такая схема использоваться не будет (в случае, если будут просматриваться метрики с единственного хоста), можно удалить в файлах JSON во всех именах метрик префикс $server_name до проведения импорта описания JSON. Однако это более трудоемкий вариант и его выбирать не рекомендуется.

Для импорта описаний информационных панелей необходимо выполнить следующие шаги:

В меню Dashboards вашего сайта Grafana выбрать пункт Manage.

В открывшемся списке папок и панелей выбрать существующую или создать новую папку.

Находясь в выбранной папке, выбрать в правой верхней части страницы пункт Import.

На открывшейся странице можно либо нажать справа вверху кнопку Upload json file и загрузить файл, либо вставить содержимое JSON-файла в поле под заголовком Or paste JSON и нажать кнопку Load.

После этого нужно заполнить необходимые параметры и выполнить загрузку JSON-описания.

При необходимости, отредактируйте наименование источника данных в модели JSON, заменив в описаниях информационных панелей значение поля datasource с Graphite на корректное значение. Более простым вариантом может быть смена имени источника данных на Graphite. Такой вариант будет предпочтительным, если источник данных до этого нигде больше не использовался.

Перечисленные выше пункты нужно повторить для каждой импортируемой панели.

Информационная панель «Операционная система»

Информационная панель представляет основные системные показатели:

Время работы экземпляра QHB;

Средняя загрузка;

Использование ОЗУ;

Использование памяти;

Использование дисковой системы, на которой расположен каталог баз данных.

Информационная панель «QHB»

Информационная панель содержит несколько разделов:

Транзакции;

Чтение и запись блоков;

События ожидания;

Контрольные точки и операции с буферами;

Архивация WAL.

В каждом разделе представлены наборы тематических панелей, отражающие основные показатели.

Примечание
По умолчанию теги различных событий не заданы, поэтому необходимо самостоятельно настроить вывод дополнительных аннотаций:

добавить необходимые комментарии к данным метрик функцией qhb_annotation (см. раздел Аннотации);

настроить вывод доступных аннотаций в информационной панели Grafana.

QHB-мониторинг для Zabbix

Пример использования сервера Zabbix 6.2 для мониторинга узла сети с QHB и агентом Zabbix.

Необходимые файлы:

template_db_qhb.yaml — файл шаблона «QHB by Zabbix agent», который нужно импортировать на сервер Zabbix 6.2;

template_db_qhb.conf — файл с пользовательскими параметрами агента Zabbix для опроса QHB;

каталог qhb/, содержащий SQL-файлы, к которым обращаются пользовательские параметры.

Архив для Zabbix 6.2 или выше расположен в репозитории по следующей ссылке.

Установка

Примечание
Более подробную информации см. в документации Zabbix по работе с шаблонами агента.

Установите Zabbix-агент на узле сети с QHB

Скопируйте каталог qhb/ в домашний каталог Zabbix-агента /var/lib/ zabbix. Если в /var/lib/ отсутствует каталог zabbix/, то его необходимо создать. Каталог qhb/ содержит SQL-файлы, необходимые для получения метрик из QHB:

# mkdir -p /var/lib/zabbix/qhb
# cd /var/lib/zabbix/qhb
# wget <сервер>/zabbix/qhb/qhb.tar
# tar -xvf qhb.tar
# chmod -R 707 /var/lib/zabbix/qhb
# rm -rf qhb.tar

Скопируйте файл template_db_qhb.conf в конфигурационную директорию Zabbix-агента /etc/zabbix/zabbix_agentd.d/:

# wget <сервер>/zabbix/template_db_qhb.conf

Создайте пользователя zbx_monitor с правами «только чтение» и доступом к кластеру QHB:

CREATE USER zbx_monitor WITH PASSWORD '<PASSWORD>' INHERIT;
GRANT pg_monitor TO zbx_monitor;

Отредактируйте файл qhb_hba.conf, чтобы разрешить подключение к Zabbix. Для этого откройте его в текстовом редакторе nano и вставьте в него следующие строки:

host all zbx_monitor 127.0.0.1/32 trust

Перезагрузите QHB и Zabbix-агент:

# systemctl restart qhb
# systemctl restart zabbix_agentd

Импортируйте файл template_db_qhb.yaml шаблона «QHB by Zabbix agent» на сервере Zabbix. Подробнее об импорте шаблонов см. в документации Zabbix по работе с импортом шаблонов.

Установите параметры макроса {$PG.HOST}, {$PG.PORT}, {$PG.USER}, {$PG.PASSWORD}, {$PG.DB} для узла сети с QHB.

Присоедините шаблон «QHB by Zabbix agent» к узлу сети с QHB.

Собираемые параметры

Таблица 66. Собираемые параметры

Группа | Название | Описание
QHB | Bgwriter: Buffers allocated per second | Количество буферов, выделенных за секунду
QHB | Bgwriter: Buffers written directly by a backend per second | Количество буферов, записанных за секунду непосредственно обслуживающим процессом
QHB | Bgwriter: Buffers backend fsync per second | Сколько раз за секунду обслуживающему процессу пришлось самому выполнять вызов fsync (обычно этим занимается фоновый процесс записи, даже когда обслуживающий процесс проводит свои операции записи)
QHB | Bgwriter: Buffers written during checkpoints per second | Количество буферов, записанных за секунду во время контрольных точек
QHB | Bgwriter: Buffers written by the background writer per second | Количество буферов, записанных за секунду фоновым процессом записи
QHB | Bgwriter: Requested checkpoints per second | Количество запрошенных контрольных точек, которые были выполнены за секунду
QHB | Bgwriter: Scheduled checkpoints per second | Количество запланированных контрольных точек, которые были выполнены за секунду
QHB | Bgwriter: Checkpoint sync time | Общее количество времени, затраченное на синхронизацию файлов с диском при обработке контрольных точек
QHB | Bgwriter: Checkpoint write time | Общее количество времени, затраченное на запись файлов на диск при обработке контрольных точек, в миллисекундах
QHB | Bgwriter: Max written per second | Сколько раз за секунду фоновый процесс записи останавливал очищающее сканирование из-за того, что записал слишком много буферов
QHB | Status: Cache hit ratio % | Доля попаданий в кэш
QHB | Status: Config hash | Хеш конфигурации QHB
QHB | Connections sum: Active | Общее количество соединений, выполняющих запросы
QHB | Connections sum: Idle | Общее количество соединений, ожидающих от клиента новой команды
QHB | Connections sum: Idle in transaction | Общее количество соединений в состоянии транзакции, но не выполняющих запрос
QHB | Connections sum: Prepared | Общее количество подготовленных транзакций
QHB | Connections sum: Total | Общее количество соединений
QHB | Connections sum: Total % | Общее количество соединений в процентах
QHB | Connections sum: Waiting | Общее количество ожидающих транзакций
QHB | Status: Ping time | Время ответа
QHB | Status: Ping | Проверка связи
QHB | Replication: standby count | Количество резервных серверов
QHB | Replication: lag in seconds | Задержка репликации с основного сервера в секундах
QHB | Replication: recovery role | Роль репликации: 1 — восстановление еще продолжается (режим резервного сервера), 0 — режим основного сервера
QHB | Replication: status | Статус репликации: 0 — потоковая передача снижается, 1 — потоковая передача возрастает, 2 — режим основного сервера
QHB | Transactions: Max active transaction time | Текущая максимальная продолжительность активной транзакции
QHB | Transactions: Max idle transaction time | Текущая максимальная продолжительность простаивающей транзакции
QHB | Transactions: Max prepared transaction time | Текущая максимальная продолжительность подготовленной транзакции
QHB | Transactions: Max waiting transaction time | Текущая максимальная продолжительность ожидающей транзакции
QHB | Status: Uptime | Полное время работы системы
QHB | Status: Version | Версия QHB
QHB | WAL: Segments count | Количество сегментов WAL
QHB | WAL: Bytes written | Объем записей WAL в байтах
QHB | DB {#DBNAME}: Database size | Размер этой базы данных
QHB | DB {#DBNAME}: Blocks hit per second | Сколько раз дисковые блоки были обнаружены уже в буферном кэше, благодаря чему в чтении не было необходимости
QHB | DB {#DBNAME}: Disk blocks read per second | Общее количество дисковых блоков, прочитанных в этой базе данных
QHB | DB {#DBNAME}: Detected conflicts per second | Общее количество запросов, отмененных из-за конфликтов с восстановлением в этой базе данных
QHB | DB {#DBNAME}: Detected deadlocks per second | Общее количество выявленных взаимоблокировок в этой базе данных
QHB | DB {#DBNAME}: Temp_bytes written per second | Общий объем данных, записанных во временные файлы запросами в этой базе данных
QHB | DB {#DBNAME}: Temp_files created per second | Общее количество временных файлов, созданных запросами в этой базе данных
QHB | DB {#DBNAME}: Tuples deleted per second | Общее количество строк, удаленных запросами в этой базе данных
QHB | DB {#DBNAME}: Tuples fetched per second | Общее количество строк, выбранных запросами в этой базе данных
QHB | DB {#DBNAME}: Tuples inserted per second | Общее количество строк, добавленных запросами в этой базе данных
QHB | DB {#DBNAME}: Tuples returned per second | Общее количество строк, возвращенных запросами в этой базе данных
QHB | DB {#DBNAME}: Tuples updated per second | Общее количество строк, измененных запросами в этой базе данных
QHB | DB {#DBNAME}: Commits per second | Количество зафиксированных транзакций в этой базе данных
QHB | DB {#DBNAME}: Rollbacks per second | Общее количество откатившихся транзакций в этой базе данных
QHB | DB {#DBNAME}: Frozen XID before autovacuum % | Доля замороженных идентификаторов транзакций перед автоочисткой, в процентах
QHB | DB {#DBNAME}: Frozen XID before stop % | Доля замороженных идентификаторов транзакций перед остановкой, в процентах
QHB | DB {#DBNAME}: Locks total | Общее количество блокировок в этой базе данных
QHB | DB {#DBNAME}: Queries slow maintenance count | Счетчик медленных обслуживающих запросов
QHB | DB {#DBNAME}: Queries max maintenance time | Максимальная продолжительность обслуживающего запроса
QHB | DB {#DBNAME}: Queries sum maintenance time | Суммарная продолжительность обслуживающих запросов
QHB | DB {#DBNAME}: Queries slow query count | Счетчик медленных запросов
QHB | DB {#DBNAME}: Queries max query time | Максимальная продолжительность запроса
QHB | DB {#DBNAME}: Queries sum query time | Суммарная продолжительность запросов
QHB | DB {#DBNAME}: Queries slow transaction count | Счетчик медленных транзакционных запросов
QHB | DB {#DBNAME}: Queries max transaction time | Максимальная продолжительность транзакционного запроса
QHB | DB {#DBNAME}: Queries sum transaction time | Суммарная продолжительность транзакционных запросов
QHB | DB {#DBNAME}: Index scans per second | Количество сканирований по индексу в этой базе данных
QHB | DB {#DBNAME}: Sequential scans per second | Количество последовательных сканирований в этой базе данных
Исходные элементы Zabbix | QHB: Get bgwriter | Статистика по активности фонового процесса записи
Исходные элементы Zabbix | QHB: Get connections sum | Сбор всех метрик из pg_stat_activity
Исходные элементы Zabbix | QHB: Get dbstat | Сбор всех метрик из pg_stat_database для каждой базы данных
Исходные элементы Zabbix | QHB: Get locks | Сбор всех метрик из pg_locks для каждой базы данных
Исходные элементы Zabbix | QHB: Get queries | Сбор всех метрик по времени выполнения запросов
Исходные элементы Zabbix | QHB: Get transactions | Сбор метрик по времени выполнения транзакций
Исходные элементы Zabbix | QHB: Get WAL | Основной элемент для сбора метрик WAL
Исходные элементы Zabbix | DB {#DBNAME}: Get frozen XID | Количество замороженных идентификаторов транзакций
Исходные элементы Zabbix | DB {#DBNAME}: Get scans | Количество сканирований, проведенных для таблицы/индекса в этой базе данных

Триггеры

Таблица 67. Триггеры

Название | Описание
QHB: Required checkpoints occurs too frequently | Запрашиваемые контрольные точки происходят слишком часто
QHB: Cache hit ratio too low | Слишком низкая доля попаданий в кэш
QHB: Configuration has changed | Изменилась конфигурация
QHB: Total number of connections is too high | Общее количество соединений слишком велико
QHB: Response too long | Слишком большое время отклика
QHB: Service is down | QHB не функционирует
QHB: Streaming lag with {#MASTER} is too high | Задержка репликации с основного сервера слишком велика
QHB: Replication is down | Репликация не функционирует
QHB: Service has been restarted | Время функционирования QHB меньше 10 минут
QHB: Version has changed | Версия QHB изменилась
DB {#DBNAME}: Too many recovery conflicts | Слишком много конфликтов между основным и резервным серверами при восстановлении
DB {#DBNAME}: Deadlock occurred | Произошла взаимоблокировка
DB {#DBNAME}: VACUUM FREEZE is required to prevent wraparound | Для предотвращения зацикливания идентификаторов транзакций требуется выполнить команду VACUUM FREEZE
DB {#DBNAME}: Number of locks is too high | Количество блокировок слишком велико
DB {#DBNAME}: Too many slow queries | Слишком много медленных запросов
QHB: Failed to get items | Zabbix не получал данные для элементов в течение последних 30 минут

Настройка сбора метрик

Для того чтобы информационной панели отображали данные метрик, необходимо выполнить некоторые настройки.

Настройка сервера метрик

Настройка сервера метрик описана в главе Сервер метрик.

Рекомендуется в параметре prefix конфигурационного файла /etc/metricsd/config.yaml сервера метрик прописать имя хоста, на котором он работает. Если сделать это для каждого сервера, все метрики будут организованы иерархически, и первый уровень иерархии будет уровнем серверов. В именах метрик в предлагаемых панелях для этих целей присутствует переменная $server_name. Подразумевается, что на хосте работает только один кластер баз данных.

Настройка параметров базы данных

Для настройки отправки метрик до релиза QHB 1.3.0 необходимо в qhb.conf установить параметр metrics_collector_id в значение, с которым запускается коллектор метрик, например 1001. Начиная с релиза QHB 1.3.0 вместо metrics_collector_id используется параметр metrics_collector_path, который по умолчанию имеет значение @metrics-collector (представляет собой путь к сокету домена Unix); сервер метрик по умолчанию запускается именно на этом адресе.

Для настройки отправки аннотаций необходимо в qhb.conf прописать следующие параметры:

grafana.address — адрес Grafana, например http://localhost:3000

grafana.token — необходимо указать токен, полученный в Grafana по адресу http://localhost:3000/org/apikeys

Пример настроек в qhb.conf для отправки метрик и аннотаций:

# До релиза QHB 1.3.0
# metrics_collector_id = 1001

# С релиза QHB 1.3.0:
metrics_collector_path = '@metrics-collector'

grafana.address = 'http://localhost:3000'
grafana.token = 'eyJrIjoiNGxTaloxMUNTQkFUMTN0blZqUTN6REN6OWI5YjM1MzMiLCJuIjoidGVzdCIsImlkIjoxfQ=='

Примечание
При необходимости записи данных метрик в CSV-файлы в качестве значения параметра metrics_collector_path нужно указать путь к файлу сокета домена Unix, например /tmp/metrics-collector.sock. Это же значение нужно указать в параметре bind_addr раздела collection в настройках сервера метрик (/etc/metricsd/config.yaml).

Для сбора системных метрик (информационная панель «Операционная система») необходимо установить параметр qhb_os_monitoring в значение on (включен). Можно также задать период сбора системной статистики qhb_os_stat_period (значение по умолчанию — 30 секунд). Не рекомендуется задавать для этого параметра слишком низкое значение, поскольку сбор системной статистики требует некоторых затрат.

В файле параметров можно прописать:

qhb_os_monitoring = on
qhb_os_stat_period = 60 # если период по умолчанию в 30 секунд не устраивает

Либо выполнить команды:

ALTER SYSTEM SET qhb_os_monitoring = ON;
ALTER SYSTEM SET qhb_os_stat_period = 60;
SELECT pg_reload_conf();

Примеры использования метрик в функциях SQL

Помимо встроенных метрик пользователи могут использовать свои метрики через следующие функции SQL.

Тип метрик Timer

Используется при фиксации промежутка времени, единицы измерения — наносекунды.

SELECT qhb_timer_report('qhb.timer.nano',10000000000 /* 10 секунд в наносекундах */);

Тип метрик Counter

Используется, когда нужно зафиксировать количество произошедших за промежуток времени событий.

SELECT qhb_counter_increase_by('qhb.example.counter',10);

Тип метрик Gauge

Используется, когда нужно установить некий статичный показатель в определенное значение или изменить его.

SELECT qhb_gauge_update('qhb.gauge_example.value', 10); /* Установка значения */
SELECT qhb_gauge_add('qhb.gauge_example.value',1); /* Увеличение значения */
SELECT qhb_gauge_sub('qhb.gauge_example.value',1); /* Уменьшение значения */

Аннотации

Используются, если нужно добавить комментарий к данным метрик. Первый параметр функции — текст комментария, последующие параметры — теги.

SELECT qhb_annotation('Начало выполнения теста', 'test','billing'); /* Текст аннотации и два тега */

Мониторинг использования диска

В этой главе рассказывается, как отслеживать использование диска в СУБД QHB.

Определение использования диска

У каждой таблицы есть основной дисковый файл кучи, где хранится большая часть данных. Если в таблице есть какие-либо столбцы с потенциально большими значениями, также может быть связанный с этой таблицей TOAST-файл, который используется для хранения слишком больших значений, не помещающихся в основной таблице (см. раздел TOAST). В таблице TOAST, если таковая имеется, будет один действительный индекс. Также там могут быть индексы, связанные с базовой таблицей. Каждая таблица и индекс хранятся в отдельном дисковом файле — возможно, в нескольких файлах, если размер файла превышает один гигабайт. Соглашения об именах этих файлов описываются в разделе Структура файлов базы данных.

Отслеживать дисковое пространство можно тремя способами: с помощью функций SQL, перечисленных в таблице Функции для получения размеров объектов базы данных, с помощью модуля oid2name или проверяя системные каталоги вручную. Функции SQL являются самыми простыми в применении, так что, в целом, рекомендуется использовать их. В оставшейся части этого раздела рассказывается, как отслеживать дисковое пространство путем проверки системных каталогов.

Используя psql в недавно очищенной или проанализированной базе данных, можно создавать запросы, чтобы увидеть, насколько использует диск любая таблица:

SELECT pg_relation_filepath(oid), relpages FROM pg_class WHERE relname = 'customer';

 pg_relation_filepath | relpages
----------------------+----------
 base/16384/16806     |       60
(1 row)

Каждая страница обычно равна 8 килобайтам. (Помните, что relpages обновляется только с помощью VACUUM, ANALYZE и нескольких команд DDL, например CREATE INDEX). Путь к файлу представляет интерес, если вы хотите проверить непосредственно дисковый файл таблицы.

Чтобы вывести пространство, используемое таблицами TOAST, используйте запрос вроде следующего:

SELECT relname, relpages
FROM pg_class,
     (SELECT reltoastrelid
      FROM pg_class
      WHERE relname = 'customer') AS ss
WHERE oid = ss.reltoastrelid OR
      oid = (SELECT indexrelid
             FROM pg_index
             WHERE indrelid = ss.reltoastrelid)
ORDER BY relname;

       relname        | relpages
----------------------+----------
 pg_toast_16806       |        0
 pg_toast_16806_index |        1

Также можно легко отобразить размеры индекса:

SELECT c2.relname, c2.relpages
FROM pg_class c, pg_class c2, pg_index i
WHERE c.relname = 'customer' AND
      c.oid = i.indrelid AND
      c2.oid = i.indexrelid
ORDER BY c2.relname;

      relname      | relpages
-------------------+----------
 customer_id_index |       26

С помощью этой информации легко найти самые большие таблицы и индексы:

SELECT relname, relpages
FROM pg_class
ORDER BY relpages DESC;

       relname        | relpages
----------------------+----------
 bigtable             |     3290
 customer             |     3144

Ошибка переполнения диска

Самая важная задача мониторинга дисков для администратора базы данных — предотвратить их переполнение. Переполнение диска с данными не приведет к повреждению данных, но может помешать выполнению полезных действий. Если переполнится диск с файлами WAL, на сервере баз данных может возникнуть сбой и последующее его отключение.

Если нет возможности освободить дополнительное место на диске, удалив другие объекты, можно переместить некоторые файлы базы данных в другие файловые системы, используя табличные пространства. Дополнительную информацию об этом см. в разделе Табличные пространства.

Совет
Некоторые файловые системы плохо работают, когда они почти заполнены, поэтому не ждите полного заполнения диска, чтобы принять необходимые меры.

Если ваша система поддерживает дисковые квоты для каждого пользователя, то на базу данных, естественно, будет распространяться любая квота, установленная для пользователя, от имени которого запущен сервер. Превышение этой квоты повлечет за собой те же негативные последствия, что и полное отсутствие места на диске.

Надежность и журнал упреждающей записи

В этой главе объясняется, как с помощью журнала упреждающей записи (Write Ahead Log, WAL) обеспечивается эффективная и надежная работа.

Надежность

Надежность является важным свойством любой серьезной СУБД, и QHB делает все возможное, чтобы гарантировать надежную работу. Одним из аспектов надежной работы является то, что все данные, записанные зафиксированной транзакцией, должны храниться в энергонезависимой области, защищенной от потери питания, сбоя операционной системы и аппаратного сбоя (разумеется, за исключением отказа самой энергонезависимой области). Успешная запись данных в постоянное хранилище компьютера (диск или равнозначный носитель) обычно соответствует этому требованию. На самом деле, даже в случае серьезного повреждения компьютера, если диски уцелели, их можно перенести на другой компьютер с похожими компонентами, и все зафиксированные транзакции останутся нетронутыми.

Хотя принудительное периодическое сохранение данных на пластинах диска может показаться простой операцией, это не так. Поскольку диск работает значительно медленнее, чем основная память и процессор, между основной памятью компьютера и пластинами диска существует несколько уровней кэширования. Во-первых, это буферный кэш операционной системы, который кэширует часто запрашиваемые дисковые блоки и объединяет записи на диск. К счастью, все операционные системы предоставляют приложениям возможность принудительно записывать из буферного кэша на диск, и QHB использует эти особенности. (Настройку этого процесса см. в описании параметра wal_sync_method.)

Далее, кэш может быть в дисковом контроллере; особенно это распространено на RAID-контроллерах. В некоторых случаях такое кэширование проводится в формате сквозной записи, то есть данные отправляются на диск сразу после поступления. Другие проводятся в формате обратной записи, то есть данные отправляются на диск через некоторое время. Такие кэширования могут представлять угрозу надежности, поскольку память в кэше дискового контроллера энергозависима и потеряет свое содержимое при сбое питания. Качественные контроллеры снабжены блоками резервных батарей (Battery Backup Unit, BBU), то есть на плате контроллера имеется батарея, которая поддерживает питание кэша в случае потери питания системы. После восстановления питания данные будут корректно записаны на диски.

И, наконец, большинство дисков имеют внутренний кэш. Некоторые из них работают в формате сквозной записи, другие — обратной, и у дисковых кэшей с обратной записью те же проблемы, касающиеся потери данных, что и у кэшей дисковых контроллеров. Особенно часто кэши обратной записи, не выдерживающие сбоя питания, встречаются на дисках IDE и SATA потребительского уровня. Многие твердотельные накопители (SSD) также имеют энергозависимые кэши обратной записи.

Эти кэши обычно можно выключить, однако способ сделать это зависит от операционной системы и типа диска:

В Linux диски IDE и SATA можно запрашивать с помощью команды hdparm -I; кэширование записи включено, если рядом со строкой Write cache есть знак *. для выключения кэширования записи можно воспользоваться командой hdparm -W 0. Диски SCSI можно запросить с помощью утилиты sdparm. Используйте sdparm --get=WCE, чтобы проверить, включен ли кэш записи, и sdparm --clear=WCE, чтобы выключить его.

В FreeBSD диски IDE можно запрашивать с помощью команды atacontrol, а кэширование записи выключается параметром hw.ata.wc=0 в файле /boot/loader.conf; диски SCSI можно запрашивать с помощью команды camcontrol identify, а кэширование записи запрашивается и изменяется с помощью утилиты sdparm, если она доступна.

В Solaris кэширование записи на диск управляется командой format -e. (Файловая система Solaris ZFS безопасна при включенном кэшировании записи на диск, поскольку она применяет собственные команды сброса кэша на диск.)

В macOS кэширования записей можно избежать, установив в параметре wal_sync_method значение fsync_writethrough.

Диски SATA последних моделей (те, которые следуют стандарту ATAPI-6 или более новому) предлагают команду сброса кэша на диска (FLUSH CACHE EXT), тогда как диски SCSI уже давно поддерживают похожую команду SYNCHRONIZE CACHE. Эти команды недоступны для QHB напрямую, но некоторые файловые системы (например ZFS, ext4 ) могут использовать их для сброса данных на пластины на дисках с включенной обратной записью. К сожалению, такие файловые системы ведут себя неоптимально при комбинировании с дисковыми контроллерами с BBU. В таких системах команда синхронизации направляет все данные из кэша контроллера на диски, что исключает большую часть преимуществ BBU. Для проверки, не ваш ли это случай, можно запустить программу qhb_test_fsync. Если это так, преимущества BBU для производительности можно восстановить, выключив барьеры записи в файловой системе или переконфигурировав контроллер диска, если это возможно. Если барьеры записи выключены, убедитесь, что батарея остается работоспособной; неисправная батарея может привести к потере данных.

Когда операционная система отправляет запрос на запись оборудованию для хранения данных, у нее мало возможностей убедиться, что данные поступили в действительно энергонезависимую область хранилища. Скорее, это администратор обязан убедиться, что все компоненты хранилища обеспечивают целостность как данных и метаданных файловой системы. Избегайте дисковых контроллеров с кэшами записи без батарей резервного питания. На уровне диска выключите кэширование с обратной записью, если диск не может гарантировать, что данные будут записаны перед выключением. Если вы используете SSD, помните, что многие из них по умолчанию не выполняют команды сброса кэша на диск. Протестировать надежность поведения подсистемы ввода/вывода можно с помощью diskchecker.pl.

Еще один риск потери данных связан с самими операциями записи на пластины диска. Пластины диска, как правило, разделены на сектора, обычно по 512 байт каждый. Каждая физическая операция чтения или записи обрабатывает сектор целиком. Когда на диск поступает запрос на запись, он может быть кратен 512 байтам (QHB обычно записывает 8192 байт или 16 секторов за раз), и в процессе записи в любой момент может произойти сбой из-за потери питания, то есть некоторые из 512-байтовых секторов будут записаны, а другие нет. Чтобы защититься от таких сбоев, перед изменением фактической страницы на диске QHB периодически записывает полные образы страниц в постоянное хранилище WAL. Благодаря этому во время восстановления после сбоя QHB может восстановить частично записанные страницы из WAL. Если ваша файловая система предотвращает частичную запись страниц (например, ZFS), можно выключить эту запись образов страниц, выключив параметр full_page_writes. Дисковые контроллеры с батареями резервного питания (BBU) не предотвращают частичную запись страниц, если не гарантируют, что данные записываются в BBU как полные (8 КБ) страницы.

QHB также защищает от некоторых видов повреждения данных на устройствах хранения, которые могут возникнуть из-за аппаратных ошибок или поломки носителя со временем, например при чтении/записи мусорных данных.

Каждая отдельная запись в файле WAL защищена проверкой CRC-32 (32-разрядная версия), которая позволяет определить правильность содержимого записи. Значение CRC устанавливается при записи каждой записи WAL и проверяется во время восстановления после сбоя, восстановления из архива и репликации.

В настоящее время страницы данных по умолчанию не имеют контрольных сумм, хотя образы страниц, сохраненные в записях WAL, будут защищены; подробную информацию о включении контрольных сумм для страниц данных см. на справочной странице утилиты qhb_bootstrap.

Внутренние структуры данных, например pg_xact, pg_subtrans, pg_multixact, pg_serial, pg_notify, pg_stat, pg_snapshots, напрямую не проверяются при помощи контрольных сумм, а страницы не защищены записями полных страниц. Однако там, где такие структуры данных являются постоянными, пишутся записи WAL, которые позволяют точно воссоздать последние изменения при восстановлении после сбоя, и эти записи WAL защищены так же, как описывалось выше.

Отдельные файлы состояний в pg_twophase защищены CRC-32.

Временные файлы данных, используемые в больших запросах SQL для сортировки, материализации и промежуточных результатов, в настоящее время не проверяются, и изменения в этих файлах не будут записываться в WAL.

QHB не защищает от исправляемых ошибок памяти, и предполагается, что вы будете работать с ОЗУ, которое использует отраслевой стандарт кодов исправления ошибок (Error Correcting Codes, ECC) или более эффективную защиту.

Контрольные суммы данных

По умолчанию страницы данных не защищены контрольными суммами, но при необходимости их можно включить для кластера. В таком случае каждая страница данных будет содержать контрольную сумму, рассчитываемую при записи и проверяемую при каждом чтении страницы. Контрольными суммами защищены только страницы данных, но не внутренние структуры данных и временные файлы.

Контрольные суммы обычно включают при инициализации кластера с помощью утилиты qhb_bootstrap. Также контрольные суммы можно включить или выключить позднее, в остановленном кластере. Включить или выключить контрольные суммы данных можно на уровне всего кластера, но не для отдельной базы данных или таблицы.

Текущее состояние контрольных сумм в кластере можно узнать, просмотрев значение неизменяемой конфигурационной переменной data_checksums с помощью команды SHOW data_checksums.

При попытке восстановить страницы после повреждения иногда может потребоваться обойти защиту, обеспечиваемую контрольными суммами. Для этого можно временно установить параметр конфигурации ignore_checksum_failure.

Включение контрольных сумм в остановленном кластере

Чтобы проверить, включить или выключить контрольные суммы данных в остановленном кластере, можно воспользоваться приложением qhb_checksums.

Журнал упреждающей записи (WAL)

Запись в журнал упреждающей записи (Write Ahead Log, WAL) — это стандартный метод обеспечения целостности данных. Подробное описание можно найти в большинстве (если не во всех) справочных и учебных пособиях по обработке транзакций. Если коротко, центральная концепция WAL заключается в том, что изменения в файлах данных (в которых находятся таблицы и индексы) должны записываться только после того, как эти изменения были запротоколированы в журнал, то есть после того как записи журнала, описывающие эти изменения, были сброшены в постоянное хранилище. Если следовать этой процедуре, нет необходимости сбрасывать страницы данных на диск при каждой фиксации транзакции, потому что мы знаем, что в случае сбоя сможем восстановить базу данных с помощью этого журнала: любые изменения, которые не были применены к страницам данных можно восстановить из записей журнала. (Это называется восстановлением с повтором транзакций, также известном как REDO).

Совет
Поскольку WAL восстанавливает содержимое файла базы данных после сбоя, журналируемые файловые системы не нужны для надежного хранения файлов данных или файлов WAL. Фактически издержки на журналирование могут снизить производительность, особенно если журналирование приводит к сбросу на диск данных файловой системы. К счастью, сброс данных на диск во время журналирования можно выключить с помощью параметра монтирования файловой системы, например data=writeback в файловой системе Linux ext3. Тем не менее журналируемые файловые системы увеличивают скорость загрузки после сбоя.

Использование WAL приводит к значительному сокращению количества операций записи на диск, поскольку для фиксации транзакции нужно сбросить на диск только файл журнала, а не каждый файл данных, измененный этой транзакцией. Файл журнала записывается последовательно, поэтому затраты на синхронизацию журнала намного ниже, чем затраты на сброс страниц файлов данных на диск. Это особенно верно для серверов, обрабатывающих множество небольших транзакций, затрагивающих разные части хранилища данных. Более того, когда сервер обрабатывает много небольших параллельных транзакций, одного вызова fsync на файл журнала может быть достаточно для фиксации многих транзакций.

WAL также позволяет поддерживать резервное копирование и восстановление на определенный момент времени в режиме онлайн, как описано в разделе Непрерывное архивирование и восстановление на момент времени (PITR). Архивируя данные WAL, можно поддерживать возврат к любому моменту времени, охваченному доступными данными WAL: мы просто устанавливаем предыдущую физическую резервную копию базы данных и воспроизводим изменения из журнала до нужного момента времени. Более того, физическому резервному копированию необязательно быть мгновенным снимком состояния базы данных — если оно выполнено некоторое время назад, то воспроизведение журнала WAL за этот период устранит любые внутренние несоответствия.

Асинхронная фиксация

Асинхронная фиксация — это дополнительная функция, позволяющая транзакциям завершаться гораздо быстрее за счет того, что в случае сбоя базы данных самые последние транзакции могут быть потеряны. Во многих приложениях это приемлемый компромисс.

Как описано в предыдущем разделе, фиксация транзакции обычно выполняется синхронно: сервер ожидает сброса записей WAL транзакции в постоянное хранилище, а затем возвращает клиенту оповещение об успешном выполнении. Таким образом, клиенту гарантируется, что транзакция, о фиксации которой было сообщено, будет сохранена даже в том случае, если после этого тут же произойдет сбой сервера. Однако для коротких транзакций эта задержка занимает основную часть общего времени транзакции. Выбор режима асинхронной фиксации означает, что сервер возвращает сообщение об успешной фиксации, как только транзакция логически завершается, т. е. до того, как сгенерированные ею записи WAL действительно попадают на диск. Это может значительно увеличить скорость обработки небольших транзакций.

Асинхронная фиксация транзакций приводит к риску потери данных. Существует короткое временное окно между сообщением клиенту о завершении транзакции и моментом, когда транзакция действительно фиксируется (то есть гарантированно не будет потеряна в случае сбоя сервера). Таким образом, асинхронную фиксацию транзакций нельзя использовать, если клиент будет выполнять внешние действия, исходя из предположения, что транзакция будет сохранена. Например, банку, разумеется, не стоит использовать асинхронную фиксацию для транзакции, записывающей выдачу наличных в банкомате. Но во многих сценариях, например, при протоколировании событий в журнал, нет необходимости в серьезных гарантиях такого рода.

Риск, связанный с использованием асинхронной фиксации транзакций, — это потеря данных, а не их повреждение. При сбое базы данных она восстановится путем воспроизведения WAL вплоть до последней сброшенной на диск записи. Поэтому база данных будет восстановлена в самосогласованном состоянии, но любые транзакции, которые еще не были записаны на диск, в этом состоянии отражены не будут. Таким образом, суммарный эффект заключается в потере последних нескольких транзакций. Поскольку транзакции воспроизводятся в порядке фиксации, согласованность не нарушается — например, если транзакция B внесла изменения, полагаясь результаты предыдущей транзакции A, не может быть такого, что результаты A были утеряны, а результаты B сохранены.

Пользователь может выбрать режим фиксации каждой транзакции, что дает возможность параллельно запускать транзакции с синхронной и асинхронной фиксацией. Это обеспечивает гибкий компромисс между производительностью и уверенностью в прочности транзакций. Режим фиксации управляется устанавливаемым пользователем параметром synchronous_commit, который можно менять любым из способов настройки параметра конфигурации. Режим, используемый для любой отдельной транзакции, зависит от значения synchronous_commit на момент начала фиксации транзакции.

Некоторые служебные команды, например DROP TABLE, принудительно фиксируются синхронно, независимо от значения параметра synchronous_commit. Это необходимо для обеспечения согласованности между файловой системой сервера и логическим состоянием базы данных. Команды, поддерживающие двухфазную фиксацию, например PREPARE TRANSACTION, также всегда являются синхронными.

Если во время окна риска между асинхронной фиксацией и записью изменений в WAL транзакции происходит сбой базы данных, изменения, сделанные во время этой транзакции, будут потеряны. Длительность окна риска ограничена, поскольку фоновый процесс (процесс записи WAL) сбрасывает новые записи WAL на диск каждые wal_writer_delay миллисекунды. Фактическая максимальная длительность окна риска в три раза больше wal_writer_delay, так как процесс записи WAL предназначен для записи целых страниц за раз во время периодов активности.

ВНИМАНИЕ!
Выключение в немедленном режиме равнозначно сбою сервера и, следовательно, приведет к потере любых несохраненных асинхронных фиксаций.

Асинхронная фиксация обеспечивает поведение, отличное от установки fsync = off. Параметр fsync применяется ко всему серверу и будет изменять поведение всех транзакций. Он выключает всю логику в QHB, которая пытается синхронизировать записи в различные части базы данных, и, следовательно, сбой системы (то есть сбой оборудования или операционной системы, а не самой QHB) может привести к сколь достаточно значительному повреждению состояния базы данных. Во многих сценариях асинхронная фиксация обеспечивает улучшение производительности, почти не уступающее тому, которое можно получить, выключив fsync, но без риска повреждения данных.

Действие параметра commit_delay тоже очень похоже на асинхронную фиксацию, но на самом деле это метод синхронной фиксации (фактически commit_delay игнорируется во время асинхронной фиксации). commit_delay вызывает задержку непосредственно перед тем, как транзакция сбрасывает WAL на диск, в надежде, что одиночный сброс, выполненный одной такой транзакцией, может также обслуживать другие транзакции, фиксируемые примерно в то же время. Этот параметр можно рассматривать как способ увеличения временного окна, в котором транзакции могут присоединиться к группе, собирающейся участвовать в одиночном сбросе, чтобы распределить стоимость сброса между несколькими транзакциями.

Конфигурация WAL

Существует несколько параметров конфигурации, связанных с WAL, которые влияют на производительность базы данных. В этом раздел рассказывается об их использовании. Общую информацию об установке параметров конфигурации сервера см. в главе Конфигурация сервера.

Контрольные точки — это точки в последовательности транзакций, в которых гарантируется, что в файлы кучи и индексов попала вся информация об изменениях, записанная до этой контрольной точки. Во время контрольной точки все грязные страницы данных сбрасываются на диск, и в файл журнала записывается специальная запись контрольной точки. (Записи изменений были до этого сброшены в файлы WAL). В случае сбоя процедура восстановления после сбоя находит последнюю запись контрольной точки, чтобы определить точку в журнале (называемую записью REDO), с которой она должна начать операцию REDO. Любые изменения, внесенные в файлы данных до этого момента, гарантированно уже находятся на диске. Следовательно, после контрольной точки сегменты журнала, предшествующие тому, в котором содержится запись REDO, больше не нужны и могут быть использованы повторно или удалены. (Когда архивация WAL завершается, сегменты журнала должны быть заархивированы перед повторным использованием или удалением).

Требование контрольной точки — сброс всех грязных страниц данных на диск — может стать причиной значительной нагрузки на ввод/вывод. По этой причине активность контрольной точки регулируется таким образом, чтобы операции ввода/вывода начинались при запуске контрольной точки и завершались до начала следующей контрольной точки; это минимизирует снижение производительности во время контрольных точек.

Процесс контрольной точки сервера время от времени автоматически выполняет контрольную точку. Контрольная точка производится каждые checkpoint_timeout секунд или если вот-вот будет превышено значение max_wal_size, в зависимости от того, что произойдет раньше. Значения по умолчанию — 5 минут и 1 ГБ соответственно. Если с предыдущей контрольной точки не было записи в WAL, новые контрольные точки будут пропущены, даже если прошло время checkpoint_timeout. (Если используется архивация WAL, и вы хотите установить более низкий предел частоты архивирования файлов, чтобы ограничить возможную потерю данных, следует настроить параметр archive_timeout, а не параметры контрольной точки). Также можно принудительно установить контрольную точку с помощью команды SQL CHECKPOINT.

Уменьшение checkpoint_timeout и/или max_wal_size приводит к тому, что контрольные точки случаются чаще. Это позволяет быстрее восстанавливаться после сбоя, так как потребуется меньше работы. Тем не менее следует сопоставить это с возросшими затратами на более частый сброс грязных страниц данных на диск. Если установлен параметр full_page_writes (по умолчанию это так), необходимо учитывать еще один фактор. Чтобы обеспечить согласованность страницы данных, первая модификация страницы данных после каждой контрольной точки приводит к протоколированию в журнал всего содержимого этой страницы. В этом случае чем меньше интервал между контрольными точками, тем больше объем вывода в журнал WAL, что частично сводит на нет смысл уменьшенного интервала и в любом случае приводит к увеличению дискового ввода/вывода.

Контрольные точки довольно затратны, во-первых, потому что они требуют записи всех текущих грязных буферов на диск, а во-вторых, потому что из-за них, как описано выше, создается дополнительный трафик WAL. Поэтому целесообразно устанавливать параметры контрольных точек достаточно высокими, чтобы контрольные точки не происходили слишком часто. Для простой проверки работоспособности параметров контрольной точки можно установить параметр checkpoint_warning. Если контрольные точки следуют друг за другом чаще, чем через каждые checkpoint_warning секунд, в журнал сервера будет выведено сообщение с рекомендацией увеличить значение max_wal_size. Единичные появления такого сообщения не являются поводом для тревоги, но если оно возникает часто, следует увеличить значения управляющих параметров контрольной точки. Массовые операции, например, перенос большого объема данных с помощью COPY, могут вызвать появление нескольких таких предупреждений, если не установить в max_wal_size достаточно большое значение.

Чтобы избежать переполнения системы ввода/вывода всплеском интенсивности записи страниц, запись грязных буферов во время контрольной точки растягивается на некоторый промежуток времени. Этот период контролируется параметром checkpoint_completion_target, который задается как часть интервала между контрольными точками (конфигурируемого параметром checkpoint_timeout). Скорость ввода/вывода регулируется таким образом, чтобы контрольная точка заканчивалась по истечении заданной доли от checkpoint_timeout секунд или до превышения порога max_wal_size, в зависимости от того, что наступит раньше. При значении по умолчанию 0.9 можно ожидать, что QHB завершит каждую контрольную точку несколько раньше, чем запустится следующая запланированная контрольная точка (примерно на 90% выполнения предыдущей контрольной точки). Это максимально растягивает ввод/вывод, равномерно распределяя нагрузку ввода/вывода при контрольных точках в интервалах между ними. Недостатком этого является то, что растягивание контрольных точек влияет на время восстановления, поскольку необходимо будет хранить больше сегментов WAL, которые могут понадобиться для восстановления. Пользователю, обеспокоенному чрезмерной продолжительностью восстановления, может понадобиться уменьшить значение checkpoint_timeout так, чтобы контрольные точки происходили чаще, но при этом ввод/вывод по-прежнему растягивался на интервал между ними. Либо можно уменьшить значение checkpoint_completion_target, но в результате ввод/вывод временами будет более интенсивным (во время контрольной точки) а временами — менее (после завершения контрольной точки, но до следующей запланированной контрольной точки), так что этот вариант не рекомендуется. Хотя в checkpoint_completion_target можно задать значение до 1.0, лучше не делать его выше 0.9 (значение по умолчанию), поскольку контрольные точки включают в себя некоторые другие действия помимо записи грязных буферов. При значении 1.0 контрольные точки, скорее всего, не будут выполнены вовремя, что приведет к снижению производительности из-за неожиданного изменения количества необходимых сегментов WAL.

На платформах Linux и POSIX checkpoint_flush_after позволяет принудительно заставить ОС сбрасывать страницы, записанные контрольной точкой, на диск после определенного количества байтов. В противном случае эти страницы могут храниться в кэше страниц ОС, вызывая затормаживание при вызове fsync в конце контрольной точки. Этот параметр часто помогает уменьшить задержки транзакций, но также может негативно повлиять на производительность, особенно когда объем нагрузок больше, чем shared_buffers, но меньше, чем кэш страниц ОС.

Количество файлов сегментов WAL в каталоге pg_wal зависит от параметров min_wal_size и max_wal_size, а также от объема WAL, сгенерированного в предыдущих циклах контрольных точек. Когда старые файлы сегментов журнала становятся не нужны, они удаляются или перерабатываются (то есть переименовываются, чтобы стать будущими сегментами в пронумерованной последовательности). Если из-за кратковременного пика скорости вывода журнала превышается предел max_wal_size, ненужные файлы сегментов будут удаляться, пока система не опустится ниже этого предела. Находясь ниже этого предела, система повторно использует такое количество файлов WAL, которого достаточно для покрытия расчетной потребности до следующей контрольной точки, и удаляет остальные. Расчет основан на скользящем среднем количества файлов WAL, использованных в предыдущих циклах контрольных точек. Если фактическое использование превышает расчетное, то скользящее среднее немедленно увеличивается, поэтому оно в некоторой степени накапливает пиковое использование, а не среднее. Параметр min_wal_size задает минимальное количество файлов WAL для будущего использования; ровно столько WAL всегда будет использовано повторно, даже если система простаивает и оценка использования WAL предполагает, что сегментов требуется мало.

Независимо от max_wal_size, последние wal_keep_size мегабайт файлов WAL плюс один дополнительный файл WAL хранятся всегда. Кроме того, если используется архивация WAL, старые сегменты нельзя удалить или переработать, пока они не заархивированы. Если архивация WAL не успевает за генерацией WAL или если archive_command или archive_library неоднократно завершается с ошибкой, старые файлы WAL будут накапливаться в pg_wal до тех пор, пока ситуация не разрешится. Медленный или давший сбой резервный сервер, использующий слот репликации, будет давать такой же эффект (см. подраздел Слоты репликации).

В режиме восстановления архива или в режиме резерва сервер периодически выполняет точки перезапуска, которые схожи с контрольными точками в обычной работе: сервер переносит все свое состояние на диск, обновляет файл pg_control, чтобы показать, что уже обработанные данные WAL не нужно снова сканировать, а затем перерабатывает все старые файлы сегментов журнала в каталоге pg_wal. Точки перезапуска не могут выполняться чаще, чем контрольные точки на основном сервере, потому что точки перезапуска могут выполняться только в записях контрольных точек. Точка перезапуска срабатывает при достижении записи контрольной точки, если прошло не менее checkpoint_timeout секунд с момента последней точки перезапуска или если размер WAL вот-вот превысит max_wal_size. Однако из-за ограничений на время выполнения точки перезапуска max_wal_size часто превышается во время восстановления вплоть до объема WAL, записываемого за один цикл контрольной точки. (В любом случае max_wal_size не является жестким лимитом, поэтому следует всегда оставлять значительный запас, чтобы избежать нехватки дискового пространства для файлов журнала).

Существуют две широко используемые внутренние функции WAL: XLogInsertRecord и XLogFlush. XLogInsertRecord используется для помещения новой записи в буферы WAL в разделяемой памяти. Если для новой записи нет места, XLogInsertRecord придется записать (переместить в кэш ядра) несколько заполненных буферов WAL. Это нежелательно, поскольку XLogInsertRecord используется при каждом низкоуровневом изменении базы данных (например при добавлении строки) в тот момент, когда на задействованных страницах данных удерживается эксклюзивная блокировка, поэтому операция должна быть максимально быстрой. Что еще хуже, запись в буферы WAL тоже может привести к созданию нового сегмента журнала, что занимает еще больше времени. Обычно буферы WAL должны записываться и сбрасываться на диск вызовом XLogFlush, который, по большей части, выполняется во время фиксации транзакции, чтобы обеспечить сброс записей транзакции в постоянное хранилище. В системах с высокой скоростью заполнения журнала вызовы XLogFlush могут происходить недостаточно часто, чтобы не давать XLogInsertRecord производить записи. В таких системах следует увеличить количество буферов WAL, изменив параметр wal_buffers. Если установлен параметр full_page_writes и система сильно нагружена, увеличение значения wal_buffers поможет сгладить скачки времени отклика в период, идущий сразу после каждой контрольной точки.

Параметр commit_delay определяет, на сколько микросекунд главный процесс групповой фиксации будет находиться в спящем режиме после получения блокировки в XLogFlush, пока ведомые процессы стоят в очереди после главного. Эта задержка позволяет другим серверным процессам добавлять свои записи фиксации в буферы WAL, чтобы все они были сброшены итоговой синхронизирующей операцией главного процесса. Спящего режима не будет, если не включен параметр fsync или если в данный момент активны транзакции менее чем в commit_siblings сеансах; таким образом, главный процесс не засыпает, когда маловероятно, что какой-либо другой сеанс в ближайшее время зафиксирует транзакцию. Обратите внимание, что на некоторых платформах разрешение запроса на переход в спящий режим составляет десять миллисекунд, поэтому любое ненулевое значение параметра commit_delay от 1 до 10000 микросекунд будет иметь одинаковый эффект. Также обратите внимание, что на некоторых платформах состояние сна может продолжаться немного дольше, чем указано в параметре.

Поскольку цель commit_delay состоит в том, чтобы распределить стоимость каждой операции сброса на диск между параллельно фиксирующимися транзакциями (возможно, за счет задержки транзакции), прежде чем выбрать разумное значение для этого параметра, необходимо количественно оценить эту стоимость. Чем выше эта стоимость, тем потенциально более эффективно commit_delay будет способствовать увеличению производительности транзакций (до некоторой степени). Для измерения среднего времени (в микросекундах), затрачиваемого на одну операцию по сбросу WAL на диск, можно воспользоваться программой qhb_test_fsync. Значение, составляющее половину выводимого этой утилитой среднего времени сброса на диск после однократной операции записи 8 КБ, зачастую является наиболее эффективным значением для параметра commit_delay, поэтому оно рекомендуется как отправная точка при оптимизации для конкретной рабочей нагрузки. Хотя настройка commit_delay особенно полезна, когда журнал WAL хранится на вращающихся дисках с высокой задержкой, преимущества могут быть значительными даже для носителей с очень быстрым временем синхронизации, таких как твердотельные накопители или массивы RAID с резервными батареями для кэш-записи, но это, безусловно, необходимо проверить при репрезентативной нагрузке. В таких случаях следует использовать более высокие значения commit_siblings, тогда как меньшие значения commit_siblings часто полезны на носителях с более высокой задержкой. Обратите внимание, что слишком большое значение commit_delay вполне может увеличить задержку транзакции настолько, что пострадает общая производительность транзакций.

Когда commit_delay равен нулю (по умолчанию), групповая фиксация все еще может произойти, но каждая группа будет состоять только из сеансов, достигших точки, где они должны сбрасывать свои записи фиксации на диск в тот промежуток времени, когда выполняется предыдущая операция сброса (если таковая выполняется). Если клиентов больше, то, как правило, между сеансами возникает «давка», и эффект от групповой фиксации становится значительным, даже когда commit_delay равен нулю. Таким образом, явная установка commit_delay может и не оказать особого влияния на производительность. Установка commit_delay может помочь, только когда есть несколько параллельно фиксирующихся транзакции либо производительность до некоторой степени ограничена скоростью фиксации транзакции. Тем не менее при высокой вращательной задержке диска этот параметр может быть полезен для увеличения производительности транзакций даже всего с двумя клиентами.

Параметр wal_sync_method определяет, как QHB будет запрашивать у ядра принудительную запись изменений WAL на диск. Все параметры должны быть одинаковыми с точки зрения надежности, за исключением fsync_writethrough, который иногда может вызвать сброс дискового кэша, даже если другие параметры этого не делают. Однако что из этого будет быстрее, зависит от платформы, на которой работает QHB. Скорость при различных методах можно протестировать с помощью утилиты qhb_test_fsync. Обратите внимание, что этот параметр не имеет значения, если был выключен fsync.

Включение параметра конфигурации wal_debug (при условии, что QHB была скомпилирована с его поддержкой) приведет к тому, что каждый связанный с WAL вызов XLogInsertRecord и XLogFlush будет протоколироваться в журнал сервера. В будущем этот параметр может быть заменен на более общий механизм.

Имеются две внутренние функции для записи данных WAL на диск: XLogWrite и issue_xlog_fsync. Когда включен параметр track_wal_io_timing, общее время, затраченное XLogWrite на запись данных WAL на диск, а issue_xlog_fsync на их синхронизацию с диском, рассчитывается в представлении pg_stat_wal как wal_write_time и wal_sync_time соответственно. Функцию XLogWrite обычно вызывают функции XLogInsertRecord (когда в буферах WAL нет места для новой записи) и XLogFlush и процесс записи WAL, чтобы записать буферы WAL на диск и вызвать issue_xlog_fsync. Функцию issue_xlog_fsync обычно вызывает XLogWrite, чтобы синхронизировать файлы WAL с диском. Если параметр wal_sync_method имеет значение open_datasync или open_sync, операция записи в XLogWrite обеспечивает синхронизацию записанных данных WAL с диском, а issue_xlog_fsync ничего не делает. Если же wal_sync_method имеет значение fdatasync, fsync или fsync_writethrough, то операция записи перемещает буферы WAL в кэш ядра, а issue_xlog_fsync синхронизирует их с диском. Независимо от значения параметра track_wal_io_timing, в полях wal_write и wal_sync представления pg_stat_wal также отображается, сколько раз XLogWrite записывает данные WAL на диск и сколько раз issue_xlog_fsync синхронизирует их с диском соответственно.

Для уменьшения времени ожидания ввода/вывода во время восстановления можно воспользоваться параметром recovery_prefetch, указав ядру инициировать чтение дисковых блоков, которые скоро понадобятся, но в данный момент отсутствуют в пуле буферов QHB. Значения параметров maintenance_io_concurrency и wal_decode_buffer_size ограничивают количество параллельных операций и объем предварительной выборки соответственно. По умолчанию в параметре recovery_prefetch установлено значение try, которое включает эту функциональность в системах, где доступна функция posix_fadvise.

Внутреннее устройство WAL

WAL включается автоматически; со стороны администратора не требуется никаких действий, кроме как обеспечивать соблюдение требований к дисковому пространству для журналов WAL и выполнять необходимую настройку (см. раздел Конфигурация WAL).

Записи WAL добавляются в журналы WAL по мере возникновения каждой новой записи. Позиция добавления описывается порядковым номером в журнале (Log Sequence Number, LSN), который представляет собой байтовое смещение в журналах, монотонно увеличивающееся с каждой новой записью. Значения LSN возвращаются с типом данных pg_lsn. Значения можно сравнивать для расчета объема данных WAL, которые их разделяют, поэтому они используются для оценки прогресса репликации и восстановления.

Журналы WAL хранятся в каталоге pg_wal, находящемся в каталоге данных, в виде набора файлов сегментов, обычно размером по 16 МБ каждый (но этот размер можно изменить, указав в параметре --wal-segsize утилиты qhb_bootstrap другое значение). Каждый сегмент делится на страницы, обычно по 8 кБ каждая (этот размер можно изменить с помощью параметра конфигурации --with-wal-blocksize). Заголовки записей журнала описываются в файле access/xlogrecord.h; содержимое записи зависит от типа запротоколированного события. В качестве имен файлам сегмента присваиваются постоянно растущие номера, начиная с 000000010000000000000000. При переполнении эти номера не зацикливаются, но чтобы исчерпать доступный запас номеров потребуется очень и очень много времени.

Бывает выгодно размещать журнал на диске, отличном от того, где находятся основные файлы базы данных. Это можно сделать, переместив каталог pg_wal в другое место (разумеется, при выключенном сервере) и создав символическую ссылку из исходного места в главном каталоге данных на новое.

Задача WAL — обеспечить выполнение записи в журнал до изменений записей базы данных, но этот порядок может быть нарушен дисковыми накопителями, которые ложно сообщают ядру об успешной записи, тогда как на самом деле они только кэшировали данные и еще не сохранили их на диске. Сбой питания в такой ситуации может привести к неисправимому повреждению данных. Администраторы должны позаботиться о том, чтобы диски, содержащие файлы журнала WAL QHB, не создавали подобные ложные оповещения. (См. раздел Надежность.)

После создания контрольной точки и сброса журнала на диск, позиция этой контрольной точки сохраняется в файле pg_control. Поэтому в начале восстановления сервер читает сперва pg_control а потом запись контрольной точки; затем он выполняет операцию REDO путем сканирования вперед от местоположения журнала, указанного в записи контрольной точки. Поскольку все содержимое страниц данных сохраняется в журнале при первом изменении страницы после контрольной точки (при условии, что включен параметр full_page_writes), все страницы, измененные после контрольной точки, будут восстановлены в согласованном состоянии.

Чтобы справиться с ситуацией, когда файл pg_control поврежден, следует поддерживать возможность сканирования существующих сегментов журнала в обратном порядке — от самого нового к самому старому, — чтобы найти последнюю контрольную точку. Это еще не реализовано. Файл pg_control достаточно мал (менее одной страницы диска), чтобы не страдать от проблемы частичной записи, и на момент написания этого раздела не было сообщений о сбоях баз данных исключительно из-за невозможности чтения самого pg_control. Таким образом, хотя теоретически это слабое место, по-видимому, на практике проблем с pg_control не выявлено.

Логическая репликация

Логическая репликация — это метод репликации объектов данных и изменений в них, основанный на их репликационных идентификаторах (обычно это первичный ключ). Мы используем термин «логическая» как противопоставление физической репликации, использующей точные адреса блоков и побайтовое копирование. QHB поддерживает одновременно оба механизма; см. главу Высокая доступность, балансировка нагрузки и репликация. Логическая репликация позволяет более детально управлять как репликацией данных, так и вопросами безопасности.

В логической репликации используется модель публикаций и подписок с одним или несколькими подписчиками на одну или несколько публикаций на узле-публикаторе. Подписчики извлекают данные из публикаций, на которые они подписаны, и затем могут повторно публиковать данные для обеспечения каскадной репликации или более сложных конфигураций.

Логическая репликация таблицы обычно начинается с создания снимка данных в публикующей базе данных и копирования его подписчику. После этого изменения на стороне публикатора передаются подписчику тогда, когда они происходят, в реальном времени. Подписчик применяет эти данные в том же порядке, что и публикатор, поэтому для публикаций в рамках одной подписки гарантируется транзакционная согласованность. Этот метод репликации данных иногда называется транзакционной репликацией.

Типичные сценарии использования логической репликации:

Передача подписчикам инкрементальных изменений в одной базе данных или подмножестве базы данных, когда они происходят.

Срабатывание триггеров для отдельных изменений, когда они поступают к подписчику.

Объединение нескольких баз данных в одну (например, в целях анализа).

Репликация между разными основными версиями QHB.

Репликация между экземплярами QHB на разных платформах.

Предоставление доступа к реплицированным данным разным группам пользователей.

Разделение подмножества базы данных между несколькими базами данных.

База-подписчик ведет себя так же, как и любой другой экземпляр QHB, и может использоваться как публикатор для других баз данных, определив собственные публикации. Когда подписчик воспринимается приложением как исключительно читающий, никаких конфликтов с одной подпиской не будет. С другой стороны, конфликты могут возникнуть, если приложение или другие подписчики производят запись в тот же набор таблиц.

Публикация

Публикацию можно определить на любом основном сервере физической репликации. Узел, где определяется публикация, называется публикатором. Публикация — это набор изменений, сгенерированных в таблице или в группе таблиц, который также можно описать как просто набор изменений или набор репликации. Каждая публикация существует только в одной базе данных.

Публикации отличаются от схем и не влияют на доступ к таблице. При необходимости каждую таблицу можно добавить в несколько публикаций. В настоящее время публикации могут содержать только таблицы. Объекты в них нужно добавлять явно, за исключением случаев, когда публикация создана для всех таблиц (с указанием ALL TABLES).

При желании публикации могут ограничить выдаваемые изменения любым сочетанием операций INSERT, UPDATE, DELETE и TRUNCATE подобно тому, как триггеры срабатывают для конкретных типов событий. По умолчанию реплицируются все типы операций. Эти спецификации публикаций применяются только к операциям DML; они не влияют на копирование при начальной синхронизации данных. (Фильтры строк не действуют на TRUNCATE. См. раздел Фильтры строк).

Чтобы иметь возможность реплицировать операции UPDATE и DELETE, в публикуемой таблице должен быть сконфигурирован «репликационный идентификатор», чтобы на стороне подписчика можно было определить соответствующие строки для изменения или удаления. По умолчанию это первичный ключ, если таковой имеется. Кроме того, в качестве репликационного идентификатора можно установить другой уникальный индекс (с некоторыми дополнительными условиями). Если у таблицы нет подходящего ключа, в качестве репликационного идентификатора можно установить «full», что означает, что ключом станет вся строка. Однако это крайне неэффективно и должно использоваться только как запасной вариант, если другого решения нет. Если на стороне публикатора установлен репликационный идентификатор, отличный от «full», то идентификатор, состоящий из того же или меньшего количества столбцов, должен быть также определен на стороне подписчика. Более подробную информацию о том, как установить репликационный идентификатор, см. в описании параметра REPLICA IDENTITY. Если в публикацию, которая реплицирует операции UPDATE или DELETE, добавляется таблица без репликационного идентификатора, то последующие операции UPDATE и DELETE вызовут ошибку на стороне публикатора. Операции INSERT могут выполняться вне зависимости от репликационного идентификатора.

У каждой публикации может быть множество подписчиков.

Публикация создается с помощью команды CREATE PUBLICATION и впоследствии может быть изменена или удалена с помощью соответствующих команд.

С помощью команды ALTER PUBLICATION в публикацию можно динамически добавлять или удалять отдельные таблицы. Операции ADD TABLE и DROP TABLE являются транзакционными, поэтому репликация таблицы начнется и закончится с корректным снимком только после фиксации транзакции.

Подписка

Подписка — это принимающая сторона логической репликации. Узел, где определяется подписка, называется подписчиком. В подписке определяется подключение к другой базе данных и набор публикаций (из одной или нескольких), на которые подписчик хочет подписаться.

База-подписчик ведет себя так же, как и любой другой экземпляр QHB, и может использоваться как публикатор для других баз данных, определив собственные публикации.

Узел-подписчик при желании может иметь несколько подписок. В одной паре публикатор- подписчик можно определить несколько подписок, но в этом случае следует позаботиться о том, чтобы публикуемые объекты в разных подписках не перекрывались.

Каждая подписка будет получать изменения через один слот репликации (см. подраздел Слоты репликации). Для начальной синхронизации данных, уже существующих в таблицах, могут потребоваться дополнительные слоты репликации, но они будут удалены в конце синхронизации.

Подписка логической репликации может быть резервным сервером для синхронной репликации (см. подраздел Синхронная репликация). При этом по умолчанию именем резервного сервера будет имя подписки. Другое имя можно задать в параметре application_name в строке подключения этой подписки.

Подписки сбрасываются на диск утилитой qhb_dump, если текущий пользователь является суперпользователем. В противном случае выдается предупреждение и подписки пропускаются, потому что обычные пользователи не могут читать всю информацию о подписках из каталога pg_subscription.

Подписка добавляется с помощью команды CREATE SUBSCRIPTION и в любой момент может быть остановлена/возобновлена с помощью команды ALTER SUBSCRIPTION и удалена с помощью команды DROP SUBSCRIPTION.

Когда подписка удаляется и пересоздается, информация о синхронизации теряется. Это означает, что после этого данные нужно синхронизировать заново.

Определения схем не реплицируются, а публикуемые таблицы должны существовать на подписчике. Целью репликации могут быть только обычные таблицы. Например, нельзя реплицировать в представление.

Таблицы сопоставляются между публикатором и подписчиком по полностью уточненным именам. Репликация в таблицу с другим именем на стороне подписчика не поддерживается.

Столбцы таблиц тоже сопоставляются по именам. Порядок столбцов в таблице подписчика не обязан совпадать с порядком столбцов у публикатора. Типам данных столбцов тоже не обязательно совпадать, если текстовое представление данных можно преобразовать в целевой тип. К примеру, можно реплицировать из столбца типа integer в столбец типа bigint. Кроме того, целевая таблица может содержать дополнительные столбцы, отсутствующие в таблице публикатора. Все эти столбцы будут заполнены значениями по умолчанию, заданными в определении целевой таблицы.

Управление слотами репликации

Как уже упоминалось ранее, каждая (активная) подписка получает изменения из слота репликации на удаленной (публикующей) стороне.

Дополнительные слоты синхронизации таблиц обычно являются временными, создаваемыми внутренне для проведения начальной синхронизации таблиц и автоматически удаляются, когда становятся не нужны. У этих слотов синхронизации таблиц генерируются имена вида «pg_%u_sync_%u_%llu» (параметры: oid подписки, relid таблицы, системный идентификатор sysid).

Обычно удаленный слот репликации создается автоматически, когда подписка создается с помощью команды CREATE SUBSCRIPTION, и автоматически же удаляется при удалении подписки командой DROP SUBSCRIPTION. Однако в некоторых ситуациях может быть полезно или даже необходимо манипулировать подпиской и нижележащим слотом репликации по отдельности. Ниже приведены некоторые возможные сценарии:

При создании подписки слот репликации уже существует. В этом случае подписку можно создать с параметром create_slot = false, чтобы связать ее с существующим слотом.

При создании подписки удаленный хост недоступен или находится в неопределенном состоянии. В этом случае подписку можно создать с параметром connect = false. Тогда с удаленным хостом вообще не будет устанавливаться соединение. Этот вариант использует qhb_dump. Тогда для активации подписки удаленный слот репликации нужно будет создавать вручную.

При удалении подписки нужно сохранить слот репликации. Это может быть полезно, когда база-подписчик переносится на другой хост и будет активироваться оттуда. В этом случае перед удалением подписки следует отвязать от нее слот с помощью команды ALTER SUBSCRIPTION.

При удалении подписки удаленный хост недоступен. В этом случае перед удалением подписки следует отвязать от нее слот с помощью команды ALTER SUBSCRIPTION. Если удаленный экземпляр базы данных уже не существует, в дальнейших действиях нет необходимости. Однако если удаленный экземпляр базы данных просто недоступен, слот репликации (и все оставшиеся слоты синхронизации таблиц) нужно будет удалить вручную; в противном случае они продолжат сохранять WAL и в итоге могут вызвать переполнение диска. Такие случаи следует разбирать особенно тщательно.

Примеры подписок

Создайте нескольких тестовых таблиц на публикаторе.

test_pub=# CREATE TABLE t1(a int, b text, PRIMARY KEY(a));
CREATE TABLE
test_pub=# CREATE TABLE t2(c int, d text, PRIMARY KEY(c));
CREATE TABLE
test_pub=# CREATE TABLE t3(e int, f text, PRIMARY KEY(e));
CREATE TABLE

Создайте такие же таблицы на подписчике.

test_sub=# CREATE TABLE t1(a int, b text, PRIMARY KEY(a));
CREATE TABLE
test_sub=# CREATE TABLE t2(c int, d text, PRIMARY KEY(c));
CREATE TABLE
test_sub=# CREATE TABLE t3(e int, f text, PRIMARY KEY(e));
CREATE TABLE

Добавьте данные в таблице на стороне публикатора.

test_pub=# INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three');
INSERT 0 3
test_pub=# INSERT INTO t2 VALUES (1, 'A'), (2, 'B'), (3, 'C');
INSERT 0 3
test_pub=# INSERT INTO t3 VALUES (1, 'i'), (2, 'ii'), (3, 'iii');
INSERT 0 3

Создайте публикации для таблиц. Для публикаций pub2 и pub3a запрещены некоторые операции publish. У публикации pub3b имеется фильтр строк (см. раздел Фильтры строк).

test_pub=# CREATE PUBLICATION pub1 FOR TABLE t1;
CREATE PUBLICATION
test_pub=# CREATE PUBLICATION pub2 FOR TABLE t2 WITH (publish = 'truncate');
CREATE PUBLICATION
test_pub=# CREATE PUBLICATION pub3a FOR TABLE t3 WITH (publish = 'truncate');
CREATE PUBLICATION
test_pub=# CREATE PUBLICATION pub3b FOR TABLE t3 WHERE (e > 5);
CREATE PUBLICATION

Создайте подписки на публикации. Подписка sub3 оформляется на две публикации, pub3a и pub3b. Все подписки по умолчанию будут копировать начальные данные.

test_sub=# CREATE SUBSCRIPTION sub1
test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub1'
test_sub-# PUBLICATION pub1;
CREATE SUBSCRIPTION
test_sub=# CREATE SUBSCRIPTION sub2
test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub2'
test_sub-# PUBLICATION pub2;
CREATE SUBSCRIPTION
test_sub=# CREATE SUBSCRIPTION sub3
test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub3'
test_sub-# PUBLICATION pub3a, pub3b;
CREATE SUBSCRIPTION

Обратите внимание, что начальные данные таблицы копируются независимо от варианта операции publish в подписке.

test_sub=# SELECT * FROM t1;
 a |   b
---+-------
 1 | one
 2 | two
 3 | three
(3 rows)

test_sub=# SELECT * FROM t2;
 c | d
---+---
 1 | A
 2 | B
 3 | C
(3 rows)

Более того, поскольку при копировании начальных данных операция publish игнорируется, а у публикации pub3a нет фильтра строк, скопированная таблица t3 содержит все строки, даже если они не соответствуют фильтру строк публикации pub3b.

test_sub=# SELECT * FROM t3;
 e |  f
---+-----
 1 | i
 2 | ii
 3 | iii
(3 rows)

Добавьте еще данных в таблицу на стороне публикатора.

test_pub=# INSERT INTO t1 VALUES (4, 'four'), (5, 'five'), (6, 'six');
INSERT 0 3
test_pub=# INSERT INTO t2 VALUES (4, 'D'), (5, 'E'), (6, 'F');
INSERT 0 3
test_pub=# INSERT INTO t3 VALUES (4, 'iv'), (5, 'v'), (6, 'vi');
INSERT 0 3

Теперь данные на стороне публикатора выглядят так:

test_pub=# SELECT * FROM t1;
 a |   b
---+-------
 1 | one
 2 | two
 3 | three
 4 | four
 5 | five
 6 | six
(6 rows)

test_pub=# SELECT * FROM t2;
 c | d
---+---
 1 | A
 2 | B
 3 | C
 4 | D
 5 | E
 6 | F
(6 rows)

test_pub=# SELECT * FROM t3;
 e |  f
---+-----
 1 | i
 2 | ii
 3 | iii
 4 | iv
 5 | v
 6 | vi
(6 rows)

Обратите внимание, что во время обычной репликации применяются соответствующие операции publish. Это означает, что публикации pub2 и pub3a не будут реплицировать операции INSERT. Кроме того, публикация pub3b будет реплицировать только данные, соответствующие фильтру строк pub3b. Теперь данные на стороне подписчика выглядят так:

test_sub=# SELECT * FROM t1;
 a |   b
---+-------
 1 | one
 2 | two
 3 | three
 4 | four
 5 | five
 6 | six
(6 rows)

test_sub=# SELECT * FROM t2;
 c | d
---+---
 1 | A
 2 | B
 3 | C
(3 rows)

test_sub=# SELECT * FROM t3;
 e |  f
---+-----
 1 | i
 2 | ii
 3 | iii
 6 | vi
(4 rows)

Фильтры строк

По умолчанию все данные из всех опубликованных таблиц будут реплицироваться надлежащим подписчикам. Объем реплицируемых данных можно уменьшить с помощью фильтра строк. Пользователю может потребоваться воспользоваться фильтрами строк по соображениям функциональности, безопасности или производительности. Если для публикуемой таблицы задается фильтр строк, строка реплицируется, только если ее данные удовлетворяют выражению этого фильтра. Это позволяет обеспечить частичную репликацию набора таблиц. Фильтр строк определяется отдельно для каждой таблицы. Добавьте выражение WHERE после имени таблицы для каждой публикуемой таблицы, данные которой нужно отфильтровать. Предложение WHERE следует заключить в круглые скобки. Подробную информацию см. на справочной странице команды CREATE PUBLICATION.

Правила фильтров строк

Фильтры строк применяются перед публикацией изменений. Если результатом вычисления в фильтре строк является false или NULL, такая строка не реплицируется. Выражение предложения WHERE вычисляется от имени той же роли, что используется для подключения репликации (т. е. роли, указанной в предложении CONNECTION команды CREATE SUBSCRIPTION). На команду TRUNCATE фильтры строк не действуют.

Ограничения выражений

Предложение WHERE допускает только простые выражения. Оно не может содержать пользовательские функции, операторы, типы и правила сортировки, ссылки на системные столбцы или непостоянные встроенные функции.

Если в публикации публикуются операции UPDATE или DELETE, предложение WHERE фильтра строк должно содержать только те столбцы, которые входят в идентификатор реплики (см. REPLICA IDENTITY). Если в публикации публикуются только операции INSERT, предложение WHERE фильтра строк может использовать любой столбец.

Преобразования UPDATE

При каждом выполнении операции UPDATE выражение фильтра строк вычисляется как для старой, так и для новой строки (т. е. используя данные до и после обновления). Если оба результата равны true, изменение UPDATE реплицируется. Если оба результата равны false, изменение не реплицируется. Если только одна из строк (старая или новая) соответствует выражению фильтра, UPDATE преобразуется в INSERT или DELETE, чтобы избежать несогласованности данных. Строка на подписчике должна отражать то, что определяется выражением фильтра строк на публикаторе.

Если старая строка удовлетворяет выражению фильтра строк (она была передана подписчику), а новая строка — нет, то в интересах согласованности данных следует удалить старую строку с подписчика. Поэтому UPDATE преобразуется в DELETE.

Если старая строка не удовлетворяет выражению фильтра строк (она не была передана подписчику), а новая строка удовлетворяет, то в интересах согласованности данных следует добавить новую строку на подписчик. Поэтому UPDATE преобразуется в INSERT.

В Таблице 1 обобщенно представлены применяемые преобразования.

Таблица 1. Краткая сводка по преобразованию UPDATE

Старая строка | Новая строка | Преобразование
не соответствует | не соответствует | не реплицировать
не соответствует | соответствует | INSERT
соответствует | не соответствует | DELETE
соответствует | соответствует | UPDATE

Партиционированные таблицы

Если публикация содержит партиционированную таблицу, параметр публикации publish_via_partition_root определяет, какой фильтр строк использовать. Если publish_via_partition_root имеет значение true, используется фильтр строк корневой партиционированной таблицы. В противном же случае, если publish_via_partition_root имеет значение false (по умолчанию), для каждой партиции используется ее собственный фильтр строк.

Начальная синхронизация данных

Если подписка требует копирования уже существующих данных таблицы, а публикация содержит предложения WHERE, на подписчик копируются только те данные, которые удовлетворяют выражениям фильтров строк.

Если у подписки имеется несколько публикаций, в которых одна таблица была опубликована с разными предложениями WHERE, будут скопированы строки, удовлетворяющие любому из выражений. Подробную информацию см. в подразделе Объединение нескольких фильтров строк.

ПРЕДУПРЕЖДЕНИЕ
Поскольку при начальной синхронизации данных, когда копируются существующие данные таблиц, не учитывается параметр publish, могут быть скопированы строки, которые не реплицировались бы при выполнении команд DML. Примеры см. в подразделах Начальный снимок и Примеры списков столбцов.

Примечание
Если версия подписчика ниже 1.5.2, то при копировании существующих данных фильтры строк не используются, даже если они определены в публикации. Это связано с тем, что старые версии могут копировать данные таблицы только полностью.

Объединение нескольких фильтров строк

Если у подписки имеется несколько публикаций, в которых одна и та же таблица была опубликована с разными фильтрами строк (для одной и той же операции publish), эти выражения объединяются вместе логическим сложением, так что реплицироваться будут строки, удовлетворяющие любому из выражений. Это означает, что все остальные фильтры строк для той же таблицы становятся избыточными, если:

У одной из публикаций нет фильтра строк.

Одна из публикаций была создана с использованием FOR ALL TABLES. Это предложение не допускает применения фильтров строк.

Одна из публикаций была создана с использованием FOR TABLES IN SCHEMA, и вышеупомянутая таблица принадлежит указанной схеме. Это предложение не допускает применения фильтров строк.

Примеры фильтров строк

Создайте несколько таблиц для использования в следующих примерах.

test_pub=# CREATE TABLE t1(a int, b int, c text, PRIMARY KEY(a,c));
CREATE TABLE
test_pub=# CREATE TABLE t2(d int, e int, f int, PRIMARY KEY(d));
CREATE TABLE
test_pub=# CREATE TABLE t3(g int, h int, i int, PRIMARY KEY(g));
CREATE TABLE

Создайте несколько публикаций. В публикации p1 имеется одна таблица (t1), и у этой таблицы есть фильтр строк. В публикации p2 имеются две таблицы. У таблицы t1 нет фильтра строк, а у таблицы t2 — есть. В публикации p3 имеется две таблицы, и у обеих есть фильтр строк.

test_pub=# CREATE PUBLICATION p1 FOR TABLE t1 WHERE (a > 5 AND c = 'NSW');
CREATE PUBLICATION
test_pub=# CREATE PUBLICATION p2 FOR TABLE t1, t2 WHERE (e = 99);
CREATE PUBLICATION
test_pub=# CREATE PUBLICATION p3 FOR TABLE t2 WHERE (d = 10), t3 WHERE (g = 10);
CREATE PUBLICATION

Чтобы вывести выражения фильтров строк (если определены) для каждой публикации, можно воспользоваться утилитой psql.

test_pub=# \dRp+
                               Publication p1
  Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
--------+------------+---------+---------+---------+-----------+----------
 qhb    | f          | t       | t       | t       | t         | f
Tables:
    "public.t1" WHERE ((a > 5) AND (c = 'NSW'::text))

                               Publication p2
  Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
--------+------------+---------+---------+---------+-----------+----------
 qhb    | f          | t       | t       | t       | t         | f
Tables:
    "public.t1"
    "public.t2" WHERE (e = 99)

                               Publication p3
  Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
--------+------------+---------+---------+---------+-----------+----------
 qhb    | f          | t       | t       | t       | t         | f
Tables:
    "public.t2" WHERE (d = 10)
    "public.t3" WHERE (g = 10)

Также с помощью psql можно вывести выражения фильтров строк (если определены) для каждой таблицы. Обратите внимание, что таблица t1 является членом двух публикаций, но имеет фильтр строк только в p1, а таблица t2 является членом двух публикаций и имеет разные фильтры строк для каждой из них.

test_pub=# \d t1
                 Table "public.t1"
 Column |  Type   | Collation | Nullable | Default
--------+---------+-----------+----------+---------
 a      | integer |           | not null |
 b      | integer |           |          |
 c      | text    |           | not null |
Indexes:
    "t1_pkey" PRIMARY KEY, btree (a, c)
Publications:
    "p1" WHERE ((a > 5) AND (c = 'NSW'::text))
    "p2"

test_pub=# \d t2
                 Table "public.t2"
 Column |  Type   | Collation | Nullable | Default
--------+---------+-----------+----------+---------
 d      | integer |           | not null |
 e      | integer |           |          |
 f      | integer |           |          |
Indexes:
    "t2_pkey" PRIMARY KEY, btree (d)
Publications:
    "p2" WHERE (e = 99)
    "p3" WHERE (d = 10)

test_pub=# \d t3
                 Table "public.t3"
 Column |  Type   | Collation | Nullable | Default
--------+---------+-----------+----------+---------
 g      | integer |           | not null |
 h      | integer |           |          |
 i      | integer |           |          |
Indexes:
    "t3_pkey" PRIMARY KEY, btree (g)
Publications:
    "p3" WHERE (g = 10)

Создайте на узле-подписчике таблицу t1 с тем же определением, что и на публикаторе, а также создайте подписку s1, которая подписана на публикацию p1.

test_sub=# CREATE TABLE t1(a int, b int, c text, PRIMARY KEY(a,c));
CREATE TABLE
test_sub=# CREATE SUBSCRIPTION s1
test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=s1'
test_sub-# PUBLICATION p1;
CREATE SUBSCRIPTION

Вставьте несколько строк. Реплицируются только строки, удовлетворяющие предложению t1 WHERE публикации p1.

test_pub=# INSERT INTO t1 VALUES (2, 102, 'NSW');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES (3, 103, 'QLD');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES (4, 104, 'VIC');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES (5, 105, 'ACT');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES (6, 106, 'NSW');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES (7, 107, 'NT');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES (8, 108, 'QLD');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES (9, 109, 'NSW');
INSERT 0 1

test_pub=# SELECT * FROM t1;
 a |  b  |  c
---+-----+-----
 2 | 102 | NSW
 3 | 103 | QLD
 4 | 104 | VIC
 5 | 105 | ACT
 6 | 106 | NSW
 7 | 107 | NT
 8 | 108 | QLD
 9 | 109 | NSW
(8 rows)

test_sub=# SELECT * FROM t1;
 a |  b  |  c
---+-----+-----
 6 | 106 | NSW
 9 | 109 | NSW
(2 rows)

Измените некоторые данные, в которых старое и новое значения соответствуют предложению t1 WHERE публикации p1. Операция UPDATE реплицирует эти изменения как обычно.

test_pub=# UPDATE t1 SET b = 999 WHERE a = 6;
UPDATE 1

test_pub=# SELECT * FROM t1;
 a |  b  |  c
---+-----+-----
 2 | 102 | NSW
 3 | 103 | QLD
 4 | 104 | VIC
 5 | 105 | ACT
 7 | 107 | NT
 8 | 108 | QLD
 9 | 109 | NSW
 6 | 999 | NSW
(8 rows)

test_sub=# SELECT * FROM t1;
 a |  b  |  c
---+-----+-----
 9 | 109 | NSW
 6 | 999 | NSW
(2 rows)

Измените некоторые данные, в которых старые значения строк не удовлетворяют предложению t1 WHERE публикации p1, а новые значения строк — удовлетворяют. Операция UPDATE преобразуется в INSERT, и уже эти изменения реплицируются. Обратите внимание на новую строку на подписчике.

test_pub=# UPDATE t1 SET a = 555 WHERE a = 2;
UPDATE 1

test_pub=# SELECT * FROM t1;
  a  |  b  |  c
-----+-----+-----
   3 | 103 | QLD
   4 | 104 | VIC
   5 | 105 | ACT
   7 | 107 | NT
   8 | 108 | QLD
   9 | 109 | NSW
   6 | 999 | NSW
 555 | 102 | NSW
(8 rows)

test_sub=# SELECT * FROM t1;
  a  |  b  |  c
-----+-----+-----
   9 | 109 | NSW
   6 | 999 | NSW
 555 | 102 | NSW
(3 rows)

Измените некоторые данные, в которых старые значения строк удовлетворяют предложению t1 WHERE публикации p1, а новые значения строк — не удовлетворяют. Операция UPDATE преобразуется в DELETE, и уже эти изменения реплицируются. Обратите внимание, что строка была удалена с подписчика.

test_pub=# UPDATE t1 SET c = 'VIC' WHERE a = 9;
UPDATE 1

test_pub=# SELECT * FROM t1;
  a  |  b  |  c
-----+-----+-----
   3 | 103 | QLD
   4 | 104 | VIC
   5 | 105 | ACT
   7 | 107 | NT
   8 | 108 | QLD
   6 | 999 | NSW
 555 | 102 | NSW
   9 | 109 | VIC
(8 rows)

test_sub=# SELECT * FROM t1;
  a  |  b  |  c
-----+-----+-----
   6 | 999 | NSW
 555 | 102 | NSW
(2 rows)

Следующие примеры показывают, как параметр публикации publish_via_partition_root определяет, какой фильтр строк будет использоваться в случае партиционированных таблиц: родительской таблицы или дочерней.

Создайте партиционированную таблицу на публикаторе.

test_pub=# CREATE TABLE parent(a int PRIMARY KEY) PARTITION BY RANGE(a);
CREATE TABLE
test_pub=# CREATE TABLE child PARTITION OF parent DEFAULT;
CREATE TABLE

Создайте такие же таблицы на подписчике.

test_sub=# CREATE TABLE parent(a int PRIMARY KEY) PARTITION BY RANGE(a);
CREATE TABLE
test_sub=# CREATE TABLE child PARTITION OF parent DEFAULT;
CREATE TABLE

Создайте публикацию p4, а затем подпишитесь на нее. В параметре публикации publish_via_partition_root установлено значение true. При этом фильтры строк определены как для партиционированной таблице (parent), так и для партиции (child).

test_pub=# CREATE PUBLICATION p4 FOR TABLE parent WHERE (a < 5), child WHERE (a >= 5)
test_pub-# WITH (publish_via_partition_root=true);
CREATE PUBLICATION

test_sub=# CREATE SUBSCRIPTION s4
test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=s4'
test_sub-# PUBLICATION p4;
CREATE SUBSCRIPTION

Вставьте несколько значений непосредственно в таблицы parent и child. Они реплицируются с использованием фильтра строк parent (потому что параметр publish_via_partition_root имеет значение true).

test_pub=# INSERT INTO parent VALUES (2), (4), (6);
INSERT 0 3
test_pub=# INSERT INTO child VALUES (3), (5), (7);
INSERT 0 3

test_pub=# SELECT * FROM parent ORDER BY a;
 a
---
 2
 3
 4
 5
 6
 7
(6 rows)

test_sub=# SELECT * FROM parent ORDER BY a;
 a
---
 2
 3
 4
(3 rows)

Повторите тот же тест, но с другим значением для publish_via_partition_root. Теперь в параметре публикации publish_via_partition_root установлено значение false. Фильтр строк определяется для партиции (child).

test_pub=# DROP PUBLICATION p4;
DROP PUBLICATION
test_pub=# CREATE PUBLICATION p4 FOR TABLE parent, child WHERE (a >= 5)
test_pub-# WITH (publish_via_partition_root=false);
CREATE PUBLICATION

test_sub=# ALTER SUBSCRIPTION s4 REFRESH PUBLICATION;
ALTER SUBSCRIPTION

Выполните на публикаторе те же операции вставки, что и раньше. Они реплицируются с использованием фильтра строк child (потому что publish_via_partition_root имеет значение false).

test_pub=# TRUNCATE parent;
TRUNCATE TABLE
test_pub=# INSERT INTO parent VALUES (2), (4), (6);
INSERT 0 3
test_pub=# INSERT INTO child VALUES (3), (5), (7);
INSERT 0 3

test_pub=# SELECT * FROM parent ORDER BY a;
 a
---
 2
 3
 4
 5
 6
 7
(6 rows)

test_sub=# SELECT * FROM child ORDER BY a;
 a
---
 5
 6
 7
(3 rows)

Списки столбцов

Для каждой публикации можно дополнительно указать, какие столбцы каждой таблицы реплицируются на подписчики. При этом таблица на стороне подписчика должна иметь по меньшей мере все публикуемые столбцы. Если список столбцов не задан, то реплицируются все столбцы на публикаторе. Подробную информацию по синтаксису см. на справочной странице команды CREATE PUBLICATION.

Выбор столбцов может быть обусловлен соображениями функциональности или производительности. Однако не стоит полагаться на эту функциональность в отношении безопасности: злоумышленник на стороне подписчика способен получить данные из столбцов которые не были опубликованы явно. Если речь идет о безопасности, защиту следует применять на стороне публикатора.

Если список столбцов не задан, все столбцы, добавляемые позднее, автоматически реплицируются. Это означает, что наличие списка, включающего имена всех столбцов, не равнозначно его полному отсутствию.

Список столбцов может содержать только простые ссылки на столбцы. Порядок столбцов в этом списке не сохраняется.

Указание списка столбцов, когда публикация также публикует все таблицы в схеме (FOR TABLES IN SCHEMA), не поддерживается.

Для партиционированных таблиц параметр публикации publish_via_partition_root определяет, какой список столбцов использовать. Если publish_via_partition_root имеет значение true, используется список столбцов корневой партиционированной таблицы. В противном случае, если publish_via_partition_root имеет значение false (по умолчанию), для каждой партиции используется ее собственный список столбцов.

Если публикация публикует операции UPDATE или DELETE, каждый список столбцов должен включать столбцы идентификатора реплики для этой таблицы (см. REPLICA IDENTITY). Если публикация публикует только операции INSERT, то столбцы идентификатора реплики в списке столбцов могут отсутствовать.

Списки столбцов не влияют на команду TRUNCATE.

Во время начальной синхронизации данных копируются только публикуемые столбцы. Однако если подписчик имеет версию ниже 1.5.2, то во время начальной синхронизации данных копируются все столбцы таблицы, игнорируя любые списки столбцов.

ПРЕДУПРЕЖДЕНИЕ: объединение списков столбцов из нескольких публикаций
В настоящее время отсутствует поддержка подписок, охватывающих несколько публикаций, в которых одна и та же таблица была опубликована с разными списками столбцов. Команда CREATE SUBSCRIPTION не допускает создания таких подписок, но в подобную ситуацию все равно можно попасть при добавлении или изменении списков столбцов на стороне публикации после создания подписки.

Это означает, что изменение списков столбцов таблиц в публикациях, на которые уже имеются подписки, может привести к возникновению ошибок на стороне подписчика.

Если подписка затронута этой проблемой, единственный способ возобновить репликацию — изменить один из списков столбцов на стороне публикации, чтобы они все совпадали; а затем либо заново создать подписку, либо воспользоваться ALTER SUBSCRIPTION ... DROP PUBLICATION, чтобы удалить одну из проблемных публикаций и снова ее добавить

Примеры списков столбцов

Создайте таблицу t1 для использования в следующем примере.

test_pub=# CREATE TABLE t1(id int, a text, b text, c text, d text, e text, PRIMARY KEY(id));
CREATE TABLE

Создайте публикацию p1. Для таблицы t1 определяется список столбцов, призванный уменьшить количество столбцов, которые будут реплицироваться. Обратите внимание, что порядок имен столбцов в этом списке не имеет значения.

test_pub=# CREATE PUBLICATION p1 FOR TABLE t1 (id, b, a, d);
CREATE PUBLICATION

Чтобы вывести списки столбцов (если определены) для каждой публикации, можно воспользоваться утилитой psql.

test_pub=# \dRp+
                               Publication p1
  Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
--------+------------+---------+---------+---------+-----------+----------
  qhb   | f          | t       | t       | t       | t         | f
Tables:
    "public.t1" (id, a, b, d)

Также с помощью psql можно вывести списки столбцов (если определены) для каждой таблицы.

test_pub=# \d t1
                 Table "public.t1"
 Column |  Type   | Collation | Nullable | Default
--------+---------+-----------+----------+---------
 id     | integer |           | not null |
 a      | text    |           |          |
 b      | text    |           |          |
 c      | text    |           |          |
 d      | text    |           |          |
 e      | text    |           |          |
Indexes:
    "t1_pkey" PRIMARY KEY, btree (id)
Publications:
    "p1" (id, a, b, d)

На узле-подписчике создайте таблицу t1, которой теперь требуется только подмножество столбцов, содержавшихся в таблице t1 публикатора, а также создайте подписку s1, оформленную на публикацию p1.

test_sub=# CREATE TABLE t1(id int, b text, a text, d text, PRIMARY KEY(id));
CREATE TABLE
test_sub=# CREATE SUBSCRIPTION s1
test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=s1'
test_sub-# PUBLICATION p1;
CREATE SUBSCRIPTION

На узле-публикаторе вставьте в таблицу t1 несколько строк.

test_pub=# INSERT INTO t1 VALUES(1, 'a-1', 'b-1', 'c-1', 'd-1', 'e-1');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES(2, 'a-2', 'b-2', 'c-2', 'd-2', 'e-2');
INSERT 0 1
test_pub=# INSERT INTO t1 VALUES(3, 'a-3', 'b-3', 'c-3', 'd-3', 'e-3');
INSERT 0 1
test_pub=# SELECT * FROM t1 ORDER BY id;
 id |  a  |  b  |  c  |  d  |  e
----+-----+-----+-----+-----+-----
  1 | a-1 | b-1 | c-1 | d-1 | e-1
  2 | a-2 | b-2 | c-2 | d-2 | e-2
  3 | a-3 | b-3 | c-3 | d-3 | e-3
(3 rows)

Реплицируются только данные из списка столбцов публикации p1.

test_sub=# SELECT * FROM t1 ORDER BY id;
 id |  b  |  a  |  d
----+-----+-----+-----
  1 | b-1 | a-1 | d-1
  2 | b-2 | a-2 | d-2
  3 | b-3 | a-3 | d-3
(3 rows)

Конфликты

Логическая репликация ведет себя подобно обычным операциям DML в том аспекте, что данные будут обновлены, даже если они были изменены локально на узле-подписчике. Если входящие данные нарушают какие-либо ограничения, репликация остановится. Это называется конфликтом. При репликации операций UPDATE или DELETE отсутствие данных не спровоцирует конфликт, и такие операции просто будут пропускаться.

Операции логической репликации выполняются с правами роли, владеющей подпиской. Отсутствие прав доступа к целевым таблицам вызовет конфликты репликации, как и включенная для целевых таблиц защита на уровне строк, применяемая к владельцу подписки, независимо от того, запретила бы какая-нибудь политика в обычной ситуации реплицируемые операции INSERT, UPDATE, DELETE или TRUNCATE. Это ограничение, касающееся защиты на уровне строк, может быть убрано в будущих версиях QHB.

Конфликт вызовет ошибку и остановит репликацию; разрешать его пользователь должен вручную. Подробную информацию о конфликте можно найти в журнале сервера подписчика.

Разрешить конфликт можно либо изменив данные на подписчике, чтобы они не конфликтовали с входящими изменениями, либо пропустив транзакцию, конфликтующую с существующими данными. Когда конфликт вызывает ошибку, репликация останавливается, а рабочий процесс логической репликации выведет в журнал сервера подписчика сообщение следующего вида:

ERROR:  duplicate key value violates unique constraint "test_pkey"
DETAIL:  Key (c)=(1) already exists.
CONTEXT:  processing remote data for replication origin "qhb_16395" during "INSERT" for replication target relation "public.test" in transaction 725 finished at 0/14C0378
-- ОШИБКА:  дублирующееся значение ключа нарушает ограничение уникальности "test_pkey"
-- ДЕТАЛИЗАЦИЯ:  Ключ (c)=(1) уже существует.
-- КОНТЕКСТ:  обработка удаленных данных для источника репликации "qhb_16395" во время операции "INSERT" для целевого отношения репликации "public.test" в транзакции 725 закончилась на 0/14C0378

LSN транзакции, содержащей изменение, которое нарушает ограничение, а также имя источника репликации можно найти в журнале сервера (в примере выше это LSN 0/14C0378 и источник репликации qhb_16395). Транзакцию, вызвавшую конфликт, можно пропустить с помощью команды ALTER SUBSCRIPTION ... SKIP с конечным LSN (т. е. LSN 0/14C0378). Конечным LSN может быть LSN, с которым транзакция фиксируется или подготавливается на публикаторе. Как вариант, транзакцию можно пропустить, вызвав функцию pg_replication_origin_advance(). Прежде чем воспользоваться этой функцией, подписку следует либо временно отключить командой ALTER SUBSCRIPTION ... DISABLE, либо использовать подписку с параметром disable_on_error. Затем можно вызвать функцию pg_replication_origin_advance() с именем_узла (т. е. qhb_16395) и LSN, следующим за конечным (т. е. 0/14C0379). Текущую позицию источников можно посмотреть в системном представлении pg_replication_origin_status. Пожалуйста, обратите внимание, что при пропуске целой транзакции пропускаются и те изменения, которые могли не нарушать никаких ограничений. Это легко может привести к несогласованности подписчика.

Ограничения

В настоящее время логическая репликация имеет следующие ограничения и недостатки функциональности. Они могут быть устранены в будущих выпусках.

Схема базы данных и команды DDL не реплицируются. Изначальную схему можно скопировать вручную командой qhb_dump --schema-only. Последующие изменения схемы нужно будет синхронизировать вручную. (Однако обратите внимание, что схемам необязательно быть абсолютно идентичными на обеих сторонах.) Когда определения схемы меняются в активной базе данных, логическая репликация устойчива к ошибкам. Когда схема меняется на публикаторе, и реплицированные данные начинают прибывать на сторону подписчика, но не вписываются в схему таблицы, репликация будет выдавать ошибку, пока схема не обновится. Во многих случаях случайных ошибок можно избежать, применяя дополняющие изменения схемы сначала на подписчике.

Данные последовательностей не реплицируются. Данные в столбцах serial или столбцах идентификации, выдаваемые последовательностями, разумеется, будут реплицированы как часть таблицы, но сама последовательность на подписчике все равно будет демонстрировать исходное значение. Если подписчик используется как база данных только для чтения, обычно это не представляет проблемы. Однако если предполагается, что на базу-подписчик будут переключаться в работе или при отказе, то последовательности нужно обновить до текущих значений, либо скопировав текущие данные из публикатора (вероятно, с помощью qhb_dump), либо выбрав достаточно большое значение из самих таблиц.

Репликация команд TRUNCATE поддерживается, но при опустошении групп таблиц, соединенных внешними ключами, следует соблюдать осторожность. При репликации операции опустошения подписчик опустошит ту же группу таблиц, которая была опустошена на публикаторе (явным указанием или неявно собрав их посредством CASCADE), за исключением таблиц, не входящих в подписку. Это сработает корректно, если все затронутые таблицы входят в одну подписку. Но если у некоторых опустошаемых таблиц на подписчике имеются связи по внешнему ключу с таблицами, не входящими в ту же (или любую другую) подписку, то применение операции опустошения на подписчике завершится ошибкой.

Большие объекты (см. главу Большие объекты) не реплицируются. Это никак нельзя обойти, кроме как хранить данные в обычных таблицах.

Репликация поддерживается только для таблиц, включая партиционированные. Попытки реплицировать другие типы отношений, например представления, материализованные представления или сторонние таблицы, приведут к ошибке.

При репликации между партиционированными таблицами по умолчанию фактическая репликация производится из листовых партиций на публикаторе, поэтому партиции с публикатора должны существовать и на подписчике, в виде допустимых целевых таблиц. (Они тоже могут быть листовыми партициями, могут дальше разбиваться на партиции или даже могут быть независимыми таблицами.) В публикациях также может быть указано, что изменения будут реплицироваться с использованием идентификаторов и схемы корневой партиционированной таблицы, а не отдельных листовых партиций, в которых фактически произошли изменения (см. справочную страницу команды CREATE PUBLICATION).

Архитектура

Логическая репликация начинается с копирования снимка данных в базе-публикаторе. После этого изменения на публикаторе передаются подписчику тогда, когда они происходят, в реальном времени. Подписчик применяет данные в том порядке, в котором на публикаторе происходят фиксации, поэтому для публикаций в рамках одной подписки гарантируется транзакционная согласованность.

Логическая репликация собирается с архитектурой, схожей с физической потоковой репликацией (см. подраздел Потоковая репликация). Это реализуется процессами «walsender» (передача WAL) и «apply» (применение). Процесс walsender запускает логическое декодирование (описанное в главе Логическое декодирование) WAL и загружает стандартный плагин логического декодирования (pgoutput). Этот плагин преобразует изменения, считанные из WAL, в протокол логической репликации (см. раздел Протокол логической потоковой репликации) и отфильтровывает данные согласно спецификации публикации. Затем данные последовательно передаются по протоколу потоковой репликации рабочему процессу apply, которые сопоставляет их с локальными таблицами и применяет отдельные изменения по мере их поступления, в правильном транзакционном порядке.

Процесс apply в базе-подписчике всегда выполняется с параметром session_replication_role, установленным в значение replica. Это означает, что по умолчанию триггеры и правила не будут запускаться на подписчике. При желании пользователи могут включить триггеры и правила для таблицы, выполнив команду ALTER TABLE с предложениями ENABLE TRIGGER и ENABLE RULE.

В настоящее время процесс применения логической репликации запускает только триггеры уровня строк, но не триггеры уровня операторов. Однако начальная синхронизация таблицы реализуется как команда COPY и поэтому запускает для INSERT как триггеры уровня строк, так и триггеры уровня операторов.

Начальный снимок

Мгновенное состояние исходных данных существующих таблиц в подписке фиксируется в снимке, а затем они копируются в параллельном экземпляре процесса apply особого вида. Этот процесс создаст собственный слот репликации и скопирует существующие данные. Как только копирование завершится, содержимое таблицы станет видимым для других серверных процессов. Когда существующие данные будут скопированы, рабочий процесс перейдет в режим синхронизации, чтобы таблица пришла в синхронизированное состояние с основным процессом apply путем потоковой передачи всех изменений, произошедших во время начального копирования данных, используя стандартную логическую репликацию. Во время этой фазы синхронизации изменения применяются и фиксируются в том же порядке, в котором они произошли на публикаторе. По завершении синхронизации управление репликацией таблицы передается обратно основному процессу apply, в котором репликация продолжатся в обычном режиме.

Примечание
Параметр публикации publish влияет только на то, какие операции DML будут реплицироваться. Он не учитывается при начальной синхронизации данных, когда происходит копирование существующих данных таблиц.

Мониторинг

Поскольку логическая репликация основана на архитектуре, схожей с физической потоковой репликацией, мониторинг узла публикации похож на мониторинг основного сервера физической репликации (см. подраздел Мониторинг).

Данные мониторинга о подписке можно увидеть в pg_stat_subscription. Это представление содержит по одной строке для каждого рабочего процесса подписки. В зависимости от состояния подписки, у нее может быть ноль или более активных рабочих процессов.

Обычно для включенной подписки выполняется один процесс apply. Для выключенной или поврежденной подписки в этом представлении будет содержаться ноль строк. Если для каких-либо таблиц выполняется начальная синхронизация данных, будут отображаться дополнительные рабочие процессы для синхронизируемых таблиц.

Безопасность

Пользователь, имеющий возможность модифицировать схему таблиц на стороне подписчика, может выполнить произвольный код как суперпользователь. Назначайте владение и право TRIGGER для таких таблиц только тем ролям, которым доверяют суперпользователи. Более того, если недоверенные пользователи могут создавать таблицы, используйте только те публикации, где явно перечислен список таблиц. Иначе говоря, создавайте подписку FOR ALL TABLES (для всех таблиц), только когда суперпользователи доверяют всем пользователям, которым разрешено создавать не временные таблицы на публикаторе или подписчике.

Роль, используемая для подключения репликации, должна иметь атрибут REPLICATION (или быть суперпользователем). Если у роли нет атрибутов SUPERUSER и BYPASSRLS, на публикаторе могут выполняться политики защиты строк. Если эта роль не доверяет всем владельцам таблиц, добавьте в строку подключения options= -crow_security=off; если впоследствии владелец таблицы добавит политику защиты строк, такое значение параметра остановит репликацию, но не даст политике выполниться. Доступ для этой роли следует сконфигурировать в qhb_hba.conf, и у нее должен быть атрибут LOGIN.

Чтобы иметь возможность скопировать исходные данные таблицы, роль, используемая для подключения репликации, должна иметь право SELECT для публикуемой таблицы (или быть суперпользователем).

Чтобы создать публикацию, пользователь должен иметь право CREATE в базе данных.

Чтобы добавлять в публикацию таблицы, пользователь должен иметь права владельца для этих таблиц. Добавлять в публикацию все таблицы в схеме, могут только суперпользователи. Создавать публикации, автоматически публикующие все таблицы или все таблицы в схеме, могут только суперпользователи.

Создавать подписки могут только суперпользователи.

Процесс apply подписки будет выполняться в локальной базе данных с правами владельца этой подписки.

На публикаторе права проверяются только один раз при запуске подключения репликации и не перепроверяются при чтении каждой записи изменения.

На подписчике права владельца подписки перепроверяются для каждой транзакции при ее применении. Если рабочий процесс выполняет применение транзакции, когда в параллельной транзакции меняется владелец подписки, применение текущей транзакции продолжится с правами старого владельца.

Параметры конфигурации

Для логической репликации нужно установить несколько параметров конфигурации.

На стороне публикатора следует установить для параметра wal_level значение logical, а для параметра max_replication_slots — число не меньше ожидаемого количества подключающихся подписок плюс некоторый резерв для синхронизации таблиц. А для параметра max_wal_senders следует установить число не меньше max_replication_slots плюс количество физических реплик, подключающихся одновременно.

На стороне подписчика тоже необходимо установить параметр max_replication_slots. В нем следует задать как минимум количество подписок, которые будут добавлены подписчику, плюс некоторый резерв для синхронизации таблиц. В параметре max_logical_replication_workers нужно задать как минимум количество подписок и, опять же, плюс некоторый резерв для синхронизации таблиц. Кроме того, может потребоваться скорректировать параметр max_worker_processes, чтобы это число включало рабочие процессы репликации, как минимум (max_logical_replication_workers + 1). Обратите внимание, что некоторые расширения и параллельные запросы тоже занимают слоты рабочих процессов из числа max_worker_processes.

Быстрая настройка

Сначала установите параметры конфигурации в qhb.conf:

wal_level = logical

Другие необходимые параметры по умолчанию имеют значения, достаточные для базовой настройки.

Файл qhb_hba.conf нужно скорректировать, чтобы разрешить репликацию (здесь значения будут зависеть от фактической конфигурации вашей сети и пользователя, от имени которого вы желаете подключаться):

host     all     repuser     0.0.0.0/0     md5

Затем в базе-публикаторе выполните:

CREATE PUBLICATION mypub FOR TABLE users, departments;

А в базе-подписчике выполните:

CREATE SUBSCRIPTION mysub CONNECTION 'dbname=foo host=bar user=repuser' PUBLICATION mypub;

Вышеуказанная команда запустит процесс репликации, который сначала синхронизирует исходное содержимое таблиц users и departments, а затем начнет реплицировать в эти таблицы инкрементальные изменения.

JIT-компиляция

Что такое JIT-компиляция?

JIT-компиляция (Just-in-time compilation, компиляция «на лету») — это процесс, преобразующий некоторую форму вычисления интерпретируемой программы в программу, оптимизированную для используемого процессора, и делающий это во время выполнения. Например, вместо использования универсального кода, способного вычислять произвольные выражения SQL, для вычисления конкретного условия SQL вроде WHERE a.col = 3, можно сгенерировать функцию, специфичную для этого выражения, которую может выполнять собственно ЦП, тем самым повышая производительность.

В QHB имеется встроенная поддержка JIT-компиляции с использованием LLVM, которая включается, если QHB собирается с ключом --with-llvm.

Операции, ускоряемые JIT-компиляцией

В настоящее время реализация JIT-компиляции в QHB поддерживает ускорение вычисления выражений и переформирование кортежей. В будущем могут быть ускорены и некоторые другие операции.

Вычисление выражений используется для вычисления предложений WHERE, целевых списков, агрегатов и проекций. Его можно ускорить, сгенерировав код, специфичный для каждого отдельного случая.

Переформирование кортежа — это процесс трансформации дискового кортежа (см. подраздел Структура строк таблицы) в его представление в памяти. Его можно ускорить, создав функцию, специфичную для определенной структуры таблицы и количества извлекаемых столбцов.

Встраивание

QHB очень гибка и позволяет определять новые типы данных, функции, операторы и другие объекты базы данных; см. главу Расширение SQL. На самом деле встроенные объекты реализуются с помощью практически тех же механизмов. Такая гибкость подразумевает некоторые издержки, например, вследствие вызовов функций (см. раздел Пользовательские функции). Чтобы снизить эти издержки, JIT-компиляция может встраивать тела маленьких функций в использующие их выражения. Это позволяет оптимизировать значительный процент издержек.

Оптимизация

В LLVM поддерживается оптимизация сгенерированного кода. Некоторые оптимизации достаточно дешевы, чтобы выполняться при каждом использовании JIT-компиляции, тогда как другие выгодны только для длительно выполняющихся запросов. Подробнее об оптимизации рассказывается здесь.

Когда применять JIT-компиляцию?

JIT-компиляция выгодна в первую очередь для длительных запросов, зависящих от быстродействия ЦП. Чаще всего это аналитические запросы. Для коротких запросов дополнительные затраты на выполнение JIT-компиляции зачастую будут превышать сэкономленное этим время.

Определение целесообразности применения JIT-компиляции отталкивается от общей оценочной стоимости запроса (см. главу Как планировщик использует статистику и подраздел Константы стоимости для планировщика). Оценочная стоимость запроса будет сравниваться со значением jit_above_cost. Если стоимость выше, JIT-компиляция будет проведена. Затем нужно принять еще два решения. Во-первых, если оценочная стоимость превышает значение jit_inline_above_cost, используемые в запросе короткие функции и операторы будут встроены в код. Во-вторых, если оценочная стоимость превышает значение jit_optimize_above_cost, для улучшения сгенерированного кода применяются дорогостоящие оптимизации. Каждый из этих вариантов увеличивает издержки JIT-компиляции, но может значительно сократить время выполнения запроса.

Эти основанные на стоимости решения будут приниматься во время планирования, а не выполнения запроса. Это означает, что при использовании подготовленных операторов и общего плана (см. PREPARE) на решения влияют значения конфигурационных параметров, действующие во время подготовки запроса, а не во время выполнения.

Примечание
Если в параметре jit установлено значение off (выключен) или реализация JIT-компиляции недоступна (например, потому что сервер был скомпилирован без --with-llvm), JIT-компиляция выполняться не будет, даже если она была бы полезна, исходя из вышеуказанных критериев. Заданное в jit значение off действует как во время планирования, так и во время выполнения.

Для того чтобы проверить, используется ли JIT-компиляция, можно воспользоваться командой EXPLAIN. Например, ниже приводится запрос, не использующий JIT-компиляцию:

=# EXPLAIN ANALYZE SELECT SUM(relpages) FROM pg_class;
                                                 QUERY PLAN
-------------------------------------------------------------------​------------------------------------------
 Aggregate  (cost=16.27..16.29 rows=1 width=8) (actual time=0.303..0.303 rows=1 loops=1)
   ->  Seq Scan on pg_class  (cost=0.00..15.42 rows=342 width=4) (actual time=0.017..0.111 rows=356 loops=1)
 Planning Time: 0.116 ms
 Execution Time: 0.365 ms
(4 rows)

Учитывая стоимость планирования, отказ от JIT был вполне обоснован; стоимость JIT-компиляции была бы выше потенциальной экономии ресурсов. Если скорректировать ограничение стоимости, JIT-компиляция будет использоваться:

=# SET jit_above_cost = 10;
SET
=# EXPLAIN ANALYZE SELECT SUM(relpages) FROM pg_class;
                                                 QUERY PLAN
-------------------------------------------------------------------​------------------------------------------
 Aggregate  (cost=16.27..16.29 rows=1 width=8) (actual time=6.049..6.049 rows=1 loops=1)
   ->  Seq Scan on pg_class  (cost=0.00..15.42 rows=342 width=4) (actual time=0.019..0.052 rows=356 loops=1)
 Planning Time: 0.133 ms
 JIT:
   Functions: 3
   Options: Inlining false, Optimization false, Expressions true, Deforming true
   Timing: Generation 1.259 ms, Inlining 0.000 ms, Optimization 0.797 ms, Emission 5.048 ms, Total 7.104 ms
 Execution Time: 7.416 ms

Как видно в этом примере, JIT-компиляция была использована, а встраивание и дорогостоящая оптимизация — нет. Они бы использовались, если бы параметры jit_inline_above_cost или jit_optimize_above_cost тоже были уменьшены.

Конфигурация

Переменная конфигурации jit определяет, активна или неактивна JIT-компиляция. Если она активна, конфигурационные переменные jit_above_cost, jit_inline_above_cost и jit_optimize_above_cost определяют, будет ли JIT-компиляция выполняться для запрос, и сколько на это будет затрачено ресурсов.

Параметр jit_provider определяет, какая реализация JIT-компиляции будет использоваться. Необходимость изменять его возникает редко. См. подраздел Подключаемые провайдеры JIT-компиляции.

Существует также несколько дополнительных параметров конфигурации, предназначенных для целей разработки и отладки; они описаны в разделе Параметры для разработчиков.

Расширяемость

Поддержка встраивания для расширений

Реализация JIT-компиляции в QHB может встраивать в код тела функций типов C и internal, а также операторов на основе этих функций. Чтобы это работало для функций в расширениях, нужно сделать доступными определения этих функций. При использовании PGXS для сборки расширения для сервера, скомпилированного с JIT-поддержкой LLVM, соответствующие файлы будут собираться и устанавливаться автоматически.

Соответствующие файлы должны устанавливаться в $pkglibdir/bitcode/$extension/, а краткая информация о них должна вноситься в $pkglibdir/bitcode/ $extension.index.bc, где $pkglibdir — это каталог, возвращаемый командой qhb_config --pkglibdir, а $extension — это базовое имя разделяемой библиотеки расширения.

Примечание
Для функций, встроенных в саму QHB, двоичный код устанавливается в $pkglibdir/bitcode/qhb.

Подключаемые провайдеры JIT-компиляции

QHB предоставляет реализацию JIT-компиляции на базе LLVM. Интерфейс провайдера JIT-компиляции позволяет динамически подключать его и менять без перекомпиляции (хотя в настоящее время процесс сборки предоставляет данные для поддержки встраивания только для LLVM). Активный провайдер выбирается посредством параметра jit_provider.

Интерфейс провайдера JIT-компиляции

Провайдер JIT-компиляции загружается путем динамической загрузки указанной разделяемой библиотеки. Для ее нахождения используется обычный путь поиска библиотек. Чтобы предоставить требуемые функции обратного вызова провайдера JIT-компиляции и показать, что эта библиотека действительно является провайдером JIT, она должна иметь функцию на C с именем _PG_jit_provider_init. Этой функции передается структура, которую нужно заполнить указателями на функции обратного вызова для определенных действий:

struct JitProviderCallbacks
{
    JitProviderResetAfterErrorCB reset_after_error;
    JitProviderReleaseContextCB release_context;
    JitProviderCompileExprCB compile_expr;
};

extern void _PG_jit_provider_init(JitProviderCallbacks *cb);

Регрессионные тесты

Регрессионные тесты представляют собой исчерпывающий набор проверок реализации SQL в QHB. Они тестируют как стандартные операции SQL, так и расширенные возможности QHB.

Выполнение тестов

Регрессионные тесты можно выполнять на уже установленном и работающем сервере либо используя временную инсталляцию внутри дерева сборки. Более того, существуют «параллельный» и «последовательный» режимы выполнения тестов. Последовательный метод выполняет каждый тестовый скрипт отдельно, тогда как параллельный метод запускает несколько серверных процессов, чтобы выполнить группы тестов параллельно. Параллельное тестирование добавляет уверенности, что межпроцессное взаимодействие и блокировки работают корректно.

Выполнение тестов на временной инсталляции

Чтобы запустить параллельные регрессионные тесты после сборки, но до установки, введите:

make check

в каталоге верхнего уровня. (Или можно перейти в /usr/local/qhb/test/regress и выполнить команду там.) По завершении вы должны увидеть что-то вроде:

=======================
 All 193 tests passed.
=======================

или примечание о тестах, в которых произошел сбой. Прежде чем предположить, что «сбой» представляет серьезную проблему, ознакомьтесь с разделом Оценка тестирования ниже.

Поскольку этот метод тестирования выполняется на временном сервере, он не будет работать, если вы выполнили сборку под пользователем root, так как этот сервер не запустится от имени root. Рекомендуется не делать сборку из-под root или же проводить тестирование после завершения установки.

Если вы сконфигурировали QHB для установки в месте, где уже существует предыдущая инсталляция QHB, и вы выполняете make check до установки новой версии, можно столкнуться с тем, что тесты завершаются сбоем, поскольку новые программы пытаются использовать уже установленные разделяемые библиотеки. (Типичные симптомы — жалобы на неопределенные символы.) Если вы хотите выполнить тестирование до перезаписи старой инсталляции, вам понадобится проводить сборку с configure --disable-rpath. Однако этот вариант не рекомендуется использовать для окончательной инсталляции.

Параллельное регрессионное тестирование запускает довольно много процессов под вашим идентификатором пользователя. В настоящее время максимум параллельной обработки составляет двадцать параллельных тестовых скриптов, что означает сорок процессов: это серверный процесс и процесс psql для каждого тестового скрипта. Поэтому если ваша система устанавливает ограничение на количество процессов для каждого пользователя, позаботьтесь о том, чтобы этот предел составлял не меньше пятидесяти процессов или около того, иначе в параллельном тестировании можно получить кажущиеся случайными сбои. Если вы не имеете права увеличить это предел, можно снизить степень параллелизма, установив параметр MAX_CONNECTIONS. Например:

make MAX_CONNECTIONS=10 check

выполняет параллельно не больше десяти тестов.

Выполнение тестов на существующей инсталляции

Чтобы запустить тесты после установки (см. главу Установка), инициализируйте каталог данных и запустите сервер, как показано в главе Настройка и эксплуатация сервера, затем введите:

make installcheck

или для параллельного тестирования:

make installcheck-parallel

Ожидается, что тесты будут соединяться с сервером на локальном хосте с номером порта по умолчанию, если иное не задано переменными среды PGHOST и PGPORT. Тесты будут выполняться в базе данных с именем regression; любая существующая база данных с таким именем будет удалена.

Также тесты будут на короткое время создавать некоторые объекты на уровне кластера, такие как роли, табличные пространства и подписки. Имена этих объектов будут начинаться с regress_. Остерегайтесь использовать режим installcheck в инсталляции, где такие имена могут иметь существующие глобальные объекты.

Дополнительные комплекты тестов

Команды make check и make installcheck запускают только «базовые» регрессионные тесты, которые проверяют встроенную функциональность сервера QHB. Исходный пакет содержит множество дополнительных комплектов тестов, большая часть которых касается дополнительной функциональности, например, дополнительных процедурных языков.

Чтобы запустить все комплекты тестов (включая базовые), применимые к модулям, выбранным для сборки, введите одну из этих команд на самом верху дерева сборки:

make check-world
make installcheck-world

Эти команды запускают тесты, используя временные серверы или уже установленный сервер соответственно, аналогично вышеописанным командам make check и make installcheck. Остальные аспекты также аналогичны изложенным выше для каждого метода. Обратите внимание, что make check-world собирает отдельный экземпляр (временный каталог данных) для каждого модуля, поэтому ей требуется больше времени и дискового пространства, чем make installcheck-world.

На современном компьютере со множеством процессорных ядер и без жестких ограничений операционной системы тестирование можно значительно ускорить с помощью распараллеливания. Способ, которым на практике пользуются большинство разработчиков QHB для выполнения всех тестов, выглядит примерно так:

make check-world -j8 >/dev/null

где ограничение -j примерно равно или чуть больше количества доступных ядер. Отказ от вывода в stdout убирает избыток сообщений, которые не представляют интереса, когда вы хотите просто убедиться в успешности тестирования. (В случае ошибки сообщений в stderr обычно достаточно, чтобы определить, где искать проблему.)

Как вариант, вы можете запускать отдельные комплекты тестов, введя make check или make installcheck в подходящий подкаталог дерева сборки. Имейте в виду, что make installcheck предполагает, что вы уже установили соответствующие модули, а не только основной сервер.

Дополнительные тесты, которые можно выбрать таким способом:

Регрессионные тесты для дополнительных процедурных языков. Эти тесты расположены в каталоге /usr/local/qhb/pl.

Регрессионные тесты для модулей contrib, расположенные в каталоге share/extension. Не для всех модулей contrib существуют тесты.

Регрессионные тесты для библиотек интерфейса, расположенные в /usr/local/qhb/interfaces/libpq/test и /usr/local/qhb/interfaces/ecpg/test.

Тесты для поддерживаемых ядром методов аутентификации, расположенные в /usr/local/qhb/test/authentication. (Дополнительные тесты, связанные с аутентификацией, см. ниже.)

Тесты для нагрузочного тестирования параллельных сеансов, расположенные в /usr/local/qhb/test/isolation.

Тесты для восстановления после сбоя и физической репликации, расположенные в /usr/local/qhb/test/recovery.

Тесты для логической репликации, расположенные в /usr/local/qhb/test/subscription.

Тесты клиентских программ, расположенные в каталоге /usr/local/qhb/bin.

При использовании режима installcheck эти тесты будут создавать и удалять тестовые базы данных с именами, включающими слово regression, например, pl_regression или contrib_regression. Остерегайтесь использовать режим installcheck в инсталляции, где такие имена могут иметь какие-либо не тестовые базы данных.

Некоторые комплекты тестов не запускаются по умолчанию — либо потому, что их небезопасно выполнять в многопользовательской системе, либо потому, что им требуется специальное программное обеспечение. Вы можете выбрать, какие именно комплекты тестов запустить дополнительно, установив в переменной PG_TEST_EXTRA (она может быть переменной make или среды) их список через пробел, например:

make check-world PG_TEST_EXTRA='kerberos ldap ssl'

В настоящее время поддерживаются следующие значения:

kerberos
Запускает комплект тестов, проверяющих функциональность Kerberos/GSSAPI, в подкаталоге /usr/local/qhb/test/kerberos. Эти тесты требуют наличия полной инсталляции MIT Kerberos, включая инструментальные средства на стороне сервера и клиента, и открывают прослушивающие сокеты TCP/IP.

ldap
Запускает комплект тестов, проверяющих функциональность LDAP, в подкаталоге /usr/local/qhb/test/ldap. Эти тесты требуют наличия полной инсталляции OpenLDAP , включая инструментальные средства на стороне сервера и клиента. Кроме того, этот комплект тестов создает сервер LDAP, принимающий подключения по TCP/IP на localhost без какого-либо фактического управления доступом.

ssl
Запускает комплект тестов для регрессионного тестирования поддержки SSL в подкаталоге /usr/local/qhb/test/ssl. Они проверяют как функциональность на стороне клиента (т. е. сертификаты сервера), так и функциональность на стороне сервера (т. е. подтверждение сертификата). Эти тесты открывают прослушивающие сокеты TCP/IP.

wal_consistency_checking
Включает проверку wal_consistency_checking=all при выполнении определенных тестов в подкаталоге /usr/local/qhb/test/recovery. По умолчанию проверка выключена, поскольку потребляет много ресурсов.

Тесты функциональности, которая не поддерживается в текущей конфигурации сборки, не запускаются, даже если они указаны в PG_TEST_EXTRA.

Кроме того, в каталоге /usr/local/qhb/test/modules есть тесты, которые будут запускаться make check-world, но не make installcheck-world. Это связано с тем, что они устанавливают расширения, неподходящие для производственной среды, или имеют иные побочные эффекты, которые считаются нежелательными для рабочей инсталляции. При желании можно выполнить в подкаталогах с этими тестами make install и make installcheck, но делать это с не тестовым сервером не рекомендуется.

Локаль и кодировка

По умолчанию тесты, выполняющиеся на временной инсталляции, используют локаль, определенную в текущей среде, и соответствующую кодировку базы данных, выбранную во время qhb_bootstrap (или initdb). Может быть полезно протестировать различные локали, установив подходящие переменные среды, например:

make check LANG=C
make check LC_COLLATE=en_US.utf8 LC_CTYPE=fr_CA.utf8

По причинам реализации установка переменной LC_ALL для этой цели не работает; все остальные переменные среды, относящиеся к локали, работают.

При тестировании на существующей инсталляции локаль определяется существующим кластером базы данных и не может быть установлена отдельно для выполнения теста.

Также можно явно выбрать кодировку базы данных, установив переменную ENCODING, например:

make check LANG=C ENCODING=EUC_JP

Установка кодировки базы данных таким способом обычно имеет смысл только для локали C; в противном случае кодировка выбирается автоматически из локали, и указание кодировки, не соответствующей локали, приведет к ошибке.

Кодировку базы данных можно установить для тестирования как на временной, так и на существующей инсталляции, хотя в последнем случае она должна быть совместимой с локалью этой инсталляции.

Пользовательские параметры сервера

При выполнении комплекта регрессионных тестов можно использовать пользовательские параметры сервера, которые задаются в переменной среды PGOPTIONS (для параметров, которые это позволяют):

make check PGOPTIONS="-c force_parallel_mode=regress -c work_mem=50MB"

При выполнении на временной инсталляции пользовательские параметры также можно установить, предоставив предварительно написанный файл qhb.conf:

echo 'log_checkpoints = on' > test_qhb.conf
echo 'work_mem = 50MB' >> test_qhb.conf
make check EXTRA_REGRESS_OPTS="--temp-config=test_qhb.conf"

Это может быть полезно для включения дополнительного протоколирования в журнал, корректировки ограничений ресурсов или включения дополнительных проверок во время выполнения, например debug_discard_caches.

Дополнительные тесты

Комплект основных регрессионных тестов содержит несколько тестовых файлов, которые не запускаются по умолчанию, поскольку они могут зависеть от платформы или выполняться слишком. Вы можете запустить те или иные дополнительные тестовые файлы, установив переменную EXTRA_TESTS. Например, запустить тест numeric_big:

make check EXTRA_TESTS=numeric_big

Оценка тестирования

Некоторые правильно установленные и полностью функциональные инсталляции QHB могут вызывать «сбой» в некоторых из этих регрессионных тестов из-за специфических для платформы артефактов, таких как различное представление чисел с плавающей запятой и формулировка сообщений. В настоящее время тесты оцениваются с помощью простого сравнения diff с выходными данными, сгенерированными в эталонной системе, поэтому результаты чувствительны к небольшим различиям между системами. Когда сообщается о «сбое» в тесте, всегда проверяйте разницу между ожидаемыми и фактическими результатами; возможно, вы обнаружите, что разница несущественна. Тем не менее мы все же стремимся поддерживать точные эталонные файлы на всех поддерживаемых платформах, чтобы можно было ожидать прохождения всех тестов.

Актуальные результаты регрессионных тестов хранятся в файлах каталога /usr/local/qhb/test/regress/results. Тестовый скрипт использует команду diff, чтобы сравнить каждый файл результата с ожидаемыми результатами, хранящимися в каталоге /usr/local/qhb/test/regress/expected. Все различия сохраняются для проверки в файле /usr/local/qhb/test/regress/regression.diffs. (При выполнении комплекта тестов, отличного от основного, эти файлы, разумеется, появляются в соответствующем подкаталоге, а не в /usr/local/qhb/test/regress.)

Если вам не нравятся параметры, используемые в diff по умолчанию, установите переменную среды PG_REGRESS_DIFF_OPTS, например PG_REGRESS_DIFF_OPTS='-c'. (Или, если хотите, можете запустить diff самостоятельно.)

Если по какой-то причине определенная платформа генерирует «сбой» для данного теста, но проверка результата убеждает вас, что результат правильный, вы можете добавить новый файл сравнения, чтобы подавить появление отчета об ошибке при последующих выполнениях теста. Подробную информацию см. в разделе Вариантные сравнительные файлы.

Различия в сообщениях об ошибках

Некоторые регрессионные тесты предусматривают применение заведомо недопустимых входных значений. Сообщения об ошибках могут исходить как из кода QHB, так и из системных подпрограмм платформы хоста. В последнем случае сообщения могут различаться в зависимости от платформы, но должны отражать одинаковую информацию. Эти различия в сообщениях приводят к «сбою» регрессионного теста, который можно устранить при проверке.

Разница локалей

Если вы выполняете тесты на сервере, который был инициализирован с локалью с упорядочиванием, отличным от C, то здесь могут возникнуть различия, связанные с порядком сортировки, и, как следствие сбои. Для решения этой проблемы устанавливается комплект регрессионных тестов, предоставляющий альтернативные файлы результата, которые в совокупности могут справиться с большим количеством локалей.

Чтобы запустить тесты на другой локали при использовании метода тестирования на временной инсталляции, передайте в командной строке make подходящие переменные среды, относящиеся к локали, например:

make check LANG=de_DE.utf8

(Драйвер регрессионного теста обнуляет LC_ALL, поэтому выбор локали с помощью этой переменной не работает.) Чтобы не использовать локаль, либо обнулите все переменные среды, относящиеся к локали (или установите их в C), либо выполните следующую специальную команду:

make check NO_LOCALE=1

При выполнении тестов на существующей инсталляции настройки локали определяются этой инсталляцией. Чтобы изменить это, инициализируйте кластер баз данных с другой локалью, передав qhb_bootstrap (или initdb) соответствующие параметры.

В целом, рекомендуется по возможности выполнять регрессионные тесты при тех настройках локали, которые понадобятся при промышленной эксплуатации, поскольку тогда будут проверены те участки кода, относящиеся к локали и кодировке, которые действительно будут использоваться в работе. В зависимости от среды операционной системы, вы можете столкнуться со сбоями, но вы хотя бы будете знать, какого поведения локали ожидать при запуске реальных приложений.

Разница в дате и времени

Большая часть результатов проверки даты и времени зависит от часового пояса среды. Эталонные файлы генерируются для пояса PST8PDT (Беркли, Калифорния), поэтому если проводить тесты не с этим часовым поясом, появятся мнимые сбои. Драйвер регрессионного теста устанавливает в переменной среды PGTZ значение PST8PDT, что обычно гарантирует получение корректных результатов.

Разница в числах с плавающей запятой

Некоторые тесты предусматривают вычисление 64-битных чисел с плавающей запятой (double precision) из столбцов таблицы. Наблюдались различия в результатах при использовании математических функций для столбцов double precision. Тесты float8 и geometry особенно предрасположены выдавать небольшие различия между платформами или даже между настройками оптимизации компилятора. Чтобы определить реальную значимость этих отличий, которые обычно начинаются с десятого знака после запятой, нужно сравнивать их глазами.

Некоторые системы отображают минус ноль как -0, тогда как другие отображают просто 0.

Некоторые системы сигнализируют об ошибках в pow() и exp() не по тому механизму, какой ожидает текущий код QHB.

Разница в сортировке строк

Возможно, вам встретятся различия в том, что одни и те же строки выводятся в ином порядке, нежели в файле ожидаемых результатов. В большинстве случаев это не является, строго говоря, ошибкой. Большая часть скриптов регрессионных тестов не настолько педантична, чтобы использовать ORDER BY для каждой отдельной команды SELECT, и поэтому в их результате порядок строк не конкретизируется согласно спецификации SQL. На практике, поскольку мы имеем дело с одинаковыми запросами, выполняющимися для одних и тех же данных на одном и том же программном обеспечении, мы обычно получаем результаты в одинаковом порядке на всех платформах, так что отсутствие ORDER BY не является проблемой. Однако некоторые запросы действительно выявляют межплатформенные различия в сортировке. При тестировании на уже установленном сервере разница в сортировке также может быть вызвана тем, что локаль установлена не в C, или в каких-то параметрах заданы значения не по умолчанию, например, пользовательские значения в work_mem или параметры стоимости планировщика.

Поэтому, если вы видите различия в сортировке строк, не стоит об этом беспокоиться, если только в запросе не фигурирует условие ORDER BY, которое нарушает ваш результат. Причина, по которой это условие не было явно добавлено во все запросы регрессионных тестов, чтобы избавиться от этой проблемы раз и навсегда, заключается в том, что это снизит полезность регрессионных тестов, а не наоборот. В этом случае они бы имели тенденцию выполнять планы запросов, выдающих отсортированные результаты, и исключать запросы без сортировки.

Недостаточная глубина стека

Если тест errors приводит к краху сервера при выполнении команды select infinite_recurse(), это означает, что предел платформы для размера стека процесса меньше, чем показывает параметр max_stack_depth. Это можно исправить, запустив сервер с увеличенным пределом размера стека (рекомендованное значение по умолчанию для max_stack_depth составляет 4 МБ). Если вы не можете этого сделать, в качестве альтернативы можно уменьшить значение параметра max_stack_depth.

На платформах, поддерживающих функцию getrlimit(), сервер должен автоматически выбирать безопасное значение max_stack_depth; поэтому, если вы не переопределяли это значение вручную, сбой такого типа является ошибкой, подлежащей регистрации.

Тест «случайных результатов»

Тестовый скрипт random предназначен для получения случайных результатов. В очень редких случаях это приводит к сбоям в регрессионном тестировании. Выполнение:

diff results/random.out expected/random.out

должно выводить только одну или несколько строк различий. Вам не стоит беспокоиться, кроме случаев, когда сбои в этом тесте происходят постоянно.

Параметры конфигурации

При выполнении тестирования на существующей инсталляции некоторые нестандартные значения параметров могут вызвать сбои в тестах. Например, изменение таких параметров, как enable_seqscan или enable_indexscan может вызвать изменения в плане, которые повлияют на результаты тестов, использующих EXPLAIN.

Вариантные сравнительные файлы

Поскольку некоторые тесты заведомо выдают результаты, зависящие от среды, мы предоставили способы задавать вариантные «ожидаемые» файлы результата. Каждый регрессионный тест может иметь несколько сравнительных файлов, показывающих возможные результаты на разных платформах. Существует два независимых механизма для определения, какой именно сравнительный файл будет использоваться для каждого теста.

Первый механизм позволяет выбирать сравнительные файлы для конкретных платформ. Имеется файл сопоставления /usr/local/qhb/test/regress/resultmap, который определяет, какой сравнительный файл применить для каждой платформы. Чтобы устранить ложные «сбои» тестирования для конкретной платформы, сначала нужно выбрать или создать вариант файла результата, а затем добавить строку в файл resultmap.

Каждая строка в файле сопоставления имеет следующую форму:

testname:output:platformpattern=comparisonfilename

Имя теста (testname) — это просто имя конкретного модуля регрессионного теста. Значение output показывает, какой выходной файл проверять. Для стандартных регрессионных тестов это всегда out. Значение соответствует расширению выходного файла. Шаблон платформы (platformpattern) — это шаблон в стиле Unix-утилиты expr (то есть регулярное выражение с неявным якорем ^ в начале). Этот шаблон сопоставляется с именем платформы, которое выводится из config.guess. Имя сравнительного файла (comparisonfilename) — это базовое имя замещающего сравнительного файла для результата.

Пример: в некоторых системах отсутствует работающая функция strtof, для которой с нашим обходным приемом возникают ошибки округления в регрессионном тесте float4. Поэтому мы предоставляем вариант сравнительного файла float4-misrounded-input.out, содержащий результаты, ожидаемые в таких системах. Чтобы подавить сообщение о ложном «сбое» на платформах HP-UX 10, файл resultmap содержит строку:

float4:out:hppa.*-hp-hpux10.*=float4-misrounded-input.out

которая запустит наш файл на любой машине, где вывод config.guess совпадает с hppa.*-hp-hpux10.*. При необходимости в других строках resultmap будут выбираться варианты сравнительного файла для других платформ.

Второй механизм выбора для вариантных сравнительных файлов гораздо более автоматический: он просто использует наиболее соответствующий из нескольких предлагаемых сравнительных файлов. Скрипт драйвера регрессионного теста рассматривает стандартный сравнительный файл для теста, имя_теста.out, и вариантные файлы имя_теста_цифра.out (где цифра — любое одноразрядное число от 0 до 9). Если какой-нибудь из этих файлов полностью совпадает, тест считается пройденным; в противном случае для создания отчета об ошибке используется файл с наименьшим различием. (Если в resultmap содержится запись для конкретного теста, то в базовое имя_теста подставляется имя вариантного файла, заданное в resultmap.)

Например, для теста char сравнительный файл char.out содержит результаты, ожидаемые для локалей C и POSIX, тогда как файл char_1.out содержит результаты в том порядке, в каком они располагаются во многих других локалях.

Механизм наибольшего соответствия был разработан, чтобы справляться с результатами, зависящими от локали, но его можно использовать в любой ситуации, когда результаты теста сложно предсказать, исходя только из названия платформы. Ограниченность этого механизма заключается в том, что драйвер теста не может сказать, какой вариант действительно «корректный» для текущей среды; он просто выберет тот вариант, который покажется ему наиболее подходящим. Поэтому безопаснее всего использовать этот механизм только для вариантных результатов, которые вы готовы считать равно допустимыми во всех контекстах.

Проверка тестового покрытия

Исходный код QHB можно скомпилировать с инструментами определения тестового покрытия, благодаря чему появляется возможность проверить, какие части кода покрываются регрессионными тестами или любым другим комплектом тестов, выполняемым в коде. В настоящее время эта возможность поддерживается при компиляции с GCC и требует наличия программ gcov и lcov.

Типичный рабочий процесс выглядит так:

./configure --enable-coverage ... OTHER OPTIONS ...
make
make check # или другой комплект тестов
make coverage-html

Затем откройте в своем HTML-браузере страницу coverage/index.html.

Если у вас нет lcov или вы предпочитаете HTML-отчету текстовый вывод, вы можете выполнить

make coverage

вместо make coverage-html и получить выходные файлы .gcov для каждого исходного файла, относящегося к тесту. (Команды make coverage и make coverage-html перезапишут файлы друг друга, поэтому при одновременном их использовании может возникнуть путаница.)

Перед созданием отчета о тестовом покрытии можно запустить несколько различных тестов; счетчики выполнений будут накапливать значения. Если вы хотите сбросить счетчики выполнения между тестами, выполните:

make coverage-clean

Если вы хотите получить отчет о покрытии только для части дерева кода, можно запустить команду make coverage-html или make coverage в подкаталоге.

После завершения проверки выполните очистку с помощью make distclean.

Процедурные языки

QHB позволяет писать пользовательские функции на других языках помимо SQL и C/RUST. Эти другие языки обычно называются процедурными языками (procedural language, PL). Для функции, написанной на процедурном языке, сервер баз данных не имеет встроенных возможностей для интерпретации исходного текста функции. Вместо этого такая задача передается специальному обработчику, досконально знающему данный язык. Обработчик может либо выполнять всю работу по интерпретации, синтаксическому анализу, выполнению и т. д. сам, либо служить «связующим звеном» между QHB и существующей реализацией языка программирования. Сам обработчик является функцией на языке C, скомпилированной в общий объект и загружаемой по требованию, как и любая другая функция на C.

В настоящее время в стандартном дистрибутиве QHB доступен язык PL/pgSQL (глава PL/pgSQL). Существуют и другие процедурные языки, поддержка которых не включена в базовый дистрибутив. Кроме того, пользователи могут реализовать собственные языки; основы разработки нового процедурного языка рассматриваются в главе Написание обработчика процедурного языка.

Установка процедурных языков

Процедурный язык должен быть «установлен» в каждой базе данных, где он будет использоваться. Однако процедурные языки, установленные в базе данных template1, автоматически становятся доступными во всех впоследствии создаваемых базах данных, так как их записи в template1 будут скопированы командой CREATE DATABASE. Таким образом, администратор базы данных может решать, какие языки будут доступны в определенных базах данных, и при желании может сделать некоторые языки доступными по умолчанию.

Для установки в текущей базе данных языков, поставляемых со стандартным дистрибутивом, необходимо только выполнить CREATE EXTENSION имя_языка. Описанная ниже ручная процедура рекомендуется только для установки языков, которые не были упакованы как расширения.

Ручная процедура установки процедурных языков

Процедурный язык устанавливается в базе данных в пять этапов, которые должны выполняться суперпользователем базы данных. В большинстве случаев требуемые команды SQL должны быть упакованы в виде установочного скрипта «расширения», чтобы для их выполнения можно было использовать команду CREATE EXTENSION.

Разделяемый объект для функции обработки языка должен быть скомпилирован и установлен в соответствующий каталог библиотеки. Это работает по тому же принципу, что и сборка и установка модулей с обычными пользовательскими функциями C/RUST; см. подраздел Компиляция и связывание динамически загружаемых функций. Зачастую обработчик языка будет зависеть от внешней библиотеки, обеспечивающей фактический механизм языка программирования; в этом случае она также должна быть установлена.

Обработчик следует создать командой

CREATE FUNCTION имя_функции_обработки()
    RETURNS language_handler
    AS 'путь-к-разделяемому-объекту'
    LANGUAGE C;

Специальный возвращаемый тип language_handler сообщает СУБД, что эта функция не возвращает ни один из определенных типов данных SQL и не может напрямую использоваться в операторах SQL.

Дополнительно обработчик языка может предоставить функцию обработки «встраиваемого кода», выполняющую обработку блоков анонимного кода (команды DO), написанных на этом языке. Если функция для встраиваемого кода предоставляется языком, необходимо объявить ее следующей командой:

CREATE FUNCTION имя_функции_обработки_встраиваемого_кода(internal)
    RETURNS void
    AS 'путь-к-разделяемому-объекту'
    LANGUAGE C;

Дополнительно обработчик языка может предоставить функцию-«валидатор», проверяющую правильность определения функции без фактического ее выполнения. Валидатор, если имеется, вызывается командой CREATE FUNCTION. Если эта функция предоставляется языком, необходимо объявить ее следующей командой:

CREATE FUNCTION имя_функции_валидатора(oid)
    RETURNS void
    AS 'путь-к-разделяемому-объекту'
    LANGUAGE C STRICT;

Наконец, процедурный язык следует объявить командой:

CREATE [TRUSTED] LANGUAGE имя_языка
    HANDLER имя_функции_обработки
    [INLINE имя_функции_обработки_встраиваемого_кода]
    [VALIDATOR имя_функции_валидатора] ;

Необязательное ключевое слово TRUSTED указывает, что язык не предоставляет доступ к данным, которого у пользователя не было бы без него. Доверенные языки предназначены для обычных пользователей базы данных (без прав суперпользователя) и позволяют им безопасно создавать функции и процедуры. Поскольку функции на процедурном языке выполняются внутри сервера баз данных, флаг TRUSTED следует указывать только для языков, не позволяющим обращаться к внутренним компонентам сервера баз данных или файловой системе. Язык PL/pgSQL считается доверенным.

При установке QHB по умолчанию обработчик для языка PL/pgSQL собирается и устанавливается в каталог «library». Более того, сам язык PL/pgSQL установлен во всех базах данных.

Написание обработчика процедурного языка

Все вызовы функций, написанных на языке, отличном от вызываемого через текущий интерфейс «версии 1» для компилируемых языков (сюда входят функции, написанные на пользовательских процедурных языках, и функции, написанные на SQL), выполняются через функцию-обработчик вызова для указанного языка. В обязанности обработчика входит выполнение функции надлежащим образом, например, интерпретируя переданный ему исходный текст этой функции. В этой главе в общих чертах рассказывается, как можно написать обработчик вызова для нового процедурного языка.

Обработчик вызова для нового процедурного языка — это «обычная» функция, которая должна быть написана на компилируемом языке, например, на C, вызывается через интерфейс версии 1 и регистрируется в QHB как не принимающая аргументы и возвращающая тип language_handler. Этот специальный псевдотип определяет функцию как обработчик вызова и препятствует ее вызову напрямую в командах SQL. Более подробную информацию о соглашении о вызовах и динамической загрузке кода на языке C см. в разделе Функции на нативном языке.

Обработчик вызова вызывается так же, как и любая другая функция: он получает указатель на структуру FunctionCallInfoBaseData, содержащую значения аргументов и информацию о вызываемой функции, и должен вернуть результат типа Datum (и, возможно, установить поле isnull в структуре FunctionCallInfoBaseData, если желает вернуть результат SQL NULL). Разница между обработчиком вызова и обычной вызываемой функцией состоит в том, что поле flinfo->fn_oid структуры FunctionCallInfoBaseData будет содержать OID фактически вызываемой функции, а не самого обработчика вызова. Обработчик вызова должен использовать это поле для определения того, какую функцию выполнить. Кроме того, список передаваемых аргументов формируется в соответствии с объявлением целевой функции, а не обработчика вызова.

Обработчик вызова должен сам выбрать запись функции системного каталога pg_proc и проанализировать типы аргументов и результаты вызываемой функции. Содержимое предложения AS команды CREATE FUNCTION для этой функции можно будет найти в столбце prosrc ее строки в pg_proc. Как правило, это исходный текст на процедурном языке, но теоретически это может быть и что-то другое, например, путь к файлу или что-то еще, указывающее обработчику вызова, что конкретно ему делать.

Зачастую одна и та же функция многократно вызывается в одном операторе SQL. Обработчик вызова может избежать повторных поисков информации о вызываемой функции, воспользовавшись полем flinfo->fn_extra. Изначально оно содержит NULL, но обработчик вызова может установить в нем указатель на нужную информацию. При последующих вызовах, если flinfo->fn_extra уже отлично от NULL, им можно воспользоваться и пропустить этап поиска информации. Обработчик вызова должен позаботиться о том, чтобы flinfo->fn_extra указывал на место в памяти, которое не будет освобождено как минимум до конца текущего запроса, поскольку именно столько может существовать структура данных FmgrInfo. Один из способов этого добиться — разместить дополнительные данные в контексте памяти, указанном в flinfo->fn_mcxt; срок жизни таких данных обычно совпадает со сроком жизни самой структуры FmgrInfo. С другой стороны, обработчик может выбрать более долгоживущий контекст памяти, чтобы иметь возможность кэшировать информацию из определения функции между запросами.

Когда функция на процедурном языке вызывается как триггер, ей не передаются аргументы обычном способом; вместо этого поле context в структуре FunctionCallInfoBaseData указывает на структуру TriggerData, а не содержит NULL, как при обычном вызове функции. А обработчик языка должен предоставить механизмы, чтобы функции на процедурном языке получили эту информацию о запуске.

Шаблон обработчика процедурного языка, написанный как расширение на C, представлен ниже. Это рабочий пример, демонстрирующий один из способов создать обработчик процедурного языка, обработать параметры и возвратить результат.

Хотя обработчика вызова достаточно для создания простейшего процедурного языка, существуют еще две функции, которые можно предоставить дополнительно, чтобы пользоваться языком было удобнее. Это валидатор и обработчик встраиваемого кода. Валидатор позволяет проводить проверку синтаксиса конкретного языка во время CREATE FUNCTION. Обработчик же встраиваемого кода позволяет языку поддерживать выполнение анонимных блоков кода посредством команды DO.

Если для процедурного языка предоставляется валидатор, он должен быть объявлен как функция, принимающая один параметр типа oid. Результат валидатора игнорируется, поэтому обычно он объявляется как возвращающий тип void. Валидатор будет вызываться в конце выполнения команды CREATE FUNCTION, создающей или изменяющей функцию на процедурном языке. Переданный ему OID принадлежит строке в pg_proc для этой функции. Валидатор должен выбрать эту строку обычным способом и произвести все необходимые проверки. Сначала нужно вызвать CheckFunctionValidatorAccess(), чтобы валидатор отличал явные вызовы этой функции, которые пользователь не мог произвести посредством команды CREATE FUNCTION. После этого обычные проверки включают подтверждение того, что типы аргументов и результата функции поддерживаются языком и что тело функции синтаксически правильно для данного языка. Если валидатор удостоверяется, что с функцией все в порядке, он просто завершается. Если же он обнаруживает ошибку, то должен сообщить о ней через обычный механизм регистрации ошибок ereport(). Выданная ошибка приведет к откату транзакции и тем самым предотвратит фиксацию определения некорректной функции.

Функции-валидаторы обычно должны учитывать параметр check_function_bodies: если он выключен, то все затратные или контекстно-зависимые проверки следует пропустить. Если язык предусматривает выполнение кода во время компиляции, валидатор должен отменять проверки, которые могут вызвать такое выполнение. В частности, этот параметр выключает утилита qhb_dump, чтобы иметь возможность загружать функции на процедурных языках, не беспокоясь о побочных эффектах или зависимостях тел функций от других объектов базы данных. (Из-за этого требования обработчик вызова не должен предполагать, что валидатор полностью проверил функцию. Смысл наличия валидатора не в том, чтобы позволять обработчику опускать проверки, а в том, чтобы немедленно уведомить пользователя, если в команде CREATE FUNCTION имеются очевидные ошибки.) Хотя выбор, что именно проверять, по большей части остается на усмотрение функции-валидатора, обратите внимание, что основной код CREATE FUNCTION выполняет предложения SET, связанные с функцией, только когда check_function_bodies включен. Таким образом, проверки, на результаты которых могут повлиять параметры GUC, определенно должны пропускаться, когда check_function_bodies выключен, во избежание ложных ошибок при восстановлении базы из дампа.

Если для процедурного языка предоставляется обработчик встраиваемого кода, он должен объявляться как функция, принимающая один параметр типа internal. Результат обработчика встраиваемого кода игнорируется, поэтому обычно он объявляется как возвращающий тип void. Обработчик встраиваемого кода будет вызываться при выполнении оператора DO с указанным процедурным языком. Параметр, передающийся на самом деле, является указателем на структуру InlineCodeBlock, содержащую информацию о параметрах оператора DO, в частности, текст выполняемого анонимного блока кода. Обработчик встраиваемого кода должен выполнить этот код и завершиться.

Все подобные объявления функций, а также саму команду CREATE LANGUAGE рекомендуется упаковывать в расширение так, чтобы для установки языка достаточно было простой команды CREATE EXTENSION. Информацию о написании расширений см. в разделе Упаковка связанных объектов в расширение.

Процедурные языки, включенные в стандартный пакет, могут послужить хорошим примером при написании собственного обработчика языка. Некоторые полезные детали также можно узнать на справочной странице команды CREATE LANGUAGE.

Пример написания обработчика

/*
 * Обработчик процедурного языка PL/Sample
 */

#include "qhb.h"

#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(plsample_call_handler);

static Datum plsample_func_handler(PG_FUNCTION_ARGS);
static HeapTuple plsample_trigger_handler(PG_FUNCTION_ARGS);

/*
 * Вызовы функции-обработчика, процедуры и триггера.
 */
Datum
plsample_call_handler(PG_FUNCTION_ARGS)
{
   Datum       retval = (Datum) 0;

   /*
    * Для многих языков потребуется очистка, которая происходит даже в случае
    * ошибки.  Это может произойти в блоке PG_FINALLY.  Если очистка не нужна,
    * эту конструкцию PG_TRY можно опустить.
    */
   PG_TRY();
   {
       /*
        * Определяем, будет ли обработчик вызываться как функция или как триггер,
        * и вызываем подходящий вложенный обработчик.
        */
       if (CALLED_AS_TRIGGER(fcinfo))
       {
           /*
            * Эта функция была вызвана как триггер, у которого в строку
            * (TriggerData *) fcinfo->context включена информация о контексте.
            */
           retval = PointerGetDatum(plsample_trigger_handler(fcinfo));
       }
       else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
       {
           /*
            * Эта функция была вызвана как триггер события, у которого в строку
            * (EventTriggerData *) fcinfo->context включена информация о контексте.
            *
            * TODO: provide an example handler.
            */
       }
       else
       {
           /* Обычный обработчик функции */
           retval = plsample_func_handler(fcinfo);
       }
   }
   PG_FINALLY();
   {
   }
   PG_END_TRY();

   return retval;
}

/*
 * plsample_func_handler
 *
 * Функция, вызванная обработчиком вызовов для выполнения функции.
 */
static Datum
plsample_func_handler(PG_FUNCTION_ARGS)
{
   HeapTuple   pl_tuple;
   Datum       ret;
   char       *source;
   bool        isnull;
   FmgrInfo   *arg_out_func;
   Form_pg_type type_struct;
   HeapTuple   type_tuple;
   Form_pg_proc pl_struct;
   volatile MemoryContext proc_cxt = NULL;
   Oid        *argtypes;
   char      **argnames;
   char       *argmodes;
   char       *proname;
   Form_pg_type pg_type_entry;
   Oid         result_typioparam;
   Oid         prorettype;
   FmgrInfo    result_in_func;
   int         numargs;

   /* Выбираем запись функции в каталоге pg_proc. */
   pl_tuple = SearchSysCache1(PROCOID,
                              ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
   if (!HeapTupleIsValid(pl_tuple))
       elog(ERROR, "cache lookup failed for function %u",
            fcinfo->flinfo->fn_oid);

   /*
    * Извлекаем и выводим исходный текст функции.  Это можно использовать как
    * основу для валидации и выполнения функции.
    */
   pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
   proname = pstrdup(NameStr(pl_struct->proname));
   ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
   if (isnull)
       elog(ERROR, "could not find source text of function \"%s\"",
            proname);
   source = DatumGetCString(DirectFunctionCall1(textout, ret));
   ereport(NOTICE,
           (errmsg("source text of function \"%s\": %s",
                   proname, source)));

   /*
    * Выделяем в памяти контекст, где будут содержаться все данные QHB для
    * процедуры.
    */
   proc_cxt = AllocSetContextCreate(TopMemoryContext,
                                    "PL/Sample function",
                                    ALLOCSET_SMALL_SIZES);

   arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
   numargs = get_func_arg_info(pl_tuple, &argtypes, &argnames, &argmodes);

   /*
    * Выполняем итерацию всех аргументов функции, выводя все входные значения.
    */
   for (int i = 0; i < numargs; i++)
   {
       Oid         argtype = pl_struct->proargtypes.values[i];
       char       *value;

       type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
       if (!HeapTupleIsValid(type_tuple))
           elog(ERROR, "cache lookup failed for type %u", argtype);

       type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
       fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
       ReleaseSysCache(type_tuple);

       value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
       ereport(NOTICE,
               (errmsg("argument: %d; name: %s; value: %s",
                       i, argnames[i], value)));
   }

   /* Тип результата */
   prorettype = pl_struct->prorettype;
   ReleaseSysCache(pl_tuple);

   /*
    * Получение информацию, требуемой для входного преобразования возвращаемого
    * значения.
    *
    * Если результатом функции является VOID, лучше, чтобы возвращался NULL.
    * В любом случае, будем откровенны.  Это просто шаблон, так что мы мало что
    * можем тут поделать.  Функция не возвращает NULL, только если тип результата
    * текстовый, когда результатом является исходный текст функции.
    */
   if (prorettype != TEXTOID)
       PG_RETURN_NULL();

   type_tuple = SearchSysCache1(TYPEOID,
                                ObjectIdGetDatum(prorettype));
   if (!HeapTupleIsValid(type_tuple))
       elog(ERROR, "cache lookup failed for type %u", prorettype);
   pg_type_entry = (Form_pg_type) GETSTRUCT(type_tuple);
   result_typioparam = getTypeIOParam(type_tuple);

   fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt);
   ReleaseSysCache(type_tuple);

   ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
   PG_RETURN_DATUM(ret);
}

/*
 * plsample_trigger_handler
 *
 * Функция, вызываемая обработчиком вызовов для выполнения триггера.
 */
static HeapTuple
plsample_trigger_handler(PG_FUNCTION_ARGS)
{
   TriggerData *trigdata = (TriggerData *) fcinfo->context;
   char       *string;
   volatile HeapTuple rettup;
   HeapTuple   pl_tuple;
   Datum       ret;
   char       *source;
   bool        isnull;
   Form_pg_proc pl_struct;
   char       *proname;
   int         rc PG_USED_FOR_ASSERTS_ONLY;

   /* Убедимся, что это было вызвано из триггера. */
   if (!CALLED_AS_TRIGGER(fcinfo))
       elog(ERROR, "not called by trigger manager");

   /* Подключимся к менеджеру SPI */
   if (SPI_connect() != SPI_OK_CONNECT)
       elog(ERROR, "could not connect to SPI manager");

   rc = SPI_register_trigger_data(trigdata);
   Assert(rc >= 0);

   /* Выбираем запись функции в каталоге pg_proc. */
   pl_tuple = SearchSysCache1(PROCOID,
                              ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
   if (!HeapTupleIsValid(pl_tuple))
       elog(ERROR, "cache lookup failed for function %u",
            fcinfo->flinfo->fn_oid);

   /*
    * Получение кода
    *
    * Извлекаем и выводим исходный текст функции.  Это можно использовать как
    * основу для валидации и выполнения функции.
    */
   pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
   proname = pstrdup(NameStr(pl_struct->proname));
   ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
   if (isnull)
       elog(ERROR, "could not find source text of function \"%s\"",
            proname);
   source = DatumGetCString(DirectFunctionCall1(textout, ret));
   ereport(NOTICE,
           (errmsg("source text of function \"%s\": %s",
                   proname, source)));

   /*
    * Мы закончили с кортежем pg_proc, так что освобождаем его.  (Обратите внимание,
    * что теперь строки "proname" и "source" являются отдельными копиями.)
    */
   ReleaseSysCache(pl_tuple);

   /*
    * Расширение кода
    *
    * Здесь можно расширить исходный текст, к примеру, обернуть его как
    * тело функции на целевом языке, добавив перед списком параметров такие
    * имена, как TD_name, TD_relid, TD_table_name, TD_table_schema,
    * TD_event, TD_when, TD_level, TD_NEW, TD_OLD, и аргументы, используя
    * подходящие типы из целевого языка. Расширенный текст можно кэшировать
    * в долгоживущем контексте памяти или, если в целевом языке имеется
    * этап компиляции, это можно сделать сейчас, кэшируя результат компиляции.
    */

   /*
    * Выполнение кода
    *
    * Здесь функция (исходный текст (возможно, расширенный) или результат
    * компиляции, если в целевом языке имеется этот этап) должна быть
    * выполнена, после привязывания значений из структуры TriggerData
    * к соответствующим параметрам.
    *
    * В данном примере мы просто выводим большой объем информации посредством ereport.
    */

   PG_TRY();
   {
       ereport(NOTICE,
               (errmsg("trigger name: %s", trigdata->tg_trigger->tgname)));
       string = SPI_getrelname(trigdata->tg_relation);
       ereport(NOTICE, (errmsg("trigger relation: %s", string)));

       string = SPI_getnspname(trigdata->tg_relation);
       ereport(NOTICE, (errmsg("trigger relation schema: %s", string)));

       /* Пример обработки различных аспектов триггера. */

       if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
       {
           ereport(NOTICE, (errmsg("triggered by INSERT")));
           rettup = trigdata->tg_trigtuple;
       }
       else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
       {
           ereport(NOTICE, (errmsg("triggered by DELETE")));
           rettup = trigdata->tg_trigtuple;
       }
       else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
       {
           ereport(NOTICE, (errmsg("triggered by UPDATE")));
           rettup = trigdata->tg_trigtuple;
       }
       else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
       {
           ereport(NOTICE, (errmsg("triggered by TRUNCATE")));
           rettup = trigdata->tg_trigtuple;
       }
       else
           elog(ERROR, "unrecognized event: %u", trigdata->tg_event);

       if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
           ereport(NOTICE, (errmsg("triggered BEFORE")));
       else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
           ereport(NOTICE, (errmsg("triggered AFTER")));
       else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
           ereport(NOTICE, (errmsg("triggered INSTEAD OF")));
       else
           elog(ERROR, "unrecognized when: %u", trigdata->tg_event);

       if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
           ereport(NOTICE, (errmsg("triggered per row")));
       else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
           ereport(NOTICE, (errmsg("triggered per statement")));
       else
           elog(ERROR, "unrecognized level: %u", trigdata->tg_event);

       /*
        * Выполняем итерацию всех аргументов триггера, выводя все входные
        * значения.
        */
       for (int i = 0; i < trigdata->tg_trigger->tgnargs; i++)
           ereport(NOTICE,
                   (errmsg("trigger arg[%i]: %s", i,
                           trigdata->tg_trigger->tgargs[i])));
   }
   PG_CATCH();
   {
       /* Здесь мог бы появиться код ошибки при очистке */
       PG_RE_THROW();
   }
   PG_END_TRY();

   if (SPI_finish() != SPI_OK_FINISH)
       elog(ERROR, "SPI_finish() failed");

   return rettup;
}

Клиентские приложения QHB

В этом разделе содержится справочная информация о клиентских приложениях и утилитах QHB. Не все эти команды имеют общее назначение; некоторые из них могут требовать особых прав. Общей особенностью этих приложений является то, что их можно запустить на любом хосте, независимо от того, где находится сервер баз данных.

При указании в командной строке имени пользователя и базы данных, они передаются без изменения регистра — наличие пробелов и специальных символов может потребовать заключения в кавычки. Имена таблиц и другие идентификаторы не зависят от регистра, за исключением отдельно описанных случаев, и тоже могут потребовать кавычек.

Содержание

clusterdb — кластеризовать базу данных QHB

createdb — создать новую базу данных QHB

createuser — определить новую учетную запись пользователя QHB

dropdb — удалить базу данных QHB

dropuser — удалить учетную запись пользователя QHB

qhb_amcheck — проверить одну или несколько баз данных QHB на предмет повреждения

qhb_basebackup — создать базовую резервную копию кластера QHB

pgbench — запустить тест оценки производительности QHB

pg_config — получить информацию об установленной версии QHB

qhb_dump — выгрузить базу данных QHB в файл скрипта или другой архивный файл

qhb_dumpall — выгрузить кластер баз данных QHB в файл скрипта

qhb_isready — проверить состояние соединения с сервером QHB

qhb_receivewal — принимать потоковую передачу журналов упреждающей записи с сервера QHB

qhb_recvlogical — управлять потоками логического декодирования QHB

qhb_restore — восстановить базу данных QHB из архивного файла, созданного qhb_dump

qhb_verifybackup — проверить целостность базовой резервной копии кластера QHB

psql — интерактивный терминал QHB

reindexdb — переиндексировать базу данных QHB

vacuumdb — провести очистку памяти и анализ базы данных QHB

clusterdb

clusterdb — кластеризовать базу данных QHB

Синтаксис

clusterdb [параметр-подключения...] [ --verbose | -v ] [ --table | -t таблица ] ... [имя_бд]

clusterdb [параметр-подключения...] [ --verbose | -v ] --all | -a

Описание

clusterdb — это утилита для повторной кластеризации таблиц в базе данных QHB. Она находит ранее кластеризованные таблицы и снова кластеризует их на основании последнего использованного индекса. Таблицы, которые никогда не кластеризовались, не затрагиваются.

clusterdb — это обертка для команды SQL CLUSTER. Между кластеризацией баз данных с помощью этой утилиты и кластеризацией иными способами при обращении к серверу по сути нет никакой разницы.

Параметры

Утилита clusterdb принимает следующие аргументы командной строки:

-a
--all
Кластеризовать все базы данных.

[-d] имя_бд
[--dbname=]имя_бд
Задает имя кластеризуемой базы данных, когда не используется параметр -a/--all. Если этот аргумент отсутствует, имя базы данных считывается из переменной среды PGDATABASE. Если эта переменная не установлена, используется имя пользователя, указанное для подключения. В имени_бд может задаваться строка подключения. В этом случае параметры в строке подключения переопределяют любые конфликтующие параметры в командной строке.

-e
--echo
Отобразить на экране команды, которые clusterdb генерирует и отправляет на сервер.

-q
--quiet
Не показывать сообщения о прогрессе выполнения.

-t таблица
--table=таблица
Кластеризовать конкретную таблицу. Написав ключ -t несколько раз, можно кластеризовать несколько таблиц.

-v
--verbose
Выводить подробную информацию во время процесса.

-V
--version
Вывести версию clusterdb и завершиться.

-?
--help
Показать справку об аргументах командной строки clusterdb и завершиться.

Кроме того, в качестве параметров подключения clusterdb принимает следующие аргументы командной строки:

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как clusterdb автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, clusterdb потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

--maintenance-db=имя_бд
Задает имя базы данных, к которой будет производиться подключение для выявления подлежащих кластеризации баз, когда используется параметр -a/--all. Если это имя не указано, будет выбрана база данных qhb, а если она не существует — template1. В данном аргументе может задаваться строка подключения. В этом случае параметры в строке подключения переопределят все конфликтующие параметры в командной строке. Кроме того, все параметры в строке подключения, за исключением самого имени базы данных, будут повторно использоваться при подключении к другим базам данных.

Переменные среды

PGDATABASE
PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

В случае возникновения затруднений обратитесь к справочным страницам CLUSTER и psql, где рассматриваются потенциальные проблемы и сообщения об ошибках. Сервер баз данных должен работать на целевом хосте. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Примеры

Выполнить кластеризацию базы данных test:

$ clusterdb test

Выполнить кластеризацию отдельной таблицы foo в базе данных xyzzy:

$ clusterdb --table=foo xyzzy

См. также

CLUSTER

createdb

createdb — создать новую базу данных QHB

Синтаксис

createdb [параметр-подключения...] [параметр...] [имя_бд [описание]]

Описание

Утилита createdb создает новую базу данных QHB.

Обычно пользователь базы данных, который выполняет эту команду, становится владельцем новой базы данных. Однако с помощью параметра -O можно указать и другого владельца, если у пользователя, выполняющего эту команду, имеются соответствующие права.

createdb — это обертка для команды SQL CREATE DATABASE. Между созданием баз данных с помощью этой утилиты и их созданием иными способами при обращении к серверу по сути нет никакой разницы.

Если имя базы данных не указано, то используется имя пользователя, от которого выполняется программа; аналогичный алгоритм применяется и относительно владельца базы данных.

Если базу данных невозможно создать, то программа выведет сообщение об ошибке, при этом иногда предлагая решение проблемы.

Параметры

Утилита createdb принимает следующие аргументы командной строки:

имя_бд
Задает имя создаваемой базы данных. Имя должно быть уникальным среди всех баз данных QHB в этом кластере. По умолчанию в качестве имени новой базы данных выбирается имя текущего системного пользователя.

описание
Задает комментарий, который будет связан с создаваемой новой базой данных.

-D табличное_пространство
--tablespace=табличное_пространство
Задает табличное пространство, которое по умолчанию будет использоваться в базе данных. (Это имя обрабатывается как идентификатор, заключенный в кавычки.)

-e
--echo
Отобразить на экране команды, которые createdb генерирует и отправляет на сервер.

-E кодировка
--encoding=кодировка
Задает схему кодировки символов, которая будет использоваться в этой базе данных. Поддерживаемые сервером QHB кодировки описаны в подразделе Поддерживаемые кодировки.

-l локаль
--locale=локаль
Задает локаль, которая будет использоваться в этой базе данных. Этот параметр равнозначен одновременной установке флагов --lc-collate и --lc-ctype.

--lc-collate=локаль
Задает параметр LC_COLLATE, который будет использоваться в этой базе данных.

--lc-ctype=локаль
Задает параметр LC_CTYPE, который будет использоваться в этой базе данных.

-icu-locale=локаль
Задает идентификатор локали ICU, который будет использоваться в этой базе данных, если выбран провайдер локали ICU.

--locale-provider={libc|icu}
Задает провайдера локали для правила сортировки базы данных по умолчанию.

-O владелец
--owner=владелец
Указывает пользователя базы данных, которому будет принадлежать новая база данных. (Это имя обрабатывается как идентификатор, заключенный в кавычки.)

-S шаблон
--strategy=стратегия
Задает стратегию создания базы данных. Подробную информацию см. на справочной странице команды CREATE DATABASE.

-T шаблон
--template=шаблон
Задает шаблон, на основе которого будет создана эта база данных. (Это имя обрабатывается как идентификатор, заключенный в кавычки.)

-v
--verbose
Задает уровень ведения журнала в Debug. Значение по умолчанию — Info.

-V
--version
Вывести версию createdb и завершиться.

-?
--help
Показать справку об аргументах командной строки createdb и завершиться.

Параметры -D, -l, -E, -O и -T соответствуют параметрам нижележащей команды SQL CREATE DATABASE; там можно найти дополнительную информацию по ним.

Кроме того, в качестве параметров подключения createdb принимает следующие аргументы командной строки:

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения. Значение по умолчанию — 5432.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как createdb автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, createdb потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

--maintenance-db=имя_бд
Задает имя базы данных, к которой будет производиться подключение при создании новой базы данных. Если это имя не указано, будет выбрана база данных qhb, а если она не существует (или это имя новой базы данных) — template1. В данном аргументе может задаваться строка подключения. В этом случае параметры в строке подключения переопределят все конфликтующие параметры в командной строке.

Переменные среды

PGDATABASE
Если установлена и не переопределена в командной строке, задает имя создаваемой базы данных.

PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию. PGUSER также определяет имя создаваемой базы данных, если оно не указано в командной строке или в переменной среды PGDATABASE.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

В случае возникновения затруднений обратитесь к справочным страницам CREATE DATABASE и psql, где рассматриваются потенциальные проблемы и сообщения об ошибках. Сервер баз данных должен работать на целевом хосте. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Примеры

Сценарии использования

Создать базу данных new на сервере, используемом по умолчанию:

$ createdb new

Создать базу данных new с именем пользователя, от которого запущена программа:

$ createdb

Создать базу данных new с локалью en_US.UTF-8, взяв за основу базу template0 и подключившись к обслуживающей базе qhb:

$ createdb new -l en_US.UTF-8 -T template0 --maintenance-db qhb

Создать базу данных new с локалью en_us и кодировкой LATIN1, взяв за основу базу template0.

$ createdb new "my comment" -l en_US -E LATIN1 -T template0 -v

После создания на базу накладывается комментарий "my comment".
Флаг -v обеспечивает вывод отладочной информации:

[DEBUG] Running getpwuid_r for user #1000
[DEBUG] Loading user with uid 1000
[DEBUG] executing statement batch: SELECT pg_catalog.set_config('search_path', '', false);
[DEBUG] executing statement batch: CREATE DATABASE new2 ENCODING 'LATIN1' TEMPLATE template0 LC_COLLATE 'en_US' LC_CTYPE 'en_US';
[DEBUG] executing statement batch: COMMENT ON DATABASE new2 IS 'my comment';
[DEBUG] Database has been successfully created

-- [DEBUG] Выполняется getpwuid_r для пользователя #1000
-- [DEBUG] Загружается пользователь с uid 1000
-- [DEBUG] выполняется пакет операторов: SELECT pg_catalog.set_config('search_path', '', false);
-- [DEBUG] выполняется пакет операторов: CREATE DATABASE new2 ENCODING 'LATIN1' TEMPLATE template0 LC_COLLATE 'en_US' LC_CTYPE 'en_US';
-- [DEBUG] выполняется пакет операторов: COMMENT ON DATABASE new2 IS 'my comment';
-- [DEBUG] База данных была успешно создана

Особенности использования

Записи

createdb new -l en_US.UTF-8 -T template0

и

createdb new -l en_US -E UTF-8 -T template0

не равнозначны. В первом случае база данных будет успешно создана, во втором случае будет выдана ошибка.

[ERROR] db error: ERROR: encoding "UTF8" does not match locale "en_US": SQL: "CREATE DATABASE new3 ENCODING 'UTF-8' TEMPLATE template0 LC_COLLATE 'en_US' LC_CTYPE 'en_US';"
DETAIL: The chosen LC_CTYPE setting requires encoding "LATIN1".

-- [ОШИБКА] ошибка базы данных: ОШИБКА: кодировка "UTF8" не соответствует локали "en_US": SQL: "CREATE DATABASE new3 ENCODING 'UTF-8' TEMPLATE template0 LC_COLLATE 'en_US' LC_CTYPE 'en_US';"
-- ДЕТАЛИЗАЦИЯ: Выбранному значению LC_CTYPE требуется кодировка "LATIN1".

Невозможно добавить комментарий к базе данных, не указав ее имя.

Флаг -l/--locale объединяет в себе --lc-collate и --lc-ctype, поэтому вместе их указывать нельзя. То есть:

createdb new -l en_US = CREATE DATABASE new LC_COLLATE 'en_US' LC_CTYPE 'en_US';

А при одновременном указании этих двух параметров

createdb new --lc-collate en_US --lc-ctype en_US

будет выдана ошибка.

По умолчанию clusterdb пытается использовать базу template1 как обслуживающую новую базу данных ( флаг --maintenance-db), а если подключиться к ней не выходит, то используется база данных qhb.

Флаг -v, --verbose помимо отладочной информации исполняет роль ECHO, выводя команды SQL, отправленные на сервер для исполнения.

См. также

dropdb, CREATE DATABASE

createuser

createuser — определить новую учетную запись пользователя QHB

Синтаксис

createuser [параметр-подключения...] [параметр...] [имя_пользователя]

Описание

Утилита createuser создает нового пользователя QHB (или, точнее, роль). Создавать новых пользователей могут только суперпользователи и пользователи с правом CREATEROLE, поэтому createuser должна вызываться от их лица.

При необходимости создать роль с правом SUPERUSER, REPLICATION или BYPASSRLS следует подключиться как суперпользователь, одного права CREATEROLE недостаточно. Суперпользователи имеют возможность обходить все проверки прав доступа в базе данных, поэтому беспечно такие права предоставлять не стоит. Право CREATEROLE тоже дает крайне обширные привилегии.

createuser — это обертка для команды SQL CREATE ROLE. Между созданием пользователей с помощью этой утилиты и их созданием иными способами при обращении к серверу по сути нет никакой разницы. Подробную информацию про атрибуты ролей см. в разделе Атрибуты ролей.

Параметры

Утилита createuser принимает следующие аргументы командной строки:

имя_пользователя
Задает имя создаваемого пользователя QHB. Это имя должно отличаться от всех существующих ролей в этой установке QHB.

-c число
--connection-limit=число
Задает максимальное число подключений для нового пользователя. По умолчанию без ограничений.

-d
--createdb
Новому пользователю будет разрешено создавать базы данных.

-D
--no-createdb
Новому пользователю не будет разрешено создавать базы данных. Это поведение по умолчанию.

-e
--echo
Отобразить на экране команды, которые createuser генерирует и отправляет на сервер.

-E
--encrypted
Этот параметр устарел, но все еще принимается для обратной совместимости.

-g роль
--role=роль
Указывает роль, к которой создаваемая роль будет немедленно добавлена в качестве нового члена. Написав ключ -g несколько раз, можно задать несколько таких ролей.

-i
--inherit
Новая роль автоматически унаследует права ролей, членом которых она является. Это поведение по умолчанию.

-I
--no-inherit
Новая роль не будет автоматически наследовать права ролей, членом которых она является.

--interactive
Запросить имя пользователя, а также значения параметров -d/-D, -r/-R и -s/-S, если они не указаны в командной строке.

-l
--login
Новому пользователю будет разрешено авторизоваться на сервере (то есть его имя может использоваться как идентификатор исходного пользователя сеанса). Это поведение по умолчанию.

-L
--no-login
Новому пользователю будет запрещено авторизоваться на сервере. (Роль без права авторизации на сервере все равно полезна в качестве средства управления разрешениями в базе данных.)

-P
--pwprompt
Если этот параметр указан, то createuser выдаст запрос на пароль для нового пользователя. Если не планируется аутентификация по паролю, в этом нет необходимости.

-r
--createrole
Новому пользователю будет разрешено создавать, менять, удалять, комментировать другие роли, а также менять для них метку безопасности и предоставлять и отзывать членство в других ролях (то есть этот пользователь наделяется правом CREATEROLE). Более подробную информацию о возможностях, предоставляемых этим правом, см. в разделе Атрибуты ролей.

-R
--no-createrole
Новому пользователю не будет разрешено создавать новые роли. Это поведение по умолчанию.

-s
--superuser
Новый пользователь будет суперпользователем.

-S
--no-superuser
Новый пользователь не будет суперпользователем. Это поведение по умолчанию.

-V
--version
Вывести версию createuser и завершиться.

--replication
Новый пользователь будет наделен правом REPLICATION, которое более подробно описано в документации по CREATE ROLE.

--no-replication
Новый пользователь не будет наделен правом REPLICATION.

-?
--help
Показать справку об аргументах командной строки createuser и завершиться.

Кроме того, в качестве параметров подключения createuser принимает следующие аргументы командной строки:

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение (не имя создаваемого пользователя).

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль (для подключения к серверу, а не пароль для нового пользователя).
Это несущественный параметр, так как createuser автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, createuser потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

Переменные среды

PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

В случае возникновения затруднений обратитесь к справочным страницам CREATE ROLE и psql, где рассматриваются потенциальные проблемы и сообщения об ошибках. Сервер баз данных должен работать на целевом хосте. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Примеры

Создать пользователя joe на сервере баз данных по умолчанию:

$ createuser joe

Создать пользователя joe на сервере баз данных по умолчанию с запросом дополнительных атрибутов:

$ createuser --interactive joe
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n

Создать того же пользователя joe с явно заданными атрибутами, ознакомившись с нижележащей командой, подключившись к серверу на хосте eden, порт 5000:

$ createuser -h eden -p 5000 -S -D -R -e joe
CREATE ROLE joe NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;

Создать пользователя joe как суперпользователя с немедленным назначением пароля:

$ createuser -P -s -e joe
Enter password for new role: xyzzy
Enter it again: xyzzy
CREATE ROLE joe PASSWORD 'md5b5f5ba1a423792b526f799ae4eb3d59e' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;

В приведенном выше примере новый пароль на самом деле не отображается на экране при вводе, но для ясности мы показываем, что было введено. Как можно заметить, перед отправкой клиенту пароль шифруется.

См. также

dropuser, CREATE ROLE

dropdb

dropdb — удалить базу данных QHB

Синтаксис

dropdb [параметр-подключения...] [параметр...] имя_бд

Описание

Утилита dropdb удаляет существующую базу данных QHB. Пользователь, который выполняет эту команду, должен быть суперпользователем или владельцем этой базы.

dropdb — это обертка для команды SQL DROP DATABASE. Между удалением баз данных с помощью этой утилиты и их удалением иными способами при обращении к серверу по сути нет никакой разницы.

Для сохранения соединения необходимо подключиться к обслуживающей базе данных (флаг maintenance-db), так как нельзя удалить базу данных, к которой подключен. Также нельзя удалить базу template1 и/или template0.

Параметры

Утилита dropdb принимает следующие аргументы командной строки:

имя_бд
Задает имя удаляемой базы данных.

-e
--echo
Отобразить на экране команды, которые dropdb генерирует и отправляет на сервер.

-f
--force
Попытаться принудительно завершить все существующие подключения к целевой базе, прежде чем удалять ее. Подробную информацию об этом см. на справочной странице команды DROP DATABASE.

-i
--interactive
Выдает перед удалением запрос на подтверждение.

-V
--version
Вывести версию dropdb и завершиться.

--if-exists
Не считать ошибкой, если базы данных не существует. В этом случае выдается замечание.

-?
--help
Показать справку об аргументах командной строки dropdb и завершиться.

Кроме того, в качестве параметров подключения dropdb принимает следующие аргументы командной строки:

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как dropdb автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, dropdb потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

--maintenance-db=имя_бд Задает имя базы данных, к которой будет произведено производиться при удалении целевой базы. Если это имя не указано, будет выбрана база данных qhb, а если она не существует (или это имя удаляемой базы) — template1. В данном аргументе может задаваться строка подключения. В этом случае параметры в строке подключения переопределят все конфликтующие параметры в командной строке.

Переменные среды

PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию

PG_COLOR
Указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

В случае возникновения затруднений обратитесь к справочным страницам DROP DATABASE и psql, где рассматриваются потенциальные проблемы и сообщения об ошибках. Сервер баз данных должен работать на целевом хосте. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Примеры

Удалить базу данных new_db на сервере, используемом по умолчанию:

$ dropdb new_db

Удалить базу данных new_db на сервере localhost, порт 5432, с запросом подтверждения и просмотром нижележащей команды:

$ dropdb -h localhost -p 5432 -i new_db
Database "new_db" will be permanently removed.
Are you sure? (y/n) y
DROP DATABASE new_db;

См. также

createdb, DROP DATABASE

dropuser

dropuser — удалить учетную запись пользователя QHB

Синтаксис

dropuser [параметр-подключения...] [параметр...] [имя_пользователя]

Описание

Утилита dropuser удаляет существующего пользователя QHB. Только суперпользователи и пользователи с правом CREATEROLE могут удалять пользователей QHB. (Чтобы удалить суперпользователя, нужно самому быть суперпользователем.)

dropuser — это обертка для команды SQL DROP ROLE. Между удалением пользователей с помощью этой утилиты и их удалением иными способами при обращении к серверу по сути нет никакой разницы.

Параметры

Утилита dropuser принимает следующие аргументы командной строки:

имя_пользователя
Задает имя удаляемого пользователя QHB. Если в командной строке ничего не указано и используется параметр -i/--interactive, имя будет запрошено.

-e
--echo
Отобразить на экране команды, которые dropuser генерирует и отправляет на сервер.

-i
--interactive
Запросить подтверждение перед фактическим удалением пользователя и запросить его имя, если оно не было указано в командной строке.

-V
--version
Вывести версию dropuser и завершиться.

--if-exists
Не считать ошибкой, если пользователя не существует. В этом случае выдается замечание.

-?
--help
Показать справку об аргументах командной строки dropuser и завершиться.

Кроме того, в качестве параметров подключения dropuser принимает следующие аргументы командной строки:

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение (а не удаляемого пользователя).

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как dropuser автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, dropuser потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

Переменные среды

PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию

PG_COLOR
Указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

В случае возникновения затруднений обратитесь к справочным страницам DROP ROLE и psql, где рассматриваются потенциальные проблемы и сообщения об ошибках. Сервер баз данных должен работать на целевом хосте. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Примеры

Удалить пользователя alex с сервера баз данных по умолчанию:

$ dropuser alex

Удалить пользователя alex на сервере eden, порт 5000, с запросом подтверждения и просмотром нижележащей команды:

$ dropuser -p 5000 -h eden -i -e alex
Role "alex" will be permanently removed.
Are you sure? (y/n) y
DROP ROLE alex;

См. также

createuser, DROP ROLE

qhb_amcheck

qhb_amcheck — проверить одну или несколько баз данных QHB на предмет повреждения

Синтаксис

qhb_amcheck [параметр...] [имя_бд]

Описание

Утилита qhb_amcheck поддерживает запуск реализованных в расширении amcheck функций проверки на повреждения в одной или нескольких базах данных с параметрами для выбора проверяемых схем, таблиц и индексов, видов проверки, а также возможностью запустить эти проверки в параллельном режиме и задать для него число параллельных подключений.

В настоящее время поддерживаются только табличные отношения (обычные и TOAST), материализованные представления, последовательности и индексы B-деревья. Другие типы отношений молча пропускаются.

Если задается имя_бд, это должно быть имя одной проверяемой базы данных, и никакие другие параметры выбора баз данных задаваться не должны. В ином случае, если присутствуют какие-либо параметры выбора баз данных, будут проверяться все соответствующие базы. В отсутствие таких параметров будет проверяться база данных по умолчанию. Параметрами выбора баз данных являются --all, --database и --exclude-database. Также сюда можно включить --relation, --exclude- relation, --table, --exclude-table, --index и --exclude-index, но только когда эти параметры используются с трехкомпонентным шаблоном (например, mydb*.myschema*.myrel*). И, наконец, к ним можно отнести --schema и --exclude-schema, когда эти параметры используются с двухкомпонентным шаблоном (например, mydb*.myschema*).

В качестве параметра имя_бд может также передаваться строка подключения.

Параметры

Следующие параметры командной строки определяют, что будет проверяться:

-a
--all
Проверить все базы данных, кроме исключенных параметром --exclude-database.

-d шаблон
--database=шаблон
Проверить базы данных, соответствующие заданному шаблону, кроме исключенных параметром --exclude-database. Этот параметр можно указать неоднократно.

-D шаблон
--exclude-database=шаблон
Исключить базы данных, соответствующие заданному шаблону. Этот параметр можно указать неоднократно.

-i шаблон
--index=шаблон
Проверить индексы, соответствующие заданному шаблону, если они не исключены каким-либо образом. Этот параметр можно указать неоднократно.
Этот параметр схож с параметром --relation за исключением того, что он применяется только к индексам, а не к другим типам отношений.

-I шаблон
--exclude-index=шаблон
Исключить индексы, соответствующие заданному шаблону. Этот параметр можно указать неоднократно.
Этот параметр схож с параметром --exclude-relation, за исключением того, что он применяется только к индексам, а не к другим типам отношений.

-r шаблон
--relation=шаблон
Проверить отношения, соответствующие заданному шаблону, если они не исключены каким-либо образом. Этот параметр можно указать неоднократно.
Шаблоны могут быть неполными, например myrel*, либо могут быть дополнены схемой, например myschema*.myrel*, либо могут быть дополнены базой данных и схемой, например mydb*.myschema*.myrel*. Если шаблон указан с базой данных, все соответствующие базы будут добавлены в список проверяемых.

-R шаблон
--exclude-relation=шаблон
Исключить отношения, соответствующие заданному шаблону. Этот параметр можно указать неоднократно.
Как и с --relation, шаблон может быть неполным, дополненным схемой или базой данных и схемой.

-s шаблон
--schema=шаблон
Проверить таблицы и индексы в схемах, соответствующие заданному шаблону, если они не исключены каким-либо образом. Этот параметр можно указать неоднократно.
Чтобы выбрать таблицы только в схемах, соответствующих определенному шаблону, можно написать что-то вроде --table=ШАБЛОН_СХЕМЫ.* --no-dependent-indexes. Чтобы выбрать только индексы, можно использовать синтаксис --index=ШАБЛОН_СХЕМЫ.*.
Шаблон схемы можно дополнить указанием базы данных. Например, можно написать --schema=mydb*.myschema*, чтобы выбрать схемы, соответствующие myschema*, в базах данных, соответствующих mydb*.

-S шаблон
--exclude-schema=шаблон
Исключить таблицы и индексы, соответствующие заданному шаблону. Этот параметр можно указать неоднократно.
Как и в --schema, шаблон можно дополнить указанием базы данных.

-t шаблон
--table=шаблон
Проверить таблицы, соответствующие заданному шаблону, если они не исключены каким-либо образом. Этот параметр можно указать неоднократно.
Этот параметр схож с параметром --relation, за исключением того, что он применяется только к таблицам, материализованным представлениям и последовательностям, а не к индексам.

-T шаблон
--exclude-table=шаблон
Исключить таблицы, соответствующие заданному шаблону. Этот параметр можно указать неоднократно.
Этот параметр схож с параметром --exclude-relation, за исключением того, что он применяется только к таблицам, материализованным представлениям и последовательностям, а не к индексам.

--no-dependent-indexes
По умолчанию, если проверяется таблица, также будут проверяться все индексы B-деревья, даже если они не были явно выбраны параметрами вроде --index или --relation. Данный параметр подавляет это поведение.

--no-dependent-toast
По умолчанию, если проверяется таблица, также будет проверяться ее таблица TOAST (если таковая имеется), даже она не была явно выбрана параметрами вроде --table или --relation. Данный параметр подавляет это поведение.

--no-strict-names
По умолчанию, если аргументу --database, --table, --index или --relation не соответствует ни один объект, это является критической ошибкой. Данный параметр снижает уровень ошибки до предупреждения.

Следующие параметры командной строки управляют проверками таблиц:

--exclude-toast-pointers
По умолчанию, когда в таблице встречается указатель на TOAST, производится поиск для проверки того, что указатель ссылается на однозначно корректные записи в таблице TOAST. Эти проверки могут быть довольно медленными, и данный параметр позволяет пропустить их.

--on-error-stop
Прекратить обработку табличного отношения после сообщения обо всех повреждениях на первой странице, где они были обнаружены, и перейти к следующей таблице или индексу.
Обратите внимание, что проверка индекса всегда прекращается после обнаружения первой поврежденной страницы. Этот параметр актуален только для табличных отношений.

--skip=параметр
Если задано all-frozen, проверки повреждений таблиц будут пропускать страницы, помеченные как полностью замороженные, во всех таблицах.
Если задано all-visible, проверки повреждений таблиц будут пропускать страницы, помеченные как полностью видимые, во всех таблицах.
По умолчанию никакие страницы не пропускаются. Это поведение задается значением none, но поскольку это значение по умолчанию, указывать его необязательно.

--startblock=блок
Начать проверку с блока с указанным номером. Если в проверяемом табличном отношении находится меньше этого числа блоков, будет выдана ошибка. Этот параметр не применим к индексам и, вероятно, полезен только при проверке одного табличного отношения. Дополнительные пояснения см. в описании --endblock.

--endblock=блок
Закончить проверку на блоке с указанным номером. Если в проверяемом табличном отношении находится меньше этого числа блоков, будет выдана ошибка. Этот параметр не применим к индексам и, вероятно, полезен только при проверке одного табличного отношения. Если проверяется и обычная таблица, и таблица TOAST, этот параметр подействует на обе, но при проверке указателей TOAST все равно возможны обращения к блокам за указанным пределом, если только эта проверка не выключена параметром --exclude-toast-pointers.

Следующие параметры командной строки управляют проверками индексов B-деревьев:

--heapallindexed
Для каждого обрабатываемого индекса проверять наличие всех кортежей кучи в виде индексных кортежей с применением параметра heapallindexed расширения amcheck.

--parent-check
Для каждого проверяемого индекса B-дерева выполнять функцию amcheck bt_index_parent_check, которая при проверке индекса проводит дополнительные проверки отношений родитель/потомок.
По умолчанию выполняется функция bt_index_check утилиты amcheck, но обратите внимание, что при использовании параметра --rootdescend неявно выбирается функция bt_index_parent_check.

--rootdescend
Для каждого проверяемого индекса повторно находить кортежи на уровне листьев, для каждого кортежа заново проводя поиск с корневой страницы с помощью параметра rootdescend расширения amcheck.
При использовании этого параметра также неявно выбирается параметр --parent-check.
Эта форма проверки изначально была написана в помощь разработке функциональности индексов B-деревьев. Она может иметь ограниченное применение или вовсе быть бесполезной для обнаружения тех видов повреждений, которые встречаются на практике. Также она может значительно замедлить проверку на повреждения и потребовать гораздо больше серверных ресурсов.

ВНИМАНИЕ!
Дополнительные проверки, выполняемые для индексов B-деревьев, когда указан параметр --parent-check или --rootdescend, требуют относительно тяжелых блокировок на уровне отношений. Только эти проверки блокируют параллельное изменение данных командами INSERT, UPDATE и DELETE.

Следующие параметры командной строки управляют подключением к серверу:

-h имя_хоста
--host=имя_хоста
Задает имя компьютера, на котором работает сервер. Если значение начинается со слэша, оно определяет каталог сокета домена Unix.

-p порт
--port=порт
Указывает порт TCP или расширение файла локального сокета домена Unix, на котором сервер принимает подключения.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя для подключения.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных, даже если он не будет использоваться.
Это несущественный параметр, поскольку qhb_amcheck автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы понять, что требуется пароль, qhb_amcheck потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

--maintenance-db=имя_бд
Задает базу данных или строку подключения для установки подключения, через которое будет получен список баз данных для проверки. Если не установлен ни --all, ни какой-либо из параметров, задающих шаблон баз данных, такое подключение не требуется, и этот параметр ничего не делает. В ином случае все параметры из строки подключения, кроме имени базы данных, включенные в значение этого параметра, также будут использованы при подключении к проверяемым базам данных. Если этот параметр опущен, по умолчанию выполняется подключение к базе qhb, а если к ней подключиться не удается — к базе template1.

Также доступны другие параметры:

-e
--echo
Выводить в stdout все запросы SQL, передаваемые серверу.

-j число
--jobs=число
Использовать заданное число одновременных подключений к серверу или, если проверяемых объектов меньше, чем заданных подключений, по одному на объект.
По умолчанию используется одно подключение.

-P
--progress
Показывать информацию о прогрессе операции. Информация о прогрессе включает число отношений, для которых уже была проведена проверка, и общий размер этих отношений. В нее также включается общее число отношений, которые в итоге будут проверены, и примерный размер этих отношений.

-v
--verbose
Выводить больше сообщений. В частности, будут выводиться сообщения о каждом проверенном отношении, а также повысится уровень детализации серверных ошибок.

--version
Вывести версию qhb_amcheck и завершиться.

--install-missing
--install-missing=схема
Установить все отсутствующие расширения, требуемые для проверки баз(ы) данных. Если это расширение еще не было установлено, все его объекты будут установлены в указанную схему или, если она не задана, в схему pg_catalog.
В настоящее время единственным необходимым расширением является amcheck.

-?
--help
Показать справку об аргументах командной строки qhb_amcheck и завершиться.

См. также

amcheck

qhb_basebackup

qhb_basebackup — создать базовую резервную копию кластера QHB

Синтаксис

qhb_basebackup [параметр...]

Описание

Утилита qhb_basebackup применяется для создания базовой резервной копии работающего кластера баз данных QHB. Создание копии не влияет на работу других клиентов базы данных, и эту копию можно использовать как для восстановления на момент времени (см. раздел Непрерывное архивирование и восстановление на момент времени (PITR)), так и в качестве отправной точки для резервного сервера при доставке журналов или потоковой репликации (см. раздел Доставка журналов на резервные серверы).

qhb_basebackup создает точную копию файлов кластера баз данных, при этом контролируя автоматическое переключение системы в режим резервного копирования и обратно. Резервные копии всегда создаются для кластера целиком; невозможно создание копии отдельных баз данных или их объектов. Для выборочного копирования следует использовать другие инструменты, к примеру утилиту qhb_dump.

Резервное копирование выполняется через обычное соединение QHB, использующее протокол репликации. Подключаться нужно от имени суперпользователя или пользователя с правом REPLICATION (см. раздел Атрибуты ролей), а файл qhb_hba.conf должен разрешать подключение для репликации. Кроме того, значение параметра max_wal_senders на сервере должно быть достаточно большим, чтобы как минимум один передатчик WAL был доступен для резервного копирования и еще один — для потоковой передачи WAL (если она используется).

Можно запустить несколько команд qhb_basebackup одновременно, но с точки зрения производительности лучше выполнить одно резервное копирование и скопировать полученный результат.

qhb_basebackup может сделать базовую копию не только с главного, но и с резервного сервера. Для этого нужно настроить резервный сервер, чтобы тот мог принимать подключения репликации (т. е. соответствующим образом настроить параметры max_wal_senders и hot_standby и конфигурацию qhb_hba.conf). Также нужно будет включить на главном сервере full_page_writes.

Обратите внимание, что при резервном копировании с резервного сервера есть некоторые ограничения:

Файл истории резервного копирования в целевом кластере баз данных не создается.

qhb_basebackup не может принудительно переключить резервный сервер на новый файл WAL в конце копирования. Если используется режим -X none и активность записи на основном сервере низкая, есть вероятность, что qhb_basebackup придется долго ждать переключения и архивирования последнего файла WAL, необходимого для полноты копии. В таком случае будет целесообразно запустить на основном сервере функцию pg_switch_wal для немедленного переключения файла WAL.

Если во время копирования резервный сервер становится главным, оно прерывается.

Все записи WAL, необходимые для резервного копирования, должны содержать достаточное количество полных страниц, для чего необходимо включить full_page_writes на главном сервере.

Когда qhb_basebackup создает базовую копию, в представлении pg_stat_progress_basebackup отображается состояние этого процесса. Подробную информацию см. в подразделе Отчет о ходе выполнения базового резервного копирования.

Параметры

Следующие параметры командной строки управляют расположением и форматом вывода:

-D каталог
--pgdata=каталог
Задает целевой каталог для записи резервной копии. Если его не существует, qhb_basebackup создаст его (и все отсутствующие родительские каталоги). Если каталог уже существует, он должен быть пустым.
Если резервная копия создается в формате tar, имя целевого каталога можно задать как - (тире), и тогда tar-файл будет записан в stdout.
Этот параметр является обязательным.

-F формат
--format=формат
Выбирает формат вывода. формат может принимать одно из следующих значений:

p
plain
Записать результат в виде простых файлов, сохраняя структуру текущего каталога хранения данных и табличных пространств. Если в кластере нет дополнительных табличных пространств, вся база данных будет помещена в целевой каталог. В ином случае основной каталог хранения данных будет помещен в целевой каталог, а все остальные табличные пространства — в те же абсолютные пути, что и на исходном сервере. (Чтобы изменить это, воспользуйтесь параметром --tablespace-mapping).
Это формат по умолчанию.

t
tar
Записать результат в целевой каталог в виде tar-файлов. Основной каталог хранения данных будет записан в файл с именем base.tar, а все остальные табличные пространства будут записаны в отдельные tar-файлы, названные в соответствии с OID этих табличных пространств.
Если в качестве имени целевого каталога задано - (тире), данные будут записаны в стандартный вывод, подходящий для передачи, например, в gzip. Это возможно только в том случае, если в кластере нет дополнительных табличных пространств и не используется потоковая передача WAL.

-R
--write-recovery-conf
Создает файл standby.signal и добавляет параметры подключения в файл qhb.auto.conf в целевом каталоге (или в базовый архивный файл, если используется формат tar). Это упрощает настройку резервного сервера с помощью результатов резервного копирования.
В файл qhb.auto.conf будут записаны параметры подключения и, если указан слот репликации, который использует qhb_basebackup, так что впоследствии при потоковой репликации будут использованы те же параметры.

-t цель
--target=цель
Указывает серверу, куда поместить базовую резервную копию. Целью по умолчанию является клиент, что означает, что резервная копия должна быть отправлена на компьютер, где запущена утилита qhb_basebackup. Если вместо этого в качестве цели задано server:/некоторый/путь, то резервная копия будет сохранена на компьютере, где запущен сервер, в каталоге /некоторый/путь. Для сохранения резервной копии на сервере требуются права суперпользователя или роли pg_write_server_files. Если целью указано blackhole, то содержимое копии отбрасывается и нигде не сохраняется. Это значение следует использовать только в целях тестирования, поскольку фактически никакой резервной копии вы не получите.

Поскольку прием потока WAL реализуется qhb_basebackup, а не сервером, этот параметр нельзя использовать совместно с -X stream. Поскольку это режим по умолчанию, то при указании данного параметра следует также указать -X fetch или -X none.

-T старый_каталог=новый_каталог
--tablespace-mapping=старый_каталог=новый_каталог
Перемещает табличное пространство из старого_каталога в новый_каталог в процессе копирования. Чтобы перемещение произошло, старый_каталог должен точно соответствовать пути табличного пространства, как тот определен на исходном сервере. (Однако если в старом_каталоге исходного сервера не окажется табличного пространства, это не будет ошибкой.) В то же время новый_каталог является каталогом в файловой системе получающего хоста. Как и основной целевой каталог, новый_каталог может не существовать, но если он уже существует, то должен быть пустым. И старый_каталог, и новый_каталог должны задаваться абсолютными путями. Если путь содержит знак равенства (=), его нужно экранировать обратным слэшем. Этот параметр можно указать несколько раз для нескольких табличных пространств.
Если табличное пространство перемещается таким образом, символические ссылки внутри основного каталога данных обновляются, чтобы указывать на новое местоположение. Таким образом, для нового экземпляра сервера подготавливается новый каталог хранения данных, где все табличные пространства находятся в новых расположениях.
В настоящее время этот параметр работает только с простым форматом вывода; если выбран формат tar, параметр игнорируется.

--waldir=каталог_wal
Задает каталог для записи WAL (журнала упреждающей записи). По умолчанию файлы WAL будут помещаться в подкаталог pg_wal целевого каталога, но с помощью этого параметра их можно поместить в любое другое место. каталог_wal должен задаваться абсолютным путем. Как и основной целевой каталог, каталог_wal может не существовать, но если он уже существует, то должен быть пустым. Этот параметр можно задать, только если копия создается в простом формате.

-X метод
--wal-method= метод
Включает в резервную копию все необходимые файлы WAL (журнала упреждающей записи). Сюда войдут все журналы, сгенерированные в процессе резервного копирования. Если только не выбран метод none, qhbmaster можно запускать непосредственно в извлеченном каталоге без необходимости обращаться к архиву журналов; таким образом будет получена полностью автономная резервная копия.
Поддерживаются следующие методы сбора журналов упреждающей записи:

n
none
Не включать журнал упреждающей записи в резервную копию.

f
fetch
Файлы журнала упреждающей записи собираются в конце процесса резервного копирования. Поэтому необходимо задать достаточно большое значение параметра wal_keep_size на исходном сервере, чтобы журнал не удалялся до окончания резервного копирования. Если журнал был переработан до момента передачи, резервное копирование прервется, а копия будет непригодной к использованию.
При использовании формата tar файлы журнала упреждающей записи будут записываться в файл base.tar.

s
stream
Журнал упреждающей записи передается в процессе создания резервной копии. При этом открывается второе соединение с сервером, по которому происходит потоковая передача журнала упреждающей записи, одновременно с резервным копированием. Таким образом, для этого режима потребуется не одно, а два соединения репликации. Пока клиент успевает принимать передаваемые данные журнала упреждающей записи, в этом режиме исходному серверу не нужно сохранять дополнительные журналы.
При использовании формата tar файлы журнала упреждающей записи будут записываться в отдельный файл с именем pg_wal.tar.
Это значение по умолчанию.

-z
--gzip
Включает gzip-сжатие выводимого tar-файла с уровнем сжатия по умолчанию. Сжатие поддерживается только для формата tar; при этом ко всем именам файлов tar будет автоматически добавлен суффикс .gz.

-Z уровень
-Z [{client|server}-]метод[:подробная_информация]
--compress=уровень
--compress [{client|server}-]метод[:подробная_информация]
Запрашивает сжатие резервной копии. Если содержится указание client или server, оно задает место, где должно выполняться сжатие. При сжатии на сервере уменьшится объем передаваемых данных, но возрастет нагрузка на ЦП. Значение по умолчанию — client, за исключением случаев, когда используется параметр --target. В этом случае резервная копия не передается клиенту, поэтому имеет смысл только сжатие на сервере. В режиме -X stream (по умолчанию) сжатие на стороне сервера не применяется к WAL. Чтобы сжать WAL, воспользуйтесь сжатием на стороне клиента или укажите -X fetch.
В качестве метода сжатия можно задать gzip, lz4, zstd или none (без сжатия). Дополнительно можно указать строку с подробной информацией по сжатию. Если в этой строке передается целое число, оно задает уровень сжатия. В противном случае это должен быть список разделенных запятыми элементов в форме ключевое_слово или ключевое_слово=значение. В настоящее время поддерживаются ключевые слова level (уровень) и workers (рабочие процессы).
Если уровень сжатия не задан, будет использован уровень сжатия по умолчанию. Если задан только уровень, но не алгоритм сжатия, будет использован метод сжатие gzip, если уровень больше 0, а при уровне 0 сжатие не будет выполняться.
При использовании формата tar с методом gzip, lz4 или zstd, ко всем именам файлов tar будет автоматически добавлен суффикс .gz, .lz4 или .zst соответственно. При использовании простого формата сжатие на стороне клиента выполнить нельзя, но запросить сжатие на стороне сервера по-прежнему возможно. При этом сервер будет сжимать резервную копию для передачи, а клиент будет ее распаковывать и извлекать данные.
Когда этот параметр используется вместе с -X stream, pg_wal.tar будет сжат методом gzip, если выбрано сжатие gzip на стороне клиента, но не будет сжат, если выбран любой другой алгоритм сжатия или сжатие на стороне сервера.

Следующие параметры командной строки управляют генерированием резервной копии и вызовом этой программы:

-c fast|spread
--checkpoint=fast|spread
Устанавливает для контрольных точек режим fast (быстрый) или spread (протяженный, по умолчанию) (см. подраздел Создание базовой резервной копии с использованием API низкого уровня).

-C
--create-slot
Указывает, что до начала резервного копирования должен быть создан слот репликации с именем, заданным в параметре --slot. Если такой слот уже существует, возникает ошибка.

-l метка
--label=метка
Задает метку для резервной копии. Если ее не указывать, по умолчанию будет использовано значение «qhb_basebackup base backup».

-n
--no-clean
По умолчанию, когда qhb_basebackup прерывается с ошибкой, она удаляет все каталоги, которые могла создать, прежде чем обнаружила, что не может завершить работу (например целевой каталог и каталог журнала упреждающей записи). Этот параметр запрещает подобную очистку и поэтому полезен для отладки.
Обратите внимание, что каталоги табличных пространств не очищаются в любом случае.

-N
--no-sync
По умолчанию qhb_basebackup будет ждать, пока все файлы не будут гарантированно записаны на диск. С данным параметром qhb_basebackup возвращает результат немедленно, что делает ее быстрее, но означает, что при последующем сбое операционной системы базовая резервная копия может оказаться поврежденной. В целом, этот параметр полезен для тестирования, но не должен применяться при создании эксплуатационной установки.

-P
--progress
Включает отчет о прогрессе. При установке этого параметра во время создания резервной копии будет передаваться примерный процент выполнения. Поскольку в процессе копирования база данных может изменяться, это значение будет всего лишь приблизительным и может не достигать ровно 100%. В частности, когда в резервную копию включается журнал WAL, общий размер данных невозможно рассчитать заранее, и в этом случае предварительный целевой размер будет увеличиваться, как только превысит предварительный общий размер без WAL.

-r скорость_передачи
--max-rate=скорость_передачи
Задает максимальную скорость передачи данных с сервера. Это может быть полезно для ограничения воздействия qhb_basebackup на производительность сервера. Значения задаются в килобайтах в секунду. Чтобы указать мегабайты в секунду, можно использовать суффикс M. Также принимается суффикс k, но он не действует. Допустимые значения находятся в пределах от 32 Кб/с до 1024 Мб/с.
Этот параметр всегда влияет на передачу каталога данных. На передачу файлов WAL он влияет, только если выбран метода сбора данных fetch.

-S имя_слота
--slot=имя_слота
Этот параметр можно применять только вместе с -X stream. С ним для потоковой передачи WAL используется указанный слот репликации. Если базовая резервная копия предназначена для использования на резервном сервере с потоковой репликации через слот репликации, это же имя слота должно задаваться на резервном сервере в качестве primary_slot_name. Это гарантирует, что основной сервер не удалит никакие необходимые данные WAL в интервале между окончанием создания базовой копии и началом потоковой репликации на новом резервном сервере.
Если не используется параметр -C, указанный слот репликации уже должен существовать.
Если этот параметр не задан и сервер поддерживает временные слоты репликации, то для потоковой передачи WAL автоматически используется временный слот репликации.

-v
--verbose
Включить режим подробного вывода. Будут выводится некоторые дополнительные сведения об этапах в начале и завершении, а также отображаться конкретные имена файлов, которые обрабатываются в данный момент, если также включен отчет о прогрессе.

--manifest-checksums=алгоритм
Задает алгоритм контрольных сумм, который должен применяться к каждому файлу, включенному в манифест резервной копии. В настоящее время доступны алгоритмы NONE (отсутствует), CRC32C, SHA224, SHA256, SHA384 и SHA512. Значение по умолчанию — CRC32C.
Если выбран вариант NONE, манифест резервной копии не будет содержать никаких контрольных сумм. В ином случае он будет содержать контрольную сумму каждого файла копии, рассчитанную по заданному алгоритму. Кроме того, манифест всегда будет содержать контрольную сумму своего содержимого, рассчитанную по алгоритму SHA256. Алгоритмы SHA требуют значительно больше ресурсов процессора, чем CRC32C, поэтому выбор одного из них может увеличить время, требующееся для завершения копирования.
Применение функций хеширования SHA обеспечивает криптографически защищенную обработку каждого файла, если нужны гарантии того, что резервная копия не была искажена, тогда как алгоритм CRC32C вычисляет контрольную сумму гораздо быстрее; он подходит для выявления ошибок из-за случайных изменений, но неустойчив против злонамеренных модификаций. Обратите внимание, что для защиты от злоумышленника, имеющего доступ к резервной копии, манифест этой копии должен храниться отдельно в надежном месте или еще как-то проверяться на предмет отсутствия модификаций с момента создания копии.
Для проверки целостности копии по ее манифесту можно воспользоваться утилитой qhb_verifybackup.

--manifest-force-encode
Принудительно включает шестнадцатеричное кодирование всех имен файлов в манифесте резервной копии. Если этот параметр не задан, шестнадцатеричному кодированию подвергаются только имена файлов в кодировке, отличной от UTF8. Этот параметр предназначен в основном для проверки того, что инструменты, читающие файл манифеста резервной копии, правильно воспринимают такие имена.

--no-estimate-size
Не позволяет серверу рассчитывать общий объем копируемых данных, который будет передаваться, в результате чего столбец backup_total в представлении pg_stat_progress_basebackup всегда будет равен NULL.
Без этого параметра копирование начнется с пересчитывания всех файлов в базе данных, а затем вернется к отправке фактического содержимого. Это может увеличить длительность процесса копирования, в частности пройдет больше времени до начала отправки данных. Этот параметр помогает исключать подсчет объема, когда тот оказывается слишком долгим.
Этот параметр нельзя использовать вместе с параметром --progress.

--no-manifest
Выключает создание манифеста резервной копии. Если этот параметр не задан, сервер будет генерировать и передавать манифест резервной копии, который можно проверить с помощью qhb_verifybackup. Манифест представляет собой список всех файлов, находящихся в копии, за исключением файлов WAL, которые могут быть в нее добавлены. Также в нем хранится размер, время последнего изменения и, возможно, контрольная сумма каждого файла.

--no-slot
Препятствует созданию временного слота репликации для резервного копирования.
По умолчанию, если выбрана потоковая передача журнала, но в параметре -S не задано имя слота, создается временный слот репликации (если это поддерживает исходный сервер).
Основное предназначение этого параметра состоит в том, что он позволяет создать базовую резервную копию, когда на сервере нет свободных слотов репликации. Использование слота репликации почти всегда предпочтительнее, поскольку это предотвращает удаление сервером необходимых файлов WAL во время резервного копирования.

--no-verify-checksums
Отключает проверку контрольных сумм, если они включены на сервере, с которого делается резервная копия.
По умолчанию контрольные суммы проверяются, и при наличии в них несоответствия выдается ненулевой код завершения. Однако в этом случае базовая резервная копия не удаляется, как если бы использовался параметр --no-clean. Несоответствия контрольных сумм также будут отображаться в представлении pg_stat_database.

Следующие параметры командной строки управляют подключением к исходному серверу:

-d строка_подключения
--dbname=строка_подключения
Задает параметры, используемые для подключения к серверу, в виде строки подключения; они будут переопределять любые конфликтующие параметры командной строки.
Этот параметр называется --dbname для согласованности с другими клиентскими приложениями, но поскольку qhb_basebackup не подключается к какой-либо конкретной базе данных кластера, любое имя базы данных будет проигнорировано.

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix. Значение по умолчанию берется из переменной среды PGHOST, если она установлена; иначе производится попытка подключения к сокету домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения. Значение по умолчанию определяется переменной среды PGPORT, если она установлена, или значением, заданным при компиляции.

-s интервал
--status-interval=интервал
Задает интервал в секундах между сообщениями о состоянии, отсылаемыми исходному серверу. Чем меньше это значение, тем тщательнее сервер будет мониторить ход выполнения резервного копирования. Нулевое значение полностью выключает периодическое обновление состояния, хотя сообщения все равно будут передаваться по запросу сервера во избежание отключений по превышению времени ожидания. Значение по умолчанию — 10 секунд.

-U имя_пользователя
--username=имя_пользователя
Задает имя пользователя, под которым производится подключение.

-w
--no-password
Предотвращает запрос на ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Заставляет qhb_basebackup запрашивать пароль перед подключением к исходному серверу.
Это несущественный параметр, так как qhb_basebackup автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, qhb_basebackup потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

Также доступны и другие параметры:

-V
--version
Вывести версию qhb_basebackup и завершиться.

-?
--help
Показать справку об аргументах командной строки qhb_basebackup и завершиться.

Переменные среды

Эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

В начале резервного копирования на исходном сервере следует выполнить контрольную точку. Это может занять некоторое время (особенно если не установлен параметр --checkpoint=fast), в течение которого qhb_basebackup будет бездействовать.

Резервная копия будет включать в себя все файлы каталога хранения данных и табличных пространств, в том числе файлы конфигурации и все дополнительные файлы, помещенные в этот каталог третьими лицами, за исключением некоторых временных файлов, управляемых QHB. Однако копируются только обычные файлы и каталоги, а помимо этого сохраняются лишь символические ссылки на табличные пространства. Символические ссылки, указывающие на определенные каталоги, известные QHB, копируются как пустые каталоги. Другие символические ссылки и файлы специальных устройств пропускаются. Дополнительную информацию см. в разделе Протокол потоковой репликации.

Если не указан флаг --tablespace-mapping, по умолчанию табличные пространства в простом формате будут копироваться в тот же путь, что и на сервере. Без этого параметра при использовании табличных пространств создать копию базы в простом формате на том же сервере не получится, поскольку резервная копия должна быть записана в те же каталоги, что и исходные табличные пространства.

Когда используется формат tar, пользователь должен позаботиться о распаковке каждого tar-файла перед запуском сервера QHB, использующего эти данные. При наличии дополнительных табличных пространств их tar-файлы необходимо распаковать в правильные расположения. В таком случае сервером будут созданы символические ссылки на эти табличные пространства, в соответствии с содержимым файла tablespace_map, включенного в файл base.tar.

qhb_basebackup сохранит групповые разрешения для файлов данных, если те включены в исходном кластере.

Примеры

Создать базовую резервную копию сервера на mydbserver и сохранить ее в локальном каталоге /var/lib/qhb/data:

$ qhb_basebackup -h mydbserver -D /var/lib/qhb/data

Создать резервную копию локального сервера в отдельных файлах tar для каждого табличного пространства и сохранить ее в каталоге backup, отчитываясь о прогрессе во время выполнения:

$ qhb_basebackup -D backup -Ft -z -P

Создать резервную копию локальной базы данных с одним табличным пространством и сжать ее с помощью bzip2:

$ qhb_basebackup -D - -Ft -X fetch | bzip2 > backup.tar.bz2

(Эта команда не будет выполнена, если в базе данных есть несколько табличных пространств.)

Создать резервную копию локальной базы данных с перемещением табличного пространства из /opt/ts в ./backup/ts:

$ qhb_basebackup -D backup/data -T /opt/ts=$(pwd)/backup/ts

Создать резервную копию локального сервера, в которой будет по одному tar-файлу для каждого табличного пространства (сжатие методом gzip с уровнем 9), и сохранить ее в каталоге backup:

$ qhb_basebackup -D backup -Ft --compress=gzip:9

См. также

qhb_dump, подраздел Отчет о ходе выполнения базового резервного копирования

pgbench

pgbench — запустить тест оценки производительности QHB

Синтаксис

pgbench -i [параметр...] [имя_бд]

pgbench [параметр...] [имя_бд]

Описание

Утилита pgbench — это простая программа для запуска тестов оценки производительности QHB. Она многократно выполняет одну и ту же последовательность команд SQL, возможно, в параллельных сеансах базы данных, а затем вычисляет среднюю скорость транзакций (число транзакций в секунду). По умолчанию pgbench тестирует сценарий, имеющий некоторое сходство с TPC-B, который включает в себя пять команд SELECT, UPDATE и INSERT на каждую транзакцию. Однако можно легко протестировать и другие сценарии, написав собственные файлы скрипта транзакции.

Типичный вывод pgbench выглядит так:

transaction type: <builtin: TPC-B (sort of)>
scaling factor: 10
query mode: simple
number of clients: 10
number of threads: 1
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
number of failed transactions: 0 (0.000%)
latency average = 11.013 ms
latency stddev = 7.351 ms
initial connection time = 45.758 ms
tps = 896.967014 (without initial connection time)

В первых семи строках отображаются значения некоторых наиболее важных параметров. В шестой строке отображается максимальное число попыток для транзакций с ошибками сериализации или взаимоблокировки (подробную информацию см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки). В восьмой строке отображается количество выполненных и запланированных транзакций (последнее является произведением количества клиентов на количество транзакций для одного клиента); эти числа будут различаться, только если выполнение досрочно завершится сбоем или в каких-либо командах SQL произойдет ошибка. (В режиме -T выводится только количество фактически выполненных транзакций.) В следующей строке отображается количество транзакций, не выполненных из-за ошибок сериализации или взаимоблокировок (подробную информацию см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки). В последней строке отображается число транзакций в секунду.

По умолчанию для теста транзакций типа TPC-B требуется предварительно подготовить специальные таблицы. Чтобы создать и наполнить эти таблицы, pgbench следует запустить с параметром -i (инициализировать). (При тестировании нестандартного скрипта этот шаг не нужен, но все же следует подготовить конфигурации, необходимую для этого теста.) Инициализация выглядит так:

pgbench -i [ другие-параметры ] имя_бд

где имя_бд — это имя уже созданной базы данных, в которой будет проводиться тест. (Кроме того, вам может понадобиться добавить параметры -h, -p и/или -U, чтобы указать, как подключиться к серверу баз данных.)

ВНИМАНИЕ
pgbench -i создает четыре таблицы pgbench_accounts, pgbench_branches, pgbench_history и pgbench_tellers, при этом уничтожая все существующие таблицы с этими именами. Если в вашей базе данных имеются таблицы с этими именами, обязательно воспользуйтесь для тестов другой базой!

С «коэффициентом масштабирования» по умолчанию, равным 1, эти таблицы изначально содержат столько строк:

table                   # of rows
---------------------------------
pgbench_branches        1
pgbench_tellers         10
pgbench_accounts        100000
pgbench_history         0

Вы можете (и в большинстве случаев должны) увеличить число строк, воспользовавшись параметром -s (коэффициент масштабирования). На этом этапе также может быть полезен параметр -F (коэффициент наполнения).

Завершив необходимую настройку, можно запустить тест оценки производительности командой без -i, то есть

pgbench [ параметры ] имя_бд

Практически во всех случаях, чтобы получить полезный тест, понадобятся какие-либо параметры. К наиболее важным параметрам относятся -c (число клиентов), -t (число транзакций), -T (ограничение по времени) и -f (указать файл с нестандартным скриптом). Полный список параметров см. ниже.

Параметры

Все параметры разделены на три подраздела. Во время инициализации базы данных и во время тестовых прогонов используются разные параметры, но некоторые параметры полезны в обоих случаях.

Параметры инициализации

Утилита pgbench принимает следующие аргументы командной строки для инициализации:

имя_бд
Задает имя базы данных, в которой должен проходить тест. Если оно не задано, используется значение переменной среды PGDATABASE. Если и она не установлена, используется имя пользователя, под которым проводится подключение.

-i
--initialize
Требуется для вызова режима инициализации.

-I этапы_инициализации
--init-steps=этапы_инициализации
Выполнять только выбранный набор из всех обычных этапов инициализации. В параметре этапы_инициализации указываются этапы, которые должны выполняться (по одному символу на этап). Все этапы вызываются в заданном порядке. Значение по умолчанию — dtgvp. Возможные этапы:

d (Drop, удалить)
Удалить все существующие таблицы pgbench.

t (create Tables, создать таблицы)
Создать таблицы, используемые стандартным сценарием pgbench, а именно: pgbench_accounts, pgbench_branches, pgbench_history и pgbench_tellers.

g или G (Generate data, сгенерировать данные на стороне клиента или на стороне сервера)
Сгенерировать данные и загрузить их в стандартные таблицы, заменив все уже находящиеся там данные.
С ключом g (генерирование данных на стороне клиента) данные генерируются на клиенте pgbench, а затем передаются на сервер. При этом пропускная способность соединения клиент/сервер интенсивно используется командой COPY. Если не включено партиционирование, pgbench использует параметр FREEZE, чтобы ускорить последующую команду VACUUM. При генерировании с ключом g данных для таблицы pgbench_accounts каждые 100 000 строк выводится сообщение о прогрессе.
С ключом G (генерирование данных на стороне сервера) клиент pgbench передает только небольшие запросы, а затем уже сами данные генерируются на сервере. В этом варианте высокая пропускная способность не требуется, но возрастет нагрузка на сервер. При генерировании данных с ключом G никаких сообщений о прогрессе не выводится.
По умолчанию во время инициализации генерирование данных происходит на стороне клиента (равнозначно использованию ключа g).

v (Vacuum, очистка)
Вызывать команду VACUUM для стандартных таблиц.

p (create Primary keys, создать первичные ключи)
Создать индексы первичных ключей в стандартных таблицах.

f (create Foreign keys, создать внешние ключи)
Создать ограничения внешних ключей между стандартными таблицами. (Обратите внимание, что по умолчанию этот этап не выполняется.)

-F фактор_заполнения
--fillfactor=фактор_заполнения
Создать таблицы pgbench_accounts, pgbench_tellers и pgbench_branches с заданным фактором заполнения. Значение по умолчанию — 100.

-n
--no-vacuum
Не выполнять очистку во время инициализации. (Этот параметр подавляет этап инициализации v, даже если тот был указан в -I.)

-q
--quiet
Переключить журналирование в тихий режим, выводя только одно сообщение о прогрессе в 5 секунд. По умолчанию сообщения выдаются через каждые 100 000 строк, из-за чего за секунду часто выводится много строк (особенно на хорошем оборудовании).
Этот параметр не действует, если в -I указан ключ G.

-s коэффициент_масштабирования
--scale=коэффициент_масштабирования
Умножить количество генерируемых строк на заданный коэффициент масштабирования. К примеру, с -s 100 в таблице pgbench_accounts будет сгенерировано 10 000 000 строк. Значение по умолчанию — 1. При коэффициенте, равном 20 000 или выше, столбцы, содержащие идентификаторы счетов (столбцы aid), переключатся на более крупные целые числа (тип bigint), чтобы в них уместился весь диапазон идентификаторов счетов.

--foreign-keys
Создать ограничения внешних ключей между стандартными таблицами. (Этот параметр добавляет к последовательности этапов инициализации этап f, если тот отсутствует.)

--index-tablespace=табличное_пространство_индексов
Создает индексы в заданном табличном пространстве, а не в табличном пространстве по умолчанию.

--partition-method=ИМЯ
Создать партиционированную таблицу pgbench_accounts методом ИМЯ. Ожидаемые значения: range (диапазонный) или hash (хеш). Для этого параметра требуется, чтобы в --partitions было ненулевое значение. Если ничего не задано, метод по умолчанию — range.

--partitions=ЧИСЛО
Создать партиционированную таблицу pgbench_accounts с заданным ЧИСЛОМ партиций примерно одинакового размера для масштабированного количества счетов. Значение по умолчанию — 0, что подразумевает отключение партиционирования.

--tablespace=табличное_пространство
Создать таблицы в заданном табличном пространстве, а не в табличном пространстве по умолчанию.

--unlogged-tables
Создать все таблицы как нежурналируемые, а не как постоянные.

Параметры тестирования производительности

Утилита pgbench принимает следующие аргументы командной строки для тестирования производительности:

-b имя_скрипта[@вес]
--builtin=имя_скрипта[@вес]
Добавить заданный встроенный скрипт в список скриптов, которые будут выполняться. Доступные встроенные скрипты: tpcb-like, simple-update и select-only. Допускается указание однозначных префиксов их имен. Со специальным именем list показать список встроенных скриптов и немедленно завершиться.
Дополнительно можно указать целочисленный вес после @, чтобы скорректировать вероятность выбора этого скрипта относительно других. По умолчанию вес равен 1. Подробную информацию см. ниже.

-c клиенты
--client=клиенты
Число имитируемых клиентов, то есть число параллельных сеансов базы данных. Значение по умолчанию — 1.

-C
--connect
Устанавливать новое подключение для каждой транзакции вместо одного для каждого клиентского сеанса. Это полезно для оценки издержек подключений.

-d
--debug
Выводить отладочные сообщения.

-D имя_переменной=значение
--define=имя_переменной=значение
Определить переменную для использования в пользовательском скрипте (см. ниже). Параметр -D можно указать несколько раз.

-f имя_файла[@вес]
--file=имя_файла[@вес]
Добавить скрипт транзакции, читаемый из файла имя_файла, в список скриптов, которые будут выполняться.
Дополнительно можно указать целочисленный вес после @, чтобы скорректировать вероятность выбора этого скрипта относительно других. По умолчанию вес равен 1. (При указании имени файла скрипта, содержащего символ @, добавьте вес, чтобы избежать неоднозначности, например, filen@me@1.) Подробную информацию см. ниже.

-j потоки
--jobs=потоки
Количество рабочих потоков в pgbench. Использование нескольких потоков может быть полезно на многопроцессорных машинах. Клиенты распределяются по доступным потокам по возможности равномерно. Значение по умолчанию — 1.

-l
--log
Записать информацию о каждой транзакции в файл журнала. Подробную информацию см. ниже.

-L предел
--latency-limit=предел
Транзакции, длящиеся дольше заданного предела (в миллисекундах), подсчитываются и отражаются отдельно, как опаздывающие.
При дросселировании запросов (--rate=...) транзакции, отстающие от графика более чем на заданный предел (в мс) и поэтому никак не укладывающиеся в этот предел задержки, вообще не передаются на сервер. Они подсчитываются и отражаются отдельно, как пропущенные.
При использовании параметра --max-tries транзакция, завершившаяся сбоем из-за аномалии сериализации или взаимоблокировки, не будет повторяться, если общее время всех ее попыток превышает заданный предел (в мс). Чтобы ограничить только время попыток, а не их количество, установите --max-tries=0. По умолчанию в параметре --max-tries установлено значение 1, и транзакции с ошибками сериализации/взаимоблокировки не повторяются. Более подробную информацию о повторении таких транзакций см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки.

-M режим_запросов
--protocol=режим_запросов
Протокол, который будет использоваться для передачи запросов на сервер:

simple: использовать простой протокол запросов.

extended: использовать расширенный протокол запросов.

prepared: использовать расширенный протокол запросов с подготовленными операторами. В режиме prepared pgbench повторно использует результат синтаксического анализа, начиная со второй итерации запроса, и поэтому выполняется быстрее, чем в других режимах.
По умолчанию установлен простой протокол запросов. (Подробную информацию см. в главе Клиент-серверный протокол.)

-n
--no-vacuum
Не выполнять очистку таблиц перед проведением теста. Этот параметр необходим, если вы запускаете собственный сценарий, не включающий стандартные таблицы pgbench_accounts, pgbench_branches, pgbench_history и pgbench_tellers.

-N
--skip-some-updates
Запустить встроенный скрипт simple-update (простое добавление). Краткая запись для -b simple-update.

-P секунды
--progress=секунды
Отображать отчет о прогрессе через указанное число секунд. Отчет включает время, прошедшее с момента запуска, скорость (в TPS) с момента предыдущего отчета, а также среднее время задержки транзакций, стандартной отклонение и количество прервавшихся транзакций с момента последнего отчета. При дросселировании запросов (-R) время задержки вычисляется относительно запланированного времени начала транзакции, а не фактического времени ее начала, поэтому оно также включает среднее время отставания от графика . При использовании параметра --max-tries для включения повторения транзакций после ошибок сериализации/взаимоблокировки в отчет включается количество повторявшихся транзакций и общее число всех повторов.

-r
--report-per-command
Отображать следующую статистику для каждой команды после завершения теста на производительность: среднее время задержки по операторам (время выполнения с точки зрения клиента), число сбоев и число повторений после ошибок сериализации или взаимоблокировки в этой команде. Статистика по повторениям отображается в отчете, только если параметр --max-tries не равен 1.

-R скорость
--rate=скорость
Выполнять транзакции, ориентируясь на указанную скорость, а не с максимально возможной скоростью (поведение по умолчанию). Скорость задается в транзакциях в секунду. Если указанная скорость превышает максимально возможную, это ограничение скорости не повлияет на результаты.
Заданная скорость достигается запуском транзакций по графику, обусловленному распределением Пуассона. Запланированное время запуска сдвигается относительно начала операций клиента, а не завершения предыдущей транзакции. Такой подход означает, что когда транзакции отстанут от исходного запланированного времени завершения, есть возможность, что последующие транзакции снова нагонят график.
При активном дросселировании запросов задержка транзакций, выводимое в конце тестового прогона, вычисляется, исходя из запланированного времени запуска, поэтому она включает в себя время, которое каждая транзакция вынуждена была ждать завершения предыдущей транзакции. Это время ожидания называется временем отставания от графика, и его среднее и максимальное значения тоже выводятся отдельно. Задержку транзакции относительно времени ее фактического запуска, т. е. время, потраченное на выполнение транзакции в базе данных, можно вычислить, вычтя время отставания от графика из зафиксированного времени задержки.
Если вместе с --rate применяется параметр --latency-limit, транзакция может отставать так сильно, что уже превышает предел времени задержки, когда завершается предыдущая транзакция, поскольку время задержки вычисляется относительно запланированного времени запуска. Подобные транзакции не передаются на сервер, а пропускаются и подсчитываются отдельно.
Большое значение отставания от графика является показателем того, что система не может обработать транзакции с заданной скоростью и выбранным количеством клиентов и потоков. Когда среднее время выполнения транзакции превышает запланированный интервал между транзакциями, каждая последующая транзакция будет отставать все больше, и по мере выполнения тестирования время отставания от графика будет неуклонно возрастать. Когда это происходит, следует снизить заданную скорость транзакций.

-s коэффициент_масштабирования
--scale=коэффициент_масштабирования
Отобразить заданный коэффициент масштабирования в выводе pgbench. Для встроенных тестов в этом нет необходимости; правильный коэффициент масштабирования будет определен путем подсчета количества строк в таблице pgbench_branches. Однако при выполнении только пользовательских тестов производительности (параметр -f) без этого параметра коэффициент масштабирования будет выводиться как равный 1.

-S
--select-only
Запустить встроенный скрипт select-only (только выборка). Краткая запись для -b select-only.

-t транзакции
--transactions=транзакции
Количество транзакций, выполняемых каждым клиентом. Значение по умолчанию — 10.

-T секунды
--time=секунды
Выполнять тест в течение заданного числа секунд, а не по фиксированному количеству транзакций на каждого клиента. Параметры -t и -T являются взаимоисключающими.

-v
--vacuum-all
Очищать все четыре стандартные таблицы перед запуском теста. Без параметров -n и -v утилита pgbench будет очищать таблицы pgbench_tellers и pgbench_branches и опустошать таблицу pgbench_history.

--aggregate-interval=секунды
Продолжительность интервала агрегации (в секундах). Можно использовать только с параметром -l. С этим параметром журнал будет содержать сводные данные по интервалам, как описано ниже.

--failures-detailed
Отображать ошибки в журнале по транзакциям и в журнале по агрегированию, а также в основном отчете и отчетах по скриптам, группируя их по следующим типам:

ошибки сериализации;

ошибки взаимоблокировки. Подробную информацию см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки.

--log-prefix=префикс
Установить префикс для имен файлов журнала, создаваемых с параметром --log. Значение по умолчанию — pgbench_log.

--max-tries=число_попыток
Включить повторения для транзакций с ошибками сериализации/взаимоблокировки и установить максимальное число этих попыток. Этот параметр можно сочетать с параметром --latency-limit, ограничивающим общее время всех попыток для транзакции; более того, не установив параметр --latency-limit или --time, нельзя задать неограниченное число попыток (--max-tries=0). Значение по умолчанию — 1, то есть транзакции с ошибками сериализации/взаимоблокировки не повторяются. Подробную информацию о повторении подобных транзакций см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки.

--progress-timestamp
При отображении прогресса (параметр -P) использовать временную метку (в формате эпохи Unix) вместо количества секунд, прошедших с начала запуска. Единицей измерения являются секунды с точностью др миллисекунд (в формате дроби). Это помогает сравнивать журналы, генерируемые различными программными средствами.

--random-seed=затравочное_значение
Установить затравочное значение для генератора случайных чисел. Вводит затравочное значение в системный генератор случайных чисел, который затем выдает последовательность начальных состояний генераторов, по одному для каждого потока. затравочным_значением может быть: time (по умолчанию, затравочное значение основано на текущем времени), rand (использовать надежный источник случайных чисел, выдать ошибку, если такового нет) или беззнаковое десятичное целое число. Генератор случайных чисел вызывается явно из скрипта pgbench (функции random...) или неявно (к примеру, параметр --rate использует его для создания графика выполнения транзакций). Если он устанавливается явно, значение, применяемое для затравки, отображается в терминале. Любое значение, допустимое в качестве затравочного_значения, можно также задать в переменной среды PGBENCH_RANDOM_SEED. Чтобы предоставленное затравочное значение точно действовало во всех возможных случаях использования, поставьте этот параметр первым или воспользуйтесь переменной среды .
Явное указание затравочного значения позволяет точно воспроизвести выполнение pgbench в том, что касается случайных чисел. Поскольку случайное состояние поддерживается в потоке, это означает, что при одинаковых запусках выполнение pgbench будет полностью идентичным, если один поток используется одним клиентом и отсутствуют внешние зависимости или зависимости от данных. С точки зрения статистики точное воспроизведение выполнения — идея неудачная, поскольку это может скрыть изменчивость производительности или чрезмерно завысить показатели производительности, например из-за попадания на те же страницы, что и во время предыдущего выполнения. Тем не менее это может быть и крайне полезно для отладки, к примеру, для повторения каверзного сценария, приводящего к ошибке. Применяйте этот параметр обдуманно.

--sampling-rate=частота
Частота выборки, применяемая при записи данных в журнал, которая позволяет снижать объем генерируемого журнала. Если этот параметр задан, протоколируется только указанная доля транзакций. Значение 1.0 означает, что протоколироваться будут все транзакции, значение 0.05 означает, что протоколироваться будут только 5% транзакций.
Обрабатывая файл журнала, не забудьте учесть частоту выборки. Например, при вычислении значений TPS, вам нужно будет умножить содержащиеся в нем числа соответствующим образом (к примеру, с частотой выборки 0.01 вы получите только 1/100 фактической TPS).

--show-script=имя_скрипта
Отобразить фактический код встроенного скрипта имя_скрипта в stderr и немедленно завершиться.

--verbose-errors
Выводить сообщения обо всех ошибках сериализации/взаимоблокировки и отказах (ошибки без последующего повторения транзакций), включая то, какой предел повторений был превышен и насколько. (Обратите внимание, что в этом случае объем вывода может значительно увеличиться.) Подробную информацию см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки.

Общие параметр

Утилита pgbench также принимает в качестве параметров подключения следующие аргументы командной строки:

-h имя_хоста
--host=имя_хоста
Имя хост-компьютера сервера баз данных

-p порт
--port=порт
Номер порта сервера баз данных

-U логин
--username=логин
Имя пользователя, под которым производится подключение

-V
--version
Вывести версию pgbench и завершиться.

-?
--help
Показать справку об аргументах командной строки pgbench и завершиться.

Код завершения

Успешное выполнение завершится с кодом 0. Код завершения 1 указывает на статичные проблемы, такие как недопустимые параметры командной строки или внутренние ошибки, которые не должны происходить. Начальные ошибки, возникающие при запуске теста, например, сбои при подключении, тоже имеют код завершения 1. При ошибках во время выполнения, например, ошибках в базе данных или проблемах в скрипте, будет выдан код завершения 2. В последнем случае pgbench выведет частичные результаты.

Переменные среды

PGDATABASE
PGHOST
PGPORT
PGUSER
Переменные подключения по умолчанию.

Эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Переменная среды PG_COLOR указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечание

Какие транзакции фактически выполняются в pgbench?

Утилита pgbench выполняет тестовые скрипты, выбираемые случайным образом из указанного списка. Это могут быть как встроенные скрипты, заданные ключом -b, так и пользовательские скрипты, заданные ключом -f. Для каждого скрипта можно задать относительный вес после символа @, чтобы изменить вероятность его выбора. По умолчанию вес равен 1. Скрипты с весом 0 игнорируются.

Стандартный встроенный скрипт транзакции (также вызываемый с ключом -b tpcb-like) выполняет по семь команд на транзакцию со случайно выбранными aid, tid, bid и delta. Этот сценарий основан на тесте производительности TPC-B, но на самом деле им не является, отсюда и название.

BEGIN;

UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;

SELECT abalance FROM pgbench_accounts WHERE aid = :aid;

UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;

UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;

INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);

END;

При выборе встроенного скрипта simple-update (также вызывается с ключом -N) шаги 4 и 5 в транзакцию не включаются. Это позволит избежать конкуренции при добавлении данных в эти таблицы, но делает тест еще менее похожим на TPC-B.

При выборе встроенного скрипта select-only (также вызывается с ключом -S) выполняется только команда SELECT.

Пользовательские скрипты

Утилита pgbench поддерживает запуск пользовательских сценариев теста производительности путем замещения стандартного скрипта транзакции (описанного выше) скриптом транзакции, считываемым из файла (параметр -f). В этом случае «транзакцией» считается одно выполнение файла скрипта.

Файл скрипта содержит одну или несколько команд SQL, оканчивающихся точкой с запятой. Пустые строки и строки, начинающиеся с --, игнорируются. Также файлы скриптов могут содержать «метакоманды», которые обрабатывает сама pgbench, как описано ниже.

Примечание
Предполагается, что скрипты pgbench не содержат незавершенных блоков транзакций SQL. Если во время выполнения клиент достигает конца скрипта, не завершив последний блок транзакции, его работа будет прервана.

Для файлов скриптов существует простой механизм подстановки переменных. Имена переменных должны состоять из букв (которые могут быть не латинскими), цифр и подчеркиваний, причем цифра не может быть первым символом. Переменные можно установить параметром командной строки -D, описанным выше, или метакомандами, описанными ниже. Кроме переменных, которые можно заранее установить параметрами командной строки -D, существует несколько переменных, заранее устанавливаемых автоматически. Они перечислены в Таблице 1. Значения, указанные для этих переменных с помощью -D, переопределяют автоматические значения. Установленное значение переменной можно добавить в команду SQL, написав :имя_переменной. При запуске нескольких клиентских сеансов каждый сеанс получает собственный набор переменных. Утилита pgbench поддерживает использование до 255 переменных в одном операторе.

Таблицы 1. Автоматические переменные pgbench

Переменная | Описание
client_id | уникальный номер, идентифицирующий клиентский сеанс (начинается с нуля)
default_seed | затравочное значение, по умолчанию используемое в функциях хеша и псевдослучайных перестановок
random_seed | затравочное значение генератора случайных чисел (если не переписано ключом -D)
scale | текущий коэффициент масштабирования

Метакоманды в файле скрипта начинаются с обратного слэша (\) и обычно продолжаются до конца строки, хотя их можно переносить на следующую строку, написав символы обратного слэша и возврата каретки. Аргументы метакоманд разделяются пробелами. Поддерживаются следующие метакоманды:

\gset [префикс] \aset [префикс]

Эти команды можно использовать для завершения запросов SQL вместо завершающей точки с запятой (;).

При использовании команды \gset ожидается, что предыдущий запрос SQL возвратит одну строку, столбцы которой сохраняются в переменных, названных по именам этих столбцов, а если предоставлен префикс, он добавляется к этим именам.

При использовании команды \aset столбцы всех совмещенных запросов SQL (разделенных \;) сохраняются в переменных, названных по именам этих столбцов, а если предоставлен префикс, он добавляется к этим именам. Если запрос не возвращает строк, присваивание не производится, и это можно определить, проверив переменные на существование. Если запрос возвращает несколько строк, сохраняется последнее значение.

Команды \gset и \aset нельзя использовать в конвейерном режиме, поскольку результаты запросов еще не будут доступны к тому моменту, когда могут понадобиться командам.

В следующем примере итоговый баланс из первого запроса попадает в переменную abalance, а целочисленные значения из третьего запроса — в переменные p_two и p_three. Результат второго запроса отбрасывается. Результат двух последних совмещенных запросов сохраняется в переменных four и five.

UPDATE pgbench_accounts
  SET abalance = abalance + :delta
  WHERE aid = :aid
  RETURNING abalance \gset
-- объединяет два запроса
SELECT 1 \;
SELECT 2 AS two, 3 AS three \gset p_
SELECT 4 AS four \; SELECT 5 AS five \aset

\if выражение
\elif выражение
\else
\endif

Эта группа команд реализует вкладываемые условные блоки, подобно \if выражение в psql. Условные выражения идентичны таковым в команде \set, причем ненулевые выражения интерпретируются как true.

\set имя_переменной выражение

Устанавливает в переменной имя_переменной значение, вычисленное из выражения. Выражение может содержать константу NULL, логические константы TRUE и FALSE, целочисленные константы, например, 5432, константы двойной точности с плавающей запятой, например, 3.14159, ссылки на переменные :имя_переменной, операторы с обычными для SQL приоритетностью и ассоциативностью, вызовы функций, общие условные выражения CASE SQL и круглые скобки.

Функции и большинство операторов возвращают NULL для входных данных NULL.

В контексте проверки условий ненулевые числовые значения являются TRUE, нулевые числовые значения и NULL — FALSE.

Слишком большие или слишком маленькие целочисленные константы или константы двойной точности с плавающей запятой, а также целочисленные арифметические операторы (+, -, * и /) вызывают ошибки переполнения.

Если в конструкции CASE не предоставлено заключительное предложение ELSE, значением по умолчанию является NULL.

Примеры:

\set ntellers 10 * :scale
\set aid (1021 * random(1, 100000 * :scale)) % \
           (100000 * :scale) + 1
\set divx CASE WHEN :x <> 0 THEN :y/:x ELSE NULL END

\sleep число [ us | ms | s ]

Приостанавливает выполнение скрипта на заданный период в микросекундах (us), миллисекундах (ms) или секундах (s). Если единицы опущены, по умолчанию значение считается заданным в секундах. Задаваемое число может быть целочисленной константой или ссылкой :имя_переменной на переменную с целочисленным значением.

Пример:

\sleep 10 ms

\setshell имя_переменной команда [ аргумент ... ]

Устанавливает в переменной имя_переменной результат команды оболочки команда с заданными аргументами. Эта команда должна возвращать целочисленное значение в свой стандартный вывод.

команда и каждый аргумент могут быть текстовой константой или ссылкой на переменную :имя_переменной. Если вы хотите использовать аргумент, начинающийся с двоеточия, напишите в начале этого аргумента дополнительное двоеточие.

Пример:

\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon

\shell команда [ аргумент ... ]

То же, что и \setshell, но результат команды отбрасывается.

Пример:

\shell command literal_argument :variable ::literal_starting_with_colon

\startpipeline
\endpipeline

Эти команды разделяют начало и конец конвейера команд SQL. В конвейерном режиме команды передаются серверу, не дожидаясь результатов предыдущих команд. Подробную информацию см. в разделе Конвейерный режим. Для конвейерного режима нужно использовать расширенный протокол запросов.

Встроенные операторы

Арифметические, битовые и логические операторы, перечисленные в Таблице 2, встроены в pgbench и могут использоваться в выражениях, находящихся в \set. Эти операторы перечислены в порядке возрастания их приоритета. Не считая отмеченных исключений, операторы, принимающие два числовых аргумента, будут выдавать значение двойной точности с плавающей запятой, если какой-либо из аргументов имеет такой тип; в противном случае они выдают целочисленный результат.

Таблица 2. Операторы pgbench

Оператор

Описание
Пример(ы)

логическое_значение OR логическое_значение → логическое_значение

Логическое ИЛИ
5 or 0 → TRUE

логическое_значение AND логическое_значение → логическое_значение

Логическое И
3 and 0 → FALSE

NOT логическое_значение → логическое_значение

Логическое НЕ
not false → TRUE

логическое_значение IS [NOT] (NULL|TRUE|FALSE) → логическое_значение

Проверки логических значений
1 is null → FALSE

значение ISNULL|NOTNULL → логическое_значение

Проверки на NULL
1 notnull → TRUE

число = число → логическое_значение

Равно
5 = 4 → FALSE

число <> число → логическое_значение

Не равно
5 <> 4 → TRUE

число != число → логическое_значение

Не равно
5 != 5 → FALSE

число < число → логическое_значение

Меньше
5 < 4 → FALSE

число <= число → логическое_значение

Меньше или равно
5 <= 4 → FALSE

число > число → логическое_значение

Больше
5 > 4 → TRUE

число >= число → логическое_значение

Больше или равно
5 >= 4 → TRUE

целое_число | целое_число → целое_число

Битовое ИЛИ
1 | 2 → 3

целое_число # целое_число → целое_число

Битовое исключающее ИЛИ
1 # 3 → 2

целое_число & целое_число → целое_число

Битовое И
1 & 3 → 1

~ целое_число → целое_число

Битовое НЕ
~ 1 → -2

целое_число << целое_число → целое_число

Битовый сдвиг влево
1 << 2 → 4

целое_число >> целое_число → целое_число

Битовый сдвиг вправо
5 <= 4 → FALSE

число + число → число

Сложение
5 + 4 → 9

число - число → число

Вычитание
3 - 2.0 → 1.0

число * число → число

Умножение
5 * 4 → 20

число / число → число

Деление (если оба аргумента целочисленные, результат округляется в сторону нуля)
5 / 3 → 1

целое_число % целое_число → целое_число

Остаток от деления (по модулю)
3 % 2 → 1

- число → число

Смена знака
- 2.0 → -2.0

Встроенные функции

Функции, перечисленные в Таблице 3, встроены в pgbench и могут использоваться в выражениях, находящихся в \set.

Таблица 3. Функции pgbench

Функция

Описание
Пример(ы)

abs ( число ) → тип аргумента

Абсолютное значение (модуль числа)
abs(-17) → 17

debug ( число ) → тип аргумента

Выводит аргумент в stderr и возвращает этот аргумент.
debug(5432.1) → 5432.1

debug ( число ) → double

Приводит аргумент к значению двойной точности с плавающей запятой.
double(5432) → 5432.0

exp ( число ) → double

Экспонента (e возводится в заданную степень)
exp(1.0) → 2.718281828459045

greatest ( число [, ... ] ) → double, если какой-либо из аргументов double, иначе — integer

Выбирает наибольшее значение среди аргументов.
greatest(5, 4, 3, 2) → 5

hash ( значение [, затравочное_значение ] ) → integer

Это псевдоним для hash_murmur2.
hash(10, 5432) → -5817877081768721676

hash_fnv1a ( значение [, затравочное_значение ] ) → integer

Вычисляет хеш по алгоритму FNV-1a.
hash_fnv1a(10, 5432) → -7793829335365542153

hash_murmur2 ( value [, seed ] ) → integer

Вычисляет хеш по алгоритму MurmurHash2.
hash_murmur2(10, 5432) → -5817877081768721676

int ( число ) → integer

Приводит аргумент к целочисленному значению.
int(5.4 + 3.8) → 9

least ( число [, ... ] ) → double, если какой-либо из аргументов double, иначе — integer

Выбирает наименьшее значение среди аргументов.
least(5, 4, 3, 2.1) → 2.1

ln ( число ) → double

Натуральный логарифм
ln(2.718281828459045) → 1.0

mod ( целое, целое ) → integer

Остаток от деления (по модулю)
mod(54, 32) → 22

permute ( i, размер [, затравочное_значение ] ) → integer

Переставленное значение i в диапазоне [0, размер). Это новая позиция i (по модулю величины) в псевдослучайной перестановке целых чисел 0...размер-1, параметризированной затравочным_значением; см. ниже.
permute(0, 4) → an integer between 0 and 3

pi () → double

Приближенное значение π
pi() → 3.14159265358979323846

pow ( x, y ) → double
power ( x, y ) → double

x возводится в степень y
pow(2.0, 10) → 1024.0

random ( lb, ub ) → integer

Вычисляет случайное целое число с равномерным распределением в диапазоне [lb, ub].
random(1, 10) → целое между 1 и 10

random_exponential ( lb, ub, параметр ) → integer

Вычисляет случайное целое число с экспоненциальным распределением в диапазоне [lb, ub]; см. ниже.
random_exponential(1, 10, 3.0) → целое между 1 и 10

random_gaussian ( lb, ub, параметр ) → integer

Вычисляет случайное целое число с распределением Гаусса в диапазоне [lb, ub]; см. ниже.
random_gaussian(1, 10, 2.5) → целое между 1 и 10

random_zipfian ( lb, ub, parameter ) → integer

Вычисляет случайное целое число с распределением Ципфа в диапазоне [lb, ub]; см. ниже.
random_zipfian(1, 10, 1.5) → целое между 1 и 10

sqrt ( число ) → double

Квадратный корень
sqrt(2.0) → 1.414213562

Функция random генерирует значения, используя равномерное распределение, то есть все значения выдаются из заданного диапазона и с равной вероятностью. Функциям random_exponential, random_gaussian и random_zipfian требуется дополнительный параметр типа double, определяющий точную форму распределения.

Для экспоненциального распределения параметр управляет распределением путем усечения быстро снижающегося экспоненциального распределения в точке параметр, а затем проецирования его на целые числа между границами. Точнее говоря, с
f(x) = exp(-параметр * (x - min) / (max - min + 1)) / (1 - exp(-параметр))
значение i между min и max (включительно) выдается с вероятностью: f(i) - f(i + 1).
С точки зрения интуиции чем больше параметр, тем чаще происходит обращение к значениям, близким к min, и тем реже — к значениям, близким к max. Чем параметр ближе к 0, тем более плоским (более равномерным) будет распределение. В грубом приближении при таком распределении наиболее частый 1% значений в диапазоне, близком к min, выдается параметр% времени. Значение параметра должно быть строго положительным.

Для распределения Гаусса интервал отображается на стандартное нормальное распределение (классическая кривая Гаусса в форме колокола), обрезанное в точке -параметр слева и +параметр справа. При этом вероятнее всего будут выданы значения в середине интервала. Точнее говоря, если PHI(x) — кумулятивная функция стандартного нормального распределения со средним значением mu, определенным как (max + min) / 2.0, и
f(x) = PHI(2.0 * параметр * (x - mu) / (max - min + 1)) /
(2.0 * PHI(параметр) - 1)
то значение i между min и max (включительно) выдается с вероятностью: f(i + 0.5) - f(i - 0.5). С точки зрения интуиции чем больше параметр, тем чаще выдаются значения ближе к середине интервала, и тем реже — значения ближе к границам min и max. Примерно 67% значений выдаются из среднего интервала 1.0 / параметр, то есть примерно плюс/минус 0.5 / параметр от среднего значения, и 95% из среднего интервала 2.0 / параметр, то есть примерно плюс/минус 1.0 / параметр от среднего значения; к примеру, если параметр равен 4.0, 67% значений выдаются из средней четверти (1.0 / 4.0) интервала (т. е. от 3.0 / 8.0 до 5.0 / 8.0) и 95% — из средней половины (2.0 / 4.0) интервала (из второй и третьей четвертей). Минимально допустимое значение параметра — 2.0.

Функция random_zipfian генерирует ограниченное распределение Ципфа. параметр определяет, насколько несимметрично распределение. Чем больше параметр, тем чаще выдаются значения ближе к началу интервала. Это распределение таково, что, исходя из предположения, что диапазон начинается с 1, отношение вероятности получения k к вероятности получения k+1 равно ((k+1)/k)**параметр. К примеру, random_zipfian(1, ..., 2.5) выдает значение 1 примерно в (2/1)**2.5 = 5.66 раза чаще значения 2, которое, в свою очередь, выдается в (3/2)**2.5 = 2.76 раз чаще значения 3, и т. д.
Реализация этого распределения в pgbench основана на книге "Non-Uniform Random Variate Generation" (Генерирование неравномерно распределенных случайных переменных), Люк Деврой (Luc Devroye), стр. 550-551, Springer 1986. Из-за ограничений этого алгоритма значение параметра ограничено диапазоном [1.001, 1000].

Примечание
При разработке теста оценки производительности, выбирающей строки неравномерно, учтите, что выбираемые строки могут коррелировать с другими данными, например, с идентификаторами из последовательности или с физическим порядком строк, что может искажать показатели производительности.

Чтобы избежать этого, можно использовать функцию permute (или какой-то другой дополнительный шаг со сходным эффектом) для перемешивания выбранных строк и удаления таких корреляций.

Функции хеширования hash, hash_murmur2 и hash_fnv1a принимают на вход значение и необязательный параметр с затравочным значением. В случае, если затравочное значение не предоставляется, используется значение переменной :default_seed, которая инициализируется случайным образом, если не установлена параметром командной строки -D.

Функция permute принимает на вход значение, размер и необязательный параметр затравочного значения. Она генерирует псевдослучайную перестановку целых чисел в диапазоне [0, размер) и возвращает индекс входного значения в переставленных значениях. Выбираемая перестановка параметризируется затравочным значением, которое (если не задано) по умолчанию будет иметь значение :default_seed. В отличие от функций хеширования, функция permute гарантирует отсутствие пересечений и пропусков в выходных значениях. Входные значения за пределами указанного интервала интерпретируются по модулю размера. Если размер не положительный, эта функция выдает ошибку. permute можно использовать для дисперсии распределения результатов функций, выдающих неравномерно распределенные случайные числа, таких как random_zipfian или random_exponential, чтобы чаще выдаваемые значения заведомо не коррелировали. Например, следующий скрипт pgbench эмулирует возможную реальную нагрузку, типичную для социальных медиа- и блог-платформ, где несколько пользователей генерируют избыточную нагрузку:

\set size 1000000
\set r random_zipfian(1, :size, 1.07)
\set k 1 + permute(:r, :size)

В некоторых случаях требуется несколько различных распределений, не коррелирующих друг с другом, и тогда пригодится необязательный параметр затравочного значения:

\set k1 1 + permute(:r, :size, :default_seed + 123)
\set k2 1 + permute(:r, :size, :default_seed + 321)

Похожего поведения можно добиться с помощью функции hash:

\set size 1000000
\set r random_zipfian(1, 100 * :size, 1.07)
\set k 1 + abs(hash(:r)) % :size

Однако поскольку hash генерирует коллизии, некоторые значения окажутся недоступными, а другие будут встречаться чаще, чем ожидалось, судя по исходному распределению.

В качестве примера можно рассмотреть полное распределение встроенной транзакции типа TPC-B:

\set aid random(1, 100000 * :scale)
\set bid random(1, 1 * :scale)
\set tid random(1, 10 * :scale)
\set delta random(-5000, 5000)
BEGIN;
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
END;

Этот скрипт позволяет каждой итерации транзакции обращаться к разным, случайно выбранным строкам. (Кроме того, этот пример показывает, почему важно, чтобы в каждом клиентском сеансе были собственные переменные — в противном случае они не будут независимо затрагивать разные строки.)

Протоколирование транзакций

С параметром -l (но без параметра --aggregate-interval) pgbench записывает информацию о каждой транзакции в файл журнала. Этот файл журнала будет называться префикс.nnn, где префикс по умолчанию — pgbench_log, а nnn — PID процесса pgbench. Префикс можно изменить с помощью параметра --log-prefix. Если параметр -j равен 2 или выше, чтобы было несколько рабочих потоков, каждый из них будет иметь собственный файл журнала. Первый рабочий процесс будет использовать для своего файла журнала то же имя, что и в стандартном случае с одним рабочим процессом. Дополнительные файлы журнала для остальных рабочих процессов будут называться префикс.nnn.mmm, где mmm — порядковый номер отдельного рабочего процесса, начиная с 1.

Каждая строка в файле журнала описывает одну транзакцию. Она содержит следующие поля, разделенные пробелами:

id_клиента
идентифицирует клиентский сеанс, в котором выполнялась транзакция

число_транзакций
считает, сколько транзакций было выполнено в этом сеансе

продолжительность
время, затраченное на выполнение транзакции, в микросекундах

номер_скрипта
идентифицирует файл скрипта, использовавшегося для транзакции (полезно, когда в параметрах -f или -b указано несколько скриптов)

время_эпохи
время завершения транзакции, в виде метки времени эпохи Unix

время_мкс
доли секунды в отметке времени завершения транзакции, в микросекундах

отставание_от_графика
задержка начала транзакции, то есть разница между запланированным временем запуска транзакции и временем фактического запуска, в микросекундах (присутствует, только если задан параметр --rate)

повторения
число повторений после ошибок сериализации или взаимоблокировки во время транзакции (присутствует, только если параметр --max-tries не равен единице)

Когда одновременно используются параметры --rate и --latency-limit в поле продолжительность для пропущенных транзакций будет выводиться skipped. Если транзакция завершается ошибкой, в поле продолжительность будет выводиться failed. Если вы используете параметр --failures-detailed, в поле продолжительность неуспешной транзакции будет выводиться serialization или deadlock в зависимости от типа ошибки (подробную информацию см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки).

Ниже представлен фрагмент файла журнала, сгенерированного при выполнении с одним клиентом:

0 199 2241 0 1175850568 995598
0 200 2465 0 1175850568 998079
0 201 2513 0 1175850569 608
0 202 2038 0 1175850569 2663

Еще один пример с --rate=100 и --latency-limit=5 (обратите внимание на дополнительный столбец отставание_от_графика):

0 81 4621 0 1412881037 912698 3005
0 82 6173 0 1412881037 914578 4304
0 83 skipped 0 1412881037 914578 5217
0 83 skipped 0 1412881037 914578 5099
0 83 4722 0 1412881037 916203 3108
0 84 4142 0 1412881037 918023 2333
0 85 2465 0 1412881037 919759 740

В этом примере транзакция 82 опоздала, поскольку ее длительность (6.173 мс) превысила ограничение в 5 мс. Следующие две транзакции были пропущены, поскольку опоздали еще до того, как были запущены.

В следующем примере показан фрагмент файла журнала с ошибками и повторениями, с максимальным числом попыток, равном 10 (обратите внимание на дополнительный столбец повторения):

3 0 47423 0 1499414498 34501 3
3 1 8333 0 1499414498 42848 0
3 2 8358 0 1499414498 51219 0
4 0 72345 0 1499414498 59433 6
1 3 41718 0 1499414498 67879 4
1 4 8416 0 1499414498 76311 0
3 3 33235 0 1499414498 84469 3
0 0 failed 0 1499414498 84905 9
2 0 failed 0 1499414498 86248 9
3 4 8307 0 1499414498 92788 0

Если используется параметр --failures-detailed, тип ошибки в поле продолжительность выводится следующим образом:

3 0 47423 0 1499414498 34501 3
3 1 8333 0 1499414498 42848 0
3 2 8358 0 1499414498 51219 0
4 0 72345 0 1499414498 59433 6
1 3 41718 0 1499414498 67879 4
1 4 8416 0 1499414498 76311 0
3 3 33235 0 1499414498 84469 3
0 0 serialization 0 1499414498 84905 9
2 0 serialization 0 1499414498 86248 9
3 4 8307 0 1499414498 92788 0

При выполнении длительного тестирования на аппаратуре, способной обработать большое количество транзакций, файлы журнала могут стать крайне объемными. Чтобы протоколировать только случайную выборку транзакций, может воспользоваться параметром --sampling-rate.

Протоколирование с агрегацией

С параметром --aggregate-interval для файлов журнала используется другой формат. Каждая строка журнала описывает один интервал агрегации. Она содержит следующие поля, разделенные пробелами:

начало_интервала
начальное время интервала в виде метки времени эпохи Unix

число_транзакций
количество транзакций в этом интервале

сумма_времени_отклика
суммарное время отклика транзакций

сумма_времени_отклика_2
сумма квадратов времени отклика транзакций

мин_время_отклика
минимальное время отклика транзакции

макс_время_отклика
максимальное время отклика транзакции

сумма_задержек
сумма задержек начала транзакций (ноль, если не указан параметр --rate)

сумма_задержек_2
сумма квадратов задержек начала транзакций (ноль, если не указан параметр --rate)

мин_задержка
минимальная задержка начала транзакции (ноль, если не указан параметр --rate)

макс_задержка
максимальная задержка начала транзакции (ноль, если не указан параметр --rate)

пропущеные
количество транзакций, пропущенных из-за того, что они начались бы слишком поздно (ноль, если не указаны параметры --rate и --latency-limit)

повторенные
количество повторенных транзакций (ноль, если параметр --max-tries не равен единице)

повторы
число повторов после ошибок сериализации или взаимоблокировки (ноль, если параметр --max-tries не равен единице)

сбои_сериализации количество транзакций, в которых произошла ошибка сериализации и которые затем не были повторены (ноль, если не указан параметр --failures-detailed)

сбои_взаимоблокировки
количество транзакций, в которых произошла ошибка взаимоблокировки и которые затем не были повторены (ноль, если не указан параметр --failures-detailed)

Ниже приведен пример вывода, сгенерированного с этими параметрами:

pgbench --aggregate-interval=10 --time=20 --client=10 --log --rate=1000 --latency-limit=10 --failures-detailed --max-tries=10 test

1650260552 5178 26171317 177284491527 1136 44462 2647617 7321113867 0 9866 64 7564 28340 4148 0
1650260562 4808 25573984 220121792172 1171 62083 3037380 9666800914 0 9998 598 7392 26621 4527 0

Обратите внимание, что в то время как простой формат журнала (без агрегации) показывает, какой скрипт был использован для каждой транзакции, формат с агрегацией этого не делает. Поэтому если вам нужны данные по каждому скрипту, вам придется агрегировать данные самостоятельно.

Отчет по операторам

С параметром -r pgbench собирает следующую статистику по каждому оператору:

latency — время, затраченное на транзакцию, для каждого оператора. pgbench выводит среднее значение всех успешных выполнений оператора.

Число сбоев в этом операторе. Дополнительную информацию см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки.

Число повторов после ошибок сериализации или взаимоблокировки в этом операторе. Дополнительную информацию см. в параграфе Сбои и повторы из-за ошибок сериализации/взаимоблокировки.

Отчет отображает статистику повторов, только если параметр --max-tries не равен 1.

Все значения вычисляются для каждого оператора, выполняемого каждым клиентом и выводятся после завершения теста производительности.

Для скрипта по умолчанию вывод будет выглядеть приблизительно следующим образом:

starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
number of failed transactions: 0 (0.000%)
number of transactions above the 50.0 ms latency limit: 1311/10000 (13.110 %)
latency average = 28.488 ms
latency stddev = 21.009 ms
initial connection time = 69.068 ms
tps = 346.224794 (without initial connection time)
statement latencies in milliseconds and failures:
   0.012  0  \set aid random(1, 100000 * :scale)
   0.002  0  \set bid random(1, 1 * :scale)
   0.002  0  \set tid random(1, 10 * :scale)
   0.002  0  \set delta random(-5000, 5000)
   0.319  0  BEGIN;
   0.834  0  UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
   0.641  0  SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
  11.126  0  UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
  12.961  0  UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
   0.634  0  INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
   1.957  0  END;

Еще один пример вывода для скрипта по умолчанию, использующего сериализуемый уровень изоляции транзакций по умолчанию (PGOPTIONS='-c default_transaction_isolation=serializable' pgbench ...):

starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
maximum number of tries: 10
number of transactions per client: 1000
number of transactions actually processed: 6317/10000
number of failed transactions: 3683 (36.830%)
number of transactions retried: 7667 (76.670%)
total number of retries: 45339
number of transactions above the 50.0 ms latency limit: 106/6317 (1.678 %)
latency average = 17.016 ms
latency stddev = 13.283 ms
initial connection time = 45.017 ms
tps = 186.792667 (without initial connection time)
statement latencies in milliseconds, failures and retries:
  0.006     0      0  \set aid random(1, 100000 * :scale)
  0.001     0      0  \set bid random(1, 1 * :scale)
  0.001     0      0  \set tid random(1, 10 * :scale)
  0.001     0      0  \set delta random(-5000, 5000)
  0.385     0      0  BEGIN;
  0.773     0      1  UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
  0.624     0      0  SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
  1.098   320   3762  UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
  0.582  3363  41576  UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
  0.465     0      0  INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
  1.933     0      0  END;

Если заданы несколько файлов скрипта, вся статистика выводится отдельно для каждого такого файла.

Обратите внимание, что сбор дополнительных временных показателей, необходимых для вычисления времени отклика для каждого оператора, приводит к некоторым издержкам. Это замедлит среднюю скорость выполнения и снизит вычисляемый TPS. Уровень замедления во многом зависит от платформы и оборудования. Хороший способ оценить размер издержек — сравнить средние значения TPS с включенным выведением времени отклика и без него.

Сбои и повторы из-за ошибок сериализации/взаимоблокировки

Существует три типа ошибок, которые могут возникать при выполнении pgbench:

Ошибки основной программы. Они являются наиболее серьезными и всегда приводят к немедленному завершению pgbench с соответствующим сообщением об ошибке. К ним относятся:

ошибки при запуске pgbench (например, недопустимое значение параметра);

ошибки в режиме инициализации (например, сбой запроса на создание таблиц для встроенных скриптов);

ошибки перед запуском потоков (например, не удалось подключиться к серверу баз данных, синтаксическая ошибка в метакоманде, сбой при создании потока);

внутренние ошибки pgbench (которых теоретически не может быть).

Ошибки при манипулировании клиентами в потоке (например, клиент не смог запустить подключение к серверу баз данных, сокет для подключения клиента к серверу баз данных стал недействительным). В таких случаях все клиенты этого потока останавливаются, тогда как другие потоки продолжают работать.

Ошибки непосредственно клиента. Они ведут к немедленному завершению pgbench с соответствующим сообщением об ошибке только в случае внутренней ошибки pgbench. В противном случае самое худшее, что может произойти, это прерывание работы клиента с ошибкой, тогда как другие клиенты продолжат работу (однако некоторые клиентские ошибки обрабатываются без прерывания работы клиента, и о них сообщается отдельно, см. ниже). Далее в этом параграфе предполагается, что рассматриваемые ошибки относятся только к ошибкам непосредственно клиента, а не ко внутрениим ошибкам pgbench.

Работа клиента прерывается в случае серьезной ошибки; например, если было потеряно соединение с сервером баз данных или конец скрипта был достигнут до завершения последней транзакции. Кроме того, работа клиента прерывается, если выполнение команды SQL или метакоманды завершается сбоем не по причине ошибки сериализации или взаимоблокировки. Иначе же, если команда SQL завершается ошибкой сериализации или взаимоблокировки, клиент не прерывает работу. В таких случаях текущая транзакция откатывается, при этом клиентским переменным возвращаются те значения, которые были установлены до начала этой транзакции (предполагается, что один скрипт транзакции содержит только одну транзакцию; подробную информацию см. в параграфе Какие транзакции фактически выполняются в pgbench?). Транзакции с ошибками сериализации или взаимоблокировки повторяются после отката, пока не завершаются успехом или не достигают максимального числа попыток (заданного параметром --max-tries) / максимальной продолжительности повторений (заданной параметром --latency-limit) / истечения времени выполнения теста (заданного параметром --time). Если последняя попытка завершается сбоем, эта транзакция отображается как неуспешная, но клиент не прерывается и продолжает работу.

Примечание
Если не указать параметр --max-tries, транзакция никогда не будет повторяться после ошибки сериализации или взаимоблокировки, поскольку его значение по умолчанию равно 1. Чтобы ограничить только максимальную продолжительность попыток, задайте неограниченное число попыток (--max-tries=0) и установите параметр --latency-limit. Кроме того, можно воспользоваться параметром --time, чтобы ограничить продолжительность теста при неограниченном числе попыток.

Будьте осторожны при повторении скриптов, содержащих несколько транзакций: скрипт всегда повторяется полностью, поэтому успешные транзакции могут выполняться несколько раз.

Будьте осторожны при повторении транзакций с командами оболочки. В отличие от результатов команд SQL, результаты команд оболочки не откатываются, за исключением значения переменной команды \setshell.

Время отклика успешной транзакции включает общее время выполнения транзакции с учетом откатов и повторов. Время отклика измеряется только для успешных транзакций и команд, но не для завершившихся сбоем.

Основной отчет содержит количество неуспешных транзакций. Если параметр --max-tries не равен 1, основной отчет также будет содержать статистику, относящуюся к повторам: общее количество повторявшихся транзакций и общее число повторов. Отчет по скриптам наследует все эти поля от основного отчета. Отчет по операторам отображает статистику повторов, только если параметр --max-tries не равен 1.

Если вы хотите сгруппировать сбои по базовым типам в журнале по транзакциям и журнале с агрегированием, а также в основном отчете и отчете по скриптам, воспользуйтесь параметром --failures-detailed. Если вы также хотите отличать все ошибки и сбои (ошибки без последующих повторов) по типу, включая то, какой предел повторов был превышен для сбоев сериализации/взаимоблокировки и насколько, воспользуйтесь параметром --verbose-errors.

Методические рекомендации

Крайне легко получить с помощью pgbench совершенно бессмысленные числа. Приведенные здесь рекомендации помогут вам получить полезные результаты.

Во-первых, никогда не верьте тесту, длящемуся всего несколько секунд. Воспользуйтесь параметром -t или -T, чтобы тест выполнялся по меньшей мере несколько минут, чтобы исключить шум. В некоторых случаях могут понадобиться часы, чтобы получить воспроизводимые числа. Имеет смысл запустить выполнение теста несколько раз, чтобы понять, являются ли полученные числа воспроизводимыми.

Для стандартного тестового сценария по типу TPC-B коэффициент масштабирования инициализации (-s) должен быть как минимум не меньше максимального количества клиентов, с которым вы намерены провести тест (-c); иначе вы будете по большей части измерять время конкурентных изменений. В таблице pgbench_branches содержится всего -s строк, и каждая транзакция желает изменить одну из них, поэтому если значения -c превышают -s, это несомненно приведет к блокировкам многих транзакций, ожидающих завершения других транзакций.

Стандартный сценарий тестирования тоже довольно чувствителен к тому, сколько времени прошло с момента инициализации таблиц: накопление неактуальных строк и неиспользуемого пространства в таблицах меняет результаты. Для понимания результатов следует отслеживать общее число изменений и когда проводилась очистка. Если включена автоочистка, это может привести к непредсказуемым изменениям в измеряемой производительности.

Ограничение утилиты pgbench состоит в том, что она сама может стать сдерживающим фактором при попытке протестировать большое число клиентских сеансов. Этого можно избежать, запуская pgbench на другом компьютере, не на сервере баз данных, хотя при этом ключевым моментом станет низкая скорость сети. Возможно даже, будет полезно запускать для одного сервера баз данных несколько экземпляров pgbench параллельно, на нескольких клиентских компьютерах.

Безопасность

Если к базе данных, которая не приведена в соответствие с шаблоном безопасного использования схем, имеют доступ недоверенные пользователи, не запускайте pgbench в этой базе данных. pgbench использует имена, не дополненные схемой, и не манипулирует путем поиска.

pg_config

pg_config — получить информацию об установленной версии QHB

Синтаксис

pg_config [параметр...]

Описание

Утилита pg_config выводит параметры конфигурации текущей установленной версии QHB. Она предназначена, например, для использования программными пакетами, которые хотят взаимодействовать с QHB, помогая им в поиске необходимых заголовочных файлов и библиотек.

Параметры

Чтобы использовать утилиту pg_config, укажите один или несколько из следующих параметров:

--bindir
Вывести расположение пользовательских исполняемых файлов. Этот параметр можно применить, например, чтобы найти программу psql. Обычно это то же место, где находится сама программа pg_config.

--docdir
Вывести расположение файлов документации.

--htmldir
Вывести расположение файлов документации в формате HTML.

--includedir
Вывести расположение заголовочных файлов C/RUST клиентских интерфейсов.

--pkgincludedir
Вывести расположение других заголовочных файлов C/RUST.

--includedir-server
Вывести расположение заголовочных файлов C/RUST для программирования сервера.

--libdir
Вывести расположение библиотек объектного кода.

--pkglibdir
Вывести расположение динамически загружаемых модулей либо место, где сервер будет их искать. (В этом каталоге также могут быть установлены другие архитектурно-зависимые файлы данных.)

--localedir
Вывести расположение файлов поддержки локали. (Если поддержка локали не была сконфигурирована при сборке QHB, это будет пустая строка.)

--mandir
Вывести расположение страниц руководства.

--sharedir
Вывести расположение архитектурно-независимых вспомогательных файлов.

--sysconfdir
Вывести расположение общесистемных файлов конфигурации.

--pgxs
Вывести расположение сборочных файлов расширений.

--configure
Вывести параметры, которые были заданы скрипту configure при конфигурировании QHB во время сборки. Этот параметр можно использовать для воспроизведения идентичной конфигурации или для выяснения того, с какими параметрами был собран бинарный пакет. (Тем не менее обратите внимание, что бинарные пакеты часто содержат патчи, специфичные для конкретного производителя). См. также примеры ниже.

--cc
Вывести значение переменной CC, использованное при сборке QHB. Отображает использованный компилятор языка C.

--cppflags
Вывести значение переменной CPPFLAGS, использованное при сборке QHB. Отображает ключи компилятора языка C, требовавшиеся во время работы препроцессора (обычно это ключи -I).

--cflags
Вывести значение переменной CFLAGS, использованное при сборке QHB. Отображает ключи компилятора языка C.

--cflags_sl
Вывести значение переменной CFLAGS_SL, использованное при сборке QHB. Отображает дополнительные ключи компилятора языка C, использовавшиеся для сборки разделяемых библиотек.

--ldflags
Вывести значение переменной LDFLAGS, использованное при сборке QHB. Отображает ключи компоновщика.

--ldflags_ex
Вывести значение переменной LDFLAGS_EX, использованное при сборке QHB. Отображает ключи компоновщика, использовавшиеся только для сборки исполняемых файлов.

--ldflags_sl
Вывести значение переменной LDFLAGS_SL, использованное при сборке QHB. Отображает ключи компоновщика, использовавшиеся только при сборке разделяемых библиотек.

--libs
Вывести значение переменной LIBS, использованное при сборке QHB. Обычно здесь содержатся ключи -l для внешних библиотек, подключенных к QHB.

--version
Вывести версию QHB.

-?
--help
Показать справку об аргументах командной строки pg_config и завершиться.

Если указано более одного параметра, информация выводится в соответствии с их порядком, по одному элементу в строке. Если параметры не заданы, выводится вся доступная информация с метками, к чему она относится.

Пример

Чтобы воспроизвести конфигурацию сборки текущей инсталляции QHB, выполните следующую команду:

eval ./configure `pg_config --configure`

Вывод pg_config --configure заключается в кавычки, поэтому аргументы с пробелами представляются правильно. Следовательно, для получения корректных результатов нужно использовать eval.

qhb_dump

qhb_dump — выгрузить базу данных QHB в файл скрипта или другой архивный файл

Синтаксис

qhb_dump [параметр-подключения...] [параметр...] [имя_бд]

Описание

qhb_dump — это утилита для резервного копирования базы данных QHB. Она создает согласованные резервные копии, даже если база данных параллельно используется. qhb_dump не блокирует доступ к базе данных другим пользователям (ни для чтения, ни для записи).

Утилита qhb_dump выгружает только одну базу данных. Для резервного копирования всего кластера или глобальных объектов, общих для всех баз данных в кластере (например, ролей и табличных пространств), следует применять программу qhb_dumpall.

Дампы можно создавать в форматах скрипта или архивного файла. Дампы в скриптах представляют собой текстовые файлы, содержащие команды SQL, необходимые для восстановления базы данных до состояния, в котором она находилась на момент сохранения. Для восстановления из такого скрипта нужно передать его psql. Файлы скрипта можно использовать для восстановления базы данных на других компьютерах и в других архитектурах, а с некоторыми модификациями — даже в других SQL-совместимых СУБД.

Для восстановления базы данных из альтернативных архивных форматов следует применять утилиту qhb_restore. Эти форматы позволяют qhb_restore избирательно восстанавливать данные или даже изменять порядок восстанавливаемых элементов. Архивные форматы предназначены для переноса на платформы с иной архитектурой.

Применение архивных форматов и сочетание утилит qhb_restore и qhb_dump обеспечивает гибкий механизм архивирования и переноса данных. qhb_dump можно использовать для резервного копирования всей базы данных, затем с помощью qhb_restore проверить архив и/или выбрать, какие части базы данных нужно восстановить. Наиболее гибкими форматами выходных файлов являются «custom» (-Fc) и «directory» (-Fd). Они позволяют выбирать и переупорядочивать все заархивированные элементы, поддерживают многопоточное восстановление, а также сжимаются по умолчанию. При этом только формат «directory» поддерживает многопоточную выгрузку.

Во время работы qhb_dump необходимо проверять выходные данные на наличие предупреждений (они выводятся в стандартный поток ошибок), особенно в свете перечисленных ниже ограничений.

Параметры

Следующие параметры командной строки управляют содержимым и форматом вывода.

имя_бд
Задает имя базы данных, из которой будет выгружаться дамп. Если имя не задано, используется значение переменной среды PGDATABASE. Если и переменная не задана, используется имя пользователя, под которым производится подключение.

-a
--data-only
Выгружать только данные, но не схемы (определения данных). Будут выгружены данные таблиц, большие объекты и значения последовательностей.
Этот параметр схож с указанием --section=data, но по историческим причинам не идентичен.

-b
--blobs
Включить в дамп большие объекты. Это поведение по умолчанию, если только не указаны --schema, --table или --schema-only. Поэтому ключ -b полезен только для добавления больших объектов в дампы, где требовалась конкретная схема или таблица. Обратите внимание, что большие бинарные объекты относятся к данным и поэтому будут включены при использовании --data-only, но не --schema-only.

-B
--no-blobs
Исключить большие объекты из дампа.
Когда заданы и -b, и -B, большие объекты при выгрузке данных будут выводиться (см. описание -b).

-c
--clean
Включить в выходной файл команды удаления (DROP) всех выгружаемых объектов базы данных перед командами создания этих объектов. Этот параметр полезен, когда при восстановлении нужно переписать существующую базу данных. Если дополнительно не указать --if-exists, то при восстановлении в целевую базу данных, где отсутствует какой-либо из этих объектов, будут выводиться безвредные сообщения об ошибке.
Этот параметр игнорируется при генерировании архивного (не текстового) выходного файла. Для таких архивных форматов этот параметр можно указать при вызове qhb_restore.

-C
--create
Начать вывод с команды для создания базы данных и последующего подключения к ней. (При такой форме скрипта не важно, к какой базе из целевой установки было произведено подключение перед выполнением этого скрипта.) Если дополнительно указано --clean, то скрипт удаляет и пересоздает целевую базу данных перед повторным подключением к ней.
С --create в вывод также включается комментарий к базе данных (если таковой имеется) и все параметры переменных конфигурации, специфичные для этой базы данных, то есть все команды ALTER DATABASE ... SET ... и ALTER ROLE ... IN DATABASE ... SET ..., где упоминается эта база. Также выгружаются права доступа к самой базе данных, если не указано --no-acl.
Этот параметр игнорируется при генерировании архивного (не текстового) выходного файла. Для таких архивных форматов этот параметр можно указать при вызове qhb_restore.

-e шаблон
--extension=шаблон
Выгрузить только расширения, соответствующие шаблону. Когда этот параметр не задан, будут выгружены все несистемные расширения в целевой базе данных. Задав ключ -e несколько раз, можно выбрать несколько расширений. Параметр шаблон интерпретируется по тем же правилам, что и шаблон в командах \d утилиты psql (см. Шаблоны), поэтому несколько расширений также можно выбрать, вписав в шаблон подстановочные знаки. Используя такие знаки, не забывайте при необходимости заключать шаблон в кавычки, чтобы их не разворачивала оболочка системы.
Если расширение указано в --extension, в дамп включаются все его конфигурационные отношения, зарегистрированные функцией pg_extension_config_dump.

Примечание
При указании -e утилита qhb_dump не пытается выгружать прочие объекты базы данных, от которых могут зависеть выбранные расширения. Таким образом, нет гарантии, что эти расширения будут успешно восстановлены в чистой базе данных.

-E кодировка
--encoding=кодировка
Создать дамп в заданной кодировке набора символов. По умолчанию дамп создается в кодировке базы данных. (Тот же результат можно получить, задав желаемую кодировку в переменной среды PGCLIENTENCODING.) Поддерживаемые кодировки описаны в подразделе Поддерживаемые кодировки.

-f файл
--file=файл
Отправить вывод в заданный файл. Этот параметр можно опустить для форматов с выводом в файл, поскольку в этом случае используется стандартный вывод. Однако его обязательно следует задавать для формата с выводом в каталог, где этот параметр задает путь не к файлу, а к целевому каталогу. В этом случае каталог создается qhb_dump и не должен существовать заранее.

-F формат
--format=формат
Задает формат вывода дампа. формат может принимать следующие значения:

p
plain
Вывести простой текстовый SQL-скрипт (поведение по умолчанию).

c
custom
Вывести данные в специальном архивном формате, пригодном для использования утилитой qhb_restore. Вместе с форматом каталога это наиболее гибкий формат вывода, поскольку он позволяет вручную выбирать и переупорядочивать заархивированные элементы во время восстановления. Кроме того, вывод в этом формате по умолчанию сжимается.

d
directory
Вывести данные в архиве в формате каталога, пригодном для использования утилитой qhb_restore. При этом будет создан каталог, в котором у каждой выгруженной таблицы и большого объекта будет отдельный файл, а также будет создан так называемый файл-оглавление, описывающий выгруженные объекты в машиночитаемом формате, понятном qhb_restore. С архивом в формате каталога можно работать стандартными средствами Unix; например, файлы в несжатом архиве можно сжать с помощью gzip. Этот формат по умолчанию сжимается, а также поддерживает многопоточную выгрузку.

t
tar
Вывести данные в архиве в формате tar, пригодном для использования утилитой qhb_restore. Он совместим с форматом каталога: при распаковке архива в формате tar создается корректная копия в формате каталога. Однако этот формат не поддерживает сжатие. Кроме того, при использовании формата tar нельзя изменить относительный порядок элементов данных в таблице во время восстановления.

-j число_заданий
--jobs= число_заданий
Запустить многопоточную выгрузку, обрабатывая одновременно несколько таблиц (в количестве число_заданий). Это может сократить время создания дампа, но при этом увеличивает нагрузку на сервер баз данных. Этот параметр можно использовать только с форматом вывода в каталог, так как это единственный формат, в котором несколько процессов могут записывать данные одновременно.
qhb_dump откроет число_заданий + 1 соединений с базой данных, поэтому необходимо установить такое значение параметра max_connections, которое бы покрыло все эти соединения.
Если при многопоточной выгрузке будут запрашиваться эксклюзивные блокировки на объекты базы данных, может произойти сбой. Причина этого в том, что главный процесс qhb_dump запрашивает совместные блокировки (ACCESS SHARE) для объектов, которые позже будут выгружать рабочие процессы, чтобы никто не удалил их во время создания дампа. Если после этого другой клиент запросит эксклюзивную блокировку таблицы, этот запрос будет поставлен в очередь в ожидании снятия совместной блокировки главного процесса. В последующем любая попытка доступа к таблице также будет поставлена в очередь после запроса на эксклюзивную блокировку. В том числе в очередь попадет и рабочий процесс, пытающийся выгрузить таблицу. Если не принять никаких мер предосторожности, получится классическая взаимоблокировка. Для выявления таких конфликтов рабочий процесс qhb_dump запрашивает еще одну совместную блокировку, используя параметр NOWAIT. И если она не получит эту блокировку, значит, кто-то еще запросил эксклюзивную блокировку и нет возможности продолжить выгрузку, поэтому qhb_dump придется ее прервать.
Для получения согласованной резервной копии сервер баз данных должен поддерживать возможность синхронизированных снимков. Благодаря этой функциональности клиенты базы данных могут быть уверены, что видят одну и ту же версию данных, несмотря на использование разных подключений. qhb_dump -j использует множественные подключения к базе данных; первое подключение происходит с главным процессом, а последующие — с рабочими процессами. Без возможности синхронизированных снимков нет гарантии, что разные рабочие процессы в каждом подключении увидят одни и те же данные, что может привести к несогласованности данных резервной копии.

-n шаблон
--schema= шаблон
Выгрузить только схемы, соответствующие шаблону; вместе со схемами будут выгружены и все содержащиеся в них объекты. Если этот параметр не задан, будут выгружены все несистемные схемы в целевой базе данных. Задав ключ -n несколько раз, можно выбрать несколько схем. Значение шаблон интерпретируется по тем же правилам, что и шаблон в командах \d утилиты psql (см. Шаблоны), поэтому несколько схем также можно выбрать, вписав в шаблон подстановочные знаки. Используя такие знаки, не забывайте при необходимости заключать шаблон в кавычки, чтобы их не разворачивала оболочка системы; см. Примеры ниже.

Примечание
При указании -n утилита qhb_dump не пытается выгружать прочие объекты базы данных, от которых могут зависеть выбранные схемы. Таким образом, нет гарантии, что эти схемы будут успешно восстановлены в чистой базе данных.

Примечание
Объекты, не принадлежащие схемам, например, большие бинарные объекты, при указании -n не выводятся. Однако их можно добавить в дамп с помощью ключа --blobs.

-N шаблон
--exclude-schema=шаблон
Не выгружать схемы, соответствующие шаблону. Шаблон интерпретируется по тем же правилам, что и для -n. Задав ключ -N несколько раз, можно исключить схемы, соответствующие любому из нескольких шаблонов.
Когда заданы и -n, и -N, в дамп включаются схемы, соответствующие как минимум одному ключу -n и не совпадающие ни с одним из ключей -N. Если ключ -N задается один, то соответствующие ему схемы исключаются из в остальном обычного дампа.

-O
--no-owner
Не формировать команды для установки владельца объектов как в исходной базе данных. По умолчанию для назначения владельцев созданных объектов базы данных qhb_dump создает команды ALTER OWNER или SET SESSION AUTHORIZATION. Эти команды выполнятся успешно, только если скрипт будет запущен суперпользователем (или владельцем всех объектов в скрипте). Чтобы создать скрипт, который может быть восстановлен любым пользователем, и назначить этого пользователя владельцем всех объектов, укажите -O.
Этот параметр игнорируется при генерировании архивного (не текстового) выходного файла. Для архивных форматов этот параметр можно указать при вызове qhb_restore.

-R
--no-reconnect
Этот параметр устарел, но все еще принимается в целях обратной совместимости.

-s
--schema-only
Выгрузить только определения объектов (схемы), но не сами данные.
Этот параметр действует противоположно --data-only. Он похож на указание --section=pre-data --section=post-data, но по историческим причинам не идентичен ему.
(Не путайте его с параметром --schema, который использует термин «схема» в другом значении.)
Чтобы исключить данные таблицы только для подмножества таблиц в базе данных, используйте параметр --exclude-table-data.

-S имя_пользователя
--superuser=имя_пользователя
Задать имя суперпользователя, которое будет использоваться при выключении триггеров. Имеет значение только вместе с параметром --disable-triggers. (Обычно лучше вместо этого запускать полученный скрипт от лица суперпользователя.)

-t шаблон
--table=шаблон
Выгрузить только таблицы, соответствующие шаблону. Задав ключ -t несколько раз, можно выбрать несколько таблиц. Параметр шаблон интерпретируется по тем же правилам, что и шаблон в командах \d утилиты psql (см. Шаблоны), поэтому несколько таблиц также можно выбрать, вписав в шаблон подстановочные знаки. Используя такие знаки, не забывайте при необходимости заключать шаблон в кавычки, чтобы их не разворачивала оболочка системы; см. Примеры ниже.
Помимо таблиц с этим параметром можно выгрузить определения соответствующих шаблону представлений, материализованных представлений, сторонних таблиц и последовательностей. При этом содержимое представлений и материализованных представлений выгружаться не будет, а содержимое сторонних таблиц будет выгружено, только если в параметре --include-foreign-data задан соответствующий сторонний сервер.
При использовании -t не действуют ключи -n и -N, так как выбранные с его помощью таблицы все равно будут выгружаться, а нетабличные объекты выгружаться не будут.

Примечание
При указании -t утилита qhb_dump не пытается выгружать прочие объекты базы данных, от которых могут зависеть выбранные таблицы. Таким образом, нет гарантии, что эти таблицы будут успешно восстановлены в чистой базе данных.

-T шаблон
--exclude-table=шаблон
Не выгружать таблицы, соответствующие шаблону. Шаблон интерпретируется по тем же правилам, что и для -t. Задав ключ -T несколько раз, можно исключить таблицы, соответствующие любому из нескольких шаблонов.
Когда заданы и -t, и -T, в дамп включаются таблицы, соответствующие как минимум одному ключу -t и не совпадающие ни с одним из ключей -N. Если ключ -T задается один, то соответствующие ему таблицы исключаются из обычного дампа.

-v
--verbose
Включить режим подробного вывода. qhb_dump будет выводить в стандартный поток ошибок подробные комментарии к объектам и время начала/окончания выгрузки в файл дампа, а также сообщения о прогрессе выполнения. Если повторить этот параметр, в стандартный поток ошибок будут выводиться дополнительные отладочные сообщения.

-V
--version
Вывести версию qhb_dump и завершиться.

-x
--no-privileges
--no-acl
Запретить выгрузку прав доступа (команды GRANT/REVOKE).

-Z 0-9
--compress=0-9
Задать уровень сжатия данных. При нулевом значении сжатие отсутствует. Для архивов специального формата и формата каталога будут сжиматься отдельные сегменты табличных данных, и по умолчанию используется умеренный уровень сжатия. Если указать ненулевой уровень сжатия для простого текстового формата, сжиматься будет весь выходной файл, как если бы он был передан через gzip; по умолчанию простой формат не сжимается. Формат архива tar в настоящее время сжатие не поддерживает.

--binary-upgrade
Этот параметр предназначен для использования утилитами обновления на месте. Применение в других целях не рекомендуется и не поддерживается. Поведение параметра может быть изменено в будущих версиях без предварительного уведомления.

--column-inserts
--attribute-inserts
Выгружать данные в виде команды INSERT с явными указанием имен столбцов (INSERT INTO таблица (столбец, ...) VALUES ...). Восстановление при этом будет происходить очень медленно; в основном этот вариант оправдан при создании дампов, которые можно загружать в СУБД, отличные от QHB. Любая ошибка во время восстановления приведет к потере только тех строк INSERT, в которых возникли ошибки, а не всего содержимого таблицы.

--disable-dollar-quoting
Этот параметр выключает возможность заключать тела функций в долларовые кавычки, заставляя использовать кавычки из стандартного синтаксиса SQL.

--disable-triggers
Этот параметр актуален только при создании дампа, содержащего одни данные. Он предписывает qhb_dump включать в дамп команды для временного выключения триггеров на целевых таблицах во время восстановления данных. Применяется в ситуациях, когда существуют проверки ссылочной целостности или другие триггеры для таблиц, которые нежелательно вызывать во время восстановления данных.
В настоящее время команды, генерируемые для --disable-triggers, должны выполняться суперпользователем. Следовательно, необходимо задать имя суперпользователя с помощью ключа -S или запустить полученный скрипт от лица суперпользователя.
Этот параметр игнорируется при генерировании архивного (не текстового) выходного файла. Для архивных форматов этот параметр можно указать при вызове qhb_restore.

--enable-row-security
Этот параметр представляет интерес только при выгрузке содержимого таблиц, в которых есть защита строк. По умолчанию qhb_dump устанавливает в row_security значение off (выключен), чтобы из таблицы были гарантированно выгружены все данные. Если у пользователя недостаточно прав для обхода защиты строк, возникает ошибка. Этот параметр предписывает qhb_dump установить в row_security значение on (включен), позволяя пользователю выгрузить части содержимого таблицы, к которым у него есть доступ.
Обратите внимание, что в настоящее время при использовании этого параметра желательно, чтобы дамп был в формате INSERT, так как команда COPY FROM в процессе восстановления не поддерживает защиту строк.

--exclude-table-data=шаблон
Не выгружать данные из таблиц, соответствующих шаблону. Шаблон интерпретируется по тем же правилам, что и для -t. Задав ключ --exclude-table-data несколько раз, можно исключить таблицы, соответствующие любому из нескольких шаблонов. Этот параметр полезен, когда необходимо получить определение конкретной таблицы, но содержащиеся в ней данные не нужны.
Чтобы не выгружать данные из всех таблиц базы, воспользуйтесь --schema-only.

--extra-float-digits=число_цифр
Выгружать числа с плавающей запятой не с максимальной доступной точностью, а с заданным значением extra_float_digits. При обычной выгрузке в целях резервного копирования этот параметр использовать не следует.

--if-exists
Использовать команды DROP ... IF EXISTS при удалении объектов в режиме --clean. Это подавляет ошибки «не существует», которые в противном случае могут выводиться. Этот параметр не работает без указания --clean.

--include-foreign-data=сторонний_сервер
Выгрузить данные всех сторонних таблиц со стороннего сервера, имя которого соответствует шаблону сторонний_сервер. Задав ключ --include-foreign-data несколько раз, можно выбрать несколько сторонних серверов. Кроме того, параметр сторонний_сервер интерпретируется по тем же правилам, что и шаблон в командах \d утилиты psql (см. Шаблоны), поэтому несколько сторонних серверов также можно выбрать, вписав в шаблон подстановочные знаки. Используя такие знаки, не забывайте при необходимости заключать шаблон в кавычки, чтобы их не разворачивала оболочка системы; см. Примеры ниже. Единственное отличие от этих правил в том, что шаблон не может быть пустым.

Примечание
При указании --include-foreign-data утилита qhb_dump не проверяет возможность записи в стороннюю таблицу. Таким образом, нет гарантии, что выгруженная сторонняя таблица может быть успешно восстановлена.

--inserts
Выгрузить данные в виде команд INSERT (а не COPY). Восстановление при этом будет происходить очень медленно; в основном этот вариант оправдан при создании дампов, которые можно загружать в СУБД, отличные от QHB. Любая ошибка во время восстановления приведет к потере только тех строк INSERT, в которых возникли ошибки, а не всего содержимого таблицы. Обратите внимание, что восстановление может полностью провалиться, если у таблицы был изменен порядок столбцов. В таком случае можно воспользоваться параметром --column-inserts, для которого порядок столбцов не важен, но он работает еще медленнее.

--load-via-partition-root
При выгрузке данных партиции таблицы выводить команды COPY или INSERT, ссылающиеся на корневую таблицу в содержащей ее иерархии партиционирования, а не на саму эту партицию. В результате при загрузке данных для каждой строки будет заново выбираться подходящая партиция. Это может быть полезно при перезагрузке данных, когда на целевом сервере строки не всегда попадают в те же партиции, в которых они были на исходном сервере. Это возможно, например, когда у столбца разбиения текстовый тип и в двух системах по-разному определено правило сортировки, по которому упорядочивается этот столбец.

--lock-wait-timeout=время_ожидания
Не ждать бесконечно получения совместных блокировок таблиц в начале выгрузки. Вместо этого выдать ошибку, если не удастся заблокировать таблицу за указанное время_ожидания. Время ожидания можно указать в любом из форматов, принимаемых командой SET statement_timeout. (Допустимые форматы различаются в зависимости от версии сервера, с которого выгружаются данные, однако целое число миллисекунд принимается всеми версиями.)

--no-comments
Не выгружать комментарии.

--no-publications
Не выгружать публикации.

--no-security-labels
Не выгружать метки безопасности.

--no-subscriptions
Не выгружать подписки.

--no-sync
По умолчанию qhb_dump будет ждать, пока все файлы не будут надежно записаны на диск. Этот параметр заставляет qhb_dump завершаться без ожидания, т. е. выполняться быстрее, но впоследствии при неожиданном сбое операционной системы дамп может оказаться поврежден. В целом, этот параметр предназначен для тестирования, и его не следует использовать при выгрузке данных из производственной установки.

--no-table-access-method
Не выводить команды для выбора табличных методов доступа. При указании этого параметра все объекты будут создаваться с тем табличным методом доступа, который устанавливается по умолчанию во время восстановления.
Этот параметр игнорируется при генерировании архивного (не текстового) выходного файла. Для архивных форматов этот параметр можно указать при вызове qhb_restore.

--no-tablespaces
Не выводить команды для выбора табличных пространств. При указании этого параметра все объекты будут создаваться в том табличном пространстве, которое устанавливается по умолчанию во время восстановления.
Этот параметр игнорируется при генерировании архивного (не текстового) выходного файла. Для архивных форматов этот параметр можно указать при вызове qhb_restore.

--no-toast-compression
Не формировать команды, задающие методы сжатия TOAST. При указании этого параметра все столбцы будут восстановлены с методом сжатия, выбранным по умолчанию.

--no-unlogged-table-data
Не выгружать содержимое нежурналируемых таблиц и последовательностей. Этот параметр не влияет на выгрузку определений (схем) таблиц и последовательностей; он лишь подавляет выгрузку их данных. При выгрузке с резервного сервера содержимое нежурналируемых таблиц и последовательностей всегда исключается.

--on-conflict-do-nothing
Добавить предложения ON CONFLICT DO NOTHING в команды INSERT. Этот параметр недопустим, если также не указаны параметры --inserts, --column-insert или --rows-per-insert.

--quote-all-identifiers
Принудительно заключать в кавычки все идентификаторы. Этот параметр рекомендуется при выгрузке базы данных с сервера QHB, основная версия которого отличается от версии qhb_dump, или когда дамп предназначен для загрузки на сервер с другой основной версией. По умолчанию qhb_dump заключает в кавычки только те идентификаторы, которые являются зарезервированными словами в ее собственной основной версии. Иногда это приводит к проблемам совместимости при работе с серверами других версий, в которых наборы зарезервированных слов могут немного отличаться. Использование параметра --quote-all-identifiers предотвращает подобные проблемы ценой ухудшения читаемости скрипта с дампом.

--rows-per-insert=число_строк
Выгружать данные в виде команд INSERT (вместо COPY). Этот параметр задает максимальное число строк в одной команде INSERT. Указанное значение должно быть больше нуля. Любая ошибка во время восстановления приведет к потере только тех строк INSERT, в которых возникли ошибки, а не всего содержимого таблицы.

--section=имя_раздела
Выгрузить только заданный раздел. Именем раздела может быть pre-data, data или post-data. Для выбора нескольких разделов этот параметр можно указать более одного раза. По умолчанию выгружаются все разделы.
Раздел data содержит фактические данные таблиц, содержимое больших объектов и значения последовательности. Раздел post-data содержит определения индексов, триггеров, правил и ограничений (кроме ограничений проверки, созданных без NOT VALID). Раздел pre-data содержит все остальные элементы определений данных.

--serializable-deferrable
Использовать для выгрузки транзакцию с уровнем изоляции serializable для гарантированного получения снимка, согласованного с последующими состояниями базы данных. Однако для этого нужно дождаться момента, когда в потоке транзакций не может быть никаких аномалий, и поэтому нет риска сбоя выгрузки или отката других транзакций с ошибкой serialization_failure. Более подробную информацию об изоляции транзакций и управлении параллельным доступом см. в главе Управление параллельным доступом.
Этот параметр не несет практической пользы для дампа, предназначенного только для восстановления после сбоя. Он может быть полезен для дампа, используемого для загрузки копии базы данных, в которой формируются отчеты и выполняются другие операции чтения, в то время как в исходной базе продолжается обычная работа. Без этого параметра дамп может отражать состояние, не согласующееся ни с каким последовательным выполнением фиксируемых в итоге транзакций. Например, если используются методы пакетной обработки, статус пакета может отображаться как завершенный, хотя на самом деле в дампе будут находиться не все элементы пакета.
Этот параметр не повлияет на результат, если при запуске qhb_dump нет активных транзакций чтения/записи. Если же такие транзакции есть, то начало выгрузки может быть отложено на неопределенный срок. После запуска выгрузки производительность с этим ключом или без него будет одинаковой.

--snapshot=имя_снимка
Использовать заданный синхронизированный снимок при создании дампа базы данных (подробную информацию см. в таблице Функции синхронизации снимков).
Этот параметр полезен, когда необходимо синхронизировать дамп со слотом логической репликации (см. главу Логическое декодирование) или с другим одновременным сеансом.
В случае многопоточной выгрузки вместо создания нового снимка будет использовано имя снимка, определенное этим параметром.

--strict-names
Требовать, чтобы каждому квалификатору расширения (-e/--extension), схемы (-n/--schema) и таблицы (-t/--table) соответствовал как минимум один объект расширение/схема/таблица в выгружаемой базе данных. Обратите внимание, что если ни для одного из этих квалификаторов не найдется соответствующего объекта, qhb_dump выдаст ошибку даже без --strict-names.
Этот параметр не действует на ключи -N/--exclude-schema, -T/ --exclude-table и --exclude-table-data. Если не находятся объекты, соответствующие шаблонам исключения, это не считается ошибкой.

--use-set-session-authorization
Выводить команды SET SESSION AUTHORIZATION, соответствующие стандарту SQL, вместо команд ALTER OWNER, чтобы определить владельца объекта. В итоге дамп будет более стандартизированным, но, в зависимости от истории объектов в дампе, может не восстановиться должным образом. Кроме того, при использовании SET SESSION AUTHORIZATION для корректного восстановления из дампа безусловно понадобятся права суперпользователя, тогда как ALTER OWNER требует меньших привилегий.

-?
--help
Показать справку об аргументах командной строки qhb_dump и завершиться.

Следующие параметры командной строки управляют параметрами подключением к базе данных.

-d имя_бд
--dbname=имя_бд
Задает имя базы данных для подключения. Это равнозначно указанию имени_бд в качестве первого аргумента, не являющегося ключом, в командной строке. В имени_бд может задаваться строка подключения. В этом случае параметры в строке подключения переопределяют любые конфликтующие параметры в командной строке.

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix. Значение по умолчанию определяется переменной среды PGHOST, если она установлена. В противном случае производится попытка подключения к сокету домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения. Значение по умолчанию определяется переменной среды PGPORT, если она установлена, или значением по умолчанию, заданным при компиляции.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как qhb_dump автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, qhb_dump потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

--role=имя_роли
Задает имя роли, которая будет использоваться для создания дампа. При указании этого параметра qhb_dump выполнит команду SET ROLE имя_роли после подключения к базе данных. Это полезно, когда проходящий проверку пользователь (заданный ключом -U) не имеет прав, требуемых qhb_dump, но может переключиться на роль, наделенную этими правами. В некоторых установках имеется политика, запрещающая подключаться к серверу непосредственно от имени суперпользователя, и этот параметр позволяет создавать дампы, не нарушая ее.

Переменные среды

PGDATABASE
PGHOST
PGOPTIONS
PGPORT
PGUSER
Параметры подключения по умолчанию.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

qhb_dump внутренне выполняет команды SELECT. При возникновении проблем с запуском qhb_dump убедитесь, что в базе данных можно выполнить SELECT, например, с помощью psql. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Обычно активность qhb_dump в базе данных собирается системой накопительной статистики. Если это нежелательно, то можно установить параметр track_counts в false в переменной среды PGOPTIONS или в команде ALTER USER.

Примечания

Если в базу данных template1 в кластере баз были локально установлены какие-либо дополнительные объекты, необходимо проследить за тем, чтобы вывод qhb_dump выгружался в действительно пустую базу данных; в противном случае есть вероятность возникновения ошибок из-за дублирования определений добавленных объектов. Чтобы создать пустую базу данных без каких-либо локальных дополнений, копируйте ее из шаблона template0, а не template1, например:

CREATE DATABASE foo WITH TEMPLATE template0;

Если выбрано создание дампа только с данными и используется параметр --disable-triggers, qhb_dump генерирует команды для выключения триггеров в пользовательских таблицах перед вставкой данных, а затем — команды, снова включающие их после вставки данных. Если восстановление прерывается в середине процесса, системные каталоги могут остаться в неправильном состоянии.

Файл дампа, созданный qhb_dump, не содержит статистику, используемую оптимизатором для принятия решений по планированию запросов. Поэтому после восстановления из файла дампа имеет смысл выполнить команду ANALYZE для обеспечения оптимальной производительности; дополнительную информацию см. в подразделах Обновление статистики планировщика и Процесс «Автовакуум».

Поскольку qhb_dump используется для переноса данных в более новые версии QHB, предполагается, что вывод qhb_dump загрузится на сервер QHB более новой версии, чем ее собственная. Также qhb_dump может создавать дампы серверов QHB более старых версий, чем ее собственная. Однако qhb_dump не может создавать дампы серверов QHB более новых основных версий, чем ее собственная; она не станет даже пытаться это делать, во избежание риска некорректной выгрузки. Кроме того, не гарантируется, что вывод qhb_dump может быть загружен на сервер более старой основной версии, даже если дамп был взят с сервера той же версии. Загрузка такого файла может потребовать его редактирования вручную, с удалением синтаксиса, не воспринимаемого старой версией. В случаях разных версий рекомендуется использовать параметр --quote-all-identifiers, поскольку он может предотвратить проблемы, возникающие из-за изменения списков зарезервированных слов в разных версиях QHB.

Выгружая подписки логической репликации, qhb_dump будет генерировать команды CREATE SUBSCRIPTION которые используют параметр connect = false, так что при восстановлении подписки не устанавливаются удаленные подключения для создания слота репликации или для начального копирования таблиц. Таким образом, дамп может быть восстановлен без сетевого доступа к удаленным серверам. В задачу пользователя входит последующая повторная активация подписок должным образом. Если задействованные хосты поменялись, возможно, потребуется изменить информацию о подключении. Также может быть целесообразно опустошить целевые таблицы перед началом нового полного копирования таблиц. Если пользователи намереваются копировать исходные данные во время обновления, им следует создать слот с параметром two_phase = false. После начальной синхронизации параметр two_phase будет автоматически включен подписчиком, если подписка изначально была создана с параметром two_phase = true.

Примеры

Выгрузка базы данных mydb в файл SQL-скрипта:

$ qhb_dump mydb > db.sql

Восстановление из такого скрипта в (только что созданную) базу данных с именем newdb:

$ psql -d newdb -f db.sql

Выгрузка базы данных в архивный файл в специальном формате:

$ qhb_dump -Fc mydb > db.dump

Выгрузка базы данных в архив в формате каталога:

$ qhb_dump -Fd mydb -f dumpdir

Выгрузка базы данных в архив в формате каталога с параллельным выполнением 5 рабочих процессов:

$ qhb_dump -Fd mydb -j 5 -f dumpdir

Восстановление из архивного файла в (только что созданную) базу данных с именем newdb:

$ qhb_restore -d newdb db.dump

Восстановление из архивного файла в ту же базу данных, из которой он был выгружен, с предварительным удалением текущего содержимого этой базы данных:

$ qhb_restore -d qhb --clean --create db.dump

Выгрузка отдельной таблицы с именем mytab:

$ qhb_dump -t mytab mydb > db.sql

Выгрузка всех таблиц, имена которых начинаются с emp и которые принадлежат схеме detroit, кроме таблицы employee_log:

$ qhb_dump -t 'detroit.emp*' -T detroit.employee_log mydb > db.sql

Выгрузка всех схем, имена которых начинаются с east или west, а заканчиваются на gsm, при этом исключая любые схемы, имена которых содержат слово test:

$ qhb_dump -n 'east*gsm' -n 'west*gsm' -N '*test*' mydb > db.sql

То же самое, но с использованием записи регулярного выражения для объединения ключей:

$ qhb_dump -n '(east|west)*gsm' -N '*test*' mydb > db.sql

Выгрузка всех объектов базы данных, кроме таблиц, имена которых начинаются с ts_:

$ qhb_dump -T 'ts_*' mydb > db.sql

Чтобы указать имя в верхнем или смешанном регистре в ключе -t и связанных с ним ключах, нужно заключить это имя в кавычки, иначе оно будет приведено к нижнему регистру (см. Шаблоны). Но кавычки являются спецсимволом для оболочки, поэтому и они, в свою очередь, должны быть заключены в кавычки. Таким образом, чтобы выгрузить одну таблицу с именем в смешанном регистре, нужно написать что-то вроде

$ qhb_dump -t "\"MixedCaseName\"" mydb > mytab.sql

См. также

qhb_dumpall, qhb_restore, psql

qhb_dumpall

qhb_dumpall — выгрузить кластер баз данных QHB в файл скрипта

Синтаксис

qhb_dumpall [параметр-подключения...] [параметр...]

Описание

qhb_dumpall — это утилита для записи («выгрузки») всех баз данных кластера QHB в один файл скрипта. Этот файл содержит команды SQL, которые можно передать на вход psql для восстановления баз данных. Файл создается путем вызова qhb_dump для каждой базы данных в кластере. qhb_dumpall также выгружает глобальные объекты, общие для всех баз данных, то есть роли и табличные пространства. (Утилита qhb_dump не сохраняет эти объекты.)

Поскольку qhb_dumpall читает таблицы из всех баз данных, для получения полного дампа подключаться, скорее всего, придется от лица суперпользователя. Кроме того, права суперпользователя понадобятся при выполнении сохраненного скрипта, чтобы иметь возможность добавлять роли и создавать базы данных.

SQL-скрипт будет записан в стандартный вывод. Чтобы перенаправить его в файл, используйте параметр -f/--file или операторы оболочки,

qhb_dumpall нужно подключаться к серверу QHB несколько раз (по разу для каждой базы данных). Если вы используете аутентификацию по паролю, эта утилита каждый раз будет запрашивать пароль. В таких случаях удобно иметь файл ~/.pgpass. Дополнительную информацию см. в разделе Файл паролей.

Параметры

Следующие параметры командной строки управляют содержимым и форматом вывода.

-a
--data-only
Выгружать только данные, но не схемы (определения данных).

-c
--clean
Выводить команды SQL для удаления (DROP) всех баз данных, ролей и табличных пространств перед их воссозданием. Этот параметр полезен, когда при восстановлении нужно перезаписать существующий кластер. Если дополнительно не указать --if-exists, то при восстановлении в целевой кластер, где отсутствует какой-либо из этих объектов, будут выводиться безвредные сообщения об ошибке.

-E кодировка
--encoding=кодировка
Создать дамп в заданной кодировке набора символов. По умолчанию дамп создается в кодировке базы данных. (Тот же результат можно получить, задав желаемую кодировку дампа в переменной среды PGCLIENTENCODING.)

-f имя_файла
--file=имя_файла
Отправить вывод в указанный файл. Если этот параметр опущен, используется стандартный вывод.

-g
--globals-only
Выгружать только глобальные объекты (роли и табличные пространства), без баз данных.

-O
--no-owner
Не выводить команды для установки владельца объектов как в исходной базе данных. По умолчанию для назначения владельцев созданных элементов схемы qhb_dumpall выдает команды ALTER OWNER или SET SESSION AUTHORIZATION. Эти команды выполнятся успешно, только если скрипт будет запущен суперпользователем (или владельцем всех объектов в скрипте). Чтобы создать скрипт, который может быть восстановлен любым пользователем, и назначить этого пользователя владельцем всех объектов, укажите -O.

-r
--roles-only
Выгружать только роли, без баз данных или табличных пространств.

-s
--schema-only
Выгружать только определения объектов (схемы), но не сами данные.

-S имя_пользователя
--superuser=имя_пользователя
Задать имя суперпользователя, которое будет использоваться при выключении триггеров. Имеет значение только вместе с параметром --disable-triggers. (Обычно лучше вместо этого запускать итоговый скрипт от лица суперпользователя.)

-t
--tablespaces-only
Выгружать только табличные пространства, без баз данных или ролей.

-v
--verbose
Включает режим подробного вывода. qhb_dumpall будет выводить в стандартный поток ошибок время начала/окончания выгрузки в файл дампа и сообщения о прогрессе выполнения. Если повторить этот параметр, в стандартный поток ошибок будут выводиться дополнительные отладочные сообщения. Этот параметр также действует и на qhb_dump.

-V
--version
Вывести версию qhb_dumpall и завершиться.

-x
--no-privileges
--no-acl
Запретить выгрузку прав доступа (команды GRANT/REVOKE).

--binary-upgrade
Этот параметр предназначен для использования утилитами обновления на месте. Применение в других целях не рекомендуется и не поддерживается. Поведение параметра может быть изменено в будущих версиях без предварительного уведомления.

--column-inserts
--attribute-inserts
Выгружать данные в виде команды INSERT с явными указанием имен столбцов (INSERT INTO таблица (столбец, ...) VALUES ...). Восстановление при этом будет происходить очень медленно; в основном этот вариант оправдан при создании дампов, которые можно загружать в СУБД, отличные от QHB.

--disable-dollar-quoting
Этот параметр выключает возможность заключать тела функций в долларовые кавычки, заставляя использовать кавычки из стандартного синтаксиса SQL.

--disable-triggers
Этот параметр актуален только при создании дампа, содержащего одни данные. Он предписывает qhb_dumpall включать в дамп команды для временного выключения триггеров на целевых таблицах во время восстановления данных. Применяется в ситуациях, когда существуют проверки ссылочной целостности или другие триггеры для таблиц, которые нежелательно вызывать во время восстановления данных.
В настоящее время команды, генерируемые для параметра --disable-triggers, должны выполняться суперпользователем. Следовательно, необходимо задать имя суперпользователя с помощью ключа -S или запустить полученный скрипт от лица суперпользователя.

--exclude-database=шаблон
Не выгружать базы данных, имена которых соответствуют шаблону. Задав ключ --exclude-database несколько раз, можно исключить имена по нескольким шаблонам. Параметр шаблон интерпретируется по тем же правилам, что и шаблон в командах \d утилиты psql (см. Шаблоны), поэтому несколько баз данных также можно исключить, вписав в шаблон подстановочные знаки. Используя такие знаки, не забывайте при необходимости заключать шаблон в кавычки, чтобы их не разворачивала оболочка системы.

--extra-float-digits=число_цифр
Выгружать числа с плавающей запятой не с максимальной доступной точностью, а с заданным значением extra_float_digits. При обычной выгрузке в целях резервного копирования этот параметр использовать не следует.

--if-exists
Использовать команды DROP ... IF EXISTS при удалении объектов в режиме --clean. Это подавляет ошибки «не существует», которые в противном случае могут выводиться. Этот параметр не работает без указания --clean.

--inserts
Выгрузить данные в виде команд INSERT (а не COPY). Восстановление при этом будет происходить очень медленно; в основном этот вариант оправдан при создании дампов, которые можно загружать в СУБД, отличные от QHB. Обратите внимание, что восстановление может полностью провалиться, если в таблицах был изменен порядок столбцов. Параметр --column-inserts безопаснее, но он работает еще медленнее.

--load-via-partition-root
При выгрузке данных партиции таблицы выводить команды COPY или INSERT, ссылающиеся на корневую таблицу в содержащей ее иерархии партиционирования, а не на саму эту партицию. В результате при загрузке данных для каждой строки будет заново выбираться подходящая партиция. Это может быть полезно при восстановлении данных, когда на целевом сервере строки не всегда попадают в те же партиции, в которых они были на исходном сервере. Это возможно, например, когда у столбца разбиения текстовый тип и в двух системах по-разному определено правило сортировки, по которому упорядочивается этот столбец.

--lock-wait-timeout=время_ожидания
Не ждать бесконечно получения совместных блокировок таблиц в начале выгрузки. Вместо этого выдать ошибку, если не удастся заблокировать таблицу за указанное время_ожидания. Время ожидания можно указать в любом из форматов, принимаемых командой SET statement_timeout.

--no-comments
Не выгружать комментарии.

--no-publications
Не выгружать публикации.

--no-role-passwords
Не выгружать пароли для ролей. После восстановления роли получат пароль NULL, и не смогут пройти аутентификацию по паролю, пока им не будут назначены пароли. Поскольку при указании этого параметра значения пароля не нужны, информация о роли читается из представления каталога pg_roles, а не pg_authid. Как следствие, этот параметр также полезен, если доступ к pg_authid ограничен какой-либо политикой безопасности.

--no-security-labels
Не выгружать метки безопасности.

--no-subscriptions
Не выгружать подписки.

--no-sync
По умолчанию qhb_dumpall будет ждать, пока все файлы не будут надежно записаны на диск. Этот параметр заставляет qhb_dumpall завершаться без ожидания, т. е. выполняться быстрее, но впоследствии при неожиданном сбое операционной системы дамп может оказаться поврежден. В целом, этот параметр предназначен для тестирования, и его не следует использовать при выгрузке данных из производственной установки.

--no-table-access-method
Не выводить команды для выбора табличных методов доступа. При указании этого параметра все объекты будут создаваться с тем табличным методом доступа, который устанавливается по умолчанию во время восстановления.

--no-tablespaces
Не выводить команды, создающие или выбирающие табличные пространства для объектов. При указании этого параметра все объекты будут создаваться в том табличном пространстве, которое устанавливается по умолчанию во время восстановления.

--no-toast-compression
Не формировать команды, задающие методы сжатия TOAST. При указании этого параметра все столбцы будут восстановлены с методом сжатия, выбранным по умолчанию.

--no-unlogged-table-data
Не выгружать содержимое нежурналируемых таблиц. Этот параметр не влияет на выгрузку определений (схем) таблиц; он лишь подавляет выгрузку данных из таблиц.

--on-conflict-do-nothing
Добавить предложения ON CONFLICT DO NOTHING в команды INSERT. Этот параметр недопустим, если также не указан --inserts или --column-insert.

--quote-all-identifiers
Принудительно заключить в кавычки все идентификаторы. Этот параметр рекомендуется при выгрузке базы данных с сервера QHB, основная версия которого отличается от версии qhb_dumpall, или когда дамп предназначен для загрузки на сервер с другой основной версией. По умолчанию qhb_dumpall заключает в кавычки только те идентификаторы, которые являются зарезервированными словами в ее собственной основной версии. Иногда это приводит к проблемам совместимости при работе с серверами других версий, в которых наборы зарезервированных слов могут немного отличаться. Использование параметра --quote-all-identifiers предотвращает подобные проблемы ценой ухудшения читаемости скрипта с дампом.

--rows-per-insert=число_строк
Выгружать данные в виде команд INSERT (вместо COPY). Этот параметр определяет максимальное число строк в одной команде INSERT. Указанное значение должно быть больше нуля. Любая ошибка во время восстановления приведет к потере только тех строк INSERT, в которых возникли ошибки, а не всего содержимого таблицы.

--use-set-session-authorization
Выводить команды SET SESSION AUTHORIZATION, соответствующие стандарту SQL, вместо команд ALTER OWNER, чтобы назначить владельцев объекта. В итоге дамп будет более стандартизированным, но, в зависимости от истории объектов, может не восстановиться должным образом.

-?
--help
Показать справку об аргументах командной строки qhb_dumpall и завершиться.

Далее описаны аргументы командной строки, управляющие подключением к базе данных.

-d строка_подключения
--dbname=строка_подключения
Задает параметры, используемые для подключения к серверу, в виде строки подключения; они будут переопределять любые конфликтующие параметры командной строки.
Этот параметр называется --dbname для согласованности с другими клиентскими приложениями, но поскольку qhb_dumpall необходимо подключаться ко многим базам данных, имя базы в строке подключения будет игнорироваться. Чтобы указать имя базы данных для начального подключения, которое будет использоваться при выгрузке глобальных объектов и выявлении других баз, которые необходимо выгрузить, воспользуйтесь параметром -l.

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix. Значение по умолчанию берется из переменной среды PGHOST, если она установлена. В противном случае производится попытка подключения к сокету домена Unix.

-l имя_бд
--database=имя_бд
Задает имя базы данных, через подключение к которой будет производиться выгрузка глобальных объектов и выявление других баз, которые необходимо выгрузить. Если имя не указано, будет использоваться база данных qhb, а если ее не существует — template1.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения. Значение по умолчанию определяется переменной среды PGPORT, если она установлена, или значением по умолчанию, заданным при компиляции.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как qhb_dumpall автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, qhb_dumpall потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.
Обратите внимание, что пароль будет запрашиваться для каждой выгружаемой базы данных. Обычно лучше настроить файл ~/.pgpass, чем полагаться на ввод пароля вручную.

--role=имя_роли
Задает имя роли, которая будет использоваться для создания дампа. При указании этого параметра qhb_dumpall выполнит команду SET ROLE имя_роли после подключения к базе данных. Это полезно, когда проходящий проверку пользователь (указанный ключом -U) не имеет прав, требуемых qhb_dumpall, но может переключиться на роль, наделенную этими правами. В некоторых установках имеется политика, запрещающая подключаться к серверу непосредственно от имени суперпользователя, и этот параметр позволяет создавать дампы, не нарушая ее.

Переменные среды

PGHOST
PGOPTIONS
PGPORT
PGUSER
Параметры подключения по умолчанию.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Примечания

Поскольку qhb_dumpall внутри себя вызывает qhb_dump, некоторые диагностические сообщения будут относиться к qhb_dump.

Параметр --clean может быть полезен, даже если вы собираетесь восстановить дамп из скрипта в новом кластере. Использование --clean позволяет скрипту удалять и пересоздавать встроенные базы данных qhb и template1, добиваясь того, чтобы эти базы сохранили свойства (например, локаль и кодировку), которые имели в исходном кластере. Без данного параметра эти базы сохранят свои существующие свойства уровня базы данных, а также все уже существующее содержимое.

После восстановления рекомендуется запускать ANALYZE для каждой базы данных, чтобы оптимизатор получил актуальную статистику. Также можно запустить vacuumdb -a -z для анализа всех баз данных.

Не следует ожидать, что скрипт дампа будет выполняться совсем без ошибок. В частности, поскольку скрипт будет содержать команду CREATE ROLE для каждой роли, существующей в исходном кластере, при попытке создать суперпользователя для начальной загрузки он обязательно получит ошибку «role already exists» (роль уже существует), если только целевой кластер не был инициализирован с другим начальным именем суперпользователя. Эта ошибка безвредна и ее следует игнорировать. Использование параметра --clean, скорее всего, вызовет появления ряда дополнительных безвредных сообщений об ошибках, связанных с несуществующими объектами; их число можно уменьшить, добавив параметр --if-exists.

qhb_dumpall требует, чтобы все необходимые каталоги табличных пространств существовали до восстановления; в противном случае создание базы данных в нестандартном размещении завершится ошибкой.

Примеры

Выгрузка всех баз данных:

$ qhb_dumpall > db.out

Восстановить базу(ы) данных из этого файла можно вот так:

$ psql -f db.out qhb

Неважно, к какой базе данных вы здесь подключаетесь, поскольку файл скрипта, созданный qhb_dumpall, будет содержать соответствующие команды для создания и подключения к сохраненным базам данных. Исключением является случай, если вы указали ключ --clean, — тогда следует сначала подключиться к базе данных qhb; скрипт попытается немедленно удалить другие базы данных, но не сможет удалить базу, к которой вы подключены.

См. также

Подробную информацию об условиях, при которых могут возникнуть ошибки, см. на справочной странице утилиты qhb_dump.

qhb_isready

qhb_isready — проверить состояние соединения с сервером QHB

Синтаксис

qhb_isready [параметр-подключения...] [параметр...]

Описание

qhb_isready — это утилита для проверки состояния соединения с сервером базы данных QHB. Результат проверки соединения указывается в коде завершения.

Параметры

-d имя_бд
--dbname=имя_бд
Задает имя базы данных для подключения. В имени_бд может задаваться строка подключения. В этом случае параметры в строке подключения переопределяют любые конфликтующие параметры в командной строке.

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения. Значение по умолчанию определяется переменной среды PGPORT, если она установлена, или значением по умолчанию, заданным при компиляции (обычно это 5432).

-q
--quiet
Не отображать сообщение о состоянии. Это полезно при выполнении скриптов.

-t секунды
--timeout=секунды
Максимальное количество секунд ожидания при попытке соединения, по истечении которого будет возвращено сообщение, что сервер не отвечает. При значении 0 выключается. Значение по умолчанию — 3 секунды.

-U имя_пользователя
--username=имя_пользователя
Подключиться к базе данных как пользователь имя_пользователя вместо пользователя по умолчанию.

-V
--version
Вывести версию qhb_isready и завершиться.

-?
--help
Показать справку об аргументах командной строки qhb_isready и завершиться.

Код завершения

Утилита qhb_isready возвращает оболочке 0, если сервер нормально принимает подключения, 1, если сервер отказывает в подключении (например, во время загрузки), 2, если при попытке подключения нет ответа, и 3, если попыток подключения не было (например, из-за неверных параметров).

Переменные среды

qhb_isready, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Переменная среды PG_COLOR указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Для получения статуса сервера необязательно указывать правильные значения имени пользователя, пароля или имени базы данных, однако при предоставлении неправильных значений сервер запротоколирует в журнал неудачную попытку подключения.

Примеры

Стандартное использование:

$ qhb_isready
/tmp:5432 - accepting connections
$ echo $?
0

Запуск с параметрами подключения во время загрузки кластера QHB:

$ qhb_isready -h localhost -p 5433
localhost:5433 - rejecting connections
$ echo $?
1

Запуск с параметрами подключения при недоступном кластере QHB:

$ qhb_isready -h someremotehost
someremotehost:5432 - no response
$ echo $?
2

qhb_receivewal

qhb_receivewal — принимать потоковую передачу журналов упреждающей записи с сервера QHB

Синтаксис

qhb_receivewal [параметр...]

Описание

Утилита qhb_receivewal используется для приема потоковой передачи журнала упреждающей записи из работающего кластера QHB. Журнал упреждающей записи передается по протоколу потоковой репликации и записывается в локальный файловый каталог. Этот каталог можно использовать в качестве расположения архива для возврата в исходное состояние с использованием восстановления на определенный момент времени (см. раздел Непрерывное архивирование и восстановление на момент времени (PITR)).

Утилита qhb_receivewal принимает журнал упреждающей записи в режиме реального времени, по мере его генерирования на сервере, и не ожидает завершения сегментов, как это делает archive_command и archive_library. По этой причине при использовании qhb_receivewal нет необходимости устанавливать параметр archive_timeout.

В отличие от приемника WAL резервного сервера QHB, qhb_receivewal по умолчанию сбрасывает данные WAL на диск только тогда, когда файл WAL закрывается. Для сброса данных WAL в режиме реального времени нужно указать параметр --synchronous. Поскольку qhb_receivewal не применяет WAL, нельзя допускать, чтобы он стал синхронным резервным сервером, когда synchronous_commit равен remote_apply. Если это произойдет, он будет казаться резервным сервером, который никогда не догонит основном сервер, что вызовет блокировку фиксаций транзакций. Чтобы избежать этого, следует либо сконфигурировать для synchronous_standby_names подходящее значение, либо задать для qhb_receivewal имя приложения (параметр application_name), которое ему не соответствует, либо изменить значение synchronous_commit на что-то, отличное от remote_apply.

Журнал упреждающей записи передается по обычному соединению QHB и использует протокол репликации. Это соединение должно быть установлено суперпользователем или пользователем, имеющим разрешения REPLICATION (см. раздел Атрибуты ролей), а в qhb_hba.conf должно быть разрешено соединение репликации. Кроме того, сервер должен быть сконфигурировать с достаточно большим значением max_wal_senders, чтобы оставить хотя бы один сеанс доступным для потоковой передачи.

Начальная точка потоковой передачи журнала упреждающей записи вычисляется при запуске pg_receivewal:

Сначала сканируется каталог, куда записываются файлы сегментов WAL, и отыскивается последний завершенный файл сегмента, и в качестве начальной точки используется начало следующего файла сегмента WAL.

Если начальную точку невозможно вычислить предыдущим способом и при этом используется слот репликации, выводится дополнительная команда READ_REPLICATION_SLOT для получения значения restart_lsn, которое будет использоваться в качестве начальной точки. Этот параметр доступен только при потоковой передаче журналов упреждающей записи из QHB версии 1.5.2 и выше.

Если начальную точку невозможно вычислить предыдущим способом, используется последнее расположение сброшенных на диск данных в WAL, которое отображается сервером посредством команды IDENTIFY_SYSTEM.

Если соединение потеряно или не может быть установлено изначально из-за некритической ошибки, qhb_receivewal будет бесконечно повторять попытку подключения и восстановит потоковую передачу как можно скорее. Чтобы избежать этого поведения, используйте параметр -n.

В отсутствие критических ошибок qhb_receivewal будет работать до тех пор, пока не прекратится сигналом SIGINT (Control+C).

Параметры

-D каталог
--directory=каталог
Каталог для записи выходных данных.
Это обязательный параметр.

-E lsn
--endpos=lsn
Автоматически останавливать репликацию и выходить с нормальным кодом завершения 0, когда при получении данных достигается указанный LSN.
Если есть запись с LSN, в точности равным lsn, эта запись будет обработана.

--if-not-exists
Не выдавать ошибку, если задан параметр --create-slot, а слот с указанным именем уже существует.

-n
--no-loop
Не зацикливаться при ошибках подключения, а сразу же завершить работу с ошибкой.

--no-sync
С этим параметром qhb_receivewal не будет принудительно сбрасывать данные WAL на диск. Это ускоряет работу, но означает, что последующий сбой операционной системы может привести к повреждению сегментов WAL. В целом, этот параметр полезен для тестирования, но не должен применяться при архивации WAL в производственной среде.
Этот параметр несовместим с --synchronous.

-s интервал
--status-interval=интервал
Задает интервал в секундах между отправками пакетов состояния обратно на сервер. Это упрощает для сервера мониторинг прогресса. Нулевое значение полностью выключает периодическое обновление состояния, хотя обновление все равно будет отправляться при запросе от сервера во избежание разъединения при длительном отсутствии сигнала. Значение по умолчанию — 10 секунд.

-S имя_слота
--slot=имя_слота
Заставляет qhb_receivewal использовать существующий слот репликации (см. подраздел Слоты репликации). Когда используется этот параметр, qhb_receivewal будет сообщать серверу позицию сброса на диск, указывая, когда каждый сегмент был синхронизирован с диском, чтобы сервер мог удалить этот сегмент, если в нем больше нет необходимости.
Когда клиент репликации qhb_receivewal сконфигурирован на сервере как синхронный резервный сервер, он будет при использование слота репликации сообщать серверу позицию сброса на диск, но только когда файл WAL закрывается. Поэтому такая конфигурация приведет к тому, что транзакции на основном сервере будут ожидать завершения долгое время и, в целом, будут работать неудовлетворительно. Для корректной работы этой конфигурации следует дополнительно указать параметр --synchronous (см. ниже).

--synchronous
Сбросить данные WAL на диск сразу после их получения. Также передать пакет состояния обратно на сервер сразу после очистки, независимо от значения --status-interval.
Этот параметр следует задавать, если клиент репликации qhb_receivewal сконфигурирован на сервере как синхронный резервный сервер, чтобы обеспечить своевременную передачу отчетов на сервер.

-v
--verbose
Включает режим подробных сообщений.

-Z уровень
-Z метод[:подробная_информация]
--compress=уровень
--compress=метод[:подробная_информация]
Включает сжатие журналов упреждающей записи.
В качестве метода сжатия можно выбрать gzip, lz4 (если QHB была скомпилирована с параметром --with-lz4) или none (без сжатия). Дополнительно можно указать строку с подробной информацией по сжатию. Если в этой строке передается целое число, оно задает уровень сжатия. В противном случае это должен быть список разделенных запятыми элементов в форме ключевое_слово или ключевое_слово=значение. В настоящее время поддерживается только ключевое слово level (уровень).
Если уровень сжатия не задан, будет использован уровень сжатия по умолчанию. Если задан только уровень, но не алгоритм сжатия, будет использован метод сжатие gzip, если уровень больше 0, а при уровне 0 сжатие не будет выполняться.
При использовании метода gzip ко всем именам файлов автоматически будет добавляться суффикс .gz, а при использовании метода lz4 — суффикс .lz4.

Следующие параметры командной строки управляют параметрами подключением к базе данных.

-d строка_подключения
--dbname=строка_подключения
Задает параметры, используемые для подключения к серверу, в виде строки подключения; они будут переопределять все конфликтующие параметры командной строки.
Этот параметр называется --dbname для согласованности с другими клиентскими приложениями, но поскольку qhb_receivewal не подключается к какой-либо конкретной базе данных в кластере, имя базы данных в строке подключения будет игнорироваться.

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix. Значение по умолчанию определяется переменной среды PGHOST, если она установлена. В противном случае производится попытка подключения к сокету домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения. Значение по умолчанию определяется переменной среды PGPORT, если она установлена, или значением по умолчанию, заданным при компиляции.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как qhb_receivewal автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, qhb_receivewal потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

Для управления слотами физической репликации qhb_receivewal может выполнить одно из двух следующих действий:

--create-slot
Создать новый слот физической репликации с именем, заданным в параметре --slot, а затем завершиться.

--drop-slot Удалить слот репликации с именем, заданным в параметре --slot, а затем завершиться.

Также доступны и другие параметры:

-V
--version
Вывести версию qhb_receivewal и завершиться.

-?
--help
Показать справку об аргументах командной строки qhb_receivewal и завершиться.

Код завершения

qhb_receivewal завершится с кодом 0 при прерывании сигналом SIGINT. (Это нормальный способ завершить данную утилиту. Следовательно, это не ошибка.) Для критических ошибок или других сигналов код завершения будет ненулевым.

Переменные среды

Эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Переменная среды PG_COLOR указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

При использовании qhb_receivewal вместо archive_command или archive_library в качестве основного метода резервного копирования WAL настоятельно рекомендуется пользоваться слотами репликации. В противном случае сервер может свободно перезаписывать или удалять файлы журнала упреждающей записи раньше, чем произойдет их резервное копирование, поскольку он не имеет никакой информации ни от archive_command или archive_library, ни от слотов репликации о том, в какой мере был заархивирован поток WAL. Однако обратите внимание, что слот репликации заполнит дисковое пространство сервера, если приемник не успевает принимать извлеченные данные WAL.

qhb_receivewal сохранит разрешения группы в полученных файлах WAL, если эти разрешения включены в исходном кластере.

Примеры

Принять потоковую передачу журнала упреждающей записи с сервера mydbserver и сохранить его в локальном каталоге /var/lib/qhb/data/archive:

$ qhb_receivewal -h mydbserver -D /var/lib/qhb/data/archive

См. также

qhb_basebackup

qhb_recvlogical

qhb_recvlogical — управлять потоками логического декодирования QHB

Синтаксис

qhb_recvlogical [параметр...]

Описание

Утилита qhb_recvlogical управляет слотами репликации логического декодирования и принимает потоковую передачу данных из таких слотов репликации.

Она создает соединение в режиме репликации, поэтому к ней применяются те же ограничения, что и к qhb_receivewal, плюс ограничения для логической репликации (см. главу Логическое декодирование).

qhb_recvlogical не имеет аналога режимам просмотра и получения интерфейса SQL логического декодирования. Она отправляет подтверждения воспроизведения для данных медленно, по мере их получения и при успешном завершении. Чтобы проверить данные, ожидающие в слоте, не принимая их, используйте функцию pg_logical_slot_peek_changes.

Параметры

Для выбора действия необходимо указать хотя бы один из следующих параметров:

--create-slot
Создать новый слот логической репликации с именем, заданным параметром --slot, используя плагин вывода, заданный параметром --plugin, для базы данных, заданной параметром --dbname.
Чтобы включить декодирование подготовленных транзакций, можно указать --create-slot вместе с параметром --two-phase.

--drop-slot
Удалить слот репликации с именем, заданным в параметре --slot, а затем завершиться.

--start
Начать прием потока изменений из слота логической репликации, заданного в параметре --slot, продолжая до прерывания сигналом. Если передача потока изменений на стороне сервера прекращается из-за завершения работы сервера или разрыва соединения с ним, повторять цикл подключения и приема, если не указан параметр --no-loop.
Формат потока определяется плагином вывода, указанным при создании слота.
Подключаться нужно к той же базе данных, которая использовалась для создания слота.

Параметры --create-slot и --start можно указывать вместе. Параметр --drop-slot нельзя объединять с другим действием.

Следующие параметры командной строки управляют расположением и форматом вывода и другим поведением репликации:

-E lsn
--endpos=lsn
В режиме --start автоматически остановить репликацию и завершиться с нормальным кодом завершения 0, когда при получении данных достигается указанный LSN. При указании этого параметра вне режима --start возникает ошибка.
Если есть запись с LSN, в точности равным lsn, эта запись будет выведена.
Параметру --endpos неизвестны границы транзакции, поэтому он может усечь вывод на середине транзакции. Все частично выведенные транзакции не будут приниматься и будут снова воспроизведены при следующем чтении из этого слота. Отдельные сообщения не усекаются никогда.

-f имя_файла
--file=имя_файла
Записать полученные и декодированные данные транзакций в этот файл. Для записи в stdout напишите -.

-F секунды_интервала
--fsync-interval=секунды_интервала
Определяет, как часто qhb_recvlogical должна производить вызовы fsync(), чтобы обеспечить благополучный сброс выходного файла на диск.
Сервер будет периодически требовать от клиента совершить сброс на диск и сообщить ему сохраненную позицию. Данный параметр является дополнением к этому, позволяя проводить сброс чаще.
При значении интервала 0 проведение вызовов fsync() полностью выключается, хотя серверу все еще сообщается о прогрессе. При этом в случае сбоя данные могут быть потеряны.

-I lsn
--startpos=lsn
В режиме --start начать репликацию с заданного LSN. Подробную информацию о том, как это работает, см. в главе Логическое декодирование и в разделе Протокол потоковой репликации. В других режимах игнорируется.

--if-not-exists
Не выдавать ошибку, если указан параметр --create-slot, а слот с заданным именем уже существует.

-n
--no-loop
При потере соединения с сервером не повторять циклично попытки подключения, а просто завершиться.

-o имя[=значение]
--option=имя[=значение]
Передать имя параметра плагину вывода вместе со значением этого параметра (если указано). Существующие параметры и их эффекты зависят от используемого плагина вывода.

-P плагин
--plugin=плагин
При создании слота использовать заданный плагин вывода логического декодирования. См. главу Логическое декодирование. Этот параметр не действует, если слот уже существует.

-s секунды_интервала
--status-interval=секунды_интервала
Этот параметр действует так же, как и одноименный параметр утилиты qhb_receivewal. См. его описание там.

-S имя_слота
--slot=имя_слота
В режиме --start использовать существующий слот логической репликации с именем имя_слота. В режиме --create-slot создать слот с этим именем. В режиме --drop-slot удалить слот с этим именем.

-t
--two-phase
Включает декодирование подготовленных транзакций. Этот параметр можно указывать только вместе с --create-slot.

-v
--verbose
Включает режим подробных сообщений.

Следующие параметры командной строки управляют параметрами подключения к базе данных.

-d имя_бд
--dbname=имя_бд
База данных для подключения. Что именно это означает, см. в описании действий программы. В имени_бд может задаваться строка подключения. В этом случае параметры в строке подключения переопределяют любые конфликтующие параметры в командной строке. По умолчанию выбирается имя пользователя.

-h имя_хоста-или-ip
--host=имя_хоста-или-ip
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix. Значение по умолчанию определяется переменной среды PGHOST, если она установлена. В противном случае производится попытка подключения к сокету домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения. Значение по умолчанию определяется переменной среды PGPORT, если она установлена, или значением по умолчанию, заданным при компиляции.

-U пользователь
--username=пользователь
Имя пользователя, под которым производится подключение. По умолчанию это имя текущего пользователя операционной системы.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как qhb_recvlogical автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, qhb_recvlogical потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

Также имеются следующие дополнительные параметры:

-V
--version
Вывести версию qhb_recvlogical и завершиться.

-?
--help
Показать справку об аргументах командной строки qhb_recvlogical и завершиться.

Переменные среды

Эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Переменная среды PG_COLOR указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Утилита qhb_recvlogical сохранит разрешения группы в полученных файлах WAL, если эти разрешения включены в исходном кластере.

Примеры

Примеры см. в разделе Примеры логического декодирования.

См. также

qhb_receivewal

qhb_restore

qhb_restore — восстановить базу данных QHB из архивного файла, созданного qhb_dump

Синтаксис

qhb_restore [параметр-подключения...] [параметр...] [имя_файла]

Описание

qhb_restore — это утилита для восстановления базы данных QHB из архива, созданного qhb_dump в одном из нетекстовых форматов. Она выдает команды, необходимые для воссоздания базы данных до состояния, в котором та находилась на момент сохранения. Архивные файлы также позволяют qhb_restore выбирать объекты для восстановления или даже переупорядочивать элементы перед восстановлением. Архивные файлы предназначены для переноса между архитектурами.

Утилита qhb_restore может работать в двух режимах. Если указано имя базы данных, qhb_restore подключается к ней и восстанавливает содержимое архива непосредственно в эту базу. В противном случае создается скрипт, содержащий команды SQL, необходимые для перестройки базы данных, который записывается в файл или стандартный вывод. Этот вывод скрипта равнозначен простому текстовому формату вывода qhb_dump. Поэтому некоторые параметры qhb_restore, управляющие выводом, аналогичны параметрам qhb_dump.

Очевидно, что qhb_restore не может восстановить информацию, которой нет в файле архива. Например, если архив был создан с использованием параметра «выгрузить данные в виде команд INSERT», qhb_restore не сможет загружать данные с помощью операторов COPY.

Параметры

Утилита qhb_restore принимает следующие аргументы командной строки.

имя_файла
Задает расположение восстанавливаемого архивного файла (или каталог, для архива в формате каталога). Если этот параметр не указан, используется стандартный ввод.

-a
--data-only
Восстановить только данные, но не схему (определения данных). Если в архиве имеются данные таблиц, большие объекты и значения последовательности, они тоже восстанавливаются.
Этот параметр похож, но по историческим причинам не идентичен указанию параметра --section=data.

-c
--clean
Перед восстановлением объектов базы данных выдать команды для удаления (DROP) восстанавливаемых объектов. Этот параметр полезен для перезаписи существующей базы данных. Если дополнительно не указан параметр --if-exists и какой-либо из объектов не существует в целевой базе данных, могут генерироваться безвредные сообщения об ошибке.

-C
--create
Создать базу данных перед восстановлением в нее данных. Если также указан параметр --clean, удалить и заново создать целевую базу данных перед подключением к ней.
Кроме того, с помощью --create qhb_restore восстанавливает комментарий к базе данных (если таковой имеется) и все значения переменных конфигурации, специфичные для этой базы данных, то есть любые команды ALTER DATABASE ... SET ... и ALTER ROLE ... IN DATABASE ... SET ..., в которых упоминается эта база данных. Права доступа к самой базе данных тоже восстанавливаются, если не указан параметр --no-acl.
При использовании этого параметра база данных, заданная в параметре -d, применяется только для выполнения начальных команд DROP DATABASE и CREATE DATABASE. Все данные восстанавливаются в базу данных с именем, указанным в архиве.

-d имя_бд
--dbname=имя_бд
Подключиться к базе данных имя_бд и восстановить данные непосредственно в эту базу. В имени_бд может задаваться строка подключения. В этом случае параметры в строке подключения переопределяют любые конфликтующие параметры в командной строке.

-e
--exit-on-error
Завершиться, если при отправке команд SQL в базу данных возникла ошибка. По умолчанию операция продолжается, а количество ошибок отображается в конце восстановления.

-f имя_файла
--file=имя_файла \ Задать выходной файл для сгенерированного скрипта или для оглавления (если используется вместе с ключом -l). Для вывода в stdout напишите -.

-F формат
--format=формат
Указать формат архива. В этом нет необходимости, поскольку qhb_restore определит формат автоматически. Если этот параметр задается, допускается один из следующих вариантов:

c
custom
Архив сохранен в специальном формате qhb_dump.

d
directory
Архив сохранен в каталоге.

t
tar
Архив сохранен в формате tar.

-I индекс
--index=индекс
Восстановить определение только именованного индекса. Задав ключ -I несколько раз, можно указать несколько индексов.

-j количество_заданий
--jobs=количество_заданий
Выполнять самые трудоемкие этапы qhb_restore — те, где загружаются данные, создаются индексы или ограничения, — одновременно, используя параллельные сеансы числом до количества_заданий. С этим параметром можно значительно сократить время восстановления большой базы данных на сервере, работающем на многопроцессорной машине. Когда вместо прямого подключения к серверу баз данных генерируется скрипт, этот параметр игнорируется.
Каждое задание представляет собой один процесс или один поток, в зависимости от операционной системы, и использует отдельное подключение к серверу.
Оптимальное значение для этого параметра зависит от аппаратной настройки сервера, клиента и сети. Факторы включают количество ядер ЦП и настройку диска. Хорошей отправной точкой является количество ядер ЦП на сервере, но более высокие значения во многих случаях тоже могут привести к ускоренному восстановлению. Конечно, слишком высокие значения приведут к снижению производительности из-за перегрузки.
Этот параметр поддерживается только со специальными форматами и форматами каталогов. На вход должен передаваться обычный файл или каталог (а не, например, канал или стандартный ввод). Кроме того, несколько заданий нельзя выполнять, если указан параметр --single-transaction.

-l
--list
Вывести оглавление архива. Выходные данные этой операции можно использовать как входные данные для параметра -L. Обратите внимание, что если с -l используются ключи фильтрации, например, -n или -t, они сократят список элементов.

-L файл-список
--use-list=файл-список
Восстановить только те элементы архива, которые перечислены в файле-списке, причем сделать это в порядке их перечисления. Обратите внимание, что если с -L используются ключи фильтрации, например, -n или -t, они дополнительно сократят список элементов.
Обычно файл-список создается путем редактирования вывода предыдущей операции -l. Строки можно перемещать или удалять, а также закомментировать, поместив в начале строки точку с запятой (;). Примеры см. ниже.

-n схема
--schema=схема
Восстановить объекты только из именованной схемы. Задав ключ -n несколько раз, можно указать несколько схем. Этот параметр можно скомбинировать с параметром -t, чтобы восстановить только определенную таблицу.

-N схема
--exclude-schema=схема
Не восстанавливать объекты из именованной схемы. Задав ключ -N несколько раз, можно исключить несколько схем.
Если в ключах -n and -N задается одинаковое имя схемы, ключ -N побеждает, и схема исключается.

-O
--no-owner
Не выдавать команды для установки владельца объектов в соответствии с исходной базой данных. По умолчанию для установки владельца созданных элементов схемы qhb_restore выполняет операторы ALTER OWNER или SET SESSION AUTHORIZATION. Эти операторы выполнятся, только если исходное соединение с базой данных установлено суперпользователем (или пользователем, которому принадлежат все объекты в скрипте). С ключом -O начальное подключение может произвести любой пользователь, и он же станет владельцем всех созданных объектов.

-P имя-функции(тип-аргумента[, ...])
--function=имя-функции(тип-аргумента[, ...])
Восстановить только именованную функцию. Следите за тем, чтобы писать имя функции и аргументы в точности так, как они фигурируют в оглавлении файла дампа. Задав ключ -P несколько раз, можно указать несколько функций.

-R
--no-reconnect
Этот параметр устарел, но все еще принимается для обратной совместимости.

-s
--schema-only
Восстановить только схему (определения данных), а не данные, и в том объеме, в котором записи схемы присутствуют в архиве.
Этот параметр является обратным параметру --data-only. Он похож, но по историческим причинам не идентичен указанию --section=pre-data --section=post-data.
(Не путайте этот параметр с параметром --schema, где слово «схема» применяется в другом значении.)

-S имя_пользователя
--superuser=имя_пользователя
Задать имя суперпользователя, которое будет использоваться при выключении триггеров. Имеет смысл только вместе с параметром --disable-triggers.

-t таблица
--table=таблица
Восстановить определение и/или данные только именованной таблицы. В этом случае слово «таблица» включает в себя представления, материализованные представления, последовательности и сторонние таблицы. Задав ключ -t несколько раз, можно указать несколько таблиц. Этот параметр можно скомбинировать с параметром -n, чтобы указать таблицу(ы) в конкретной схеме.

Примечание
При указании -t утилита qhb_restore не пытается восстановить прочие объекты базы данных, от которых могут зависеть выбранные таблицы. Таким образом, нет гарантии, что конкретные таблицы будут успешно восстановлены в чистой базе данных.

Примечание
Этот флаг не ведет себя идентично флагу -t утилиты qhb_dump. В настоящее время qhb_restore не поддерживает подбор соответствий по подстановочным знакам, а кроме того, нельзя включить в -t имя схемы. И хотя с флагом -t утилиты qhb_dump также будут выгружаться вспомогательные объекты (например, индексы) выбранных таблиц, с флагом -t утилиты qhb_restore такие вспомогательные объекты не включаются.

-T триггер
--trigger=триггер
Восстановить только именованный триггер. Задав ключ -T несколько раз, можно указать несколько триггеров.

-v
--verbose
Задает режим вывода подробных сообщений. qhb_restore будет выводить в стандартный поток ошибок подробные комментарии к объектам и время начала/окончания восстановления из файла вывода, а также сообщения о прогрессе выполнения. Если повторить этот параметр, в стандартный поток ошибок будут выводиться дополнительные отладочные сообщения.

-V
--version
Вывести версию qhb_restore и завершиться.

-x
--no-privileges
--no-acl
Не допустить восстановления прав доступа (команды GRANT/REVOKE).

-1
--single-transaction
Выполнить восстановление как одну транзакцию (то есть обернуть сгенерированные команды в BEGIN/COMMIT). Это гарантирует что либо все команды завершаются успешно, либо изменения не применяются. Этот параметр подразумевает --exit-on-error.

--disable-triggers
Этот параметр актуален только при выполнении восстановления одних данных. Он предписывает qhb_restore выполнять команды для временного выключения триггеров на целевых таблицах во время восстановления данных. Применяется в ситуациях, когда существуют проверки ссылочной целостности или другие триггеры для таблиц, которые нежелательно вызывать во время восстановления данных.
В настоящее время команды, генерируемые для --disable-triggers, должны выполняться суперпользователем. Следовательно, необходимо задать имя суперпользователя с помощью ключа -S или, что предпочтительно, запустить qhb_restore как суперпользователь QHB.

--enable-row-security
Этот параметр актуален только при восстановлении содержимого таблицы с защитой строк. По умолчанию qhb_restore устанавливает в row_security off (выключен), чтобы в таблице гарантированно были восстановлены все данные. Если у пользователя недостаточно прав для обхода защиты строк, возникает ошибка. Этот параметр предписывает qhb_restore установить в row_security on (включен), позволяя пользователю попытаться восстановить содержимое таблицы с включенной защитой строк. Это все равно может не сработать, если у пользователя нет права добавлять в таблицу строки из дампа.
Обратите внимание, что в настоящее время при использовании этого параметра также необходимо, чтобы дамп был в формате INSERT, так как команда COPY FROM не поддерживает защиту строк.

--if-exists
Использовать команды DROP ... IF EXISTS при удалении объектов в режиме --clean. Это подавляет ошибки «не существует», которые в противном случае могут выводиться. Этот параметр не работает без указания --clean.

--no-comments
Не выдавать команды для восстановления комментариев, даже если архив содержит их.

--no-data-for-failed-tables
По умолчанию данные таблицы восстанавливаются, даже если команда создания таблицы не выполнена (например, потому что таблица уже существует). С этим параметром данные для такой таблицы пропускаются. Это поведение полезно, если целевая база данных уже содержит желаемое содержимое таблицы. Например, вспомогательные таблицы для расширений QHB, в частности, PostGIS, могут быть уже загружены в целевую базу данных; указание этого параметра предотвращает загрузку в них дубликатов или устаревших данных.
Этот параметр действует только при восстановлении непосредственно в базу данных, но не при создании скрипта SQL.

--no-publications
Не выводить команды для восстановления публикаций, даже если архив содержит их.

--no-security-labels
Не выводить команды для восстановления меток безопасности, даже если архив содержит их.

--no-subscriptions
Не выводить команды для восстановления подписок, даже если архив содержит их.

--no-table-access-method
Не выводить команды для выбора табличных методов доступа. С этим параметром все объекты будут созданы с табличным методом доступа по умолчанию на момент восстановления.

--no-tablespaces
Не выводить команды для выбора табличных пространств. С этим параметром все объекты будут восстановлены в табличном пространстве по умолчанию на момент восстановления.

--section=имя_раздела
Восстановить только заданный раздел. Именем раздела может быть pre-data, data или post-data. Для выбора нескольких разделов этот параметр можно указать более одного раза. По умолчанию восстанавливаются все разделы.
Раздел data содержит фактические данные таблиц, содержимое больших объектов и значения последовательности. Раздел post-data содержит определения индексов, триггеров, правил и ограничений (кроме ограничений проверки, созданных без NOT VALID). Раздел pre-data содержит все остальные элементы определений данных.

--strict-names
Требовать, чтобы каждому квалификатору схемы (-n/--schema) и таблицы (-t/--table) соответствовал как минимум один объект схема/таблица в файле резервной копии.

--use-set-session-authorization
Выводить команды SET SESSION AUTHORIZATION, соответствующие стандарту SQL, вместо команд ALTER OWNER, чтобы определить владельца объекта. В итоге дамп будет более стандартизированным, но, в зависимости от истории объектов, может не восстановиться должным образом.

-?
--help
Показать справку об аргументах командной строки qhb_restore и завершиться.

Кроме того, в качестве параметров подключения qhb_restore принимает следующие аргументы командной строки:

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix. Значение по умолчанию определяется переменной среды PGHOST, если она установлена. В противном случае производится попытка подключения к сокету домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения. Значение по умолчанию определяется переменной среды PGPORT, если она установлена, или значением по умолчанию, заданным при компиляции.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как qhb_restore автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, qhb_restore потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

--role=имя_роли
Задает имя роли, которая будет использоваться для проведения восстановления. При указании этого параметра qhb_restore выполнит команду SET ROLE имя_роли после подключения к базе данных. Это полезно, когда проходящий проверку пользователь (заданный ключом -U) не имеет прав, требуемых qhb_restore, но может переключиться на роль, наделенную этими правами. В некоторых установках имеется политика, запрещающая подключаться к серверу непосредственно от имени суперпользователя, и этот параметр позволяет проводить восстановления, не нарушая ее.

Переменные среды

PGHOST
PGOPTIONS
PGPORT
PGUSER
Параметры подключения по умолчанию.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

Когда прямое подключение к базе данных задается с помощью параметра -d, qhb_restore внутренне выполняет операторы SQL. При возникновении проблем с запуском qhb_restore убедитесь, что можете выбирать информацию из базы данных, например, с помощью psql. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Примечания

Если в базу данных template1 в вашей инсталляции были локально установлены какие-либо дополнительные объекты, необходимо проследить за тем, чтобы вывод qhb_restore выгружался в действительно пустую базу данных; в противном случае есть вероятность возникновения ошибок из-за дублирования определений добавленных объектов. Чтобы создать пустую базу данных без каких-либо локальных дополнений, копируйте ее из шаблона template0, а не template1, например:

CREATE DATABASE foo WITH TEMPLATE template0;

Утилита qhb_restore имеет следующие ограничения:

При восстановлении данных в существующей таблице и использовании параметра --disable-triggers утилита qhb_restore генерирует команды для выключения триггеров в пользовательских таблицах перед добавлением данных, а затем генерирует команды для их повторного включения после добавления данных. Если восстановление прерывается в середине процесса, системные каталоги могут остаться в неправильном состоянии.

qhb_restore не может выборочно восстанавливать большие объекты; например, только для определенной таблицы. Если в архиве содержатся большие объекты, то либо все они будут восстановлены, либо ни один из них, (если они исключены посредством -L, -t или других параметров).

Подробную информацию об ограничениях qhb_dump см. на справочной странице этой утилиты.

После восстановления целесообразно запускать ANALYZE для всех восстановленных таблиц, чтобы оптимизатор имел полезную статистику; дополнительную информацию см. в подразделах Обновление статистики планировщика и Процесс «Автовакуум».

Примеры

Предположим, мы выгрузили базу данных mydb в файл дампа специального формата:

$ qhb_dump -Fc mydb > db.dump

Удаление этой базы данных и воссоздание ее из дампа:

$ dropdb mydb
$ qhb_restore -C -d qhb db.dump

База данных, указанная в ключе -d, может быть любой базой данных, существующей в кластере; qhb_restore использует ее только для выполнения для mydb команды CREATE DATABASE. С ключом -C данные всегда восстанавливаются в базу данных с именем, которое фигурирует в файле дампа.

Восстановление данных из дампа в новую базу данных с именем newdb:

$ createdb -T template0 newdb
$ qhb_restore -d newdb db.dump

Обратите внимание, что мы не используем -C, а вместо этого напрямую подключаемся к базе данных, в которую нужно провести восстановление. Также обратите внимание, что мы клонируем новую базу данных из template0, а не template1, чтобы она гарантированно была изначально пуста.

Чтобы изменить порядок элементов базы данных, сначала необходимо выгрузить оглавление архива:

$ qhb_restore -l db.dump > db.list

Файл-список содержит заголовок и по одной строке для каждого элемента, например:

;
; Archive created at Mon Sep 14 13:55:39 2019
;     dbname: DBDEMOS
;     TOC Entries: 81
;     Compression: 9
;     Dump Version: 1.10-0
;     Format: CUSTOM
;     Integer: 4 bytes
;     Offset: 8 bytes
;     Dumped from database version: 1.5.1
;     Dumped by qhb_dump version: 1.5.2
;
;
; Selected TOC Entries:
;
3; 2615 2200 SCHEMA - public pasha
1861; 0 0 COMMENT - SCHEMA public pasha
1862; 0 0 ACL - public pasha
317; 1247 17715 TYPE public composite pasha
319; 1247 25899 DOMAIN public domain0 pasha

Точки с запятой обозначают начало комментария, а цифры в начале строк ссылаются на внутренний идентификатор архива, назначенный каждому элементу.

Строки в этом файле могут быть закомментированы, удалены и переупорядочены. Например:

10; 145433 TABLE map_resolutions qhb
;2; 145344 TABLE species qhb
;4; 145359 TABLE nt_header qhb
6; 145402 TABLE species_records qhb
;8; 145416 TABLE ss_old qhb

может использоваться в качестве входных данных для qhb_restore и восстановит только элементы 10 и 6 в следующем порядке:

 qhb_restore -L db.list db.dump

См. также

qhb_dump, qhb_dumpall, psql

qhb_verifybackup

qhb_verifybackup — проверить целостность базовой резервной копии кластера QHB

Синтаксис

qhb_verifybackup [параметр...]

Описание

Утилита qhb_verifybackup используется для проверки целостности резервной копии кластера баз данных, сделанной утилитой qhb_basebackup по манифесту backup_manifest, сгенерированному сервером во время резервного копирования. Эта копия должна храниться в формате «plain»; копию в формате «tar» можно проверить, разархивировав ее.

Важно отметить, что валидация, выполняемая qhb_verifybackup, не включает и не может включать в себя все проверки, которые будет выполнять работающий сервер при попытке воспользоваться резервной копией. Даже если вы пользуетесь этим инструментом, вам все равно следует выполнить тестовое восстановление данных и убедиться, что полученные в результате базы данных работают как положено и содержат корректные данные. Тем не менее qhb_verifybackup может выявить многие распространенные проблемы, возникающие вследствие неисправности хранилища или ошибок пользователя.

Проверка резервной копии производится в четыре этапа. На первом этапе qhb_verifybackup читает файл backup_manifest. Если этот файл не существует, не может быть прочитан, неправильно оформлен или не соответствует своей внутренней контрольной сумме, qhb_verifybackup завершит работу с критической ошибкой.

На втором этапе qhb_verifybackup попытается проверить, что файлы данных, в настоящий момент хранящиеся на диске, в точности совпадают с файлами данных, которые собирается передать сервер, за некоторыми исключениями, описанными ниже. При этом будут выявлены все лишние и отсутствующие файлы, за некоторыми исключениями. На этом этапе будет игнорироваться наличие, отсутствие или какое-либо изменение файлов qhb.auto.conf, standby.signal и recovery.signal, поскольку ожидается, что эти файлы могут создаваться или меняться в процессе создания резервной копии. Также не будет учитываться файл backup_manifest в целевом каталоге и все содержимое каталога pg_wal, несмотря на то что эти файлы не будут перечислены в манифесте резервной копии. Проверяются только файлы; наличие или отсутствие каталогов контролируется только косвенно: если каталог отсутствует, все файлы, которые он должен содержать, тоже безусловно будут отсутствовать.

Далее qhb_verifybackup проверит контрольные суммы всех файлов, сравнивая их со значениями в манифесте, и выдаст ошибки для тех файлов, у которых вычисленная контрольная сумма не соответствует хранящейся в манифесте. Этот этап не выполняется для файлов, вызвавших ошибки на предыдущем этапе, поскольку уже известно, что они проблемные. Файлы, игнорировавшиеся на предыдущем этапе, игнорируются и на этом.

В конце qhb_verifybackup воспользуется манифестом для проверки наличия в журнале упреждающей записи всех записей, необходимых для восстановления из резервной копии, а также того, можно ли их прочитать и проанализировать. В файле backup_manifest содержится информация о том, какие записи WAL понадобятся, и qhb_verifybackup воспользуется этой информацией для утилиты qhb_waldump, чтобы синтаксически проанализировать эти записи. При этом будет передан флаг --quiet, так что qhb_waldump будет сообщать только об ошибках, не выводя каких-либо других сообщений. Хотя этот уровень проверки достаточен для выявления очевидных проблем, таких как отсутствие файла или несоответствие внутренних контрольных сумм, он недостаточно масштабен для выявления всех возможных проблем, которые могут возникнуть при попытке восстановления базы данных. К примеру, этим методом нельзя обнаружить ошибку сервера, приводящую к появлению записей WAL с правильными контрольными суммами, но при этом задающих бессмысленные действия.

Обратите внимание, что при наличии дополнительных файлов WAL, не требующихся для восстановления из резервной копии, они не будут проверяться этим инструментом, хотя для этой цели можно отдельно вызвать qhb_waldump. Также обратите внимание, что проверка WAL зависит от версии: следует использовать версию qhb_verifybackup и, как следствие, qhb_waldump, соответствующую проверяемой копии. И наоборот, проверки целостности файлов данных должны работать с любой версией сервера, сгенерировавшего файл backup_manifest.

Параметры

Утилита qhb_verifybackup принимает следующие аргументы командной строки:

-e
--exit-on-error
Завершиться сразу после обнаружения проблемы с резервной копией. Если этот параметр не задан, qhb_verifybackup продолжит проверять копию даже после обнаружения первой проблемы и будет сообщать обо всех проблемах как об ошибках.

-i путь
--ignore=путь
Игнорировать указанный файл или каталог, который должен быть задан относительным путем, при сравнении списка файлов данных, фактически присутствующих в резервной копии, со списком в файле backup_manifest. Если указывается каталог, этот параметр действует на все дерево подкаталогов с корнем в заданном расположении. Если относительный путь файла совпадает с указанным, предупреждения о лишних или отсутствующих файлах, разницах в размерах файлов или несоответствиях контрольных сумм выдаваться не будут. Этот параметр можно задать несколько раз.

-m путь
--manifest-path=путь
Использовать файл манифеста по указанному пути, а не тот, который расположен в корневом каталоге резервной копии.

-n
--no-parse-wal
Не пытаться проанализировать данные журнала упреждающей записи, которые понадобятся для восстановления из этой резервной копии.

-q
--quiet
Не выводить ничего, когда резервная копия успешно проходит проверку.

-s
--skip-checksums
Не проверять контрольные суммы файлов данных. При этом все равно будет проверяться наличие или отсутствие этих файлов и их размеры. С этим параметром проверка выполняется гораздо быстрее, так как нет необходимости читать сами файлы.

-w путь
--wal-directory=путь
Попытаться проанализировать файлы WAL, хранящиеся в указанном каталоге, а не в pg_wal. Это может быть полезно, если резервная копия сохраняется отдельно от архива WAL.

Также доступны и другие параметры:

-V
--version
Вывести версию qhb_verifybackup и завершиться.

-?
--help
Показать справку об аргументах командной строки qhb_verifybackup и завершиться.

Примеры

Создать базовую резервную копию сервера mydbserver и проверить целостность этой копии:

$ qhb_basebackup -h mydbserver -D /var/lib/qhb/data/data
$ qhb_verifybackup /var/lib/qhb/data/data

Создать базовую резервную копию сервера mydbserver, переместить файл манифеста куда-нибудь во внешний каталог и проверить эту копию:

$ qhb_basebackup -h mydbserver -D /var/lib/qhb/data/backup1234
$ mv /var/lib/qhb/data/backup1234/backup_manifest /my/secure/location/backup_manifest.1234
$ qhb_verifybackup -m /my/secure/location/backup_manifest.1234 /var/lib/qhb/data/backup1234

Проверить резервную копию, игнорируя файл, который был вручную добавлен в ее каталог, а также пропустив проверку контрольных сумм:

$ qhb_basebackup -h mydbserver -D /var/lib/qhb/data/data
$ edit /var/lib/qhb/data/data/note.to.self
$ qhb_verifybackup --ignore=note.to.self --skip-checksums /var/lib/qhb/data/data

См. также

qhb_basebackup

psql

psql — интерактивный терминал QHB

Синтаксис

psql [параметр...] [имя_бд [имя_пользователя]]

Описание

psql — это терминальный клиент для работы с QHB. Он позволяет интерактивно вводить запросы, выполнять их в QHB и просматривать их результаты. Как вариант, запросы можно получать из файла или из аргументов командной строки. Кроме того, psql предоставляет ряд метакоманд и различную функциональность, схожую с имеющимися у командных оболочек, для облегчения написания скриптов и автоматизации широкого спектра задач.

Параметры

-a
--echo-all
Отправить все непустые входные строки в стандартный вывод по мере их чтения. (Это не относится к строкам, считанным интерактивно.) Это равнозначно установке переменной ECHO в значение all.

-A
--no-align
Переключает на режим невыровненного вывода. (По умолчанию используется режим aligned (выровненный).) Это равнозначно команде \pset format unaligned.

-b
--echo-errors
Отправлять команды SQL с ошибками в стандартный вывод для ошибок. Это равнозначно установке переменной ECHO в значение errors.

-c команда
--command=команда
Указывает, что psql должна выполнить заданную командную строку (команду). Этот параметр можно вводить несколько раз и комбинировать в любом порядке с параметром -f. Когда указывается -c или -f, psql не читает команды со стандартного ввода; вместо этого она завершается после обработки всех параметров -c и -f по порядку.
Заданная команда должна быть либо командной строкой, которая полностью анализируется сервером (т. е. не содержит специфическую функциональность psql), либо одиночной командой с обратным слэшем. Таким образом, в параметре -c нельзя смешивать метакоманды SQL и psql. Чтобы добиться этого, можно передать несколько ключей -c или передать строку в psql через канал, например:

psql -c '\x' -c 'SELECT * FROM foo;'

или

echo '\x \\ SELECT * FROM foo;' | psql

(\\ — разделитель метакоманд.)
Каждая строка команд SQL, переданная в -c, отправляется на сервер как один запрос. Поэтому сервер выполняет ее в одной транзакции, даже когда эта строка содержит несколько команд SQL, если только в ней не содержатся явные команды BEGIN/COMMIT, разделяющие ее на несколько транзакций. (Подробнее о том, как сервер обрабатывает строки с несколькими командами, рассказывается в подразделе Несколько операторов в простом запросе.) Кроме того, psql выводит результат только последней команды SQL в строке. Это отличается от поведения, когда та же строка считывается из файла или подается на стандартный ввод, потому что в последнем случае psql передает каждую команду SQL по отдельности.
Из-за такого поведения указание нескольких команд SQL в одной строке -c зачастую приводит к неожиданным результатам. Лучше использовать параметр -c несколько раз или подавать команды на стандартный ввод psql, применяя либо параметр echo, как показано выше, либо создаваемый непосредственно в оболочке документ, например:

psql <<EOF
\x
SELECT * FROM foo;
EOF

--csv
Переключает в режим вывода CSV (Comma-Separated Values, значения, разделенные запятыми). Это равнозначно команде \pset format csv.

-d имя_бд
--database=имя_бд
Задает имя базы данных для подключения. Это равнозначно указанию имени_бд в качестве первого аргумента, не являющегося ключом, в командной строке. В имени_бд может задаваться строка подключения. В этом случае параметры в строке подключения переопределяют любые конфликтующие параметры в командной строке.

-e
--echo-queries
Копировать все команды SQL, отправляемые на сервер, еще и на стандартный вывод. Это равнозначно установке переменой ECHO в значение queries.

-E
--echo-hidden
Отображать фактические запросы, генерируемые \d и другими командами, начинающимися с обратного слэша. Это можно использовать для изучения внутренних операций psql. Это равнозначно установке переменой ECHO_HIDDEN в значение on (включен).

-f имя_файла
--file=имя_файла
Читать команды из файла имя_файла, а не из стандартного ввода. Этот параметр можно вводить несколько раз и комбинировать в любом порядке с параметром -c. Когда указывается -c или -f, psql не читает команды со стандартного ввода; вместо этого она завершается после обработки всех параметров -c и -f по порядку. Не считая этого, данный параметр практически равнозначен метакоманде \i.
Если имя_файла задано символом - (минус), то стандартный ввод считывается до признака конца файла или до метакоманды \q. Это позволяет перемежать интерактивный ввод с вводом из файлов. Однако обратите внимание, что Readline в таком случае не применяется (почти так же, как с указанием -n).
Использование этого параметра немного отличается от написания psql < имя_файла. В целом, оба варианта будут делать то, что вы ожидаете, но с -f доступен некоторая полезная функциональность, например, сообщения об ошибках с номерами строк. Также есть небольшая вероятность, что использование этого параметра снизит издержки при запуске. С другой стороны, вариант с перенаправлением ввода из оболочки (в теории) гарантирует получение точно такого же вывода, какой вы получили бы, введя все вручную.

-F разделитель
--field-separator=разделитель
Использовать разделитель в качестве разделителя полей для невыровненного вывода. Это равнозначно команде \pset fieldsep или \f.

-h имя_хоста
--host=имя_хоста
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-H
--html
Переключает в режим вывода HTML. Это равнозначно команде \pset format html или \H.

-l
--list
Вывести список всех доступных баз данных и завершиться. Другие параметры, не связанные с подключением, игнорируются. Это похоже на метакоманду \list.
При использовании этого параметра psql будет подключаться к базе данных qhb, если только в командной строке не задана другая база (в параметре -d или не через параметры, а, допустим, через запись службы, но не через переменную среды).

-L имя_файла
--log-file=имя_файла
В дополнение к обычному расположению вывода, записывать вывод результатов всех запросов в файл имя_файла.

-n
--no-readline
Не использовать Readline для редактирования командной строки и не использовать историю команд. Это может быть полезно для выключения расширенных действий клавиш табуляции при вырезании и вставке.

-o имя_файла
--output=имя_файла
Записать вывод результатов всех запросов в файл имя_файла. Это равнозначно команде \o.

-p порт
--port=порт
Указывает порт TCP или расширение файла локального сокета домена Unix, на котором сервер принимает подключения. Значение по умолчанию определяется переменной среды PGHOST, или, если она не установлена, портом, заданным при компиляции, обычно 5432.

-P присвоение
--pset=присвоение
Задает параметры печати, в стиле команды \pset. Обратите внимание, что здесь придется разделить имя присваиваемого параметра и значение знаком равенства, а не пробела. Например, чтобы установить формат вывода в LaTeX, нужно написать -P format=latex.

-q
--quiet
Указывает, что psql должна работать «тихо». По умолчанию она выводит приветствия и различные информационные сообщения. При использовании данного параметра этого не произойдет. Имеет смысл применять его вместе с параметром c. Это равнозначно установке переменной QUIET в значение on (включена).

-R разделитель
--record-separator=разделитель
Использовать разделитель в качестве разделителя записей для невыровненного вывода. Это равнозначно команде \pset recordsep.

-s
--single-step
Работать в пошаговом режиме. Это означает, что пользователь будет подтверждать выполнение каждой команды перед ее отправкой на сервер, с возможностью отменить выполнение. Используется для отладки скриптов.

-S
--single-line
Запуск в построчном режиме, при котором символ перевода строки завершает команду SQL, так же как это делает точка с запятой.

Примечание
Этот режим предусмотрен для тех, кто настаивал на его необходимости, но это не значит, что его обязательно использовать всем. В частности, если смешивать в одной строке команды SQL и метакоманды, порядок их выполнения может быть не всегда понятен для неопытного пользователя.

-t
--tuples-only
Выключить вывод имен столбцов и результирующей строки с количеством выбранных записей и т. д. Это равнозначно команде \t или \pset tuples_only.

-T параметры_таблицы
--table-attr=параметры_таблицы
Задает параметры, которые будут вставлены в тег HTML table. Подробную информацию см. в описании команды \pset tableattr.

-U имя_пользователя
--username=имя_пользователя
Использовать для подключения имя_пользователя вместо установленного по умолчанию. (Разумеется, это потребует соответствующего разрешения.)

-v присвоение
--set=присвоение
--variable=присвоение
Произвести присвоение значения переменной, как делает метакоманда \set. Обратите внимание, что в командной строке нужно разделить имя переменной и значение (если таковое имеется) знаком равенства. Чтобы сбросить переменную, опустите знак равенства. Чтобы установить пустое значение, добавьте знак равенства, но опустите значение. Эти присвоения выполняются во время обработки командной строки, поэтому переменные, отражающие состояние подключения, потом будут переписаны.

--version
Вывести версию psql и завершиться.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.
Обратите внимание, что этот параметр действует в течение всего сеанса и, следовательно, влияет на использование метакоманды \connect, а также на первую попытку подключения.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных, даже если он не будет использоваться.
Если сервер требует аутентификацию по паролю и пароль нельзя получить из других источников, например из файла .pgpass, psql запросит пароль в любом случае. Однако чтобы понять, что требуется пароль, psql потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.
Обратите внимание, что этот параметр действует в течение всего сеанса и, следовательно, влияет на использование метакоманды \connect, а также на первую попытку подключения.

-x
--expanded
Включить режим развернутого форматирования таблицы. Это равнозначно команде \x или \pset.

-X
--no-psqlrc
Не читать стартовые файлы (ни общесистемный файл psqlrc, ни пользовательский файл ~/.psqlrc).

-z
--field-separator-zero
Установить нулевой байт в качестве разделителя полей для невыровненного вывода. Это равнозначно команде \pset fieldsep_zero.

-0
--record-separator-zero
Установить нулевой байт в качестве разделителя записей для невыровненного вывода. Это полезно при взаимодействии, например, с xargs -0. Равнозначно команде \pset recordsep_zero.

-1
--single-transaction
Этот параметр можно использовать только в сочетании с одним или несколькими параметрами -c и/или -f. С ним psql выполняет команду BEGIN перед обработкой первого такого параметра и команду COMMIT после последнего, тем самым заворачивая все команды в одну транзакцию. Это гарантирует, что либо все команды завершатся успешно, либо никакие изменения не сохранятся.
Если в самих этих командах содержатся операторы BEGIN, COMMIT или ROLLBACK, этот параметр не даст желаемого эффекта. Кроме того, если какая-либо отдельная команда не может выполняться внутри блока транзакции, указание этого параметра приведет к тому, что вся транзакция прервется с ошибкой.

-?
--help[=тема]
Показать справку по psql и завершиться. Необязательный параметр тема (по умолчанию options) выбирает описание интересующей части psql: commands описывает команды psql с обратным слэшем; options описывает параметры командной строки, которые можно передать psql; а variables выдает справку по переменным конфигурации psql.

Код завершения

psql возвращает в командную оболочку 0 при нормальном завершении, 1 — если произошла фатальная ошибка в самой psql (например недостаточно памяти, файл не найден), 2 — при неудачном подключении к серверу и неинтерактивном сеансе и 3 — при ошибке в скрипте и установленной переменной ON_ERROR_STOP.

Использование

Подключение к базе данных

psql — это обычное клиентское приложение QHB. Для подключения к базе данных необходимо знать имя целевой базы, имя хоста и номер порта сервера, а также имя пользователя, под которым вы хотите подключиться. Для psql эти параметры можно задать через аргументы командной строки, а именно -d, -h, -p и -U соответственно. При обнаружении в командной строке аргумента, не относящегося ни к одному параметру, оно будет воспринято как имя базы данных (или имя пользователя, если имя базы данных уже задано). Задавать все эти аргументы необязательно; у них есть подходящие значения по умолчанию. Если опустить имя хоста, psql подключится через сокет домена Unix к серверу на локальном хосте или через TCP/IP к localhost, если в системе нет сокетов домена Unix. Номер порта по умолчанию определяется во время компиляции. Поскольку сервер базы данных использует то же значение по умолчанию, в большинстве случаев указывать порт нет необходимости. Имя пользователя по умолчанию, как и имя базы данных по умолчанию — это ваше имя пользователя в операционной системе. Обратите внимание, что просто так подключиться к любой базе данных под любым именем пользователя не получится. О правах доступа можно узнать у администратора баз данных.

Если значения по умолчанию не вполне подходят, вместо ввода нужных параметров вручную можно установить переменные среды PGDATABASE, PGHOST, PGPORT и/или PGUSER в подходящие значения. (Другие переменные среды описаны в разделе Переменные среды.) Также удобно иметь файл ~/.pgpass, чтобы каждый раз не вводить пароли. Дополнительную информацию см. в разделе Файл паролей.)

Альтернативный способ указания параметров подключения — использование вместо имени базы данных строки conninfo или URI. Этот механизм дает широкие возможности для управления подключением. Например:

$ psql "service=myservice sslmode=require"
$ psql postgresql://dbmaster:5433/mydb?sslmode=require

Этот способ также позволяет использовать для получения параметров подключения LDAP, как описано в разделе Поиск параметров подключения через LDAP. Более подробную информацию обо всех доступных параметрах подключения см. в подразделе Ключевые слова-параметры.

Если по какой-либо причине соединение не может быть установлено (например, недостаточно прав, сервер на целевом хосте не работает и т. д.), psql вернет ошибку и прекратит работу.

Если и стандартный ввод, и стандартный вывод являются терминалом, то psql установит кодировку клиента в «auto», и подходящая клиентская кодировка будет определяться из настроек локалей (переменная среды LC_CTYPE в системах Unix). Если это работает не так, как ожидалось, клиентскую кодировку можно изменить, установив переменную среды PGCLIENTENCODING.

Ввод команд SQL

При работе в штатном режиме в командной строке psql отображается имя базы данных, к которой psql подключена в данный момент, а затем идет строка =>. Например:

$ psql testdb
psql (1.0)
Type "help" for help.

testdb=>

В командной строке пользователь может вводить команды SQL. Обычно введенные строки отправляются на сервер по достижении точки с запятой, завершающей команду. Конец строки не завершает команду. Таким образом, команды можно для ясности разбить на несколько строк. Если команда была отправлена и выполнена без ошибок, ее результаты отображаются на экране.

Если к базе данных, в которой не был внедрен шаблон безопасного использования схем, имеют доступ недоверенные пользователи, начинайте сеанс с удаления общедоступных схем из search_path (пути поиска). Для этого перед выполнением других команд SQL можно выполнить команду SELECT pg_catalog.set_config('search_path', '', false). Это касается не только psql, но и всех остальных интерфейсов для выполнения произвольных команд SQL.

При каждом выполнении команды psql также проверяет асинхронные уведомления о событиях, генерируемые командами LISTEN и NOTIFY.

Тогда как блоки комментариев в стиле C/RUST передаются на сервер для обработки и удаления, комментарии в стандарте SQL удаляются psql.

Метакоманды

Все, что вводится в psql без кавычек, и начинается с обратного слэша, является метакомандой psql, которая обрабатывается самой утилитой. Эти команды делают psql более полезной для администрирования или написания скриптов. Метакоманды часто называют командами слэша или обратного слэша.

Команды psql имеют следующий формат: обратный слэш, сразу за ним команда, затем аргументы. Аргументы отделяются от команды и друг от друга любым числом пробелов.

Чтобы включить в аргумент пробел, нужно заключить его в апострофы. Чтобы включить в аргумент апостроф, нужно написать два апострофа внутри текста в апострофах. Все, что содержится в апострофах, в дальнейшем подлежит заменам, принятым в языке C: \n (новая строка), \t (табуляция), \b (backspace), \r (возврат каретки), \f (подача страницы), \цифры (восьмеричное число) и \xцифры (шестнадцатеричное число). Внутри апострофов обратный слэш, стоящий перед любым другим символом, экранирует этот символ.

Если внутри аргумента встречается двоеточие (:) без кавычек с последующим именем переменной psql, оно заменяется значением этой переменной, как описано ниже в параграфе Интерполяция SQL. Также будут работать описанные там формы :'имя_переменной' и :"имя_переменной". Синтаксис :{?имя_переменной} позволяет проверить, определена ли переменная. Он заменяется значением TRUE или FALSE. Экранирование двоеточия обратным слэшем защищает его от замены.

Внутри аргумента текст, взятый в обратные кавычки (`), считается командной строкой, которая передается в командную оболочку. Вывод этой команды (с удаленными в конце символами перевода строки) заменяет текст в обратных кавычках. Внутри этого текста не обрабатываются никакие кавычки или другие управляющие символы или последовательности, за исключением того, что все вхождения :имени_переменной, где имя_переменной — это имя переменной psql, заменяются значением этой переменной. Также вхождения :'имени_переменной' заменяются значением переменной, заключенным в подходящие кавычки, и становятся одним аргументом команды оболочки. (Последняя форма почти всегда предпочтительна, если только вы не абсолютно точно знаете, что содержится в переменной.) Поскольку символы возврата каретки и перевода строки невозможно надежно защитить на всех платформах, форма :'имя_переменной' выводит сообщение об ошибке и не производит подстановку значения переменной, когда это значение содержит такие символы.

Некоторые команды принимают в качестве аргумента идентификатор SQL (например имя таблицы). Такие аргументы следуют правилам синтаксиса SQL: буквы, не заключенные в кавычки, преобразуются в нижний регистр, тогда как кавычки (") защищают буквы от преобразования регистра и позволяют включать пробелы в идентификатор. Внутри кавычек в итоговом имени две кавычки сокращаются до одной. Например, FOO"BAR"BAZ интерпретируется как fooBARbaz, а "A weird"" name" становится A weird" name.

Разбор аргументов останавливается в конце строки или когда встречается другой обратный слэш, не взятый в кавычки. Обратный слэш не внутри кавычек принимается за начало новой метакоманды. Специальная последовательность \\ (два обратных слэша) обозначает окончание аргументов и продолжение разбора команд SQL, если таковые имеются. Таким образом, команды SQL и psql можно свободно смешивать в одной строке. Но в любом случае аргументы метакоманд не могут выходить за пределы текущей строки.

Многие из метакоманд оперируют с буфером текущего запроса. Это обычный буфер, содержащий произвольный текст команды SQL, который был введен, но еще не передан серверу для выполнения. В него будут входить и предыдущие строки ввода, а также текст, находящийся в той же строке перед метакомандой.

Определены следующие метакоманды:

\a

Если текущий формат вывода таблицы невыровненный, то он переключается на выровненный. Если текущий формат выровненный, то устанавливается невыровненный. Эта команда поддерживается для обратной совместимости. См. \pset для более общего решения.

\c или \connect [ -reuse-previous=on|off ] [ имя_бд [ имя_пользователя ] [ хост ] [ порт ] | строка_подключения ]

Устанавливает новое подключение к серверу QHB. Параметры подключения можно указывать как позиционно (один или несколько по списку: база данных, пользователь, хост и порт), так и передавая аргумент строка_подключения (подробнее о строках подключения рассказывается в подразделе Строки подключения). Если аргументы отсутствуют, новое подключение устанавливается с теми же параметрами, что и предыдущее.

Указание в параметре имя_бд, имя_пользователя, хост или порт значения - равнозначно опущению этого параметра.

Новое подключение может повторно использовать параметры предыдущего; не только имя базы данных, пользователя, хост и порт, но и, к примеру, режим_ssl. По умолчанию параметры используются повторно при позиционной записи, но не когда указывается строка_подключения. Если передать в первом аргументе -reuse- previous=on или -reuse-previous=off, это поведение переопределяется. При повторном использовании любой параметр, не заданный явно в виде позиционного или в строке_подключения, берется из параметров текущего подключения. Исключение составляет параметр адрес_хоста — если в новом подключении значение хост меняется в позиционной записи, значение адрес_хоста из текущих параметров подключения сбрасывается. Кроме того, пароль существующего подключения будет использован повторно, только если имя пользователя, хост и порт не меняются. Если какой-либо параметр не указан в этой команде и не используется повторно, действует принятое в libpq значение по умолчанию.

Если новое подключение успешно установлено, предыдущее подключение закрывается. Если попытка подключения не удалась (неверное имя пользователя, доступ запрещен и т. д.), то предыдущее соединение останется активным, если psql находится в интерактивном режиме. Но если скрипт выполняется неинтерактивно, предыдущее соединение закрывается с сообщением об ошибке. При этом скрипт может завершиться или не завершиться; в последнем случае все команды, обращающиеся к базе данных, будут неуспешными, пока не будет успешно выполнена еще одна команда \connect. Различное поведение было выбрано для удобства пользователя: с одной стороны в качестве защиты от опечаток, а с другой — как защитный механизм, не позволяющий случайно запустить скрипты в неправильной базе данных. Обратите внимание, что когда команда \connect пытается повторно использовать параметры, она использует параметры последнего удачного подключения, а не какой-либо из последующих неудачных попыток. Однако в случае неудачи при неинтерактивном выполнении команды \connect никакие параметры не позволено использовать впоследствии, так как скрипт, скорее всего, рассчитывает на то, что должны повторно использоваться значения именно от неуспешной команды \connect.

Примеры:

=> \c mydb myuser host.dom 6432
=> \c service=foo
=> \c "host=localhost port=5432 dbname=mydb connect_timeout=10 sslmode=disable"
=> \c -reuse-previous=on sslmode=require    -- меняется только sslmode
=> \c qhb://tom@localhost/mydb?application_name=myapp

\C [ заголовок ]

Задает заголовок любых таблиц, который будет выводиться в результате запроса, или отменяет установленный ранее заголовок. Эта команда равнозначна \pset title заголовок. (Название этой команды происходит от слова «caption» (заголовок), поскольку раньше она применялась только для задания заголовков HTML-таблиц.)

\cd [ каталог ]

Меняет текущий рабочий каталог на каталог. Без аргумента устанавливается домашний каталог текущего пользователя.

Совет
Для отображения текущего рабочего каталога используйте команду \! pwd.

\conninfo

Выводит информацию о текущем подключении к базе данных.

\copy { таблица [ ( список_столбцов ) ] } from { 'имя_файла' | program 'команда' | stdin | pstdin } [ [ with ] ( параметр [, ...] ) ] [ where условие ]
\copy { таблица [ ( список_столбцов ) ] | ( запрос ) } to { 'имя_файла' | program 'команда' | stdout | pstdout } [ [ with ] ( параметр [, ...] ) ]

Производит копирование данных с участием терминала (клиента). Данная операция запускает выполнение команды SQL COPY, но вместо чтения или записи указанного файла на сервере, его читает или записывает psql и пересылает данные между сервером и терминалом. Это означает, что здесь используются доступ к файлу и права локального пользователя, а не сервера, и не требуются права суперпользователя SQL.
Если указан параметр program, psql выполняет команду, и данные, поступающие из/в нее, передаются между сервером и клиентом. Опять же, для выполнения программ используются права локального пользователя, а не сервера, и не требуются права суперпользователя SQL.
При выполнении \copy ... from stdin строки данных считываются из источника, выполнившего команду, и считываются до тех пор, пока не встретится \. или не будет достигнут конец файла. Этот параметр полезен для заполнения таблиц непосредственно в SQL-скриптах. При выполнении \copy ... to stdout вывод направляется в то же место, что и вывод команд psql, а статус команды COPY не отображается (так как его можно перепутать со строкой данных). Для чтения/ записи стандартного ввода или вывода psql, вне зависимости от источника текущей команды или параметра \o, напишите from pstdin или to pstdout.
Синтаксис этой команды похож на синтаксис команды SQL COPY. Все параметры, кроме источника/получателя данных, задаются так же, как в COPY. Из-за этого при разборе метакоманды \copy применяются другие правила. В отличие от большинства других метакоманд, для нее остаток строки всегда воспринимается как аргументы \copy, и в этих аргументах не выполняется ни интерполяция переменных, ни раскрытие обратных кавычек.

Совет
Другой способ получить тот же результат, что и с \copy ... to, — использовать команду SQL COPY ... TO STDOUT и завершить ее командой \g имя_файла или \g |программа. В отличие от \copy, этот метод позволяет разбивать команду на несколько строк, а также использовать интерполяцию переменных и раскрытие обратных кавычек.

Совет
Эти операции не так эффективны, как команда SQL COPY, в которой источником или получателем данных является файл, поскольку все данных должны перемещаться по клиент-серверному соединению. Для больших объемов данных команда SQL может оказаться предпочтительнее.

\copyright

Показывает информацию об авторских правах и условиях распространения QHB.

\crosstabview [ столбВ [ столбГ [ столбД [ столбсортГ ] ] ] ]

Выполняет содержимое буфера текущего запроса (как \g) и показывает результаты в виде перекрестной таблицы. Этот запрос должен возвращать как минимум три столбца. Столбец вывода, заданный параметром столбВ, становится вертикальным заголовком, а столбец вывода, заданный параметром столбГ — горизонтальным заголовком. Параметр столбД задает столбец с данными для отображения внутри таблицы. столбсортГ задает необязательный столбец сортировки для горизонтального заголовка.
Каждое указание столбца может быть номером (начиная с 1) или именем столбца. К именам применяются обычные принятые в SQL правила учета регистра и кавычек. Если аргументы опущены, то в качестве столбВ подразумевается столбец 1, а в качестве столбГ — столбец 2. столбГ должен отличаться от столбВ. Если столбД не задан, в результате запроса должно быть ровно три столбца, и в качестве столбД выбирается столбец, отличный от столбВ и столбГ.
Вертикальный заголовок, отображаемый в самом левом столбце, содержит значения из столбца столбВ в том же порядке, что и в результате запроса, но без дубликатов.
Горизонтальный заголовок, отображаемых в первой строке, содержит значения из столбца столбГ без дубликатов. По умолчанию они располагаются в том же порядке, что и в результате запроса. Но если задан необязательный аргумент столбсортГ, он определяет столбец, который должен содержать целые числа, и тогда значения из столбГ будут располагаться в горизонтальном заголовке в соответствии с порядком значений в столбсортГ.
Внутри перекрестной таблицы для каждого уникального значения x в столбГ и каждого уникального значения y в столбВ ячейка, находящаяся на пересечении (x,y), содержит значение столбД в строке результата запроса, в которой значение столбГ равно х, а значение столбВ — у. Если такой строки нет, ячейка остается пустой. Если таких строк несколько, выдается ошибка.

\d[S+] [ шаблон ]

Для каждого отношения (таблицы, представления, материализованного представления, индекса, последовательности или сторонней таблицы) или составного типа, соответствующих шаблону, показать все столбцы, их типы, табличное пространство (если это не значение по умолчанию) и все специальные атрибуты, например, NOT NULL или значения по умолчанию. Также показываются связанные индексы, ограничения, правила и триггеры. Для сторонних таблиц также показывается связанный сторонний сервер. («Соответствие шаблону» определяется ниже в подразделе Шаблоны.)
Для некоторых типов отношений \d показывает дополнительную информацию по каждому столбцу: значения столбца для последовательностей, индексируемые выражения для индексов и параметры обертки сторонних данных для сторонних таблиц.
Форма команды \d+ идентична \d, но отображает больше информации: показываются все комментарии к столбцам таблицы, а также наличие в таблице OID, определение представления, если отношение является представлением, значение параметра replica identity, если оно нестандартное, и имя метода доступа, если для отношения определен метод доступа.
По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S.

Примечание
Если \d используется без аргумента шаблон, эта команда равнозначна \dtvmsE, то есть показывает список всех видимых таблиц, представлений, материализованных представлений, последовательностей и сторонних таблиц. Это сделано исключительно ради удобства.

\da[S] [ шаблон ]

Выводит список агрегатных функций вместе с типом возвращаемого значения и типами данных, которыми они оперируют. Если задан шаблон, показываются только те агрегатные функции, имена которых соответствуют этому шаблону. По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S.

\dA[+] [ шаблон ]

Выводит список методов доступа. Если задан шаблон, показываются только те методы доступа, имена которых соответствуют этому шаблону. При добавлении к имени команды + каждый метод доступа выводится вместе со своей функцией обработки и описанием.

\dAc[+] [шаблон-методов-доступа [ шаблон-входных-типов ] ]

Выводит список классов операторов (см. подраздел Индексные методы и классы операторов). Если задан шаблон-методов-доступа, выводятся только классы операторов, связанные с методами доступа, имена которых соответствуют этому шаблону. Если задан шаблон-входных-типов, выводятся только классы операторов, связанные с входными типами, имена которых соответствуют этому шаблону. При добавлении к имени команды + каждый класс операторов выводится вместе со своими семейством операторов и владельцем.

\dAf[+] [шаблон-методов-доступа [ шаблон-входных-типов ] ]

Выводит список семейств операторов (см. подраздел Классы операторов и семейства операторов). Если задан шаблон-методов-доступа, выводятся только семейства операторов, связанные с методами доступа, имена которых соответствуют этому шаблону. Если задан шаблон-входных-типов, выводятся только семейства операторов, связанные с входными типами, имена которых соответствуют этому шаблону. При добавлении к имени команды + каждое семейство операторов выводится вместе со своим владельцем.

\dAo[+] [шаблон-методов-доступа [шаблон-семейств-операторов ] ]

Выводит список операторов, связанных с семействами операторов (см. подраздел Стратегии индексного метода). Если задан шаблон-методов-доступа, выводятся только члены семейств операторов, связанных с методами доступа, имена которых соответствуют этому шаблону. Если задан шаблон-семейств-операторов, выводятся только члены семейств операторов, имена которых соответствуют этому шаблону. При добавлении к имени команды + каждый оператор выводится вместе со своим семейством операторов сортировки (если это оператор упорядочивания).

\dAp[+] [шаблон-методов-доступа [шаблон-семейств-операторов]]

Выводит список вспомогательных функций, связанных с семействами операторов (см. подраздел Вспомогательные процедуры индексного метода). Если задан шаблон-методов-доступа, выводятся только функции семейств операторов, связанных с методами доступа, имена которых соответствуют этому шаблону. Если задан шаблон-семейств-операторов, выводятся только функции семейств операторов, имена которых соответствуют этому шаблону. При добавлении к имени команды + функции отображаются в развернутом виде, со списками их фактических параметров.

\db[+] [ шаблон ]

Выводит список табличных пространств. Если задан шаблон, показываются только те табличные пространства, имена которых соответствуют этому шаблону. При добавлении к имени команды + каждое табличное пространство выводится вместе со своими параметрами, объемом на диске, правами и описанием.

\dc[S+] [ шаблон ]

Выводит список преобразований между кодировками наборов символов. Если задан шаблон, выводятся только те преобразования кодировок, имена которых соответствуют этому шаблону. По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S. При добавлении к имени команды + каждый объект выводится вместе со своим описанием.

\dC[+] [ шаблон ]

Выводит список приведений типов. Если задан шаблон, выводятся только те приведения типов, у которых исходные или целевые типы соответствуют этому шаблону. При добавлении к имени команды + каждый объект выводится вместе со своим описанием.

\dd[S] [ шаблон ]

Показывает описания объектов следующих видов: constraint (ограничение), operator class (класс операторов), operator family (семейство операторов), rule (правило) и trigger (триггер). Все остальные комментарии можно посмотреть посредством соответствующих метакоманд для этих типов объектов.
\dd отображает описания для объектов, соответствующих шаблону, или для доступных объектов указанных типов, если аргументы не заданы. Но в любом случае выводятся только те объекты, у которых есть описание. По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S.
Описания для объектов можно создать при помощи команды SQL COMMENT.

\dD[S+] [ шаблон ]

Выводит список доменов. Если задан шаблон, показываются только те домены, имена которых соответствуют этому шаблону. По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S. При добавлении к имени команды + каждый объект выводится вместе со своими правами и описанием.

\ddp [ шаблон ]

Выводит список прав доступа по умолчанию. Показывается запись для каждой роли (и схемы, если применимо), для которой права доступа по умолчанию отличаются от встроенных. Если задан шаблон, выводятся строки только для тех ролей или схем, имена которых соответствуют этому шаблону.
Права доступа по умолчанию устанавливаются командой ALTER DEFAULT PRIVILEGES. Смысл отображаемых прав объясняется в разделе Права.

\dE[S+] [ шаблон ]
\di[S+] [ шаблон ]
\dm[S+] [ шаблон ]
\ds[S+] [ шаблон ]
\dt[S+] [ шаблон ]
\dv[S+] [ шаблон ]

В этой группе команд буквы E, i, m, s, t и v обозначают стороннюю таблицу, индекс, материализованное представление, последовательность, таблицу и представление соответственно. Можно указывать все или часть этих букв в произвольном порядке, чтобы получить список объектов этих типов. Например, \dit выводит список индексов и таблиц. При добавлении к имени команды + каждый объект выводится вместе со своими состоянием хранения (постоянный, временный или нежурналируемый), физическим размером на диске и описанием, если таковое имеется. Если задан шаблон, выводятся только те объекты, имена которых соответствуют этому шаблону. По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S.

\des[+] [ шаблон ]

Выводит список сторонних серверов (мнемоника: «external servers» (внешние серверы)). Если задан шаблон, выводятся только те серверы, имена которых соответствуют этому шаблону. Если используется форма \des+, то показывается полное описание каждого сервера, включая права доступа, тип, версию, параметры и описание.

\det[+] [ шаблон ]

Выводит список сторонних таблиц (мнемоника: «external tables» (внешние таблицы)). Если задан шаблон, выводятся только те записи, у которых имя таблицы или схемы соответствует этому шаблону. Если используется форма \det+, то также отображаются общие параметры и описание сторонней таблицы.

\deu[+] [ шаблон ]

Выводит список сопоставлений пользователей (мнемоника: «external users» (внешние пользователи)). Если задан шаблон, выводятся только те сопоставления, в которых имена пользователей соответствуют этому шаблону. Если используется форма \deu+, то показывается дополнительная информация о каждом сопоставлении пользователя.

ВНИМАНИЕ!
\deu+ также может отображать имя и пароль удаленного пользователя, поэтому следует позаботиться о том, чтобы не раскрывать их.

\dew[+] [ шаблон ]

Выводит список оберток сторонних данных (мнемоника: «external wrappers» (внешние обертки)). Если задан шаблон, выводятся только те обертки сторонних данных, имена которых соответствуют этому шаблону. Если используется форма \dew+, то также показываются права доступа, параметры и описание обертки.

\df[antwS+] [ шаблон [ арг_шаблон ... ] ]

Выводит список функций с типами данных их результатов, аргументов и классификацией: «agg» (агрегатная), «normal» (обычная), «procedure» (процедурная), «trigger» (триггерная) или «window» (оконная). Чтобы получить функции только определенного вида(ов), добавьте в команду буквы a, n, p, t или w соответственно. Если задан шаблон, показываются только те функции, имена которых соответствуют этому шаблону. Любые дополнительные аргументы являются шаблонами имен типов, которые сопоставляются с именами типов первого, второго и последующих аргументов функций. (У сопоставленных функций может быть больше аргументов, чем вы задали. Чтобы предотвратить это, напишите в качестве последнего арг_шаблона минус (-).) По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S. Если используется форма \df+, то показывается дополнительная информация по каждой функции, включая изменчивость, допустимость распараллеливания, владельца, классификацию по безопасности, права доступа, язык, исходный код и описание.

\dF[+] [ шаблон ]

Выводит список конфигураций текстового поиска. Если задан шаблон, показываются только те конфигурации, имена которых соответствуют этому шаблону. Если используется форма \dF+, то показывается полное описание каждой конфигурации, включая нижележащий синтаксический анализатор и список словарей для каждого типа синтаксических единиц.

\dFd[+] [ шаблон ]

Выводит список словарей текстового поиска. Если задан шаблон, показываются только те словари, имена которых соответствуют этому шаблону. Если используется форма \dFd+, то показывается дополнительная информация о каждом выбранном словаре, включая нижележащий шаблон текстового поиска и параметры инициализации.

\dFp[+] [ шаблон ]

Выводит список анализаторов текстового поиска. Если задан шаблон, показываются только те анализаторы, имена которых соответствуют этому шаблону. Если используется форма \dFp+, то показывается полное описание каждого анализатора, включая нижележащие функции и список распознаваемых типов синтаксических единиц.

\dFt[+] [ шаблон ]

Выводит список шаблонов текстового поиска. Если задан шаблон, показываются только те шаблоны, имена которых соответствуют этому шаблону. Если используется форма \dFt+, то показывается дополнительная информация о каждом шаблоне, включая имена нижележащих функций.

\dg[S+] [ шаблон ]

Выводит список ролей базы данных. (Поскольку понятия «пользователи» и «группы» были объединены в «роли», теперь эта команда равнозначна \du.) По умолчанию показываются только роли, созданные пользователями; чтобы включить системные роли, нужно добавить модификатор S. Если задан шаблон, выводятся только те роли, имена которых соответствуют этому шаблону. Если используется форма \dg+, показывается дополнительная информация о каждой роли; в настоящее время это комментарий к роли.

\dl

Это псевдоним для \lo_list, который показывает список больших объектов.

\dL[S+] [ шаблон ]

Выводит список процедурных языков. Если задан шаблон, выводятся только те языки, имена которых соответствуют этому шаблону. По умолчанию показываются только языки, созданные пользователями; чтобы включить системные объекты, нужно добавить модификатор S. При добавлении к имени команды + каждый язык выводится вместе со своими функциями обработки вызова и проверки, правами доступа и указанием, является ли этот язык системным объектом.

\dn[S+] [ шаблон ]

Выводит список схем (пространств имен). Если задан шаблон, выводятся только те схемы, имена которых соответствуют этому шаблону. По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S. При добавлении к имени команды + каждый объект выводится вместе со своими правами и описанием, если таковое имеются.

\do[S+] [ шаблон [ арг_шаблон [ арг_шаблон ] ] ]

Выводит список операторов с их операндами и типами результатов. Если задан шаблон, выводятся только те операторы, имена которых соответствуют этому шаблону. Если задан один арг_шаблон, выводятся только те префиксные операторы, у которых тип правого аргумента соответствует этому шаблону. Если задано два арг_шаблона, выводятся только те бинарные операторы, у которых имена типов аргументов соответствуют этому шаблону. (Как вариант, можно написать - (минус) в качестве неиспользуемого аргумента унарного оператора.) По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S. При добавлении к имени команды + показывается дополнительная информация о каждом операторе; в настоящее время это только имя нижележащей функции.

\dO[S+] [ шаблон ]

Выводит список правил сортировки. Если задан шаблон, выводятся только те правила, имена которых соответствуют этому шаблону. По умолчанию показываются только объекты, созданные пользователями; чтобы включить системные объекты, нужно задать шаблон или добавить модификатор S. При добавлении к имени команды + каждое правило выводится вместе со своим описанием, если таковое имеется. Обратите внимание, что показываются только правила сортировки, применимые к кодировке текущей базы данных, поэтому в разных базах данных одной установки результаты этой команды могут отличаться.

\dp [ шаблон ]

Выводит список таблиц, представлений и последовательностей с их правами доступа. Если задан шаблон, выводятся только те таблицы, представления и последовательности, имена которых соответствуют этому шаблону.
Для установки прав доступа используются команды GRANT и REVOKE. Смысл отображаемых прав объясняется в разделе Права.

\dP[itn+] [ шаблон ]

Выводит список партиционированных отношений. Если задан шаблон, выводятся только те записи, имена которых соответствуют этому шаблону. К команде можно добавить модификаторы t (tables, таблицы) и i (indexes, индексы), отфильтровав список по типу отношений. По умолчанию выводятся и партиционированные таблицы, и индексы.
При использовании модификатора n («nested», вложенные) или указании шаблона в список включаются также вложенные партиционированные отношения и показывается столбец, отображающий родителя каждого партиционированного отношения.
При добавлении к имени команды + также отображается суммарный размер партиций каждого отношения и его описание. Если + дополняет модификатор n, показывается два размера: общий размер партиций листового уровня, напрямую связанных с отношением, и общий размер всех партиций, включая вложенные партиции, связанные с отношением опосредованно.

\drds [ шаблон-ролей [ шаблон-баз ] ]

Выводит список определенных параметров конфигурации. Эти параметры могут быть специфичными для роли, специфичными для базы данных или для обеих. Параметры шаблон-ролей и шаблон-баз используются для отбора определенных ролей и баз данных соответственно. Если они опущены или указано *, выводятся все параметры, в том числе неспецифичные для ролей или баз данных соответственно.
Для определения параметров конфигурации, специфичных для роли и базы данных, используются команды ALTER ROLE и ALTER DATABASE.

\dRp[+] [ шаблон ]

Выводит список публикаций репликации. Если задан шаблон, выводятся только те публикации, имена которых соответствуют этому шаблону. При добавлении к имени команды + также показываются таблицы, связанные с каждой публикацией.

\dRs[+] [ шаблон ]

Выводит список подписок на репликацию. Если задан шаблон, выводятся только те подписки, имена которых соответствуют этому шаблону. При добавлении к имени команды + показываются дополнительные свойства подписок.

\dT[S+] [ шаблон ]

Выводит список типов данных. Если задан шаблон, выводятся только те типы, имена которых соответствуют этому шаблону. При добавлении к имени команды + каждый тип выводится вместе со своими внутренним именем и размером, допустимыми значениями (если это тип enum) и связанными правами. По умолчанию показываются только объекты, созданные пользователями; для включения системных объектов нужно задать шаблон или добавить модификатор S.

\du[S+] [ шаблон ]

Выводит список ролей базы данных. (Поскольку понятия «пользователи» и «группы» были объединены в «роли», теперь эта команда равнозначна \dg.) По умолчанию показываются только роли, созданные пользователями; чтобы включить системные роли, нужно добавить модификатор S. Если задан шаблон, выводятся только те роли, имена которых соответствуют этому шаблону. Если используется форма \du+, показывается дополнительная информация о каждой роли; в настоящее время это комментарий к роли.

\dx[+] [ шаблон ]

Выводит список установленных расширений. Если задан шаблон, выводятся только те расширения, имена которых соответствуют этому шаблону. Если используется форма \dx+, то выводятся все объекты, принадлежащие каждому соответствующему расширению.

\dX [ шаблон ]

Выводит объекты расширенной статистики. Если задан шаблон, выводятся только те объекты, имена которых соответствуют этому шаблону.
Состояние каждого вида расширенной статистки показывается в столбце с именем, отражающим ее вид (например Ndistinct). Состояние defined (определен) означает, что этот вид был запрошен при создании статистики, а NULL — что он не был запрошен. Чтобы узнать, запускалась ли команда ANALYZE и доступна ли статистика для планировщика, можно обратиться к представлению pg_stats_ext.

\dy[+] [ шаблон ]

Выводит список триггеров событий. Если задан шаблон, выводятся только те триггеры событий, имена которых соответствуют этому шаблону. При добавлении к имени команды + каждый объект выводится вместе со своим описанием.

\e или \edit [ имя_файла ] [ номер_строки ]

Если задано имя_файла, этот файл открывается для редактирования; после выхода из редактора содержимое файла копируется в буфер текущего запроса. Если имя_файла не задано, буфер текущего запроса копируется во временный файл, который затем редактируется таким же образом. Либо, если буфер текущего запроса пуст, во временный файл копируется последний выполненный запрос, который затем редактируется таким же образом.
Если после редактирования файла или предыдущего запроса выйти из редактора без сохранения файла, буфер запроса очищается. В ином случае новое содержимое буфера запроса повторно разбирается в соответствии с обычными правилами psql, при этом весь буфер обрабатывается как одна строка. Все завершенные запросы немедленно выполняются; то есть если буфер запроса содержит или заканчивается точкой с запятой, все его содержимое до этой точки выполняется и удаляется из буфера. Все, что остается в буфере, повторно выводится на экран. Для его передачи серверу нужно ввести точку с запятой или \g, а для отмены путем очищения буфера запроса — \r.
Обработка буфера как одной стоки в основном влияет на метакоманды: все, что находится в буфере после метакоманды, будет восприниматься как аргумент(ы) этой метакоманды, даже если этот текст занимает несколько строк. (Поэтому таким способом нельзя выполнять скрипты с метакомандами. Для таких скриптов используйте \i.)
Если задан номер строки, psql будет позиционировать курсор на указанную строку файла или буфера запроса. Обратите внимание, что если указан один аргумент, и он числовой, psql предполагает, что это номер строки, а не имя файла.

Совет
Как конфигурировать редактор и настроить его поведение, рассказывается ниже в параграфе Переменные среды.

\echo текст [ ... ]

Печатает вычисленные аргументы в стандартный вывод, разделяя их пробелами с переводом строки в конце. Это может быть полезно для добавления дополнительной информации в вывод скриптов. Например:

=> \echo `date`
Tue Oct 26 21:40:57 CEST 1999

Если в первом аргументе передается -n без кавычек, перевод строки в конце не добавляется (и не выводится сам первый аргумент).

Совет
Если вы перенаправляете вывод запросов с помощью команды \o, возможно, вместо этой команды будет лучше применить \qecho. См. также описание \warn.

\ef [ описание_функции [ номер_строки ] ]

Эта команда извлекает определение именованной функции или процедуры в форме команды CREATE OR REPLACE FUNCTION или CREATE OR REPLACE PROCEDURE и открывает его для редактирования. Редактирование производится тем же образом, что и при выполнении \edit. Если выйти из редактора без сохранения, этот оператор сбрасывается. Если сохранить файл и выйти из редактора, измененная команда немедленно выполняется, если вы добавили к ней точку с запятой. В противном случае она повторно выводится на экран; для ее передачи серверу нужно ввести точку с запятой или \g, а для отмены — \r.
Целевую функцию можно задать только по имени или именем и аргументами, например foo(integer, text). Типы аргументов необходимо давать, если существует более одной функции с таким именем.
Если функция не задана, для редактирования открывается пустая строка CREATE FUNCTION.
Если задан номер строки, psql будет позиционировать курсор на указанную строку тела функции. (Обратите внимание, что обычно тело функции не начинается на первой строке файла.)
В отличие от большинства других метакоманд, здесь весь остаток строки всегда воспринимается как аргумент(ы) \ef, и в этих аргументах не выполняется ни интерполяция переменных, ни раскрытие обратных кавычек.

Совет
Как конфигурировать редактор и настроить его поведение, рассказывается ниже в параграфе Переменные среды.

\encoding [ кодировка ]

Устанавливает кодировку набора символов на клиенте. Без аргумента эта команда показывает текущую кодировку.

\errverbose

Повторяет последнее серверное сообщение об ошибке с максимальным уровнем детализации, как если бы для переменной VERBOSITY было установлено значение verbose, а для SHOW_CONTEXT — always.

\ev [ имя_представления [ номер_строки ] ]

Эта команда извлекает определение именованного представления в форме команды CREATE OR REPLACE VIEW и открывает его для редактирования. Редактирование производится тем же образом, что и при выполнении \edit. Если выйти из редактора без сохранения, этот оператор сбрасывается. Если сохранить файл и выйти из редактора, измененная команда немедленно выполняется, если вы добавили к ней точку с запятой. В противном случае она повторно выводится на экран; для ее передачи серверу нужно ввести точку с запятой или \g, а для отмены — \r.
Если представление не задано, для редактирования открывается пустая строка CREATE VIEW.
Если задан номер строки, psql будет позиционировать курсор на указанную строку определения представления.
В отличие от большинства других метакоманд, здесь весь остаток строки всегда воспринимается как аргумент(ы) \ev, и в этих аргументах не выполняется ни интерполяция переменных, ни раскрытие обратных кавычек.

\f [ строка ]

Устанавливает разделитель полей для невыровненного вывода запросов. По умолчанию используется вертикальная черта (|). Это равнозначно команде \pset fieldsep.

\g [ (параметр=значение [...]) ] [ имя_файла ]
\g [ (параметр=значение [...]) ] [ |команда ]

Передает содержимое буфера текущего запроса серверу для выполнения.
Если после \g идут скобки, внутри них содержится разделенный пробелами список предложений параметров форматирования в виде параметр=значение, которые воспринимаются так же, как и в команде \pset параметр значение, но действуют только во время выполнения этого запроса. В этом списке пробелы не должны находиться вокруг знаков =, но должны разделять предложения параметров. Если =значение опускается, именованный параметр меняется так же, как и при выполнении \pset параметр без явно заданного значения.
Если заданы имя_файла или |команда, вывод запроса записывается в именованный файл или передается через поток заданной команде оболочки, а не отображается как обычно. Вывод записывается в файл или команду, только если запрос успешно возвращает ноль или более кортежей, но не при сбое запроса или выполнении команды SQL, не возвращающей данные.
Если буфер текущего запроса пуст, вместо этого повторно выполняется последний из запросов. За исключением этого поведения \g без аргументов по сути равнозначна точке с запятой. С аргументами же \g предоставляет «одноразовую» альтернативу команде \o и вдобавок позволяет корректировать на время выполнения параметры форматирования вывода, которые обычно устанавливаются командой \pset.
Когда последний аргумент начинается с |, весь остаток строки воспринимается как команда, подлежащая выполнению, и в ней не производится ни интерполяция переменных, ни раскрытие обратных кавычек. Этот остаток строки просто передается оболочке в буквальном виде.

\gdesc

Показывает описание (то есть имена и типы данных столбцов) результата текущего запроса в буфере. При этом в действительности этот запрос не выполняется; однако если он содержит какие-либо синтаксические ошибки, о них сообщается обычным способом.
Если буфер текущего запроса пуст, вместо этого будет описан последний переданный запрос.

\gexec

Передает буфер текущего запроса серверу, а затем обрабатывает содержимое каждого столбца каждой строки вывода запроса (если таковой имеется) как оператор SQL, подлежащий выполнению. Например, создание индекса по каждому столбцу my_table:

=> SELECT format('create index on my_table(%I)', attname)
-> FROM pg_attribute
-> WHERE attrelid = 'my_table'::regclass AND attnum > 0
-> ORDER BY attnum
-> \gexec
CREATE INDEX
CREATE INDEX
CREATE INDEX
CREATE INDEX

Генерируемые запросы выполняются в том порядке, в каком возвращаются строки, и слева направо, если возвращается более одного столбца. Поля NULL игнорируются. Генерируемые запросы передаются для обработки на сервер в буквальном виде, так что они не могут быть метакомандами psql или содержать ссылки на переменные psql. В случае сбоя в одном из запросов, выполнение оставшихся запросов продолжается, если только не установлена переменная ON_ERROR_STOP. Выполнение каждого запроса подвергается влиянию переменной ECHO. (Используя команду \gexec, часто рекомендуется устанавливать для ECHO значение all или queries.) Протоколирование запросов, пошаговый режим, замер времени и другая функциональность, тоже действуют при выполнении каждого генерируемого запроса.
Если буфер текущего запроса пуст, вместо этого повторно выполняется последний переданный запрос.

\gset [ префикс ]

Передает буфер текущего запроса на сервер и сохраняет вывод запроса в переменных psql (см. ниже в параграфе Переменные). Выполняемый запрос должен возвращать ровно одну строку. Каждый столбец этой строки сохраняется в отдельной переменной, которая называется так же, как и столбец. Например:

=> SELECT 'hello' AS var1, 10 AS var2
-> \gset
=> \echo :var1 :var2
hello 10

Если указан префикс, то он добавляется в начало имен столбцов запроса для создания имен переменных:

=> SELECT 'hello' AS var1, 10 AS var2
-> \gset result_
=> \echo :result_var1 :result_var2
hello 10

Если результат столбца равен NULL, то вместо присвоения значения соответствующая переменная сбрасывается.
Если запрос завершается неудачно или не возвращает одну строку, то переменные не изменяются.
Если буфер текущего запроса пуст, вместо этого повторно выполняется последний переданный запрос.

\gx [ (параметр=значение [...]) ] [ имя_файла ]
\gx [ (параметр=значение [...]) ] [ |команда ]

Метакоманда \gx равнозначна метакоманде \g, за исключением того, что она принудительно включает расширенный режим вывода для этого запроса, так же, как если бы в список параметров \pset было включено указание expanded=on. См. также описание \x.

\h или \help [ команда ]

Выдает справку по синтаксису указанной команды SQL. Если команда не указана, то psql выведет список всех команд, для которых доступна справка по синтаксису. Если в качестве команды указана звездочка (*), показывается справка по синтаксису для всех команд SQL.
В отличие от большинства других метакоманд, здесь весь остаток строки всегда воспринимается как аргумент(ы) \help, и в этих аргументах не выполняется ни интерполяция переменных, ни раскрытие обратных кавычек.

Примечание
Для упрощения ввода команды, состоящие из нескольких слов, необязательно заключать в кавычки. Таким образом, можно просто писать \help alter table.

\H или \html

Включает вывод запросов в формате HTML. Если формат HTML уже включен, происходит переключение обратно на выровненный формат текста. Эта команда предназначена для совместимости и удобства, но в описании \pset можно узнать о настройке других вариантов вывода.

\i или \include имя_файла

Читает ввод из файла имя_файла и выполняет его, как если бы он был набран на клавиатуре.
Если имя_файла задано как - (минус), читается стандартный ввод до обозначения конца файла или до метакоманды \q. Это можно использовать для совмещения интерактивного ввода с вводом из файлов. Обратите внимание, что поведение Readline будет применяться, только если оно активно на внешнем уровне.

Примечание
Если вы хотите видеть строки файла на экране по мере их прочтения, следует установить для переменной ECHO значение all.

\if выражение
\elif выражение
\else
\endif

Эта группа команд реализует вложенные условные блоки. Условный блок должен начинаться командой \if и заканчиваться \endif. Между ними может быть любое количество предложений \elif, за которыми может дополнительно следовать одно предложение \else. Между командами, формирующими условный блок, могут располагаться (и обычно располагаются) обычные запросы и другие типы метакоманд.
Команды \if и \elif считывают свои аргументы и вычисляют их как логические выражения. Если такое выражение выдает true, обработка продолжается как обычно; в противном случае строки пропускаются до достижения соответствующих команд \elif, \else или \endif. Как только проверка \if или \elif успешно завершается, аргументы последующих команд \elif в том же блоке не вычисляются, а считаются ложными. Строки после \else обрабатываются, только если ни одна из предыдущих проверок \if или \elif не была успешной.
В аргументе выражение команды \if или \elif производится интерполяция переменных и раскрытие обратных кавычек, как и в аргументе любой другой метакоманды. После этого он вычисляется как значение переменной параметра вкл/выкл. Поэтому допустимым значением является любое однозначное значение (без учета регистра) из следующих: true, false, 1, 0, on, off, yes, no. Например, значения t, T и tR все будут восприниматься как true.
Если выражения не удается оценить как истинное или ложное, будет сгенерировано предупреждение, а их результат будет считаться ложным.
Пропускаемые строки анализируются как обычно для идентификации запросов и метакоманд, но запросы не передаются на сервер, а метакоманды, отличные от условных (\if, \elif, \else, \endif), игнорируются. Условные команды проверяются только на правильность вложенности. Ссылки на переменные в пропускаемых строках не разворачиваются, как и не производится раскрытие обратных кавычек.
Все метакоманды в заданном условном блоке должны находиться в одном исходном файле. Если до того, как были закрыты все локальные блоки \if, в основном файле входа будет достигнут конец файла или встретится включение другого файла (команда \include), psql выдаст ошибку.

Например:

-- проверка на существование двух отдельных записей в базе данных и сохранение
-- результатов в двух разных переменных psql
SELECT
    EXISTS(SELECT 1 FROM customer WHERE customer_id = 123) as is_customer,
    EXISTS(SELECT 1 FROM employee WHERE employee_id = 456) as is_employee
\gset
\if :is_customer
    SELECT * FROM customer WHERE customer_id = 123;
\elif :is_employee
    \echo 'is not a customer but is an employee'
    SELECT * FROM employee WHERE employee_id = 456;
\else
    \if yes
        \echo 'not a customer or employee'
    \else
        \echo 'this will never print'
    \endif
\endif

\ir или \include_relative имя_файла

Команда \ir схожа с \i, но по-другому разрешает относительные имена файлов. При выполнении в интерактивном режиме обе команды ведут себя одинаково. Однако при вызове из скрипта \ir интерпретирует имена файлов относительно каталога, в котором расположен скрипт, а не текущего рабочего каталога.

\l[+] или \list[+] [ шаблон ]

Выводит список баз данных на сервере и показывает их имена, владельцев, кодировку набора символов и права доступа. Если задан шаблон, выводятся только те базы данных, имена которых соответствуют этому шаблону. При добавлении к имени команды + также отображаются размеры баз данных, табличные пространства по умолчанию и описания. (Информация о размере доступна только для тех баз данных, к которым может подключиться текущий пользователь.)

\lo_export oid_БО имя_файла

Читает большой объект с OID oid_БО из базы данных и записывает его в файл имя_файла. Обратите внимание, что это несколько отличается от серверной функции lo_export, действующей с правами пользователя, от имени которого работает сервер баз данных, и в файловой системе сервера.

Совет
Используйте для получения OID больших объектов команду \lo_list.

\lo_import имя_файла [ комментарий ]

Сохраняет файл в большом объекте QHB. Кроме того, с объектом можно связать заданный комментарий. Пример:

foo=> \lo_import '/home/peter/pictures/photo.xcf' 'a picture of me'
lo_import 152801

Ответ указывает на то, что большой объект получил OID 152801, который в будущем можно использовать для доступа ко вновь созданному большому объекту. Для удобочитаемости рекомендуется всегда связывать все объекты с понятными для человека комментариями. OID и комментарии можно посмотреть с помощью команды \lo_list.
Обратите внимание, что эта команда слегка отличается от серверной функции lo_import, поскольку действует от имени локального пользователя в локальной файловой системе, а не пользователя сервера в файловой системе сервера.

\lo_list

Показывает список всех больших объектов QHB, в настоящее время хранящихся в базе данных, вместе с предоставленными комментариями.

\lo_unlink oid_БО

Удаляет большой объект с OID oid_БО из базы данных.

Совет
Используйте для получения OID больших объектов команду \lo_list.

\o или \out [ имя_файла ]
\o или \out [ |команда ]

Организует сохранение результатов будущих запросов в файле имя_файла или передачу их через поток команде оболочки команда. Если аргументы не заданы, результат запроса перенаправляется на стандартный вывод.
Когда последний аргумент начинается с |, весь остаток строки воспринимается как команда, подлежащая выполнению, и в ней не производится ни интерполяция переменных, ни раскрытие обратных кавычек. Этот остаток строки просто передается оболочке в буквальном виде.
«Результаты запросов» включают в себя все таблицы, ответы команд и замечания, полученные от сервера баз данных, а также вывод от метакоманд, обращающихся к базе данных (например \d), но не сообщения об ошибках.

Совет
Чтобы вставить текстовый вывод между результатами запросов, используйте команду \qecho.

\p или \print

Вывести содержимое буфера текущего запроса в стандартный вывод. Если буфер текущего запроса пуст, вместо этого выводится последний выполненный запрос.

\password [ имя_пользователя ]

Изменяет пароль указанного пользователя (по умолчанию текущего пользователя). Эта команда запрашивает новый пароль, шифрует его и передает на сервер в виде команды ALTER ROLE. Это гарантирует, что новый пароль не отображается в открытом виде в истории команд, журнале сервера или где-то еще.

\prompt [ текст ] имя

Предлагает пользователю ввести значение, которое будет присвоено переменной имя. Дополнительно можно указать строку подсказки текст. (Если подсказка состоит из нескольких слов, ее текст следует заключить в апострофы.)
По умолчанию \prompt использует для ввода и вывода терминал. Однако при указании ключа командной строки -f, \prompt использует стандартные ввод и вывод.

\pset [ параметр [ значение ] ]

Эта команда устанавливает параметры, влияющие на вывод таблиц результатов запросов. Аргумент параметр определяет, какой параметр нужно установить. Семантика значения меняется в зависимости от выбранного параметра. Для некоторых параметров отсутствие значения вызывает переключение или сброс параметра, как рассказывается ниже в описании конкретного параметра. Если такое поведение не упоминается, то отсутствие значения приводит всего лишь к отображению текущего значения параметра.
\pset без аргументов отображает текущий статус всех выведенных параметров.
Имеются следующие настраиваемые параметры:

border
Значение должно быть номером. В целом, чем больше это число, тем больше границ и линий будет в таблицах, но детали зависят от конкретного формата. В формате HTML заданное значение будет напрямую переводиться в атрибут border=.... Для большинства других форматов имеют смысл только значения 0 (нет границы), 1 (внутренние разделительные линии) и 2 (рамка таблицы), а значения больше 2 будут восприниматься как border = 2. Форматы latex и latex-longtable дополнительно поддерживают значение 3, добавляющее разделительные линии между строками данных.

columns
Устанавливает целевую ширину для формата wrapped, а также ограничение по ширине вывода, свыше которого потребуется программа листания или переключение на вертикальное отображение в режиме expanded auto. При значении ноль (по умолчанию) целевая ширина управляется переменной среды COLUMNS или, если COLUMNS не установлена, определенной шириной экрана. Кроме того, если columns равен нулю, то формат wrapped влияет только на вывод на экран. Если columns не равен нулю, то это также влияет на вывод данных в файл и передачу через поток.

csv_fieldsep
Задает разделитель полей для формата вывода CSV. Если символ-разделитель оказывается в значении поля, это поле выводится в кавычках согласно стандартным правилам CSV. Разделитель по умолчанию — запятая.

expanded (или x)
При указании значения допускаются варианты on или off, которые включают или выключают развернутый режим, или auto. Если значение опущено, команда переключает режим на противоположный. Когда развернутый режим включен, результаты запроса выводятся в два столбца: в левом имя столбца, в правом данные. Этот режим полезен, если данные не помещаются на экране в обычном «горизонтальном» режиме. Со значением auto развернутый режим используется, когда вывод запроса содержит более одного столбца и по ширине не умещается на экране; в противном случае используется обычный режим. Вариант auto действует только в форматах aligned и wrapped. С другими форматами он всегда равнозначен выключению развернутого режима.

fieldsep
Задает разделитель полей для невыровненного режима вывода. Таким способом можно создать вывод со значениями, разделенными, к примеру, табуляцией, что может быть предпочтительным для других программ. Для установки символа табуляции в качестве разделителя полей нужно написать \pset fieldsep '\t'. Разделитель по умолчанию — '|' (вертикальная черта).

fieldsep_zero
Устанавливает разделитель полей для невыровненного режима вывода в нулевой байт.

footer
При указании значения допускаются варианты on или off, которые включают или выключают отображение итоговой части таблицы с количеством выбранных записей (n rows). Если значение опущено, команда переключает состояние отображения итоговой строки на противоположное.

format
Устанавливает один из следующих форматов вывода: aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned или wrapped. Допускаются уникальные сокращения этих слов. + Формат aligned — это стандартный, удобочитаемый, хорошо отформатированный текстовый вывод; это значение по умолчанию. + В формате unaligned все столбцы выводятся в одной строке, отделенные друг от друга активным на данный момент разделителем полей. Это полезно для создания вывода, который может быть предназначен для чтения другими программами, например, для форматов с символом табуляции или запятой в качестве разделителя. Однако если разделитель полей оказывается в значении столбца, он обрабатывается как часть значения, поэтому для таких целей больше подходит формат CSV. + В формате csv значения столбцов выводятся через запятую и могут заключаться в кавычки по правилам, описанным в RFC 4180. Этот вывод совместим с форматом CSV серверной команды COPY. Если не включен параметр tuples_only, то генерируется строка заголовка с именами столбцов. Верхний и нижний колонтитулы не выводятся. Каждая строка заканчивается символом конца строки, зависящим от операционной системы; В Unix-подобных системах это обычно символ новой строки (\n). Разделители полей, отличные от запятой, можно выбрать командой \pset csv_fieldsep. + Формат wrapped похож на aligned, но переносит длинные значения на новые строки, чтобы вывод поместился в целевую ширину столбца. Задание целевой ширины описано в параметре columns. Обратите внимание, что psql не будет пытаться переносить на новые строки заголовки столбцов; таким образом, формат wrapped работает так же, как aligned, если общая ширина, требуемая для заголовков столбцов, превышает целевую. + Форматы asciidoc, html, latex, latex-longtable и troff-ms выводят таблицы, которые предназначены для включения в документы со помощью соответствующего языка разметки. Они не являются полноценными документами! Возможно, в этом нет необходимости в HTML, но для LaTeX обязателен документ- контейнер. Формат latex использует среду LaTeX tabular. Формату latex-longtable требуются пакеты LaTeX longtable и booktabs.

linestyle
Задает стиль отрисовки линий границы: ascii, old-ascii или unicode. Допускаются уникальные сокращения этих слов. (Это значит, что достаточно и одной буквы.) Стиль по умолчанию — ascii. Этот параметр действует только в форматах вывода aligned и wrapped. + Стиль ascii использует простые символы ASCII. Символы перевода строки в данных показываются с использованием символа + с правого края. Когда при формате wrapped происходит перенос данных на новую строку без символа перевода строки, с правого края первой строки и с левого края следующей строки ставится точка (.). + Стиль old-ascii использует простые символы ASCII в стиле форматирования более старых версий QHB. Символы перевода строки в данных показываются с использованием символа : вместо левого разделителя столбцов. Когда происходит перенос данных на новую строку без символа перевода строки, вместо левого разделителя столбцов используется символ ;. + Стиль unicode использует символы Unicode для рисования рамок. Символы новой строки в данных показываются с использованием символа возврата каретки с правого края. Когда происходит перенос данных на новую строку без символа перевода строки, с правого края первой строки и с левого края следующей строки ставится многоточие.

Когда значение border больше нуля, параметр linestyle также определяет символы, которыми будут рисоваться линии границы. Простые символы ASCII работают везде, но символы Unicode лучше выглядят на распознающих их терминалах.

null
Задает строку, которая будет выводиться вместо значения NULL. По умолчанию не выводится ничего, что можно по ошибке принять за пустую строку. Например, можно было бы предпочесть \pset null '(null)'.

numericlocale
При указании значения допускаются варианты on или off, которые включают или выключают отображение специфичного для локали символа, отделяющие группы цифр слева от десятичного знака. Если значение опущено, команда переключает формат отображение чисел на противоположный.

pager
Управляет использованием программы листания для вывода результатов запросов и справки по psql. Если установлена переменная среды PSQL_PAGER или PAGER, вывод передается через поток указанной программе. В противном случае используется платформо-зависимая программа по умолчанию (например more).
Когда параметр pager установлен в значение off, программа листания не используется. Когда pager имеет значение on, программа листания применяется при необходимости, т. е. когда вывод на терминал не помещается на экране. Кроме того, параметр pager может принимать значение always, при котором программа листания будет использоваться для всех выводов на терминал, независимо от того, помещается ли тот на экране. Команда \pset pager без указания значения включает/выключает программу листания.

pager_min_lines
Если в pager_min_lines задается число, превышающее высоту страницы, программа листания не будет вызываться, пока не наберется это число строк вывода для показа. Значение по умолчанию — 0.

recordsep
Задает разделитель записей (строк) для невыровненного режима вывода. Разделитель по умолчанию — символ перевода строки.

recordsep_zero
Устанавливает разделитель записей для невыровненного режима вывода в нулевой байт.

tableattr (или T)
В формате HTML этот параметр задает атрибуты, которые будут помещены в тег table. Например, это может быть cellpadding или bgcolor. Обратите внимание, что, возможно, здесь необязательно задавать border, поскольку для этого уже есть \pset border. Если значение не задано, атрибуты таблицы удаляются.
В формате latex-longtable этот параметр управляет пропорциональной шириной каждого столбца, содержащего тип данных, выровненный по левому краю. Он задается как список значений, разделенных пробелами, например '0.2 0.2 0.6'. Для столбцов вывода, которым не хватает значений, используется последнее из заданных.

title (или C)
Устанавливает заголовок таблицы для любых выводимых впоследствии таблиц. Это можно использовать для задания описательных тегов при создании вывода. Если значение не задано, заголовок удаляется.

tuples_only (или t)
При указании значения допустимы варианты on или off, которые будут включать или выключать режим вывода только кортежей. Если значение опущено, команда переключает режим на противоположный. Обычный вывод включает в себя дополнительную информацию, например заголовки столбцов и различные колонтитулы. В режиме вывода только кортежей отображаются только фактические данные таблицы.

unicode_border_linestyle
Устанавливает стиль отрисовки границы для стиля линий unicode: single (одинарный) или double (двойной).

unicode_column_linestyle
Устанавливает стиль отрисовки столбцов для стиля линий unicode: single (одинарный) или double (двойной).

unicode_header_linestyle
Устанавливает стиль отрисовки заголовка для стиля линий unicode: single (одинарный) или double (двойной).

Иллюстрации того, как могут выглядеть различные форматы, можно увидеть ниже в параграфе Примеры.

Совет
Для некоторых параметров \pset существуют короткие команды. См. \a, \C, \f, \H, \t, \T и \x.

\q или \quit

Завершает программу psql. При использовании в скрипте прекращается только выполнение этого скрипта.

\qecho текст [ ... ]

Эта команда идентична \echo за исключением того, что вывод будет записываться в канал вывода запросов, установленный командой \o.

\r или \reset

Сбрасывает (очищает) буфер запроса.

\s [ имя_файла ]

Вывести историю командной строки psql в файл имя_файла. Если имя_файла опущено, история записывается в стандартный вывод (с использованием программы листания в случае необходимости). Эта команда недоступна, если psql была собрана без поддержки Readline.

\set [ имя [ значение [ ... ] ] ]

Задает для переменной psql с заданным именем указанное значение или, если указано более одного значения, конкатенацию всех этих значений. Если задан только один аргумент, значением переменной становится пустая строка. Для сброса переменной воспользуйтесь командой \unset.
\set без аргументов отображает имена и значения всех установленных на данный момент переменных psql.
В именах переменных допустимы буквы, цифры и знаки подчеркивания. Подробнее см. ниже в параграфе Переменные. Имена переменных чувствительны к регистру.
Некоторые переменные отличаются от остальные тем, что управляют поведением psql или устанавливаются автоматически, отражая состояние соединения. Эти переменные описаны ниже в параграфе Переменные.

Примечание
Эта команда не имеет отношения к команде SQL SET.

\setenv имя [ значение ]

Устанавливает в переменной среды имя заданное значение или, если значение не задано, сбрасывает переменную среды. Пример:

testdb=> \\setenv PAGER less
testdb=> \\setenv LESS -imx4F

\sf[+] описание_функции

Эта команда извлекает и показывает определение именованной функции или процедуры в форме команды CREATE OR REPLACE FUNCTION или CREATE OR REPLACE PROCEDURE. Определение записывается в текущий канал вывода запросов, заданный командой \o.
Целевую функцию можно задать только именем или именем и аргументами, например foo(integer, text). Типы аргументов необходимо задавать, если существует более одной функции с тем же именем.
При добавлении к имени команды + строки вывода нумеруются, при этом первая строка тела функции получит номер 1.
В отличие от большинства других метакоманд, весь остаток строки всегда воспринимается как аргумент(ы) \sf, и в этих аргументах не выполняется ни интерполяция переменных, ни раскрытие обратных кавычек.

\sv[+] имя_представления

Эта команда извлекает и показывает определение именованного представления в форме команды CREATE OR REPLACE VIEW command. Определение записывается в текущий канал вывода запросов, заданный командой \o.
При добавлении к имени команды + строки вывода нумеруются, начиная с 1.
В отличие от большинства других метакоманд, весь остаток строки всегда воспринимается как аргумент(ы) \sv, и в этих аргументах не выполняется ни интерполяция переменных, ни раскрытие обратных кавычек.

\t

Включает/выключает в выводе отображение заголовков с именами столбцов и итоговой строки с количеством записей. Эта команда равнозначна \pset tuples_only и предоставлена для удобства.

\T параметры_таблицы

Задает атрибуты, которые будут помещены в тег table при формате вывода HTML. Эта команда равнозначна \pset tableattr параметры_таблицы.

\timing [ on | off ]

С параметром (в зависимости от него) эта команда включает или выключает отображение времени выполнения каждого оператора SQL. Без параметра она переключает состояние отображения на противоположное. Время отображается в миллисекундах; интервалы больше 1 секунды показываются в формате минуты:секунды, а при необходимости добавляются поля часов и дней.

\unset имя

Сбрасывает (удаляет) переменную psql с заданным именем.
Большинство переменных, управляющих поведением psql, нельзя сбросить, поэтому команда \unset воспринимается для них как установка значений по умолчанию. См. ниже в параграфе Переменные.

\w или \write имя_файла
\w или \write |команда

Записывает буфер текущего запроса в файл имя_файла или передает через поток в команду оболочки команда. Если буфер текущего запроса пуст, вместо этого будет записан последний выполненный запрос.
Если аргумент начинается с |, то весь остаток строки воспринимается как команда, подлежащая выполнению, и в ней не производится ни интерполяция переменных, ни раскрытие обратных кавычек. Этот остаток строки просто передается оболочке в буквальном виде.

\warn текст [ ... ]

Эта команда идентична \echo за исключением того, что вывод будет записываться в канал вывода ошибок psql, а не в канал стандартного вывода.

\watch [ секунды ]

Многократно выполнять текущий запрос в буфере (как делает \g), пока команда не будет прервана или не возникнет ошибка в запросе. Между выполнениями запроса ждать заданное количество секунд (по умолчанию 2). Результат каждого запроса отображается с заголовком, включающим строку \pset title (если она задана), время запуска запроса и интервал задержки.
Если буфер текущего запроса пуст, вместо этого повторно выполняется последний переданный запрос.

\x [ on | off | auto ]

Задает или переключает режим развернутого форматирования таблицы. По сути равнозначна \pset expanded.

\z [ шаблон ]

Выводит список таблиц, представлений и последовательностей с их правами доступа. Если задан шаблон, выводятся только те таблицы, представления и последовательности, имена которых соответствуют этому шаблону.
Это псевдоним для \dp («отобразить права доступа»).

\! [ команда ]

Без аргументов запускает подчиненную оболочку; когда эта оболочка завершается, psql продолжает работу. С аргументом выполняет команду оболочки команда.
В отличие от большинства других метакоманд, весь остаток строки всегда воспринимается как аргумент(ы) \!, и в этих аргументах не выполняется ни интерполяция переменных, ни раскрытие обратных кавычек. Этот остаток строки просто передается оболочке в буквальном виде.

\? [ тема ]

Показывает справочную информацию. Необязательный параметр тема (по умолчанию commands) определяет, описание какой части psql будет показано: commands описывает метакоманды psql; options — параметры командной строки, которые можно передать psql; а variables показывает справку по переменным конфигурации psql.

\;

Точка с запятой после обратного слэша не является метакомандой в том же смысле, что предыдущие; при ее вводе в буфер запросов просто добавляется точка с запятой без дальнейшей обработки.
Обычно psql передает команду SQL серверу, как только доходит до завершающей команду точки с запятой, даже если в текущей строке еще остались данные на ввод. Таким образом, например, ввод

select 1; select 2; select 3;

приведет к тому, что на сервер будут по отдельности переданы три команды SQL, и результат каждой команды будет выведен перед переходом к следующей. Однако если точку с запятой ввести как \;, она не запустит обработку команды, так что команды перед и после этих символов в сущности объединяются и передаются серверу в одном запросе. Так, например, при вводе

select 1\; select 2\; select 3;

три команды SQL передаются серверу в одном запросе по достижении неэкранированной обратным слэшем точки с запятой. Сервер выполняет такую строку как одну транзакцию, если только туда не включены явные команды BEGIN/COMMIT, разделяющие ее на несколько транзакций. (Подробнее о том, как сервер обрабатывает строки с несколькими командами, рассказывается в подразделе Несколько операторов в простом запросе.) psql выводит результат только последнего запроса из всей строки; в этом примере, несмотря на то что определенно выполнялись все три команды SELECT, psql выводит только 3.

Шаблоны

Различные команды \d принимают параметр шаблон для указания имен объектов, которые будут отображены. В простейшем случае шаблон — это просто точное имя объекта. Обычно символы внутри шаблона переводятся в нижний регистр, как в именах SQL; например, \dt FOO отобразит таблицу с именем foo. Как и в именах SQL, заключение шаблона в кавычки предотвращает перевод в нижний регистр. Если нужно включить в шаблон настоящий символ кавычки, он записывается как пара кавычек внутри последовательности в кавычках; опять же, это соответствует правилам для идентификаторов SQL в кавычках. Например \dt "FOO""BAR" отобразит таблицу с именем FOO"BAR (не foo"bar). В отличие от обычных правил для имен SQL, можно взять в кавычки только часть шаблона, например \dt FOO"FOO"BAR отобразит таблицу с именем fooFOObar.

Если шаблон вообще опущен, команды \d отображают все объекты, видимые в текущем пути поиска схем — это равнозначно использованию в качестве шаблона символа *. (Объект считается видимым, если схема, в которой он содержится, находится в пути поиска, и объект того же типа и с тем же именем в пути поиска еще не встречался. Это равнозначно утверждению, что на объект можно ссылаться по имени без явного указания схемы.) Чтобы увидеть все объекты в базе данных, независимо от видимости, используйте в качестве шаблона *.*.

Внутри шаблона * приравнивается к любой последовательности символов (включая отсутствие символов), а ? приравнивается к любому одному символу. (Это обозначение соответствует шаблонам имен файлов в Unix.) Например, \dt int* отображает таблицы, имена которых начинаются с int. Но в кавычках * и ? теряют свое специальное значение и воспринимаются буквально.

Шаблон, содержащий точку (.) интерпретируется как шаблон имени схемы, за которым следует шаблон имени объекта. Например, \dt foo*.*bar* отображает все таблицы, имена которых включают bar, расположенные в схемах, имена которых начинаются с foo. Шаблону, не содержащему точку, соответствуют только объекты, видимые в пути поиска текущей схемы. Опять же, точка в кавычках теряет свое специальное значение и воспринимается буквально.

Продвинутые пользователи могут использовать записи регулярных выражений, таких как классы символов. Например [0-9] соответствует любой цифре. Все специальные символы регулярных выражений работают, как описано в подразделе Регулярные выражения POSIX, за исключением: ., который используется в качестве разделителя, как упоминалось выше, *, который переводится в запись регулярного выражения .*, ?, который переводится в ., и $, который воспринимается буквально. При необходимости эти шаблонные символы можно эмулировать, записав ? для эмуляции ., (R+|) для R* или (R|) для R?. Символ $ не требуется в качестве символа регулярного выражения, поскольку шаблон должен соответствовать имени целиком, в отличие от обычной интерпретации регулярных выражений (другими словами, $ добавляется в шаблон автоматически). Если не хотите, чтобы шаблон закрепился, напишите * в начале и/или в конце. Обратите внимание, что в кавычках все специальные символы регулярных выражений теряют свое специальное значение и воспринимаются буквально. Кроме того, специальные символы регулярных выражений воспринимаются буквально в шаблонах имен операторов (т. е. в аргументе команды \do).

Расширенная функциональность

Переменные

psql предоставляет возможности подстановки переменных, схожие с имеющимися в обычных командных оболочках Unix. Переменные — это просто пары имя/значение, где значением может быть любая строка любой длины. Имя должно состоять из букв (включая нелатинские), цифр и символов подчеркивания.

Чтобы установить переменную, используется метакоманда psql \set. Например,

testdb=> \set foo bar

задает для переменной foo значение bar. Чтобы получить содержимое переменной, нужно поставить перед ее именем двоеточие, например:

testdb=> \echo :foo
bar

Это работает как в обычных командах SQL, так и в метакомандах; более подробно это описывается ниже в параграфе Интерполяция SQL.

При вызове \set без второго аргумента значением переменной становится пустая строка. Для сброса (т. е. удаления) переменной используйте команду \unset. Чтобы показать значения всех переменных, вызовите \set без аргументов.

Примечание
Аргументы \set подчиняются тем же правилам подстановки, что и другие команды. Таким образом, можно конструировать интересные ссылки, такие как \set :foo 'something', и получать «гибкие ссылки» или «переменные переменных» в Perl или PHP соответственно. К сожалению (или к счастью?), с этими конструкциями нельзя сделать ничего полезного. С другой стороны, \set bar :foo является прекрасным способом копирования переменной.

Ряд этих переменных обрабатывается в psql особым образом. Они представляют определенные параметры, которые можно менять во время выполнения путем изменения значения переменной, а в некоторых случаях отражают изменяемое состояние psql. По соглашению имена всех специальных переменных состоят только из заглавных букв ASCII (и, возможно, цифр и символов подчеркивания). В целях максимальной совместимости в будущем не рекомендуется использовать такие имена для собственных переменных.

Переменные, управляющие поведением psql, как правило, нельзя сбросить или задать для них недопустимые значения. Команда \unset допустима, но интерпретируется как установка в переменной значения по умолчанию. Команда \set без второго аргумента интерпретируется как присвоение переменной значения on (включен) для управляющих переменных, принимающих это значение, и не принимается для других. Кроме того, управляющие переменные, принимающие значения on и off (выключен), также примут и другие общепринятые написания логических значений, например true и false.

Специальные переменные:

AUTOCOMMIT
При значении on (по умолчанию), все команды SQL после успешного выполнения автоматически фиксируются. Чтобы отложить фиксацию в этом режиме, необходимо выполнить команду SQL BEGIN или START TRANSACTION. При значении off или сбросе переменной команды SQL не фиксируются, пока явно не выполнена команда COMMIT или END. В режиме выключенной автофиксации неявно выполняется BEGIN непосредственно перед любой командой, кроме команд, уже находящихся в транзакционном блоке, самой команды BEGIN или другой команды управления транзакциями и команды, которая не может выполняться внутри транзакционного блока (например VACUUM).

Примечание
В режиме выключенной автофиксации требуется явно откатывать неуспешные транзакции, выполняя команду ABORT или ROLLBACK. Также имейте в виду, что при выходе из сеанса без фиксации изменений проделанная работа пропадет.

Примечание
Режим включенной автофиксации является традиционным поведением QHB, а режим выключенной автофиксации ближе к спецификации SQL. Если вы предпочитаете выключить режим автофиксации, это можно сделать в общесистемном файле psqlrc или в персональном файле ~/.psqlrc.

COMP_KEYWORD_CASE
Определяет, какой буквенный регистр будет использоваться при автоматическом завершении ключевых слов SQL. Если установлено значение lower или upper, то завершаемое слово будет в нижнем или верхнем регистре соответственно. Если установлено значение preserve-lower или preserve-upper (по умолчанию), то завершаемое слово будет в том же регистре, что и уже введенное начало слова, но последующие слова, завершаемые полностью, будут в нижнем или верхнем регистре соответственно.

DBNAME
Имя базы данных, к которой вы подключены в настоящий момент. Эта переменная устанавливается каждый раз при подключении к базе данных (в том числе при запуске программы), но ее можно изменить или сбросить.

ECHO
Если установлено значение all, все непустые входные строки выдаются в стандартный вывод по мере их прочтения. (Это не относится к строкам, считываемым интерактивно.) Чтобы выбрать это поведение при запуске программы, нужно добавить ключ -a. Если установлено значение queries, psql выдает в стандартный вывод каждый запрос, отправляемый серверу. За выбор этого поведения отвечает ключ -e. Если установлено значение errors, то в стандартном выводе ошибок отображаются только неудавшиеся запросы. За выбор этого поведения отвечает ключ -b. Со значением none (по умолчанию) никакие запросы не отображаются.

ECHO_HIDDEN
Когда для этой переменной установлено значение on и метакоманда обращается к базе данных, сначала показывается текст этого запроса. Эта возможность помогает изучить внутреннее устройство QHB и реализовать похожую функциональность в своих программах. (Чтобы выбрать это поведение при запуске программы, используйте ключ -E.) Если установить для этой переменной значение noexec, запросы будут просто показываться, но не будут отправляться серверу и выполняться. Значение по умолчанию — off.

ENCODING
Текущая кодировка набора символов на стороне клиента. Эта переменная устанавливается всякий раз при подключении к базе данных (в том числе при запуске программы) и при смене кодировки с помощью команды \encoding, но ее можно изменить или сбросить.

ERROR
true в случае ошибки последнего запроса SQL, false в случае его успешного выполнения. См. также SQLSTATE.

FETCH_COUNT
Если в качестве значения этой переменной установлено целое число больше нуля, результаты запросов SELECT извлекаются из базы данных и отображаются группами с заданным количеством строк, в отличие от поведения по умолчанию, когда результирующий набор перед отображением собирается целиком. Таким образом, используется лишь ограниченный объем памяти, независимо от размера результирующего набора. При включении этой функциональности обычно устанавливаются значения от 100 до 1000. Имейте в виду, что при использовании этой функциональности запрос может завершиться ошибкой уже после отображения нескольких строк.

Совет
Хотя с этим функционалом можно использовать любой формат вывода, формат по умолчанию (aligned), как правило, выглядит плохо, потому что каждая группа из FETCH_COUNT строк будет форматироваться отдельно, приводя к разной ширине столбцов в разных группах строк. Другие форматы вывода работают лучше.

HIDE_TABLEAM
Если для этой переменной установлено значение true, информация о методах доступа таблицы не отображается. Это полезно в основном для регрессионных тестов.

HIDE_TOAST_COMPRESSION
Если для этой переменной установлено значение true, информация о методах сжатия столбцов не отображается. Это полезно в основном для регрессионных тестов.

HISTCONTROL
Если для этой переменной установлено значение ignorespace, строки, начинающиеся с пробела, не добавляются в историю. Если установлено значение ignoredups, в историю не добавляются строки, соответствующие тем, которые в ней уже есть. Значение ignoreboth объединяет эти два варианта. Если установлено значение none (по умолчанию), все строки, считываемые в интерактивном режиме, сохраняются в списке истории.

Примечание
Эта функциональность была взята из Bash.

HISTFILE
Имя файла, который будет использоваться для сохранения истории. При сбросе этой переменной имя файла берется из переменной среды PSQL_HISTORY. Если и эта переменная не установлена, имя по умолчанию — ~/.psql_history. Например, если в ~/.psqlrc записать:

\set HISTFILE ~/.psql_history-:DBNAME

psql будет вести отдельный файл истории для каждой базы данных.

Примечание
Эта функциональность была взята из Bash.

HISTSIZE
Максимальное количество команд, которые будут сохраняться в истории команд (по умолчанию 500). Если установлено отрицательное значение, ограничение не накладывается.

Примечание
Эта функциональность была взята из Bash.

HOST
Имя хоста, где работает сервер баз данных, к которому вы подключены в настоящий момент. Эта переменная устанавливается всякий раз при подключении к базе данных (включая запуск программы), но ее можно изменить или сбросить.

IGNOREEOF
Если установлено значение 1 или меньше, передача символа конца файла (обычно вводится Ctrl+D) интерактивному сеансу psql завершит работу приложения. Если установлено большее числовое значение, оно определяет, сколько последовательных символов конца файла нужно ввести, чтобы завершить интерактивный сеанс. Если для переменной установлено значение, отличное от числового, оно воспринимается как 10. Значение по умолчанию — 0.

Примечание
Эта функциональность была взята из Bash.

LASTOID
Значение последнего обработанного OID, возвращенного командой INSERT или \lo_import. Эта переменная будет гарантированно иметь корректное значение лишь до тех пор, пока не будет отображен результат следующей команды SQL. Серверы QHB последней версии больше не поддерживают системные столбцы OID, поэтому при обращении к таким серверам после INSERT значение LASTOID всегда будет равно 0.

LAST_ERROR_MESSAGE
LAST_ERROR_SQLSTATE
Основное сообщение об ошибке и связанный код SQLSTATE для последнего неудавшегося запроса в текущем сеансе psql или пустая строка и 00000, если в текущем сеансе не происходили ошибки.

ON_ERROR_ROLLBACK
Когда установлено значение on, если оператор в блоке транзакции выдает ошибку, эта ошибка игнорируется, и транзакция продолжается. Когда установлено значение interactive, такие ошибки игнорируются только в интерактивных сеансах, но не при чтении файлов скриптов. Когда установлено значение off (по умолчанию), оператор в блоке транзакции, выдающий ошибку, прерывает всю транзакцию. В режиме отката транзакции при ошибке непосредственно перед каждой командой в блоке транзакции неявно выполняется команда SAVEPOINT, и в случае неудачного завершения команды происходит откат к этой точке сохранения.

ON_ERROR_STOP
По умолчанию после возникновения ошибки обработка команд продолжается. Когда для этой переменной установлено значение on, обработка команд будет немедленно прекращена. В интерактивном режиме psql вернется в командную строку; иначе psql завершит работу, вернув код ошибки 3, чтобы отличить этот случай от состояний критической ошибки, о которых сообщается с помощью кода ошибки 1. В любом случае выполнение всех запущенных в настоящий момент скриптов (высокоуровневого скрипта и любых других, которые он мог запустить) будет немедленно прекращено. Если высокоуровневая командная строка содержит несколько команд SQL, выполнение остановится на текущей команде.

PORT
Порт сервера баз данных, к которому вы подключены в настоящий момент. Эта переменная устанавливается всякий раз при подключении к базе данных (включая запуск программы), но ее можно изменить или сбросить.

PROMPT1
PROMPT2
PROMPT3
Эти переменные определяют, как должны выглядеть приглашения psql. См. ниже в параграфе Настройка приглашений.

QUIET
Установка для этой переменной значения on равнозначна параметру командной строки -q. Вероятно, она не слишком полезна в интерактивном режиме.

ROW_COUNT
Количество строк, возвращенных или обработанных последним запросом SQL, или 0, если запрос завершился с ошибкой или не выдал количество строк.

SERVER_VERSION_NAME
SERVER_VERSION_NUM
Номер версии сервера в виде строки, например 9.6.2, 1.5 или 11beta1, и в числовом виде, например 90602 или 100001. Эта переменная устанавливается всякий раз при подключении к базе данных (включая запуск программы), но ее можно изменить или сбросить.

SHOW_CONTEXT
Для этой переменной можно установить значения never (никогда), errors (ошибки) или always (всегда), управляющие тем, будут ли в сообщениях с сервера отображаться поля CONTEXT. По умолчанию установлено значение errors (то есть контекст будет выводиться в сообщениях об ошибках, но не в замечаниях или предупреждениях). Этот параметр не действует, когда в VERBOSITY установлен уровень terse или sqlstate. (Если вам нужна подробная версия только что полученной ошибки, см. также описание команды \errverbose.)

SINGLELINE
Установка для этой переменной значения on равнозначна параметру командной строки -S.

SINGLESTEP
Установка для этой переменной значения on равнозначна параметру командной строки -s.

SQLSTATE
Код ошибки (см. раздел Приложения Коды ошибок QHB), связанной с неудачным выполнением последнего запроса SQL, или 00000, если тот завершился успешно.

USER
Пользователь базы данных, который подключен в настоящий момент. Эта переменная устанавливается всякий раз при подключении к базе данных (включая запуск программы), но ее можно изменить или сбросить.

VERBOSITY
Для этой переменной можно установить значения default, verbose, terse или sqlstate для управления уровнем детализации в сообщениях об ошибках. (Если вам нужна подробная версия только что полученной ошибки, см. также описание команды \errverbose.)

VERSION
VERSION_NAME
VERSION_NUM
Эти переменные устанавливаются при запуске программы и отражают версию psql в виде развернутой строки, краткой строки (например 9.6.2, 1.5 или 11beta1) и числа (например 90602 или 100001) соответственно. Их можно изменить или сбросить.

Интерполяция SQL

Ключевой особенностью переменных psql является возможность подставлять («интерполировать») их в обычные операторы SQL, а также в аргументы метакоманд. Помимо этого psql предоставляет средства для обеспечения корректного применения кавычек для значений переменных, используемых как литералы и идентификаторы SQL. Чтобы интерполировать значение без кавычек, необходимо добавить перед именем переменной двоеточие (:). Например,

testdb=> \set foo 'my_table'
testdb=> SELECT * FROM :foo;

будет запрашивать таблицу my_table. Обратите внимание, что это может быть небезопасно: значение переменной копируется буквально, поэтому может содержать непарные кавычки или даже метакоманды. При его применении следует убедиться, что это имеет смысл.

Когда значение предназначено к использованию в качестве литерала или идентификатора SQL, безопаснее всего заключить его в кавычки. Для заключения в кавычки значения переменной, используемого как литерал SQL, необходимо после двоеточия написать имя переменной в апострофах. Для заключения в кавычки значения переменной, используемого как идентификатор SQL, необходимо после двоеточия написать имя переменной в кавычках. Эти конструкции корректно работают с кавычками и другими специальными символами, содержащимися в значении переменной. Предыдущий пример безопаснее было бы записать так:

testdb=> \set foo 'my_table'
testdb=> SELECT * FROM :"foo";

Интерполяция переменных не будет выполняться в литералах и идентификаторах SQL, заключенных в кавычки. Поэтому такая конструкция, как ':foo', не приведет к превращению значения переменной во взятый в кавычки литерал (и если бы это работало, это было бы небезопасно, поскольку не получилось бы корректно обработать кавычки внутри значения переменной).

Один из примеров использования этого механизма — копирование содержимого файла в столбец таблицы. Сперва загрузим файл в переменную, а затем интерполируем значение переменной как строку в кавычках:

testdb=> \set content `cat my_file.txt`
testdb=> INSERT INTO my_table VALUES (:'content');

(Обратите внимание, что это все равно не будет работать, если my_file.txt содержит байты NUL. psql не поддерживает байты NUL в значениях переменных.)

Поскольку двоеточие вполне законно может присутствовать в командах SQL, явная попытка интерполяции (например, для :name, :'name' или :"name") не выполняется, если именованная переменная в настоящий момент не установлена. В любом случае можно экранировать двоеточие с помощью обратного слэша, чтобы защитить его от подстановки.

Специальный синтаксис :{?имя} возвращает TRUE или FALSE в зависимости от того, существует ли такая переменная, и поэтому всегда подменяется значением, если только двоеточие не экранировано обратным слэшем.

Синтаксис с двоеточием для переменных является стандартом SQL для встраиваемых языков запросов, таких как ECPG. Синтаксисы с двоеточием для срезов массивов и приведения типов являются расширениями QHB, которые иногда могут конфликтовать со стандартным использованием. Синтаксис с двоеточием и кавычками для экранирования значения переменной при подстановке в качестве литерала или идентификатора SQL является расширением psql.

Настройка приглашений

Приглашения, выдаваемые psql, можно настроить по своему вкусу. Три переменные PROMPT1, PROMPT2 и PROMPT3 содержат строки и специальные управляющие последовательности, описывающие внешний вид приглашения. Приглашение 1 (PROMPT1) — это обычное приглашение, которое выдается, когда psql запрашивает ввод новой команды. Приглашение 2 (PROMPT2) выдается, когда при вводе команды ожидаются дополнительные данные, например, потому что команда не была завершена точкой с запятой или не закрыты кавычки. Приглашение 3 (PROMPT3) выдается при выполнении команды SQL COPY FROM STDIN, когда в терминале нужно ввести значение новой строки.

Значение выбранных переменных приглашений выводится буквально, за исключением случаев, когда в нем фигурирует знак процента (%). В зависимости от следующего символа будет подставляться определенный текст. Описаны следующие подстановки:

%M
Полное имя хоста (с именем домена) сервера баз данных, или [local], если подключение выполнено через сокет домена Unix, или [local:/каталог/имя], если при компиляции было изменено расположение сокета домена Unix по умолчанию.

%m
Имя хоста сервера баз данных, усеченное до первой точки, или [local], если подключение выполнено через сокет домена Unix.

%>
Номер порта, который прослушивает сервер баз данных.

%n
Имя пользователя базы данных в текущем сеансе. (Это значение может меняться в течение сеанса в результате выполнения команды SET SESSION AUTHORIZATION.)

%/
Имя текущей базы данных.

%~
Похоже на %/, но выводит ~ (тильду), если текущая база данных является базой данных по умолчанию.

%#
Если пользователь сеанса является суперпользователем базы данных, то выводит #, иначе >. (Это значение может меняться в течение сеанса в результате выполнения команды SET SESSION AUTHORIZATION.)

%p
PID обслуживающего процесса для текущего подключения.

%R
В приглашении 1 это обычно =, но будет @, если сеанс находится в неактивной ветви блока условия, либо ^ в однострочном режиме, либо !, если сеанс не подключен к базе данных (что может произойти, если \connect завершилась неудачно). В приглашении 2 %R заменяется символом, который зависит от того, почему psql ожидает ввод дополнительных строк: -, если команда просто еще не была завершена; *, если есть незавершенный комментарий /* ... */; апостроф, если не завершена строка в апострофах; кавычки, если не завершен идентификатор в кавычках; знак доллара, если не завершена стока в долларах, или (, если не закрыты скобки. В приглашении 3 %R не выдает ничего.

%x
Состояние транзакции: пустая строка, когда она не в блоке транзакции; *, когда в блоке транзакции; !, когда она в блоке транзакции, в котором произошла ошибка; ?, когда состояние транзакции не определено (например, потому что отсутствует подключение).

%l
Номер строки в текущем операторе, начиная с 1.

%цифры
Подставляется символ с указанным восьмеричным кодом.

%:имя:
Значение переменной psql с заданным именем. Подробную информацию см. выше в параграфе Переменные.

%`команда`
Вывод команды; схоже с обычной подстановкой с обратными апострофами.

%[ ... %]
Приглашения могут содержать символы управления терминалом, которые, например, меняют цвет, фон и стиль текста приглашения или изменяют заголовок окна терминала. Для того чтобы возможности редактирования строк Readline работали как следует, эти невыводимые управляющие символы нужно сделать невидимыми, расположив их между %[ и %]. В приглашении может находиться несколько таких включений. Например:

testdb=> \set PROMPT1 '%[%033[1;33;40m%]%n@%/%R%[%033[0m%]%# '

сделает жирное (1;), желтое на черном фоне (33;40) приглашение для VT100-совместимых цветных терминалов.

%w
Пробелы той же ширины, что и в последнем выведенном приглашении PROMPT1. Это можно использовать при установке PROMPT2, чтобы многострочные операторы были выровнены по первой строке, но при этом вторичного приглашения не было видно.

Чтобы вставить знак процента, напишите %%. По умолчанию для приглашений 1 и 2 используется значение '%/%R%x%# ', а для приглашения 3 — '>> '.

Примечание
Эта функциональность была взята из tcsh.

Редактирование командной строки

psql поддерживает библиотеку Readline для удобного редактирования и извлечения командной строки. История команд автоматически сохраняется при выходе из psql и загружается при ее запуске. Кроме того, поддерживается завершение клавишей Tab, хотя логика завершения не претендует на роль анализатора SQL. Кроме того, запросы, генерируемые завершением по Tab, могут создавать помехи для других команд SQL, например SET TRANSACTION ISOLATION LEVEL. Если по какой-то причине вам не нравится завершение по Tab, его можно выключить, записав в файле .inputrc в вашем домашнем каталоге следующее:

$if psql
set disable-completion on
$endif

(Это функциональность не psql, а Readline. Дополнительную информацию см. в документации к Readline.)

Переменные среды

COLUMNS
Если \pset columns равна нулю, управляет шириной формата вывода wrapped, а также шириной, определяющей, требуется ли использовать в широком выводе программу листания или нужно переключиться в вертикальный формат, в режиме expanded auto.

PGDATABASE
PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию (см. раздел Переменные среды).

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

PSQL_EDITOR
EDITOR
VISUAL
Редактор, используемый командами \e, \ef и \ev. Эти переменные проверяются в вышеуказанном порядке; применяется первая установленная переменная. Если ни одна из них не установлена, по умолчанию в системах Unix используется vi.

PSQL_EDITOR_LINENUMBER_ARG
Когда команда \e, \ef или \ev используется с аргументом номера строки, эта переменная задает аргумент командной строки, применяемый для передачи начального номера строки в редактор пользователя. Для таких редакторов, как Emacs или vi, это знак плюс. Добавьте в конец значения переменной пробел, если он требуется для разделения имени параметра и номера строки. Примеры:

PSQL_EDITOR_LINENUMBER_ARG='+'
PSQL_EDITOR_LINENUMBER_ARG='--line '

Значением по умолчанию является + в системах Unix (соответствует редактору по умолчанию vi и полезно для многих других распространенных редакторов).

PSQL_HISTORY
Альтернативное расположение файла истории команд. Выполняется добавление тильды (~).

PSQL_PAGER
PAGER
Если результаты запросов не помещаются на экране, они пропускаются через эту команду. Обычные значения это more или less. Использование программы листания можно выключить, присвоив переменной PSQL_PAGER или PAGER пустую строку или изменив связанные с этой программой параметры в команде \pset. Эти переменные проверяются в вышеуказанном порядке применяется первая установленная переменная. Если ни одна из них не установлена, на большинстве платформ по умолчанию используется more, а на Cygwin — less.

PSQLRC
Альтернативное расположение пользовательского файла .psqlrc. Выполняется добавление тильды (~).

SHELL
Команда оболочки, выполняемая командой \!.

TMPDIR
Каталог для хранения временных файлов. По умолчанию /tmp.

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Файлы

psqlrc и ~/.psqlrc

При запуске без параметра -X psql пытается считать и выполнить команды из общесистемного файла запуска (psqlrc), а затем из пользовательского файла запуска (~/.psqlrc) после подключения к базе данных, но до получения обычных команд. Эти файлы можно использовать для настройки клиента и/или сервера по своему вкусу, обычно с помощью команд \set и SET.

Общесистемный файл запуска называется psqlrc и ищется в каталоге установки «конфигурация системы», который надежнее всего определяется с помощью команды pg_config --sysconfdir. По умолчанию этот каталог будет расположен в ../etc/ относительно каталога, содержащего исполняемые файлы QHB. Имя этого каталога можно задать явно через переменную среды PGSYSCONFDIR.

Персональный файл запуска называется .psqlrc и ищется в домашнем каталоге вызывающего пользователя. Расположение пользовательского файла запуска можно задать явно через переменную среды PSQLRC.

Как общесистемный, так и персональный файлы запуска можно привязать к конкретной версии psql, добавив к имени файла старший или младший номер релиза QHB, например, ~/.psqlrc-1.2 или ~/.psqlrc-1.5.1. При наличии нескольких файлов будет прочитан файл с наиболее детальным номером версии.

.psql_history

История командной строки хранится в файле ~/.psql_history.

Расположение файла истории можно задать явно через переменную psql HISTFILE или через переменную среды PSQL_HISTORY.

Примечания

psql лучше всего работает с серверами той же или более старой основной версии. Сбой метакоманд наиболее вероятен, если версия сервера новее версии самой psql. Общая функциональность выполнения команд SQL и отображение результатов запросов должны работать на серверах с более новой основной версией, но это не гарантируется во всех случаях.

Если вы хотите использовать psql для подключения к нескольким серверам с различными основными версиями, рекомендуется использовать последнюю версию psql. Как вариант, можно держать под рукой копии psql от каждой основной версии и использовать ту, которая соответствует версии сервера. Но на практике в этих дополнительных сложностях нет необходимости.

Примеры

Первый пример показывает, как распределить команду на несколько строк ввода. Обратите внимание на изменение приглашения:

testdb=> CREATE TABLE my_table (
testdb(>  first integer not null default 0,
testdb(>  second text)
testdb-> ;
CREATE TABLE

Теперь снова посмотрим на определение таблицы:

testdb=> \d my_table
              Table "public.my_table"
 Column |  Type   | Collation | Nullable | Default
--------+---------+-----------+----------+---------
 first  | integer |           | not null | 0
 second | text    |           |          |

Теперь изменим приглашение на что-нибудь более интересное:

testdb=> \set PROMPT1 '%n@%m %~%R%# '
peter@localhost testdb=>

Предположим, что вы внесли данные в таблицу и хотите на них посмотреть:

peter@localhost testdb=> SELECT * FROM my_table;
 first | second
-------+--------
     1 | один
     2 | два
     3 | три
     4 | четыре
(4 rows)

Таблицу можно вывести разными способами с помощью команды \pset:

peter@localhost testdb=> \pset border 2
Установлен стиль границ 2.
peter@localhost testdb=> SELECT * FROM my_table;
+-------+--------+
| first | second |
+-------+--------+
|     1 | один   |
|     2 | два    |
|     3 | три    |
|     4 | четыре |
+-------+--------+
(4 rows)

peter@localhost testdb=> \pset border 0
Установлен стиль границ 0.
peter@localhost testdb=> SELECT * FROM my_table;
first second
----- ------
    1 один
    2 два
    3 три
    4 четыре
(4 rows)

peter@localhost testdb=> \pset border 1
Установлен стиль границ 1.
peter@localhost testdb=> \pset format csv
Формат вывода: csv.
peter@localhost testdb=> \pset tuples_only
Режим вывода только кортежей включен.
peter@localhost testdb=> SELECT second, first FROM my_table;
один,1
два,2
три,3
четыре,4
peter@localhost testdb=> \pset format unaligned
Формат вывода: unaligned.
peter@localhost testdb=> \pset fieldsep '\t'
Разделитель полей: "    ".
peter@localhost testdb=> SELECT second, first FROM my_table;
один      1
два       2
три       3
четыре    4

Либо можно использовать короткие команды:

peter@localhost testdb=> \a \t \x
Формат вывода: aligned.
Режим вывода только кортежей выключен.
Расширенный вывод включен.
peter@localhost testdb=> SELECT * FROM my_table;
-[ RECORD 1 ]-
first  | 1
second | один
-[ RECORD 2 ]-
first  | 2
second | два
-[ RECORD 3 ]-
first  | 3
second | три
-[ RECORD 4 ]-
first  | 4
second | четыре

Кроме того, эти параметры формата вывода можно задать только для одного запроса, используя команду \g:

peter@localhost testdb=> SELECT * FROM my_table
peter@localhost testdb-> \g (format=aligned tuples_only=off expanded=on)
-[ RECORD 1 ]-
first  | 1
second | один
-[ RECORD 2 ]-
first  | 2
second | два
-[ RECORD 3 ]-
first  | 3
second | три
-[ RECORD 4 ]-
first  | 4
second | четыре

Вот пример использования команды \df для нахождения только тех функций, имя которых соответствует шаблону int*pl, а второй аргумент имеет тип bigint:

testdb=> \df int*pl * bigint
                          List of functions
   Schema   |  Name   | Result data type | Argument data types | Type
------------+---------+------------------+---------------------+------
 pg_catalog | int28pl | bigint           | smallint, bigint    | func
 pg_catalog | int48pl | bigint           | integer, bigint     | func
 pg_catalog | int8pl  | bigint           | bigint, bigint      | func
(3 rows)

Когда это уместно, результаты запросов можно показать в виде перекрестной таблицы с помощью команды \crosstabview:

testdb=> SELECT first, second, first > 2 AS gt2 FROM my_table;
 first | second | gt2
-------+--------+-----
     1 | один   | f
     2 | два    | f
     3 | три    | t
     4 | четыре | t
(4 rows)

testdb=> \crosstabview first second
 first | один | два | три | четыре
-------+------+-----+-----+--------
     1 | f    |     |     |
     2 |      | f   |     |
     3 |      |     | t   |
     4 |      |     |     | t
(4 rows)

Второй пример показывает таблицу умножения со строками, отсортированными в обратном числовом порядке, и столбцами, отсортированными независимо, в порядке возрастания числовых значений.

testdb=> SELECT t1.first as "A", t2.first+100 AS "B", t1.first*(t2.first+100) as "AxB",
testdb(> row_number() over(order by t2.first) AS ord
testdb(> FROM my_table t1 CROSS JOIN my_table t2 ORDER BY 1 DESC
testdb(> \crosstabview "A" "B" "AxB" ord
 A | 101 | 102 | 103 | 104
---+-----+-----+-----+-----
 4 | 404 | 408 | 412 | 416
 3 | 303 | 306 | 309 | 312
 2 | 202 | 204 | 206 | 208
 1 | 101 | 102 | 103 | 104
(4 rows)

reindexdb

reindexdb — переиндексировать базу данных QHB

Синтаксис

reindexdb [параметр-подключения...] [параметр...] [ -S | --schema схема ] ... [ -t | --table таблица ] ... [ -i | --index индекс ] ... [имя_бд]

reindexdb [параметр-подключения...] [параметр...] -a | --all

reindexdb [параметр-подключения...] [параметр...] -s | --system [имя_бд]

Описание

reindexdb — это утилита для перестроения индексов в базе данных QHB.

reindexdb — это обертка над командой SQL REINDEX. По сути между переиндексацией базы данных с помощью этой утилиты или посредством иных методов обращения к серверу нет никакой разницы.

Параметры

Утилита reindexdb принимает следующие аргументы командной строки:

-a
--all
Переиндексировать все базы данных.

--concurrently
Использовать параметр CONCURRENTLY. Подробную информацию обо всех особенностях этого параметра см. на справочной странице команды REINDEX.

[-d] имя_бд
[--dbname=]имя_бд
Указывает имя базы данных для переиндексации, когда не используется параметр -a/--all. Если этот параметр не указан, имя базы данных считывается из переменной среды PGDATABASE. Если она не установлена, используется имя пользователя, указанное для подключения. В имени_бд может задаваться строка подключения. В этом случае параметры в строке подключения переопределяют любые конфликтующие параметры в командной строке.

-e
--echo
Отобразить на экране команды, которые reindexdb генерирует и отправляет на сервер.

-i индекс
--index=индекс
Пересоздать только индекс. Задав ключ -i несколько раз, можно пересоздать несколько индексов.

-j число_заданий
--jobs= число_заданий
Выполнять команды переиндексации параллельно, запустив их одновременно в количестве число_заданий. Этот параметр может сократить время обработки, но при этом увеличивает нагрузку на сервер баз данных.
reindexdb откроет число_заданий соединений с базой данных, поэтому необходимо установить такое значение параметра max_connections, которое бы покрыло все эти соединения.
Обратите внимание, что этот параметр несовместим с параметрами --index и --system.

-q
--quiet
Не отображать сообщения о прогрессе выполнения.

-s
--system
Переиндексировать только системные каталоги базы данных.

-S схема
--schema=схема
Переиндексировать только схему. Задав ключ -S несколько раз, можно переиндексировать несколько схем.

-t таблица
--table=таблица
Переиндексировать только таблицу. Задав ключ -t несколько раз, можно переиндексировать несколько таблиц.

--tablespace=табличное_пространство
Задает табличное пространство, где перестраиваются индексы. (Это имя обрабатывается как идентификатор в кавычках.)

-v
--verbose
Выводить подробную информацию во время обработки.

-V
--version
Вывести версию reindexdb и завершиться.

-?
--help
Показать справку об аргументах командной строки reindexdb и завершиться.

Кроме того, в качестве параметров подключения reindexdb принимает следующие аргументы командной строки:

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как reindexdb автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, reindexdb потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

--maintenance-db=имя_бд
Задает имя базы данных, к которой будет производиться подключение для выявления подлежащих переиндексации баз данных, когда используется параметр -a/--all. Если этот параметр не указан, будет выбрана база данных qhb, а если она не существует — template1. В данном аргументе может задаваться строка подключения. В этом случае параметры в строке подключения переопределят все конфликтующие параметры в командной строке. Кроме того, параметры в строке подключения, отличающиеся от самого имени базы данных, будут повторно использоваться при подключении к другим базам данных.

Переменные среды

PGDATABASE
PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

В случае возникновения затруднений обратитесь к справочным страницам REINDEX и psql, где рассматриваются потенциальные проблемы и сообщения об ошибках. Сервер баз данных должен работать на целевом хосте. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Примечания

Утилите reindexdb может потребоваться несколько раз подключиться к серверу QHB, каждый раз запрашивая пароль. В таких случаях удобно иметь файл ~/.pgpass. Дополнительную информацию см. в разделе Файл паролей.

Примеры

Переиндексация базы данных test:

$ reindexdb test`

Переиндексация таблицы foo и индекса bar в базе данных abcd:

$ reindexdb --table=foo --index=bar abcd`

См. также

REINDEX

vacuumdb

vacuumdb — провести очистку памяти и анализ базы данных QHB

Синтаксис

vacuumdb [параметр-подключения...] [параметр...] [ -t | --table таблица [( столбец [,...] )] ] ... [имя_бд]

vacuumdb [параметр-подключения...] [параметр...] -a | --all

Описание

vacuumdb — это утилита для очистки базы данных QHB. Кроме того, vacuumdb генерирует внутреннюю статистику, используемую оптимизатором запросов QHB.

vacuumdb — это обертка над командой SQL VACUUM. По сути, между очисткой и анализом баз данных с помощью этой утилиты или посредством иных методов обращения к серверу нет никакой разницы.

Параметры

Утилита vacuumdb принимает следующие аргументы командной строки:

-a
--all
Очистить все базы данных.

[-d] имя_бд
[--dbname=]имя_бд
Указывает имя базы данных для очистки или анализа, когда не используется параметр -a/--all. Если этот параметр не указан, имя базы данных считывается из переменной среды PGDATABASE. Если она не установлена, используется имя пользователя, указанное для подключения. В имени_бд может задаваться строка подключения. В этом случае параметры в строке подключения переопределяют любые конфликтующие параметры в командной строке.

--disable-page-skipping
Выключить пропуск страниц на основе содержимого карты видимости.

-e
--echo
Отобразить на экране команды, которые vacuumdb генерирует и отправляет на сервер.

-f
--full
Выполнить «полную» очистку.

-F
--freeze
Агрессивно «замораживать» кортежи.

--force-index-cleanup
Всегда удалять записи индекса, указывающие на неиспользуемые кортежи.

-j число_заданий
--jobs= число_заданий
Выполнять команды очистки или анализа параллельно, запустив их одновременно в количестве число_заданий. Этот параметр может сократить время обработки, но при этом увеличивает нагрузку на сервер баз данных.
vacuumdb откроет число_заданий соединений с базой данных, поэтому необходимо установить такое значение параметра max_connections, которое бы покрыло все эти соединения.
Обратите внимание, что использование этого режима вместе с параметром -f (FULL) может привести к сбоям вследствие взаимоблокировок, если параллельно обрабатываются определенные системные каталоги.

--min-mxid-age возраст_мультитранзакции
Выполнять очистку или анализ только для таблиц с возрастом идентификатора мультитранзакции не менее указанного возраста_мультитранзакции. Этот параметр полезен при определении наиболее приоритетных таблиц, обработка которых предотвратит зацикливание идентификаторов мультитранзакций (см. подраздел Мультитранзакции и зацикливание).
В контексте этого параметра возрастом идентификатора мультитранзакции отношения является наибольший из возрастов основного отношения и связанной с ним таблицы TOAST, если таковая существует. Поскольку команды, выполняемые vacuumdb, при необходимости будут обрабатывать и связанную с отношением таблицу TOAST, их возрасты не нужно рассматривать по отдельности.

--min-xid-age возраст_транзакции
Выполнять очистку или анализ только для таблиц с возрастом идентификатора транзакции не менее указанного возраста_транзакции. Этот параметр полезен при определении наиболее приоритетных таблиц, обработка которых предотвратит зацикливание идентификаторов транзакций (см. подраздел Предотвращение ошибок зацикливания идентификаторов транзакций).
В контексте этого параметра возрастом идентификатора транзакции отношения является наибольший из возрастов основного отношения и связанной с ним таблицы TOAST, если таковая существует. Поскольку команды, выполняемые vacuumdb, при необходимости будут обрабатывать и связанную с отношением таблицу TOAST, их возрасты не нужно рассматривать по отдельности.

--no-index-cleanup
На удалять записи индекса, указывающие на неиспользуемые кортежи.

--no-process-toast
Пропускать таблицу TOAST, связанную с очищаемой таблицей (если таковая имеется).

--no-truncate
Не усекать пустые страницы в конце таблицы.

-P параллельные_рабочие_процессы
--parallel=параллельные_рабочие_процессы
Задать количество параллельных рабочих процессов для параллельной очистки. Это позволяет при очистке эффективно использовать несколько ЦП для обработки индексов. См. VACUUM.

-q
--quiet
Не отображать сообщения о прогрессе выполнения.

--skip-locked
Пропускать отношения, которые невозможно немедленно заблокировать для обработки.

-t таблица [ (столбец [,...]) ]
--table=таблица [ (столбец [,...]) ]
Очистить или проанализировать только таблицу. Имена столбцов можно указать только при наличии параметров --analyze или --analyze-only. Задав ключ -t несколько раз, можно очистить несколько таблиц.

Совет
Если задаете столбцы, скорее всего, вам придется экранировать скобки в оболочке. (См. примеры ниже.)

-v
--verbose
Выводить подробную информацию во время обработки.

-V
--version
Вывести версию vacuumdb и завершиться.

-z
--analyze
Дополнительно вычислить статистику для оптимизатора.

-Z
--analyze-only
Только вычислить статистику для оптимизатора (без очистки).

--analyze-in-stages
Только вычислить статистику для оптимизатора (без очистки), как с параметром --analyze-only. Чтобы быстрее получить полезную статистику, выполняется в несколько этапов (в настоящее время их три) с различными параметрами конфигурации.
Этот параметр полезен для анализа базы данных, которая только что была заполнена при восстановлении данных из дампа или с помощью утилиты qhb_upgrade. С ним vacuumdb попытается создать некоторую статистику как можно быстрее, чтобы сделать базу данных пригодной для использования, а затем, на последующих этапах, получить полную статистику.

-?
--help
Показать справку об аргументах командной строки vacuumdb и завершиться.

Кроме того, в качестве параметров подключения vacuumdb принимает следующие аргументы командной строки:

-h хост
--host=хост
Задает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как vacuumdb автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, vacuumdb потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

--maintenance-db=имя_бд
Задает имя базы данных, к которой будет производиться подключение для выявления подлежащих очистке баз данных, когда используется параметр -a/--all. Если этот параметр не указан, будет выбрана база данных qhb, а если она не существует — template1. В данном аргументе может задаваться строка подключения. В этом случае параметры в строке подключения переопределят все конфликтующие параметры в командной строке. Кроме того, параметры в строке подключения, отличающиеся от самого имени базы данных, будут повторно использоваться при подключении к другим базам данных.

Переменные среды

PGDATABASE
PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Диагностика

В случае возникновения затруднений обратитесь к справочным страницам VACUUM и psql, где рассматриваются потенциальные проблемы и сообщения об ошибках. Сервер баз данных должен работать на целевом хосте. Кроме того, будут применяться параметры подключения и переменные среды, которые по умолчанию используются библиотекой libpq на стороне клиента.

Примечания

Утилите vacuumdb может потребоваться несколько раз подключиться к серверу QHB, каждый раз запрашивая пароль. В таких случаях удобно иметь файл ~/.pgpass. Дополнительную информацию см. в разделе Файл паролей.

Примеры

Очистка базы данных test:

$ vacuumdb test

Очистка и анализ для оптимизатора базы данных с именем bigdb:

$ vacuumdb --analyze bigdb

Очистка одной таблицы foo в базе данных с именем xyzzy и анализ одного столбца bar этой таблицы для оптимизатора:

$ vacuumdb --analyze --verbose --table='foo(bar)' xyzzy

См. также

VACUUM

Серверные приложения QHB

В этом разделе содержится справочная информация о серверных приложениях и вспомогательных утилитах QHB. Эти команды будут эффективны только на том хосте, где находится сервер баз данных. Другие служебные программы перечислены в разделе Клиентские приложения QHB.

Содержание

qhb_bootstrap — создать новый кластер баз данных QHB (функциональный аналог initdb, полностью переделан с учетом особенностей QHB)

initdb — создать новый кластер баз данных QHB

qhb_archivecleanup — очистить архивные файлы WAL QHB

qhb_checksums — включить, выключить или проверить контрольные суммы данных в кластере баз данных QHB

qhb_controldata — отобразить управляющую информацию кластера баз данных QHB

qhb_ctl — инициализировать, запустить, остановить или управлять сервером QHB

qhb_resetwal — сбросить журнал упреждающей записи и другую управляющую информацию кластера баз данных QHB

qhb_rewind — синхронизировать каталог данных QHB с другим каталогом данных, который был ответвлен от него

qhb_test_fsync — определить самый быстрый wal_sync_method для QHB

qhb_test_timing — оценить издержки измерения времени

qhb_upgrade — обновить экземпляр сервера QHB

qhb_waldump — отобразить журнал упреждающей записи кластера баз данных QHB в удобочитаемом виде

initdb

initdb — создать новый кластер баз данных QHB

Синтаксис

initdb [параметр...][--pgdata|-D]каталог

Описание

Утилита initdb создает новый кластер баз данных QHB. Кластер — это коллекция баз данных, управляемых одним экземпляром сервера.

Создание кластера базы данных состоит из создания каталогов, в которых будут храниться данные базы, создания общих таблиц каталогов (таблиц, которые принадлежат всему кластеру, а не какой-то конкретной базе данных) и создания баз данных template1 и qhb. При последующем создании новой базы данных все данные будут скопированы из базы-шаблона template1. (Следовательно, все, что будет добавлено в template1, автоматически скопируется в каждую новую базу данных.) База данных qhb — это база данных по умолчанию, предназначенная для использования пользователями, утилитами и сторонними приложениями.

Хотя initdb попытается создать указанный каталог данных, ей может не хватить прав, если родительский каталог нужного каталога данных принадлежит суперпользователю root. В такой ситуации для запуска этой операции необходимо создать пустой каталог данных от имени root, затем назначить владельцем этого каталога пользователя базы данных с помощью chown, после чего выполнить su, чтобы стать пользователем базы данных и запустить initdb.

initdb должна выполняться от имени пользователя, которому будет принадлежать серверный процесс, так как серверу необходим доступ к файлам и каталогам, создаваемым initdb. Поскольку сервер не может запуститься от имени суперпользователя root, initdb тоже нельзя выполнять от его имени. (На самом деле, в таком случае выполнение initdb будет отклонено.)

По соображениям безопасности новый кластер, созданный initdb, будет доступен только владельцу кластера по умолчанию. Параметр --allow-group-access позволяет читать файлы в кластере любому пользователю из группы владельца кластера. Это полезно для выполнения резервного копирования от имени непривилегированного пользователя.

initdb инициализирует локаль и кодировку набора символов баз данных кластера, которые будут использоваться по умолчанию. Кодировка, порядок сортировки (LC_COLLATE) и классы наборов символов (LC_CTYPE, например заглавные, строчные буквы, цифры) можно задавать отдельно при создании новой базы данных. initdb определяет эти параметры для базы-шаблона template1, которая будет использоваться по умолчанию для всех остальных баз данных.

Чтобы изменить порядок сортировки по умолчанию или классы наборов символов, используются параметры --lc-collate и --lc-ctype. Порядок сортировки, отличный от C/RUST или POSIX, снижает производительность. По этим причинам при запуске initdb важно выбрать надлежащую локаль.

Остальные категории локалей можно изменить после запуска сервера. Также можно использовать параметр --locale, чтобы задать значения по умолчанию для всех категорий локалей, включая порядок сортировки и классы наборов символов. Все значения локалей сервера (lc_*) можно вывести командой SHOW ALL. Более подробную информацию см. в разделе Поддержка языковых стандартов.

Для изменения кодировки по умолчанию используется параметр --encoding. Более подробную информацию см. в разделе Поддержка кодировок.

Параметры

-A метод_аутентификации
--auth=метод_аутентификации
Этот параметр задает метод аутентификации по умолчанию для локальных пользователей, используемый в файле qhb_hba.conf (строки host и local). initdb предварительно внесет в qhb_hba.conf указанный метод аутентификации как для обычных, так и для репликационных соединений.
Не используйте trust, если не доверяете всем локальным пользователям в вашей системе. Режим trust используется по умолчанию для упрощения процесса установки.

--auth-host=метод_аутентификации
Этот параметр задает метод аутентификации для локальных пользователей, подключающихся по TCP/IP, используемый в qhb_hba.conf (строки host).

--auth-local=метод_аутентификации
Этот параметр задает метод аутентификации для локальных пользователей, подключающихся через сокет домена Unix, используемый в qhb_hba.conf (строки local).

-D каталог
--pgdata=каталог
Этот параметр задает каталог, в котором должен храниться кластер базы данных. Это единственная информация, необходимая initdb, но ее можно не вводить, если установить переменную среды PGDATA. Этот вариант может быть удобен, поскольку впоследствии сервер баз данных (qhb) сможет найти каталог базы данных по этой переменной.

-E кодировка
--encoding=кодировка
Выбирает кодировку шаблона базы данных. Эта кодировка также будет использоваться по умолчанию для любой вновь создаваемой базы данных, если ее не переопределить. Значение по умолчанию устанавливается на основе указанной локали, а если это не работает, выбирается SQL_ASCII. Кодировки, поддерживаемые сервером QHB, описаны в подразделе Поддержка кодировок.

-g
--allow-group-access
Позволяет пользователям из группы владельца кластера читать все файлы кластера, создаваемые утилитой initdb.

-k
--data-checksums
Использовать контрольные суммы в страницах данных, чтобы помочь выявить сбои системы ввода/вывода, которые в противном случае остались бы незамеченными. Включение расчета контрольных сумм может повлечь за собой заметное снижение производительности. Если этот параметр установлен, контрольные суммы будут рассчитываться для всех объектов и во всех базах данных. Все ошибки контрольных сумм будут отображаться в представлении pg_stat_database. Подробную информацию см. в разделе Контрольные суммы данных.

--locale=локаль
Устанавливает локаль по умолчанию для кластера базы данных. Если этот параметр не указан, локаль наследуется от среды, в которой запускается initdb. Поддержка локали описана в разделе Поддержка языковых стандартов.

--lc-collate=локаль
--lc-ctype=локаль
--lc-messages=локаль
--lc-monetary=локаль
--lc-numeric=локаль
--lc-time=локаль
Схожи с --locale, но устанавливают локаль только в указанной категории.

--no-locale
Равнозначен ключу --locale=C.

-N
--no-sync
По умолчанию initdb ожидает, пока все файлы не будут надежно записаны на диск. Этот параметр позволяет initdb завершаться быстрее, без ожидания, но в последующем сбой операционной системы может привести к повреждению каталога данных. В целом, этот параметр полезен для тестирования, но при создании производственной установки его применять не следует.

--no-instructions
По умолчанию initdb после всех сообщений выводит инструкции по запуску кластера. С этим параметром такие инструкции выводиться не будут. Этот параметр предназначен в первую очередь для применения на платформах, где initdb используется специальными программными средствами и где данные инструкции, скорее всего, окажутся некорректными.

--pwfile=имя_файла
Заставляет initdb читать пароль суперпользователя базы данных из файла. В качестве пароля берется первая строка этого файла.

-S
--sync-only
Безопасно записать все файлы базы данных на диск и завершиться. Никакие другие обычные операции initdb не выполняются.

-T конфигурация
--text-search-config=конфигурация
Устанавливает конфигурацию текстового поиска по умолчанию. Дополнительную информацию см. в описании параметра default_text_search_config.

-U имя_пользователя
--username=имя_пользователя
Устанавливает имя суперпользователя базы данных. По умолчанию это имя пользователя операционной системы, запустившего initdb. В действительности, само по себе имя суперпользователя неважно, но этот параметр позволяет оставить привычное имя qhb, даже если имя пользователя операционной системы другое.

-W
--pwprompt
Заставляет initdb запросить пароль, который будет назначен суперпользователю базы данных. Если не планируется использовать аутентификацию по паролю, это не важно. В противном случае этот вариант аутентификации будет неприменим, пока не задан пароль.

-X каталог
--waldir=каталог
Этот параметр задает каталог, в котором должен храниться журнал упреждающей записи.

--wal-segsize=размер
Задает размер сегмента WAL в мегабайтах, то есть размер каждого отдельного файла в журнале WAL. По умолчанию размер составляет 16 мегабайт. Значение должно задаваться степенью 2 от 1 до 1024 (в мегабайтах). Этот параметр можно установить только во время инициализации и нельзя изменить потом.
Этот размер бывает полезно поменять при настройке глубины детализации трансляции или архивации WAL. Кроме того, в базах данных с большим объемом WAL значительное количество файлов WAL в каталоге может вызвать проблемы со стороны производительности и управления. Увеличение размера файлов WAL приведет к уменьшению их количества.

Также доступны и другие, реже используемые параметры:

-d
--debug
Вывести отладочные и другие сообщения загрузчика, менее интересные широкой публике. Загрузчик — это программа, которую initdb использует для создания таблиц каталога. С этим параметром генерируется большое количество отладочных и информационных сообщений.

--discard-caches
Запустить загрузчик с параметром debug_discard_caches=1. Это очень сильно замедлит выполнение и полезно только для углубленной отладки.

-L каталог
Указывает initdb, где следует искать входные файлы для инициализации кластера базы данных. Обычно это не требуется. В случае необходимости приложение само попросит явно указать их местоположение.

-n
--no-clean
По умолчанию, когда initdb выявляет, что ошибка помешала завершить создание кластера базы данных, она удаляет все созданные к тому моменту файлы. Этот параметр предотвращает удаление файлов, вследствие чего полезен для отладки.

Прочие параметры:

-V
--version
Вывести версию initdb и завершиться.

-?
--help
Показать справку об аргументах командной строки initdb и завершиться.

Переменные среды

PGDATA
Задает каталог, в котором будет храниться кластер базы данных; можно переопределить с помощью параметра -D.

PG_COLOR
Указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

TZ
Задает для созданного кластера базы данных часовой пояс по умолчанию. Ее значением должно быть полное название часового пояса (см. подраздел Часовые пояса).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Примечания

initdb также можно вызвать командой qhb_ctl initdb.

См. также

qhb_ctl, qhb

qhb_bootstrap

qhb_bootstrap — создать новый кластер баз данных QHB

Синтаксис

qhb_bootstrap [флаг...] [параметр...] каталог_данных

Описание

Утилита qhb_bootstrap создает новый кластер базы данных QHB. Кластер — это коллекция баз данных под управлением единого экземпляра сервера.

Инициализация кластера баз данных заключается в создании каталогов для хранения данных, формировании общих системных таблиц (относящихся ко всему кластеру, а не к какой-то отдельной базе) и создании баз данных template1 и qhb. Впоследствии все новые базы будут создаваться на основе шаблона template1 (все, что будет добавлено в template1, автоматически скопируется в каждую новую базу данных). База данных qhb — это база данных по умолчанию, предназначенная для использования пользователями, утилитами и сторонними приложениями.

Безопасность

Утилита qhb_bootstrap должна выполняться от имени пользователя, под которым будет запускаться сервер, так как серверу необходим полный доступ к файлам и каталогам, создаваемым qhb_bootstrap. Сервер не может запускаться от имени суперпользователя, поэтому выполнение утилиты qhb_bootstrap от его лица будет отклонено.

Из соображений безопасности новый кластер, созданный qhb_bootstrap, будет доступен только владельцу кластера. Флаг --allow-group-access позволяет читать файлы в кластере всем пользователям, входящим в группу владельца кластера. Это полезно для выполнения резервного копирования от имени непривилегированного пользователя.

Не используйте метод аутентификации trust, если не доверяете всем локальным пользователям в вашей системе. Режим trust используется по умолчанию для облегчения процесса установки.

Локализация

Утилита qhb_bootstrap инициализирует локали и кодировки баз данных кластера, которые будут использоваться по умолчанию. Кодировка, порядок сортировки (LC_COLLATE), классы наборов символов (LC_CTYPE, например заглавные, строчные буквы, цифры) можно задавать отдельно при создании новой базы данных. qhb_bootstrap определяет параметры локали для шаблона template1, которые будут применяться по умолчанию для новых баз. Указанная локаль должна быть установлена в системе (locale -a).

Чтобы изменить порядок сортировки по умолчанию или классы наборов символов, используются параметры --lc-collate и --lc-ctype. Порядок сортировки, отличный от C/RUST или POSIX, оказывает влияние на производительность. Поэтому при выполнении qhb_bootstrap следует тщательно выбирать необходимую и достаточную локаль. См. статью «Еще раз про collation в PostgreSQL».

Остальные категории локалей можно изменить и после запуска сервера. Также можно использовать параметр --locale, чтобы задать локаль для всех категорий одновременно, включая порядок сортировки и классы наборов символов. Все значения локалей сервера (lc_*) можно вывести командой SHOW ALL. Более подробную информацию можно найти в разделе Поддержка языковых стандартов.

Параметры

-A метод_аутентификации
--auth=метод_аутентификации
Этот параметр задает метод аутентификации по умолчанию для локальных пользователей, используемый в файле qhb_hba.conf (строки host и local). Значение по умолчанию — trust.

--auth-host=метод_аутентификации
Этот параметр задает метод аутентификации для локальных пользователей, подключающихся по TCP/IP, используемый в qhb_hba.conf (строки host). Возможные значения: trust, reject, scram_sha_256, md5, password, radius, pam, bsd, ldap, ident, gss, sspi, cert. По умолчанию значение берется из параметра --auth.

--auth-local=метод_аутентификации
Этот параметр задает метод аутентификации для локальных пользователей, подключающихся через сокет домена Unix, используемый в qhb_hba.conf (строки local). Возможные значения: trust, reject, scram_sha_256, md5, password, radius, pam, bsd, ldap, peer. По умолчанию значение берется из параметра --auth.

-D каталог
--pgdata=каталог
Каталог хранения кластера. Это единственный обязательный параметр для утилиты qhb_bootstrap. Может быть задан как флагом -D/--data-dir, так и последним параметром, тогда флаги можно опустить. При этом его можно указать в переменной среды PGDATA, что будет удобно при дальнейшем использовании (QHB обращается к этой же переменной).

-E кодировка
--encoding=кодировка
Выбирает кодировку шаблона базы данных. Эта кодировка также будет использоваться по умолчанию для любой вновь создаваемой базы данных, если ее не переопределить. В настоящий момент поддерживается только UTF8. Значение по умолчанию — UTF8.

--locale=локаль
Устанавливает локаль по умолчанию для кластера базы данных. По умолчанию установлена нейтральная локаль C (C Locale).

--lc-collate=локаль
Устанавливает локаль для порядка сортировки строк. По умолчанию значение берется из параметра --locale.

--lc-ctype=локаль
Устанавливает локаль для классификации символов. По умолчанию значение берется из параметра --locale.

--lc-messages=локаль
Устанавливает язык сообщений. По умолчанию значение берется из параметра --locale.

--lc-monetary=локаль
Устанавливает локаль для форматирования валют. По умолчанию значение берется из параметра --locale.

--lc-numeric=локаль
Устанавливает локаль для форматирования чисел. По умолчанию значение берется из параметра --locale.

--lc-time=локаль
Устанавливает локаль для форматирования даты и времени. По умолчанию значение берется из параметра --locale.

--no-locale
Этот параметр равнозначен --locale=C, т. е. установке нейтральной локали по умолчанию. Нельзя задать одновременно с параметром --locale.

--pwfile=имя_файла
Заставляет qhb_bootstrap читать пароль суперпользователя базы данных из первой строки заданного файла. Параметры pwprompt и pwfile являются взаимоисключающими. Если в параметрах auth-host или auth-local указан метод аутентификации, отличный от trust, то наличие одного из параметров pwprompt или pwfile является обязательным.

-L каталог
--share-dir=каталог
Указывает qhb_bootstrap, где следует искать входные файлы для развертывания кластера базы данных. По умолчанию это share в каталоге из параметра data-dir.

-T конфигурация
--text-search-config=конфигурация
Устанавливает конфигурацию текстового поиска по умолчанию. За дополнительной информацией обратитесь к default_text_search_config. Значение по умолчанию соответствует локали, заданной в параметре lc_ctype, иначе simple.

-U имя_пользователя
--username=имя_пользователя
Устанавливает имя суперпользователя базы данных. По умолчанию это имя пользователя ОС, запустившего qhb_bootstrap. В действительности само по себе имя суперпользователя неважно, но этот параметр позволяет оставить привычное имя qhb, даже если имя пользователя ОС другое.

-X каталог
--waldir=каталог
Этот параметр задает каталог, в котором должен храниться журнал упреждающей записи. По умолчанию это pg_wal в каталоге из параметра data-dir.

--wal-segsize=размер
Задает размер сегмента WAL в мегабайтах, то есть размер каждого отдельного файла в журнале WAL. По умолчанию размер составляет 16 мегабайт. Значение должно задаваться степенью 2 от 1 до 1024 (в мегабайтах). Этот параметр можно установить только во время инициализации и нельзя изменить потом.
Этот размер бывает полезно поменять при тонкой настройке трансляции или архивации WAL. Кроме того, в базах данных с большим объемом WAL значительное количество файлов WAL в каталоге может вызвать проблемы со стороны производительности и управления. Увеличение размера файлов WAL приведет к уменьшению их количества.

Флаги

-d
--debug
Выводит отладочные и другие сообщения загрузчика. Загрузчик — это программа, которую qhb_bootstrap использует для создания таблиц каталога. С этим параметром генерируется большое количество отладочных и информационных сообщений. Значение по умолчанию — false (выключен).

-h
--help
Вывод справочной информации

-g
--allow-group-access
Позволяет пользователям из группы владельца кластера читать все файлы кластера, создаваемые утилитой qhb_bootstrap. Значение по умолчанию — false (выключен).

-k
--data-checksums
Использовать контрольные суммы в страницах данных для выявления сбоев при вводе/выводе, которые иначе останутся незамеченными. Включение расчета контрольных сумм может повлечь за собой заметное снижение производительности. Если этот параметр установлен, контрольные суммы будут рассчитываться для всех объектов и во всех базах данных. Все ошибки контрольных сумм будут видны в представлении pg_stat_database. Подробную информацию см. в разделе Контрольные суммы данных. Значение по умолчанию — false (выключен).

-n
--no-clean
По умолчанию, когда qhb_bootstrap выявляет ошибку на этапе развертывания кластера, она удаляет все созданные к тому моменту файлы. Этот параметр предотвращает удаление файлов, вследствие чего полезен для отладки. Значение по умолчанию — false (удалять файлы).

-N
--no-sync
По умолчанию qhb_bootstrap ожидает, пока все файлы не будут надежно записаны на диск. Этот параметр позволяет qhb_bootstrap завершаться быстрее, без ожидания, но в последующем сбой операционной системы может привести к повреждению каталога данных. В целом, этот параметр полезен для тестирования, но при создании производственной установки его применять не следует. Значение по умолчанию — false (выключен).

-s
--show
Вывести конфигурацию без создания кластера. Никакие другие обычные операции qhb_bootstrap не выполняются. Значение по умолчанию — false (выключен).

-S
--sync-only
Безопасно записать все файлы базы данных на диск и завершиться. Никакие другие обычные операции qhb_bootstrap не выполняются. Значение по умолчанию — false (выключен).

-V
--version
Вывести текущую версию qhb_bootstrap.

-W
--pwprompt
Заставляет qhb_bootstrap запросить пароль, который будет назначен суперпользователю базы данных. Если не планируется использовать аутентификацию по паролю, это не важно. В противном случае этот вариант аутентификации будет неприменим, пока не задан пароль.

Переменные среды

PGDATA
Задает каталог, в котором хранится кластер базы данных; можно переопределить с помощью параметра -D.

PG_COLOR
Указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

TZ
Задает для созданного кластера базы данных часовой пояс по умолчанию. Ее значением должно быть полное название часового пояса (см. раздел Часовые пояса).

Кроме того, эта утилита, как и большинство других утилит QHB, использует переменные среды, поддерживаемые libpq см. раздел Переменные среды).

Примеры

Создать кластер в каталоге /qhb/qhb-data с параметрами по умолчанию:

qhb_bootstrap --data-dir /qhb/qhb-data

Создать кластер в каталоге /qhb/qhb-data с кодировкой ru_RU и шифрованием SCRAM-SHA-256 с выводом подробной отладочной информации:

qhb_bootstrap --data-dir /qhb/qhb-data --locale ru_RU --auth scram_sha_256 -d

См. также

qhb_ctl, qhb

qhb_archivecleanup

qhb_archivecleanup — вычистить архивные файлы WAL QHB.

Синтаксис

qhb_archivecleanup [параметр...] расположение_архива старейший_сохраняемый_файл_wal

Описание

Утилита qhb_archivecleanup предназначена для использования в качестве archive_cleanup_command для удаления архивных файлов WAL при работе на резервном сервере (см. раздел Доставка журналов на резервные серверы). Также qhb_archivecleanup можно использовать и как отдельную программу для очистки архивных файлов WAL.

Чтобы использовать qhb_archivecleanup на резервном сервере, добавьте в файл конфигурации qhb.conf следующую строку:

archive_cleanup_command = 'qhb_archivecleanup расположение_архива %r'

где расположение_архива — это каталог, из которого нужно удалить файлы сегментов WAL.

При использовании этой утилиты в качестве archive_cleanup_command из расположения_архива будут удалены все файлы WAL, логически предшествующие значению аргумента %r. Это сокращает количество файлов, которые необходимо сохранить, без потери возможности аварийного перезапуска. Использование этого варианта целесообразно, если расположение_архива указывает на область рабочих файлов конкретного резервного сервера, но не когда расположение_архива — каталог с архивом WAL для долговременного хранения, или когда несколько резервных серверов восстанавливаются из одного расположения архива.

При использовании этой утилиты как самостоятельной программы из каталога расположение_архива будут удалены все файлы WAL, логически предшествующие файлу старейший_сохраняемый_файл_wal. Если в этом режиме указать имя файла с расширением .partial или .backup, старейший_сохраняемый_файл_wal будет определяться только по префиксу, без расширения. Такая интерпретация имени файла .backup позволяет без ошибки удалить все файлы WAL, заархивированные до определенной базовой копии. Например, следующая команда удалит все файлы старше файла WAL с именем 000000010000003700000010:

qhb_archivecleanup -d archive 000000010000003700000010.00000020.backup

qhb_archivecleanup:  keep WAL file "archive/000000010000003700000010" and later
qhb_archivecleanup:  removing file "archive/00000001000000370000000F"
qhb_archivecleanup:  removing file "archive/00000001000000370000000E"

qhb_archivecleanup предполагает, что каталог расположение_архива доступен для чтения и записи пользователю, владеющему серверным процессом.

Параметры

Утилита qhb_archivecleanup принимает следующие аргументы командной строки:

-d
Выводить подробные отладочные сообщения в stderr.

-n
Вывести имена файлов, которые будут удалены, в stdout (выполняет пробный запуск).

-V
--version
Вывести версию qhb_archivecleanup и завершиться.

-x расширение
Установить расширение, которое будет убрано из всех имен файлов до принятия решения об их удалении. Это обычно полезно для очистки архивов, которые были сжаты для хранения и поэтому имеют расширение, добавленное программой сжатия. Например: -x .gz.

-?
--help
Показать справку об аргументах командной строки qhb_archivecleanup и завершиться.

Переменные среды

Переменная среды PG_COLOR указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Утилита qhb_archivecleanup написана на языке C, и ее исходный код легко поддается модификации, поскольку содержит специально разработанные секции, которые можно изменять для собственных нужд.

Примеры

В системах Linux или Unix можно использовать команду:

archive_cleanup_command = 'qhb_archivecleanup -d /mnt/standby/archive %r 2>>cleanup.log'

где каталог архива физически расположен на резервном сервере, так что команда archive_command обращается к нему по NFS, но для резервного сервера эти файлы локальные. Это команда будет:

выводить отладочную информацию в cleanup.log

удалять ставшие ненужными файлы из каталога архива

qhb_checksums

qhb_checksums — включить, выключить или проверить контрольные суммы данных в кластере баз данных QHB

Синтаксис

qhb_checksums [параметр...] [[ -D | --pgdata ]каталог_данных]

Описание

Утилита qhb_checksums позволяет проверить, включить или выключить контрольные суммы данных в кластере QHB. Перед запуском qhb_checksums сервер должен быть остановлен в штатном режиме. При проверке контрольных сумм код завершения равен нулю, если ошибок нет, и отличен от нуля, если обнаружена хотя бы одна ошибка контрольной суммы. При включении или выключении контрольных сумм ненулевой код завершения означает, что выполнить операцию не удалось.

У всех файлов таблиц есть заголовок, по умолчанию в нем в поле с контрольной суммой стоит значение 0, и в общем файле ControlFile «версия контрольных сумм» равна 0 (выключено). При первом запуске нужно включить контрольные суммы с помощью ключа --enable, который установит версию контрольных сумм на 1 и изменит поле контрольной суммы во всех заголовках файлов таблиц базы данных (директории global, base, pg_tblspc). Если нужно проверить правильность контрольных сумм в файлах, программа запоминает значение контрольной суммы из файла, обнуляет его, считает новое, сравнивает и возвращает на место старое значение (если оно не совпало с подсчитанным, выводится ошибка с указанием ее расположения). Если нужно выключить контрольные суммы, программа меняет значение версии контрольных сумм на 0 и ничего не делает с файлами таблиц.

При проверке контрольных сумм сканируется каждый файл в кластере. При включении контрольных сумм каждый файл в кластере перезаписывается на месте. Выключение контрольных сумм обновляет только файл pg_control.

Параметры

Доступны следующие параметры командной строки:

-D каталог
--pgdata=каталог
Задает каталог, в котором хранится кластер базы данных.

-c
--check
Запускает проверку контрольных сумм. Это режим по умолчанию, если не указан никакой другой.

-d
--disable
Выключает контрольные суммы.

-e
--enable
Включает контрольные суммы.

-f файловый_узел
--filenode=файловый_узел
Проверить контрольные суммы только в отношении с указанным файловым_узлом.

-N
--no-sync
По умолчанию qhb_checksums ожидает, пока все файлы не будут надежно записаны на диск. Этот параметр позволяет qhb_checksums завершаться быстрее, без ожидания, но в последующем сбой операционной системы может привести к повреждению каталога данных. В целом, этот параметр полезен для тестирования, но при создании производственной установки его применять не следует.

-P
--progress
Включить вывод сообщений о прогрессе выполнения. Эти сообщения будут выводиться при проверке или включении контрольных сумм.

-v
--verbose
Включить режим подробного вывода. Выводит список всех проверенных файлов.

-V
--version
Вывести версию qhb_checksums и завершиться.

-h
--help
Показать справку об аргументах командной строки qhb_checksums и завершиться.

Переменные среды

PGDATA
Задает каталог, в котором хранится кластер базы данных; можно переопределить с помощью параметра -D.

PG_COLOR
Указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Включение контрольных сумм в большом кластере может занять много времени. Пока эта операция выполняется, нельзя запускать кластер или другие программы, выполняющие запись в каталог данных, иначе возможна потеря данных.

При использовании репликации, которая выполняется путем непосредственного копирования блоков отношений на уровне файлов (например, с помощью qhb_rewind), включение или выключение контрольных сумм может привести к повреждению страниц в виде расхождения контрольных сумм, если эта операция не будет выполнена согласованно на всех узлах. Поэтому в таких случаях рекомендуется остановить все кластеры, для того чтобы одновременно переключить их в другой режим. Еще один безопасный метод — удаление всех резервных серверов, выполнение нужной операции на основном и последующее создание резервных серверов заново.

Если qhb_checksums прерывается или процесс уничтожается при включении или выключении контрольных сумм, конфигурация контрольной суммы данных в кластере остается неизменной, и qhb_checksums можно перезапустить для выполнения той же операции.

Обработка ошибок

Ошибка Possibly encrypted block проявляется либо при шифровке файла, либо при очень серьезном его повреждении, подразумевая MismatchBlockChecksum.

Примеры

Включение контрольных сумм; в конце выводится статистика с количеством обработанных блоков и файлов. Любая ошибка приводит к остановке и выходу с кодом 1:

qhb_checksums -e

Проверка уже включенных контрольных сумм; отношение к статистике и ошибкам такое же, как у --enable:

qhb_checksums -с

Выключение контрольных сумм, несмотря на любые ошибки, кроме Permission denied на /global/pg_control:

qhb_checksums -d

Примечание
--enable, --check и --disable — три главных аргумента, к которым могут быть применены следующие ключи:

--verbose для вывода отладочной информации;

--no-sync для выключения сброса изменений на диск;

--progress для отображения хода выполнения.

qhb_controldata

qhb_controldata — отобразить управляющую информацию кластера баз данных QHB

Синтаксис

qhb_controldata [параметр] [[ --pgdata | -D ] каталог_данных]

Описание

Утилита qhb_controldata выводит информацию, заданную в процессе выполнения qhb_bootstrap (или initdb), например версию каталога. Она также показывает сведения о работе журнала упреждающей записи и контрольных точек. Эта информация относится ко всему кластеру, а не к какой-то отдельной базе данных.

Эту утилиту может запустить только пользователь, инициализировавший кластер, поскольку она требует права на чтения в каталоге хранения данных. Путь к этому каталогу можно указать в командной строке или использовать переменную среды PGDATA. Эта утилита поддерживает параметры -V и --version, которые выводят версию qhb_controldata и завершают ее выполнение, а также -? и --help, которые выдают справку по поддерживаемым аргументам.

Переменные среды

PGDATA
Расположение каталога данных по умолчанию

PG_COLOR
Указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

qhb_ctl

qhb_ctl — инициализировать, запустить, остановить или управлять сервером QHB

Синтаксис

qhb_ctl qhb_bootstrap [-D каталог_данных] [-s] [-o параметры-qhb_bootstrap ]

qhb_ctl init[db] [-D каталог_данных] [-s] [-o параметры-initdb ]

qhb_ctl start [-D каталог_данных] [-l имя_файла] [-W] [-t секунды] [-s] [-o параметры] [-p путь] [-c]

qhb_ctl stop [-D каталог_данных] [-m s[mart] | f[ast] | i[mmediate] ] [-W] [-t секунды] [-s]

qhb_ctl restart [-D каталог_данных] [-m s[mart] | f[ast] | i[mmediate] ] [-W] [-t секунды] [-s] [-o параметры] [-c]

qhb_ctl reload [-D каталог_данных] [-s]

qhb_ctl status [-D каталог_данных]

qhb_ctl promote [-D каталог_данных] [-W] [-t секунды] [-s]

qhb_ctl logrotate [-D каталог_данных] [-s]

qhb_ctl kill имя_сигнала id_процесса

Описание

qhb_ctl — это утилита для инициализации, запуска, остановки или перезапуска кластера базы данных QHB (qhb) или отображения состояния работающего сервера. Хотя сервер можно запустить вручную, qhb_ctl берет на себя такие задачи, как перенаправление вывода журнала и корректное отключение от терминала и группы процессов. Помимо этого она предоставляет удобные параметры для управления остановкой кластера.

Команда qhb_bootstrap или init(initdb) создает новый кластер баз данных QHB, то есть коллекцию баз данных, которая будет управляться одним экземпляром сервера. Эта команда вызывает утилиту qhb_bootstrap или initdb соответственно. Подробную информацию см. на справочных страницах утилит qhb_bootstrap и initdb.

Команда start запускает сервер. Сервер запускается в фоновом режиме, а его стандартный ввод связывается с /dev/null. В Unix-подобных системах стандартные вывод и ошибки по умолчанию посылаются в устройство стандартного вывода (не ошибок) qhb_ctl. Затем стандартный вывод qhb_ctl должен быть перенаправлен в файл или в другой процесс, например, программе ротации журналов rotatelogs; в противном случае qhb будет писать свой вывод в управляющий терминал (в фоновом режиме) и останется в группе процессов оболочки.

Команда stop останавливает сервер, который работает в указанном каталоге данных. Параметр -m позволяет выбрать один из трех методов остановки. Режим «Smart» запрещает новые подключения, а затем ожидает отключения всех активных клиентов и завершения всех текущих процессов резервного копирования. Если сервер находится в режиме горячего резерва, восстановление и потоковая репликация будут прерваны сразу после отключения всех клиентов. Режим «Fast» (выбирается по умолчанию) не ожидает отключения клиентов и завершает все текущие процессы резервного копирования. Все активные транзакции откатываются, клиенты принудительно отключаются, после чего сервер останавливается. Режим «Immediate» немедленно прерывает все серверные процессы, не выполняя процедуру штатной остановки. Этот вариант повлечет необходимость выполнить восстановление после сбоя при следующем запуске сервера.

Команда restart в сущности выполняет остановку с последующим запуском сервера. Это позволяет изменить параметры командной строки qhb или файла конфигурации, которые не вступят в силу без перезапуска сервера. Если при запуске сервера в командной строке были указаны относительные пути, команда restart может завершиться ошибкой, если только не вызывать qhb_ctl в том же каталоге, где производился предыдущий запуск сервера.

Команда reload просто отправляет серверному процессу qhb сигнал SIGHUP, заставляя его перечитать свои файлы конфигурации (qhb.conf, qhb_hba.conf и т. д.). Это позволяет изменять параметры файла конфигурации, которым для вступления в силу не требуется полный перезапуск сервера.

Команда status проверяет, работает ли сервер в указанном каталоге данных. Если да, отображаются PID сервера и параметры командной строки, которые использовались для его запуска. Если сервер не запущен, qhb_ctl возвращает код завершения 3. Если не указан доступный каталог данных, qhb_ctl возвращает код завершения 4.

Команда promote предписывает резервному серверу, работающему в указанном каталоге данных, выйти из режима резерва и начать операции чтения/записи.

Команда logrotate прокручивает файл журнала сервера. Подробнее о том, как использовать этот режим с внешними инструментами ротации журналов, см. в разделе Обслуживание файлов журнала.

Команда kill отправляет сигнал заданному процессу. Для получения списка имен поддерживаемых сигналов воспользуйтесь ключом --help.

Параметры

-c
--core-files
Способствует генерированию файлов дампа памяти при сбоях сервера на платформах, где это возможно, снимая все мягкие ограничения ресурсов, установленные для файлов дампа. Это полезно при отладке и диагностике проблем, поскольку позволяет получить трассировку стека отказавшего серверного процесса.

-D каталог_данных
--pgdata=каталог_данных
Задает расположение в файловой системе файлов конфигурации базы данных. Если этот параметр не указан, используется переменная среды PGDATA.

-l имя_файла
--log=имя_файла
Направляет вывод журнала сервера в файл имя_файла. Если файла не существует, он создается. При этом в umask устанавливается значение 077, поэтому доступ к файлу журнала другим пользователям по умолчанию запрещен.

-m режим
--mode=режим
Задает режим остановки кластера. режим может быть smart, fast или immediate (или первая буква этих вариантов). Если этот параметр не указан, по умолчанию выбирается режим fast.

-o параметры
--options=параметры
Задает параметры, которые будут передаваться непосредственно команде qhb. Указав ключ -o несколько раз, можно передать все имеющиеся параметры.
Обычно параметры следует заключать в апострофы или кавычки, чтобы они гарантированно передавались группой.

-o параметры-qhb_bootstrap
--options=параметры-qhb_bootstrap
Задает параметры, которые будут переданы непосредственно команде qhb_bootstrap. Указав ключ -o несколько раз, можно передать все имеющиеся параметры.
Обычно параметры-qhb_bootstrap следует заключать в апострофы или кавычки, чтобы они гарантированно передавались группой.

-o параметры-initdb
--options=параметры-initdb
Задает параметры, которые будут переданы непосредственно команде initdb. Указав ключ -o несколько раз, можно передать все имеющиеся параметры.
Обычно параметры-initdb следует заключать в апострофы или кавычки, чтобы они гарантированно передавались группой.

-p путь
Указывает расположение исполняемого файла qhb. По умолчанию исполняемый файл qhb берется из того же каталога, что и qhb_ctl, или, если это невозможно, из встроенного каталога установки. Использовать этот параметр необязательно, кроме случаев, когда вы делаете что-то необычное или получаете сообщения, что исполняемый файл qhb не найден.
В режиме qhb_bootstrap или init этот параметр аналогичным образом указывает расположение исполняемого файла qhb_bootstrap или initdb соответственно.

-s
--silent
Выводить только сообщения об ошибках, без информационных сообщений.

-t секунды
--timeout=секунды
Задает максимальное время (в секундах) ожидания завершения операции (см. параметр -w). По умолчанию используется значение переменной среды PGCTLTIMEOUT или, если оно не задано, 60 секунд.

-V
--version
Вывести версию qhb_ctl и завершиться.

-w
--wait
Ждать завершения операции. Поддерживается (и действует по умолчанию) для команд start, stop, restart и promote.
В процессе ожидания qhb_ctl несколько раз проверяет PID-файл сервера, переключаясь в неактивный режим на короткое время между проверками. Запуск считается завершенным, когда PID-файл указывает на то, что сервер готов принимать подключения. Остановка считается завершенной, когда сервер удаляет свой PID-файл. Утилита qhb_ctl возвращает код завершения в зависимости от успеха запуска или остановки.
Если операция не завершается за отведенное время ожидания (см. параметр -t), то qhb_ctl завершается с ненулевым кодом завершения. Но обратите внимание, что при этом операция может продолжаться в фоновом режиме и в конечном итоге завершиться успешно.

-W
--no-wait
Не ждать завершения операции. Этот параметр противоположен параметру -w.
Если ожидание выключено, запрошенное действие инициируется, но о его результате информации нет. В этом случае для проверки текущего состояния и результата операции потребуется обратиться к файлу журнала сервера или внешней системе мониторинга.

-?
--help
Показать справку об аргументах командной строки qhb_ctl и завершиться.

Если заданный параметр допустим, но не применим к выбранному режиму работы, qhb_ctl его игнорирует.

Переменные среды

PGCTLTIMEOUT
Максимальное время (в секундах) ожидания запуска или остановки сервера по умолчанию. Если не задано, то по умолчанию это время составляет 60 секунд.

PGDATA
Расположение каталога данных по умолчанию.

Для большинства режимов qhb_ctl требуется знать расположение каталога данных; поэтому если не задана переменная PGDATA, параметр -D является обязательным.

qhb_ctl, как и большинство других утилит QHB, также использует переменные окружения, поддерживаемые libpq (см. раздел Переменные среды).

Список дополнительных переменных, влияющих на сервер, см. на справочной странице утилиты qhb.

Файлы

qhbmaster.pid
qhb_ctl проверяет этот файл в каталоге данных, чтобы определить, работает ли сервер в настоящий момент.

postmaster.opts
Если этот файл существует в каталоге данных, qhb_ctl (в режиме restart) передаст его содержимое в качестве аргументов qhb, если это не переопределено параметром -o. Содержимое этого файла также отображается в режиме status.

Примеры

Запуск сервера

Запуск сервера и ожидание момента, когда он начнет принимать подключения:

$ qhb_ctl start

Запуск сервера на порту 5433 и без fsync:

$ qhb_ctl -o "-F -p 5433" start

Остановка сервера

Чтобы остановить сервер, выполните:

$ qhb_ctl stop

Параметр -m позволяет управлять тем, как сервер будет остановлен:

$ qhb_ctl stop -m smart

Перезапуск сервера

Перезапуск сервера практически равносилен его остановке и повторному запуску, за исключением того, что по умолчанию qhb_ctl сохраняет и повторно использует параметры командной строки, которые были переданы ранее запущенному экземпляру. Чтобы перезапустить сервер, используя те же параметры, с какими он был запущен ранее, выполните:

$ qhb_ctl restart

Но если указан параметр -o, он заменяет все предыдущие параметры. Чтобы перезапустить сервер на порту 5433 и выключить fsync при перезапуске, используйте:

$ qhb_ctl -o "-F -p 5433" restart

Отображение состояния сервера

Пример вывода состояния от qhb_ctl:

$ qhb_ctl status

qhb_ctl: server is running (PID: 13718)
/usr/local/qhb/bin/qhb "-D" "/var/lib/qhb/data" "-p" "5433" "-B" "128"

Во второй строке показана команда, которая будет выполнена в режиме перезапуска.

См. также

qhb_bootstrap, initdb, qhb

qhb_resetwal

qhb_resetwal — сбросить журнал упреждающей записи и другую управляющую информацию кластера баз данных QHB

Синтаксис

qhb_resetwal [ -f | --force ] [ -n | --dry-run ] [параметр...] [ -D | --pgdata ] каталог_данных

Описание

Утилита qhb_resetwal очищает журнал упреждающей записи (WAL) и может сбрасывать некоторую другую управляющую информацию, хранящуюся в файле pg_control. Эта функция бывает необходима, когда эти файлы повреждены. Ее следует использовать как крайнюю меру, когда из-за таких повреждений сервер не сможет запуститься.

После выполнения этой команды запуск сервера должен стать возможным, но имейте в виду, что база данных может содержать несогласованные данные вследствие частично зафиксированных транзакций. Следует немедленно выгрузить данные, запустить qhb_bootstrap (или initdb) и восстановить их. После этого проверьте согласованность базы данных и при необходимости внесите коррективы.

Эту утилиту может запустить только пользователь, который установил сервер, поскольку ей требуется доступ на чтение/запись к каталогу данных. По соображениям безопасности нужно указать каталог данных в командной строке. qhb_resetwal не использует переменную среды PGDATA.

Если qhb_resetwal сообщает, что не может определить подходящие данные из pg_control, ее можно выполнить принудительно, указав параметр -f. В этом случае отсутствующие данные будут заменены наиболее вероятными значениями. Для большинства полей это нормально, но следующее значение OID, ID и эпоху следующей транзакции, ID и и смещение следующей мультитранзакции и начальную позицию WAL, возможно, придется ввести вручную. Эти поля можно установить с помощью параметров, описанных ниже. Если вы не можете определить правильные значения для всех этих полей, -f все равно можно использовать, однако к восстановленной базе данных следует относиться с еще большим подозрением, чем обычно: нужно будет немедленно выгрузить и затем восстановить данные. Не выполняйте никаких операций по изменению данных, пока не создадите дамп, поскольку любое подобное действие может усугубить повреждение.

Параметры

-f
--force
Принудительно выполнить qhb_resetwal даже если она не может определить подходящие данные из pg_control, как рассказывается выше.

-n
--dry-run
Параметр -n/--dry-run предписывает qhb_resetwal вывести значения, восстановленные из pg_control, а также значения, которые должны быть изменены, а затем завершиться, ничего не изменяя. Это в основном инструмент отладки, но может быть полезен для проверки корректности параметров, прежде чем позволить qhb_resetwal действительно что-то сделать.

-V
--version
Отобразить информацию о версии и завершиться.

-?
--help
Показать справку и завершиться.

Следующие параметры нужны, только когда qhb_resetwal не может определить подходящие значения, прочитав pg_control. Безопасные значения можно определить, как описано ниже. Для значений, которые принимают числовые аргументы, с помощью префикса 0x можно задать шестнадцатеричные значения.

-c xid,xid
--commit-timestamp-ids=xid,xid
Вручную установить идентификаторы старейших и новейших транзакций, для которых можно получить время фиксации.
Безопасное значение для идентификатора старейшей транзакции, для которой можно получить время фиксации, можно определить, найдя наименьшее в числовом виде имя файла в подкаталоге pg_commit_ts каталога данных. И наоборот, безопасное значение для идентификатора новейшей транзакции, для которой можно получить время фиксации (вторая часть), можно определить, найдя наибольшее в числовом виде имя файла. Числа в именах файлов представлены в шестнадцатеричном формате.

-e эпоха_xid
--epoch=эпоха_xid
Вручную установить эпоху идентификатора следующей транзакции.
Эпоха идентификатора транзакции фактически не сохраняется нигде в базе данных, кроме этого поля, установленного qhb_resetwal, поэтому относительно самой базы данных любое значение будет допустимым. Возможно, вам придется настроить это значение, чтобы обеспечить правильную работу систем репликации, например Slony-I и Skytools, — для такого случая соответствующее значение должно быть получено из состояния нижележащей реплицированной базы данных.

-l файл_wal
--next-wal-file=файл_wal
Вручную установить начальную позицию WAL, указав имя файла следующего сегмента WAL.
Имя файла следующего сегмента WAL должно быть больше всех остальных имен файлов сегментов WAL, которые в настоящее время находятся в подкаталоге pg_wal каталога данных. Эти имена также представлены в шестнадцатеричном виде и состоят из трех частей. Первая часть является «идентификатором временной шкалы» и обычно должна оставаться неизменной. Например, если 00000001000000320000004A является самой большой записью в pg_wal, напишите -l 00000001000000320000004B или большее число.
Обратите внимание, что при использовании нестандартных размеров сегментов WAL числа в именах файлов WAL отличаются от LSN, выдаваемых системными функциями и представлениями. Этот параметр принимает имя файла WAL, а не LSN.

Примечание
qhb_resetwal сама просматривает файлы в pg_wal и по умолчанию выбирает для параметра -l значение, идущее следующим после последнего существующего имени файла. Поэтому задавать параметр -l вручную необходимо только в том случае, если вам известно о существовании файлов сегментов WAL, которые в данный момент отсутствуют в pg_wal (например, они находятся в отдельном архиве), либо если содержимое pg_wal было полностью утеряно.

-m mxid, mxid
--multixact-ids=mxid, mxid
Вручную установить идентификаторы следующей и самой старой мультитранзакции.
Безопасное значение для следующего идентификатора мультитранзакции (первая часть) можно определить, найдя самое большое в числовом виде имя файла в подкаталоге pg_multixact/offsets в каталоге данных, прибавив к нему единицу, а затем умножив на 65536 (0x10000). И наоборот, безопасное значение идентификатора самой старой мультитранзакции (вторая часть -m) можно определить, найдя наименьшее в числовом виде имя файла в том же каталоге и умножив его на 65536. Имена файлов представлены в шестнадцатеричном формате, поэтому самый простой способ умножить — это добавить к имени файла четыре нуля.

-o oid
--next-oid=oid
Вручную установить следующий OID.
Не существует сравнительно простого способа определить следующее за наибольшим из существующих в базе данных значений OID, но, к счастью, указание правильного значения некритично.

-O смещение_мультитранзакции
--multixact-offset=смещение_мультитранзакции
Вручную установить смещение следующей мультитранзакции.
Безопасное значение можно определить, найдя наибольшее в числовом виде имя файла в подкаталоге pg_multixact/members каталога данных, прибавив к нему единицу, а затем умножив на 52352 (0xCC80). Имена файлов представлены в шестнадцатеричном формате. Простого способа с добавлением нулей, как для других параметров, здесь нет.

--wal-segsize=размер_сегмента_wal
Установить новый размер сегмента WAL, в мегабайтах. Значение должно быть степенью 2 от 1 до 1024 (мегабайт). Дополнительную информацию см. в описании того же параметра в qhb_bootstrap.

Примечание
В то время как qhb_resetwal устанавливает начальный адрес WAL, превышающий номер последнего существующего файла сегмента WAL, при некоторых изменениях размера сегмента предыдущие имена файлов WAL могут использоваться повторно. Если наложение имен файлов WAL вызывает проблемы с вашей стратегией архивирования, рекомендуется использовать с этим параметром ключ -l, чтобы вручную установить начальный адрес WAL.

-u xid
--oldest-transaction-id=xid
Вручную установить идентификатор старейшей незамороженной транзакции.
Безопасное значение можно определить, найдя наибольшее в числовом виде имя файла в подкаталоге pg_xact каталога данных и умножив его на 1048576 (0x100000). Обратите внимание, что имена файлов представлены в шестнадцатеричном формате. Обычно проще всего указать значение этого параметра также в шестнадцатеричном формате. Например, если наибольшим значением в pg_xact является 0007, то подойдет запись -u 0x700000 (пять нулей в конце обеспечивают правильный множитель).

-x xid
--next-transaction-id=xid
Вручную установить идентификатор следующей транзакции.
Безопасное значение можно определить, найдя наибольшее в числовом виде имя файла в подкаталоге pg_xact каталога данных, прибавив к нему единицу, а затем умножив на 1048576 (0x100000). Обратите внимание, что имена файлов представлены в шестнадцатеричном формате. Обычно проще всего указать значение этого параметра также в шестнадцатеричном формате. Например, если наибольшим значением в pg_xact является 0011, то подойдет запись -x 0x1200000 (пять нулей в конце обеспечивают правильный множитель).

Переменные среды

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Эту команду нельзя использовать во время работы сервера. qhb_resetwal не станет запускаться, если обнаружит в каталоге хранения данных файл блокировки сервера. Если произошел сбой сервера, файл блокировки может остаться в системе; в этом случае его можно удалить вручную, чтобы запустить qhb_resetwal. Но прежде чем сделать это, дважды удостоверьтесь, что ни один серверный процесс не запущен.

Утилита qhb_resetwal работает только с серверами той же основной версии.

См. также

qhb_controldata

qhb_rewind

qhb_rewind — синхронизировать каталог данных QHB с другим каталогом данных, который был ответвлен от него

Синтаксис

qhb_rewind [параметр...] { -D | --target-pgdata } каталог { --source-pgdata=каталог | --source-server=строка_подключения }

Описание

Утилита qhb_rewind — это инструмент для синхронизации кластера QHB с другой копией того же кластера после того, как временные линии этих кластеров разошлись. Типичным сценарием является возвращение старого основного сервера после отказа к работе в качестве резервного, следующего за новым основным сервером

После успешной синхронизации состояние целевого каталога данных аналогично состоянию базовой резервной копии исходного каталога данных. В отличие от создания новой резервной копии или использования инструмента вроде rsync, qhb_rewind не требует сравнения или копирования неизмененных блоков отношений в кластер. Копируются только измененные блоки из существующих файлов отношений; все остальные файлы, включая новые файлы отношений, файлы конфигурации и сегменты WAL, копируются полностью. Поэтому операция синхронизации намного быстрее других подходов, когда база данных большая, а кластеры отличаются только небольшой частью блоков.

qhb_rewind изучает истории временной шкалы исходного и целевого кластеров, чтобы определить точку их расхождения, и ожидает найти WAL в каталоге pg_wal целевого кластера вплоть до точки расхождения. Точку расхождения можно найти либо на целевой или на исходной временной линии, либо у их общего предка. В типичном сценарии отработки отказа, где целевой кластер был отключен вскоре после расхождения, это не проблема, но если целевой кластер после расхождения работал долго, старые файлы WAL могут отсутствовать. В этом случае их можно вручную скопировать из архива WAL в каталог pg_wal или запустить qhb_rewind с параметром -c, чтобы автоматически извлечь их из архива WAL. Использование qhb_rewind не ограничивается переключением при отказе. Например, резервный сервер может быть повышен, выполнить несколько записывающих транзакций, а затем, после синхронизации, снова стать резервным.

После выполнения qhb_rewind нужно завершить воспроизведение WAL, чтобы каталог данных пришел в согласованное состояние. Когда целевой сервер снова запускается, он переходит в режим восстановления из архива и воспроизводит все WAL, сгенерированные на исходном сервере, начиная с последней контрольной точки перед точкой расхождения. Если при запуске qhb_rewind какие-то сегменты WAL больше не были доступны на исходном сервере и, следовательно, не могли быть скопированы, то их необходимо предоставить при запуске целевого сервера. Это можно сделать, создав файл recovery.signal в целевом каталоге данных и задав подходящую команду в параметре restore_command в qhb.conf.

Утилита qhb_rewind требует, чтобы на целевом сервере либо был включен параметр wal_log_hints в qhb.conf, либо при инициализации кластера с помощью qhb_bootstrap (или initdb) были включены контрольные суммы данных. Ни один из этих режимов в настоящее время не включен по умолчанию. Также должен быть включен параметр full_page_writes, но он и так включен по умолчанию.

ПРЕДУПРЕЖДЕНИЕ!
Если во время обработки происходит сбой qhb_rewind, целевой каталог данных, скорее всего, будет в состоянии, не подходящем для восстановления. В таком случае рекомендуется сделать новую резервную копию.

Поскольку qhb_rewind полностью копирует файлы конфигурации из источника, перед запуском целевого сервера может понадобиться скорректировать конфигурацию, использующуюся при восстановлении, особенно если целевой сервер становится резервным для исходного. Если перезапустить сервер по окончании синхронизации, но без настройки восстановления, целевой сервер может снова разойтись с основным.

qhb_rewind сразу прекратит работу, если найдет файлы, в которые не может записывать напрямую. Это может произойти, например, когда исходный и целевой серверы используют одинаковые пути файлов для ключей и сертификатов SSL, доступных только для чтения. Если такие файлы существуют на целевом сервере, рекомендуется удалить их перед запуском qhb_rewind. После синхронизации некоторые из этих файлов могут оказаться скопированы из источника, и в этом случае может понадобиться удалить скопированные данные и восстановить набор ссылок, использовавшихся до синхронизации.

Параметры

Утилита qhb_rewind принимает следующие аргументы командной строки:

-D каталог
--target-pgdata=каталог
Этот параметр задает целевой каталог данных, который синхронизируется с источником. Целевой сервер должен быть штатно отключен перед запуском qhb_rewind.

--source-pgdata=каталог
Задает путь файловой системы к каталогу данных исходного сервера, с которым нужно синхронизировать целевой. Этот параметр требует, чтобы исходный сервер был штатно отключен.

--source-server=строка_подключения
Задает строку подключения libpq для подключения к исходному серверу QHB, с которым нужно синхронизировать целевой. Это подключение должно быть обычным (без репликации) от имени роли, имеющей подходящие права для выполнения функций, используемых qhb_rewind на исходном сервере (подробную информацию см. ниже в параграфе Примечания), или от имени суперпользователя. Этот параметр требует, чтобы исходный сервер был запущен и принимал подключения.

-R
--write-recovery-conf
Создать standby.signal и добавить параметры подключения в qhb.auto.conf в выходном каталоге. Для этого параметра обязательно указание --source-server.

-n
--dry-run
Делать все, кроме фактического изменения целевого каталога.

-N
--no-sync
По умолчанию qhb_rewind будет ожидать успешной записи всех файлов на диск. С этим параметром qhb_rewind будет завершаться без ожидания, что быстрее, но в случае последующего сбоя операционной системы может привести к повреждению каталога данных. В целом, этот параметр полезен для тестирования, но его не следует использовать в производственной установке.

-P
--progress
Запускает вывод сообщений о прогрессе. При включении этого параметра во время копирования данных из исходного кластера выдается приблизительный отчет о ходе выполнения.

-c
--restore-target-wal
Использовать команду из restore_command, определенную в конфигурации целевого кластера, чтобы извлечь файлы WAL из архива WAL, если эти файлы стали недоступны в каталоге pg_wal.

--debug
Выводить подробную отладочную информацию, которые в основном полезны для разработчиков, отлаживающих qhb_rewind.

--no-ensure-shutdown
qhb_rewind требует, чтобы целевой сервер был штатно отключен перед синхронизацией. По умолчанию если целевой сервер не был отключен штатно, qhb_rewind запускает его в однопользовательском режиме, чтобы сначала завершилось восстановление после сбоя, а уже потом останавливает его. С этим параметром qhb_rewind не делает этого, а сразу завершается с ошибкой, если сервер не был штатно отключен. В таком случае ожидается, что пользователи сами разберутся с этой ситуацией.

-V
--version
Отобразить информацию о версии и завершиться.

-?
--help
Показать справку и завершиться.

Переменные среды

Когда используется параметр --source-server, qhb_rewind также использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Переменная среды PG_COLOR указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Если при выполнении qhb_rewind исходным кластером является работающий сервер, вместо суперпользователя можно использовать роль, имеющую подходящие права в исходном кластере для выполнения функций, используемых qhb_rewind. Вот как можно создать такую роль с именем rewind_user:

CREATE USER rewind_user LOGIN;
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean) TO rewind_user;

Если при выполнении qhb_rewind исходным кластером является работающий сервер, который был недавно повышен, то на нем необходимо выполнить CHECKPOINT, чтобы его управляющий файл отражал актуальную информацию о временной линии, которая используется qhb_rewind для проверки, можно ли синхронизировать целевой кластер с выбранным исходным кластером.

Как это работает

Основная идея — скопировать все изменения на уровне файловой системы из исходного кластера в целевой:

Сканируется журнал WAL целевого кластера, начиная с последней контрольной точки до точки, где история временной шкалы исходного кластера ответвилась от целевого. Для каждой записи WAL записывается каждый затронутый блок данных. В результате получается список всех блоков данных, которые были изменены в целевом кластере после того, как исходный кластер ответвился. Если некоторые файлы WAL стали недоступны, можно попробовать повторно запустить qhb_rewind с параметром -c, чтобы поискать отсутствующие файлы в архиве WAL.

Все эти измененные блоки копируются из исходного кластера в целевой, либо используя прямой доступ через файловую систему (--source-pgdata), либо через SQL (--source-server). Теперь файлы отношений находятся в том же состоянии, в котором они быть в момент последней завершенной контрольной точки перед точкой, где разошлись временные линии WAL исходного и целевого кластеров, с добавлением текущего состояния в исходном кластере тех блоков, которые были изменены в целевом после этого расхождения.

Копируются все остальные файлы, включая новые файлы отношений, сегменты WAL, pg_xact и файлы конфигурации, из исходного кластера в целевой. Как и при базовом резервном копировании, содержимое каталогов pg_dynshmem/, pg_notify/, pg_replslot/, pg_serial/, pg_snapshots/, pg_stat_tmp/ и pg_subtrans/ исключается из данных, копируемых из исходного кластера. Кроме того, исключаются файлы и каталоги, начинающиеся с pgsql_tmp, а также файлы backup_label, tablespace_map, pg_internal.init, postmaster.opts и qhbmaster.pid.

Создается файл backup_label, чтобы начать воспроизведение WAL с контрольной точки, созданной при сбое, и сконфигурировать в файле pg_control минимальный LSN согласованного состояния, определенный в результате вызова pg_current_wal_insert_lsn() при синхронизации с работающим исходным кластером или являющийся LSN последней контрольной точки при синхронизации с остановленным исходным кластером.

При запуске целевого сервера QHB воспроизводит все требуемые WAL, в результате чего каталог данных приводится в согласованное состояние.

qhb_test_fsync

qhb_test_fsync — определить самый быстрый wal_sync_method для QHB

Синтаксис

qhb_test_fsync [параметр...]

Описание

Утилита qhb_test_fsync предназначена для того, чтобы дать вам взвешенное представление о том, какой из вариантов wal_sync_method будет самым быстрым в вашей конкретной системе, а также предоставить диагностическую информацию в случае установленной проблемы ввода/вывода. Однако различия, показанные qhb_test_fsync, могут не оказывать значительного воздействия на реальную производительность базы данных, особенно учитывая, что у многих серверов баз данных скорость не ограничивается журналами упреждающей записи. qhb_test_fsync выводит среднее время операции синхронизации файлов в микросекундах для каждого варианта wal_sync_method, что также можно использовать в качестве подспорья при оптимизации значения commit_delay.

Параметры

Утилита qhb_test_fsync принимает следующие параметры командной строки:

-f
--filename
Задает имя файла для записи данных тестов. Этот файл должен располагаться в той же файловой системе, где размещается или будет размещен каталог pg_wal. (В pg_wal содержатся файлы WAL.) По умолчанию назначается файл pg_test_fsync.out в текущем каталоге.

-s
--secs-per-test
Задает продолжительность в секундах для каждого теста. Чем больше времени задано, тем выше точность теста, но тем дольше он выполняется. Значение по умолчанию — 5 секунд, что позволяет программе завершиться примерно за 2 минуты.

-V
--version
Вывести версию qhb_test_fsync и завершиться.

-?
--help
Показать справку об аргументах командной строки qhb_test_fsync и завершиться.

Переменные среды

Переменная среды PG_COLOR указывает, использовать ли цвета в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

См. также

qhb

qhb_test_timing

qhb_test_timing — оценить издержки измерения времени

Синтаксис

pg_test_timing [параметр...]

Описание

qhb_test_timing — это утилита для оценки издержек измерения времени в вашей системе и подтверждения того, что системное время никогда не идет назад. Системы, в которых сбор данных по времени проходит медленно, могут дать менее точные результаты EXPLAIN ANALYZE.

Параметры

Утилита qhb_test_timing принимает следующие параметры командной строки:

-d продолжительность
--duration=продолжительность
Задает продолжительность проверки, в секундах. Увеличение продолжительности дает чуть более высокую точность и повышенную вероятность обнаружить проблемы с обратным ходом системных часов. Продолжительность проверки по умолчанию — 3 секунды.

-V
--version
Вывести версию qhb_test_timing и завершиться.

-?
--help
Показать справку об аргументах командной строки qhb_test_timing и завершиться.

Использование

Интерпретация результатов

В случае хороших результатов будет показано, что почти все (>90%) отдельные вызовы замеров времени занимают меньше одной микросекунды. Средние издержки замера на цикл будут еще ниже, менее 100 наносекунд. Этот пример из системы Intel i7-860 с источником времени TSC показывает отличную производительность:

Testing timing overhead for 3 seconds.
Per loop time including overhead: 35.96 ns
Histogram of timing durations:
  < us   % of total      count
     1     96.40465   80435604
     2      3.59518    2999652
     4      0.00015        126
     8      0.00002         13
    16      0.00000          2

Обратите внимание, что время вызова в цикле и время в гистограмме выражается в разных единицах. Время в цикле может определяться с точностью до нескольких наносекунд (ns), тогда как продолжительность отдельных вызовов замера времени — только с точностью до одной микросекунды (us).

Измерение издержек исполнителя при замере времени

Когда исполнитель запроса выполняет оператор с помощью команды EXPLAIN ANALYZE, наравне с общим временем измеряется время отдельных операций. Издержки в вашей системе можно проверить путем подсчета строк в программе psql:

CREATE TABLE t AS SELECT * FROM generate_series(1,100000);
\timing
SELECT COUNT(*) FROM t;
EXPLAIN ANALYZE SELECT COUNT(*) FROM t;

В системе i7-860 запрос с подсчетом выполняется за 9,8 мс, тогда как версия с EXPLAIN ANALYZE занимает 16,6 мс, при этом обрабатывается чуть больше 100 000 строк. Эта разница в 6,8 мс означает, что издержки измерения времени на строку составляют 68 нс, примерно вдвое больше, чем предположила qhb_test_timing. Даже с такой относительно небольшой издержкой оператор COUNT с полным замером времени выполняется почти на 70% дольше. На более существенных запросах издержки при замерах могут быть менее проблематичны.

Смена источника времени

В некоторых современных системах Linux можно в любой момент сменить источник времени, используемый для сбора данных по замеру времени. Во втором примере демонстрируется возможное замедление от переключения на более медленный источник времени acpi_pm в той же системе, где были получены показанные выше быстрые результаты:

# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hpet acpi_pm
# echo acpi_pm > /sys/devices/system/clocksource/clocksource0/current_clocksource
# pg_test_timing
Per loop time including overhead: 722.92 ns
Histogram of timing durations:
  < us   % of total      count
     1     27.84870    1155682
     2     72.05956    2990371
     4      0.07810       3241
     8      0.01357        563
    16      0.00007          3

В этой конфигурации та же EXPLAIN ANALYZE, что и в примере выше, занимает более 115,9 мс. Это соответствует издержке на замер времени в 1061 нс — снова небольшой образец того, что измеряется этой утилитой напрямую. Такая издержка означает, что сам фактический запрос занимает лишь небольшой процент всего вычисленного времени, а основная его часть уходит на замеры времени. В этой конфигурации все общие показатели EXPLAIN ANALYZE для запросов со множеством замеряемых операций значительно увеличатся из-за издержек замера времени.

FreeBSD также позволяет сменить источник времени на ходу и выводит информацию о выбранном таймере при загрузке:

# dmesg | grep "Timecounter"
Timecounter "ACPI-fast" frequency 3579545 Hz quality 900
Timecounter "i8254" frequency 1193182 Hz quality 0
Timecounters tick every 10.000 msec
Timecounter "TSC" frequency 2531787134 Hz quality 800
# sysctl kern.timecounter.hardware=TSC
kern.timecounter.hardware: ACPI-fast -> TSC

Другие системы могут допускать настройку источника времени только при загрузке. В более старых системах единственным способом выполнить такое изменение является настройка параметра ядра «clock». И даже в некоторых более современных системах единственным параметром для источника времени, который можно увидеть, — это «jiffies». Jiffies — это старая программная реализация часов в Linux, которая может давать хорошее разрешение, когда поддерживается достаточно быстрым оборудованием для замера времени, как в этом примере:

$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
jiffies
$ dmesg | grep time.c
time.c: Using 3.579545 MHz WALL PM GTOD PIT/TSC timer.
time.c: Detected 2400.153 MHz processor.
$ pg_test_timing
Testing timing overhead for 3 seconds.
Per timing duration including loop overhead: 97.75 ns
Histogram of timing durations:
  < us   % of total      count
     1     90.23734   27694571
     2      9.75277    2993204
     4      0.00981       3010
     8      0.00007         22
    16      0.00000          1
    32      0.00000          1

Аппаратные часы и точность замера времени

Сбор точной информации по замеру времени обычно производится на компьютерах с помощью аппаратных часов с разным уровнем точности. С некоторым оборудованием операционные системы могут передавать время системных часов напрямую программам. Также системное время может передаваться с чипа, который просто генерирует прерывания по времени, периодические такты с некоторым известным интервалом. В любом случае ядра операционных систем предоставляют источник времени, скрывающий эти подробности. Но точность этого источника времени и скорость, с которой он может возвращать результаты, зависит от нижележащего оборудования.

Неточное выдерживание времени может привести к нестабильности системы. Тщательно проверяйте любые изменения, касающиеся источника времени. Иногда по умолчанию в операционной системе выбирается надежность в ущерб высокой точности. И если вы используете виртуальную машину, изучите рекомендованные для нее совместимые источники времени. Виртуальное оборудование при эмуляции таймеров сталкивается с дополнительными трудностями, и производители часто рекомендуют определенные параметры для операционных систем.

Источник времени TSC (Time Stamp Counter, Счетчик отметок времени) является наиболее точным из всех доступных для процессоров текущего поколения. Его предпочтительнее использовать для отслеживания системного времени, когда он поддерживается операционной системой и показания часов TSC надежны. Есть несколько ситуаций, в которых TSC не сможет предоставить точный источник замера времени и окажется ненадежным. В старых системах могут быть часы TSC, зависящие от температуры процессора, что делает их непригодными для замера времени. При попытке использовать TSC в некоторых старых многоядерных процессорах можно получить на разных ядрах несогласованное время. Это может привести к обратному ходу времени — проблеме, которую выявляет эта программа. И даже в самых современных системах можно получить неточные замеры времени через TSC в режимах очень агрессивного энергосбережения.

Современные операционные системы могут проверять наличие известных проблем TSC и переключаться на более медленный и стабильный источник времени, когда те выявляются. Если ваша система поддерживает источник TSC, но не выбирает его по умолчанию, возможно, он отключен по веской причине. А некоторые операционные системы могут не обнаруживать все возможные проблемы или позволяют использовать TSC даже в ситуациях, когда он заведомо неточен.

HPET (High Precision Event Timer, Таймер событий высокой точности) предпочтительней использовать в системах, где он доступен, а TSC неточен. Сам чип этого таймера можно запрограммировать для разрешения точности до 100 наносекунд, но, возможно, системное время с такой точностью вы не увидите.

ACPI (Advanced Configuration and Power Interface, Расширенный интерфейс конфигурации и питания) предоставляет таймер PM (Power Management, Управление питанием), который в Linux называется acpi_pm. Время, поступающее из acpi_pm, в лучшем случае будет обеспечивать разрешение в 300 наносекунд.

На старом компьютерном оборудовании использовались таймеры 8254 PIT (Programmable Interval Timer, Программируемый интервальный таймер), RTC (real-time clock, часы реального времени), таймер APIC (Advanced Programmable Interrupt Controller, Расширенный программируемый контроллер прерываний) и таймер Cyclone. Эти таймеры обеспечивают разрешение до миллисекунд.

См. также

EXPLAIN

qhb_upgrade

qhb_upgrade — обновить экземпляр сервера QHB

Синтаксис

qhb_upgrade -b старый_каталог_bin -B новый_каталог_bin -d старый_каталог_конфигурации -D новый_каталог_конфигурации [параметр...]

Описание

Утилита qhb_upgrade позволяет обновлять данные, хранящиеся в файлах данных QHB до более поздней версии QHB без выгрузки/ восстановления данных, обычно требующихся для обновлений основных версий (например, с 1.1.0 до 1.3.1 или с 1.4 до 1.5). Для обновлений дополнительных (например, с 1.3.0 до 1.3.1) или корректирующих версий она не нужна.

В основных выпусках QHB регулярно добавляются новые функции, которые зачастую меняют структуру системных таблиц, но формат внутреннего хранилища данных меняется редко. qhb_upgrade использует этот факт для выполнения быстрых обновлений путем создания новых системных таблиц и простого повторного использования старых файлов пользовательских данных. Если будущий основной выпуск когда-либо изменит формат хранения данных таким образом, что старый формат данных станет нечитаемым, qhb_upgrade не будет использоваться для таких обновлений.

qhb_upgrade делает все возможное для гарантии того, что старый и новый кластеры двоично-совместимы, например, путем проверки совместимых настроек времени компиляции, включая 32/64-битные двоичные файлы. Важно, чтобы любые внешние модули также были двоично-совместимыми, хотя с помощью qhb_upgrade это проверить нельзя.

В таблице ниже приведены соответствия версий баз данных, для которых работает программа qhb_upgrade.

Исходная база данных | QHB 1.1.x | QHB 1.2.x | QHB 1.3.x | QHB 1.4.x | QHB 1.5.x
QHB 1.1.x |  | + | + | + | +
QHB 1.2.x |  |  | + | + | +
QHB 1.3.x |  |  |  | + | +
QHB 1.4.x |  |  |  |  | +
PostgreSQL 12.x | + | + | + | + | +
PostgreSQL 13.x |  |  |  |  | +
PostgreSQL 14.x |  |  |  |  | +

ВАЖНО!
Вследствие использования генерируемых ключей для шифрования в QHB 1.4.0 qhb_upgrade не может обновлять на эту и более новые версии QHB с версии qhb_upgrade 1.3.0 и более ранних, если они используют шифрование (qss_mode = 1).

Параметры

Утилита qhb_upgrade принимает следующие аргументы командной строки:

-b каталог_bin
--old-bindir=каталог_bin
каталог исполняемых файлов старой версии QHB; переменная среды. PGBINOLD

-B каталог_bin
--new-bindir=каталог_bin
каталог исполняемых файлов новой версии QHB; по умолчанию это каталог, в котором располагается qhb_upgrade; переменная среды PGBINNEW.

-c
--check
только проверить кластеры, не изменять никакие данные.

-d каталог_конфигурации
--old-datadir=каталог_конфигурации
каталог конфигурации старого кластера базы данных; переменная среды PGDATAOLD.

-D каталог_конфигурации
--new-datadir=каталог_конфигурации
каталог конфигурации нового кластера базы данных; переменная среды PGDATANEW.

-j число_заданий
--jobs=число_заданий
количество одновременно используемых процессов или потоков.

-k
--link
использовать жесткие ссылки вместо копирования файлов в новый кластер (данный параметр можно применять только для обновления. Для миграции с PostgreSQL его применять нельзя).

-o параметры
--old-options параметры
параметры, подлежащие передаче непосредственно старой команде qhb; при введении нескольких параметров они конкатенируют.

-O параметры
--new-options параметры
параметры, подлежащие передаче непосредственно новой команде qhb; при введении нескольких параметров они конкатенируют.

-p порт
--old-port=порт
номер порта старого кластера; переменная среды PGPORTOLD.

-P порт
--new-port=порт
номер порта нового кластера; переменная среды PGPORTNEW.

-r
--retain
сохранить SQL и файлы журнала даже после успешного завершения.

-s каталог
--socketdir=каталог

каталог, используемый для сокетов процесса qhbmaster во время обновления; по умолчанию это текущий рабочий каталог; переменная среды PGSOCKETDIR.

-U имя_пользователя
--username=имя_пользователя
имя пользователя, устанавливающего кластер; переменная среды PGUSER.

-v
--verbose
включить подробные внутренние сообщения.

-V
--version
показать информацию о версии и завершиться.

--clone
Использовать эффективное клонирование файлов (в некоторых системах также известное как «reflinks») вместо копирования файлов в новый кластер. Это приводит к почти мгновенному копированию файлов данных, давая преимущества в скорости параметра -k/--link, но при этом оставляя старый кластер нетронутым.
Клонирование файлов поддерживается лишь в некоторых операционных и файловых системах. Если оно выбрано, но не поддерживается, запуск qhb_upgrade вызовет ошибку. В настоящее время оно поддерживается в Linux (с ядром 4.5 или новее) с Btrfs и XFS (в файловых системах, созданных с поддержкой reflink) и в macOS с APFS.

--help
показать справку и завершиться.

Использование

Далее пошагово описано выполнение обновления с помощью qhb_upgrade:

Переместить старый кластер (необязательно)

Если вы используете установочный каталог, привязанный к конкретной версии, например /opt/QHB/1, то перемещать старый кластер не требуется. Все графические установщики используют установочные каталоги, привязанные к конкретной версии.

Если ваш установочный каталог не привязан к версии, например /usr/local/qhb, то текущий установочный каталог QHB необходимо переместить, чтобы он не конфликтовал с новой установкой QHB. Когда текущий сервер QHB выключен, каталог этой установки QHB можно безопасно переместить; исходя из предположения, что старым каталогом является /usr/local/qhb, его можно переименовать:

mv /usr/local/qhb /usr/local/qhb.old

При установке из исходного кода собрать новую версию

Соберите новую версию QHB с флагами скрипта configure, совместимыми со старым кластером. qhb_upgrade проверит qhb_controldata, чтобы убедиться, что все параметры совместимы, прежде чем начать обновление.

Установить новые исполняемые файлы QHB

Установите новые исполняемые файлы сервера и вспомогательные файлы. Утилита qhb_upgrade включена в установку по умолчанию.

Если при установке из исходного кода вы хотите установить новый сервер в нестандартном каталоге, используйте переменную prefix:

make prefix=/usr/local/qhb.new install

Инициализировать новый кластер QHB

Инициализируйте новый кластер, используя qhb_bootstrap (или initdb). Используйте совместимые флаги qhb_bootstrap (или initdb), которые соответствуют флагам старого кластера. Многие готовые установщики делают этот шаг автоматически. Запускать новый кластер не нужно.

Установить разделяемые объектные файлы расширения

Многие расширения и пользовательские модули, где бы они ни находились — в share/extension, qhb-contrib или в другом месте, используют разделяемые объектные файлы (или DLL), например pgcrypto.so. Если они применяются в старом кластере, то в новый кластер следует установить разделяемые объектные файлы, соответствующие новым исполняемым файлам, обычно посредством команд операционной системы. Нет необходимости устанавливать определения схем, (например, CREATE EXTENSION pgcrypto), так как они будут скопированы из старого кластера. Если доступны обновления расширений, qhb_upgrade сообщит об этом и создаст скрипт, который можно будет запустить позже, чтобы обновить их.

Скопировать пользовательские файлы полнотекстового поиска

Скопируйте все нестандартные файлы полнотекстового поиска (словари, тезаурусы, списки синонимов и стоп-слов) из старого кластера в новый.

Настроить аутентификацию

qhb_upgrade будет подключаться к старому и новому серверам несколько раз, поэтому, возможно, имеет смысл установить режим аутентификации peer в qhb_hba.conf или использовать файл ~/.pgpass (см. раздел Файл паролей).

Остановить оба сервера

Убедитесь, что оба сервера баз данных остановлены. Для этого в Unix можно выполнить:

pg_ctl -D /opt/pgsql stop
qhb_ctl -D /opt/PostgreSQL stop

Резервные серверы с потоковой репликации и трансляцией журнала могут продолжать работать до шага 9.

Подготовиться к обновлению резервного сервера

Если вы обновляете резервные серверы, используя методы, описанные в шаге 11, убедитесь, что старые резервные серверы находятся в актуальном состоянии, запустив qhb_controldata в старых основном и резервном кластерах. Убедитесь, что значения «Последнее положение контрольной точки» совпадают во всех кластерах. (Несоответствие возникнет, если старые резервные серверы все еще работают или были отключены раньше старого основного.) Также измените значение wal_level на replica в файле qhb.conf нового основного кластера.

Запустить qhb_upgrade

Всегда запускайте исполняемые файлы qhb_upgrade нового сервера, а не старого. Для qhb_upgrade требуется указать каталоги данных и исполняемых файлов (bin) старого и нового кластеров. Также можно указать имя пользователя, порт и желаете ли вы клонировать или создавать ссылки на файлы данных, вместо того чтобы копировать их (это поведение по умолчанию).

Если вы используете режим ссылок, обновление пройдет гораздо быстрее (нет копирования файлов) и задействует меньше места на диске, но вы не сможете обращаться к старому кластеру, когда запустите новый после обновления. Кроме того, режим ссылок требует, чтобы каталоги данных старого и нового кластера находились в одной файловой системе. (Табличные пространства и pg_wal могут находиться в других файловых системах.) Режим клонирования предоставляет те же преимущества в скорости и экономии места на диске, но не делает старый кластер нерабочим при запуске нового. Режим клонирования также требует, чтобы старый и новый каталоги данных находились в одной файловой системе. Этот режим доступен только в некоторых операционных и файловых системах.

Параметр --jobs позволяет использовать для копирования/привязки файлов и для выгрузки и восстановления схем баз данных одновременно несколько ядер ЦП; в качестве начального значения лучше всего подойдет максимальное количество ядер ЦП и табличных пространств. Этот параметр может значительно сократить время обновления сервера со множеством баз данных, работающего на многопроцессорном компьютере.

ВНИМАНИЕ!
Перед обновлением кластера утилитами qhb_upgrade следует выключить параметр logon_jobs выставив его в off и перезапустить СУБД. Иначе стабильность процесса обновления кластера не гарантируется (см. раздел Профили безопасности).

ВНИМАНИЕ!
Перед обновлением кластера утилитами qhb_upgrade следует выключить параметр integrity_checks выставив его в off и перезапустить СУБД. Иначе стабильность процесса обновления кластера не гарантируется.

После запуска qhb_upgrade проверит совместимость двух кластеров, а затем выполнит обновление. Если нужно выполнить только проверки, можно использовать qhb_upgrade --check (старый сервер при этом останавливать необязательно). Также qhb_upgrade --check сообщит, какие корректировки потребуется сделать вручную после обновления. Если вы собираетесь использовать режим ссылок или клонирования, следует указать вместе с --check параметр --link или --clone, чтобы включить проверки, специфичные для выбранного режима. qhb_upgrade требуются права на запись в текущий каталог.

Очевидно, что во время обновления никто не должен обращаться к кластерам. По умолчанию qhb_upgrade запускает серверы на порту 5432, чтобы избежать непреднамеренных клиентских подключений. В процессе обновления можно использовать один номер порта для обоих кластеров, поскольку старый и новый кластеры не будут работать одновременно. Однако при проверке старого работающего сервера номера старого и нового порта должны отличаться.

Если при восстановлении схемы базы данных происходит ошибка,qhb_upgrade завершает свою работу, и вам придется вернуться к старому кластеру, как описано ниже в шаге 17. Чтобы снова попытаться запустить qhb_upgrade, нужно будет внести коррективы в старый кластер, чтобы qhb_upgrade могла успешно восстановить схему. Если проблема возникла в модуле share/extension, может потребоваться удалить этот модуль в старом кластере, а затем установить его в новом после обновления (предполагается, что этот модуль не используется для хранения пользовательских данных).

Обновить резервные серверы с потоковой репликацией и трансляцией журнала

Если вы используете режим ссылок и у вас реализована потоковая репликация (см. подраздел Потоковая репликация) или доставка журналов (см. раздел Доставка журналов на резервные серверы) для резервных серверов, то для быстрого их обновления можно выполнить следующие шаги. Вы будете запускать не qhb_upgrade на резервных серверах, а rsync на основном. На этом этапе пока не нужно запускать никаких серверов.

Если вы не используете режим ссылок, не хотите использовать rsync или вам нужно более простое решение, пропустите инструкции этого шага и просто заново создайте резервные серверы после завершения qhb_upgrade и запуска нового основного сервера.

Установите новые исполняемые файлы QHB на резервных серверах
Убедитесь, что новые исполняемые и вспомогательные файлы установлены на всех резервные серверах.

Убедитесь, что на резервных серверах не существует новых каталогов данных (или они пустые)
Если запускалась qhb_bootstrap (или initdb), удалите на резервных серверах новые каталоги данных.

Установите дополнительные разделяемые объектные файлы
Установите на новых резервных серверах те же разделяемые объектные файлы расширений, что вы установили в новом основном кластере.

Остановите резервные серверы
Если резервные серверы все еще работают, остановите их сейчас, используя приведенные выше инструкции.

Сохраните файлы конфигурации
Сохраните все файлы конфигурации из старых каталогов конфигурации резервных серверов, такие как qhb.conf (и все включенные в него файлы), qhb.auto.conf и qhb_hba.conf, так как на следующем этапе они будут перезаписаны или удалены.

Запустите rsync
При использовании режима ссылок резервные серверы можно быстро обновить с помощью rsync. Для этого в каталоге, внутри которого находятся каталоги старого и нового кластера, для каждого резервного сервера выполните на основном:

rsync --archive --delete --hard-links --size-only --no-inc-recursive old_cluster new_cluster remote_dir

где каталоги old_cluster и new_cluster задаются относительно текущего каталога на основном сервере, а remote_dir находится над каталогами старого и нового кластера на резервном. Структура подкаталогов в указанных каталогах на основном и резервном серверах должна совпадать. Обратитесь к странице руководства rsync за подробной информацией о том, как указать удаленный каталог, например так:

rsync --archive --delete --hard-links --size-only --no-inc-recursive /opt/PostgreSQL \
      /opt/PostgreSQL standby.example.com:/opt/PostgreSQL

Используя параметр rsync --dry-run, можно проверить, что будет делать команда. Хотя rsync должна быть запущена на основном сервере как минимум для одного резервного, но затем, пока обновленный резервный сервер остается остановленным, на нем можно запустить rsync для обновления других резервных серверов.

Во время этой операции записываются ссылки, созданные в режиме ссылок qhb_upgrade, которые связывают файлы в старом и новом кластерах на основном сервере. Затем находятся соответствующие файлы в старом кластере резервного и в его новом кластере создаются ссылки на них. Файлы, которые не были связаны ссылками на основном сервере, просто копируются с него на резервный. (Обычно они небольшого размера.) Это обеспечивает быстрое обновление резервного сервера. К сожалению, при этом rsync напрасно копирует файлы, связанные с временными и нежурналируемыми таблицами, поскольку эти файлы обычно не существуют на резервных серверах.

Если у вас есть табличные пространства, вам нужно будет выполнить аналогичную команду rsync для каждого каталога табличного пространства, например:

rsync --archive --delete --hard-links --size-only --no-inc-recursive /vol1/pg_tblsp/PG_9.5_201510051 \
      /vol1/pg_tblsp/PG_9.6_201608131 standby.example.com:/vol1/pg_tblsp

Если вы переместили pg_wal за пределы каталогов данных, rsync следует запустить и в этих каталогах.

Настройте резервные серверы с потоковой репликации и трансляцией журнала
Настройте серверы для трансляции журнала. (Запускать pg_start_backup() и pg_stop_backup() или делать резервную копию файловой системы не требуется, так как резервные серверы по-прежнему синхронизированы с основным.) Слоты репликации не копируются и должны создаваться заново.

Восстановить qhb_hba.conf

Если вы изменили qhb_hba.conf, восстановите его исходные установки. Также может потребоваться настроить другие файлы конфигурации в новом кластере в соответствии со старым, например qhb.conf (и все включенные в него файлы), qhb.auto.conf.

Запустить новый сервер

Теперь можно безопасно запустить новый сервер, а затем и любые резервные серверы, синхронизированные с помощью rsync.

Действия после обновления

Если требуются какие-либо действия после обновления, qhb_upgrade выдаст предупреждения об этом по завершении работы. Она также сгенерирует файлы скриптов, которые должен запустить администратор. Эти скрипты будут подключаться к каждой базе данных, которой требуются дополнительные действия после обновления. Каждый скрипт должен быть запущен следующей командой:

psql --username=qhb --file=script.sql qhb

Эти скрипты можно выполнять в любом порядке и удалить после выполнения.

ВНИМАНИЕ!
Как правило, к таблицам, которые задействованы в перестраивающих базу данных скриптах, опасно обращаться, пока эти скрипты не завершат свою работу; это может привести к некорректным результатам или плохой производительности. К таблицам, которые не задействованы в скриптах, можно обращаться немедленно.

Статистика

Поскольку статистика оптимизатора посредством qhb_upgrade не передается, вы получите указание выполнить команду для восстановления этой информации после обновления. Возможно, для этого придется установить параметры подключения к новому кластеру.

Удалить старый кластер

Если вы удовлетворены обновлением, можно удалить каталоги данных старого кластера, запустив упомянутый в qhb_upgrade скрипт после завершения ее работы. (Автоматическое удаление невозможно, если в старом каталоге данных находятся пользовательские табличные пространства.) Также можно удалить каталоги старой установки (например bin, share).

Возврат к старому кластеру

Если после запуска qhb_upgrade вы хотите вернуться к старому кластеру, есть несколько вариантов:

Если использовался параметр --check, то старый кластер не изменился; его можно просто перезапустить.

Если не использовался параметр --link, то старый кластер не изменился; его можно просто перезапустить.

Если использовался параметр --link, у старого и нового кластеров могут оказаться разделяемые файлы данных:

Если работа qhb_upgrade прервалась до начала связывания ссылками, то старый кластер не изменился; его можно просто перезапустить.

Если вы не запускали новый кластер, то старый кластер не изменился, за исключением того, что в начале связывания ссылками к имени $PGDATA/global/ pg_control был добавлен суффикс .old. Чтобы повторно использовать старый кластер, удалите суффикс .old из имени $PGDATA/global/pg_control; затем можно перезапустить старый кластер.

Если вы запустили новый кластер, он сделал записи в разделяемых файлах, и использовать старый кластер небезопасно. В этом случае старый кластер нужно будет восстановить из резервной копии.

Примечания

Утилита qhb_upgrade создает в текущем рабочем каталоге различные рабочие файлы, например дампы схемы. В целях безопасности следует позаботиться о том, чтобы этот каталог не был доступен для чтения или записи другими пользователями.

qhb_upgrade на короткое время запускает процессы qhbmaster в старом и новом каталогах данных. Временные файлы сокетов Unix для взаимодействия с этими процессами по умолчанию создаются в текущем рабочем каталоге. В некоторых ситуациях имя пути для текущего каталога может быть слишком длинным, чтобы стать допустимым именем сокета. В этом случае можно воспользоваться параметром -s, чтобы файлы сокета помещались в каталог с более коротким именем пути. В целях безопасности следует позаботиться о том, чтобы этот каталог не был доступен для чтения или записи другими пользователями.

qhb_upgrade сообщит обо всех ошибках, перестроениях и переиндексациях, если они влияют на установку; при этом для перестройки таблиц и индексов будут автоматически созданы скрипты, следующие за обновлением. Если вы попытаетесь автоматизировать обновление множества кластеров, то обнаружите, что для кластеров с идентичными схемами баз данных после обновления потребуется предпринять одинаковые шаги; это происходит потому, что эти шаги основаны на схемах баз данных, а не на данных пользователей.

Для проверки развертывания новой версии создайте копию только схемы старого кластера, добавьте в него фиктивные данные и обновите его.

qhb_upgrade не поддерживает обновление баз данных, содержащих таблицы со столбцами, использующими следующие системные типы данных reg*, ссылающиеся на OID:

regcollation
regconfig
regdictionary
regnamespace
regoper
regoperator
regproc
regprocedure
(столбцы regclass, regrole и regtype обновлять можно.)

Если вы хотите использовать режим ссылок, но чтобы при этом старый кластер не изменился при запуске нового, рассмотрите использование режима клонирования. Если он недоступен, сделайте копию старого кластера и обновите ее в режиме ссылок. Чтобы сделать рабочую копию старого кластера, используйте команду rsync для создания черновика старого кластера во время работы сервера, затем отключите старый сервер и снова запустите rsync --checksum, чтобы обновить эту копию с необходимыми изменениями и сделать ее согласованной. (Параметр --checksum необходим, поскольку у rsync глубина детализации изменения файлов составляет всего одну секунду.) При этом вы можете исключить некоторые файлы, например qhbmaster.pid, как описано в подразделе Создание базовой резервной копии с использованием API низкого уровня. Если ваша файловая система поддерживает снимки файловой системы или копирование файлов при записи, вы можете применить это для резервного копирования старого кластера и табличных пространств, однако эти снимки и копии должны быть созданы единовременно или или пока сервер баз данных отключен.

См. также

qhb_bootstrap, qhb_ctl, qhb_dump, qhb

qhb_waldump

qhb_waldump — отобразить журнал упреждающей записи кластера баз данных QHB в удобочитаемом виде

Синтаксис

qhb_waldump [параметр...] [начальный_сегмент [конечный_сегмент]]

Описание

Утилита qhb_waldump отображает журнал упреждающей записи (WAL) и в основном полезна для отладки или обучения.

Эту утилиту может запустить только тот пользователь, который установил сервер, поскольку для нее требуется доступ только для чтения к каталогу данных.

Параметры

Следующие параметры командной строки управляют расположением и форматом вывода:

начальный_сегмент
Начать чтение с указанного файла сегмента журнала. Это неявно определяет путь, по которому будет выполняться поиск файлов, и временную шкалу для использования.

конечный_сегмент
Остановиться после прочтения указанного файла сегмента журнала.

-b
--bkp-details
Выводить подробную информацию о резервных блоках страниц.

-e конец
--end=конец
Прекратить чтение в указанном месте WAL вместо чтения потока журнала до конца.

-f
--follow
Достигнув конца корректного WAL, продолжать опрос раз в секунду на предмет появления новых записей WAL.

-n предел
--limit=предел
Отобразить указанное количество записей и остановиться.

-p путь
--path=путь
Задает каталог для поиска файлов сегментов журнала или каталог с подкаталогом pg_wal, который содержит такие файлы. По умолчанию выполняется поиск в текущем каталоге, подкаталоге pg_wal текущего каталога и подкаталоге pg_wal каталога PGDATA.

-q
--quiet
Не выводить ничего, кроме ошибок. Этот параметр может быть полезен, когда вы хотите узнать, можно ли успешно проанализировать диапазон записей WAL, но содержимое записей вас не интересует.

-r менеджер_ресурсов
--rmgr=менеджер_ресурсов
Выводить только записи, сгенерированные указанным менеджером ресурсов. Если в качестве имени передается list, то вывести список допустимых имен менеджеров ресурсов и завершиться.

-s начало
--start=начало
Место в WAL, с которого нужно начать чтение. По умолчанию чтение начинается с первой корректной записи журнала в самом первом найденном файле.

-t временная_шкала
--timeline=временная_шкала
Временная шкала, с которой нужно читать записи журнала. По умолчанию используется значение параметра начальный_сегмент (если он задан); в противном случае значение по умолчанию равно 1.

-V
--version
Вывести версию qhb_waldump и завершиться.

-x xid
--xid=xid
Отобразить только те записи, которые отмечены указанным идентификатором транзакции.

-z
--stats[=record]
Отобразить сводную статистику (количество и размер записей и образов полных страниц) вместо отдельных записей. Дополнительно можно генерировать статистику для каждой записи, а не по менеджерам ресурсов.

-?
--help
Показать справку об аргументах командной строки qhb_waldump и завершиться.

Переменные среды

PGDATA
Каталог данных; см. также параметр -p.

PG_COLOR
Указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Во время работы сервера может дать неправильные результаты.

Отображается только указанная временная шкала (или значение по умолчанию, если она не указана). Записи в других временных шкалах игнорируются.

Утилита qhb_waldump не может читать файлы WAL с суффиксом .partial. Если эти файлы нужно прочитать, следует удалить суффикс .partial из имени файла.

См. также

Внутреннее устройство WAL

Написание обертки сторонних данных

Все операции со сторонней таблицей производятся через созданную для нее обертку сторонних данных, которая состоит из набора функций, вызываемых основным сервером. Обертка сторонних данных отвечает за извлечение данных из удаленного источника и возвращению их исполнителю запросов QHB. Если должно поддерживаться изменение сторонних таблиц, обертка должна выполнять и эту операцию. В этой главе описывается, как написать новую обертку сторонних данных.

Обертки сторонних данных, включенные в дистрибутив, являются хорошими примерами для написания собственных оберток (см. подкаталог share/extension дерева исходного кода). Кроме того, некоторые полезные сведения содержит справочная страница команды CREATE FOREIGN DATA WRAPPER.

Примечание
Стандарт SQL определяет интерфейс для написания оберток сторонних данных. Однако QHB не реализует этот API, потому что усилия по его размещению в QHB были бы велики, а стандартизованный API все равно не получил широкого распространения.

Функции оберток сторонних данных

Автор FDW (Foreign Data Wrapper, обертки сторонних данных) должен реализовать функцию обработки и при необходимости функцию проверки. Обе функции должны быть написаны на компилируемом языке, например C/RUST, с использованием интерфейса версии 1. Дополнительные сведения по соглашениям о вызовах на языке C/RUST и динамической загрузке см. в разделе Функции на нативном языке.

Функция обработки просто возвращает структуру указателей функций на функции обратного вызова, которые будут вызываться планировщиком, исполнителем и различными командами обслуживания. Большая часть усилий по написанию FDW заключается в реализации этих функций обратного вызова. Функция обработки должна быть зарегистрирована в QHB как не принимающая аргументы и возвращающая специальный псевдотип fdw_handler. Функции обратного вызова являются простыми функциями на C/RUST и не видны или не вызываются на уровне SQL. Функции обратного вызова описываются в разделе Подпрограммы обратного вызова обертки сторонних данных.

Функция проверки отвечает за проверку параметров, указанных в командах CREATE и ALTER для ее обертки сторонних данных, а также сторонних серверов, сопоставлений пользователей и сторонних таблиц, использующих эту обертку. Функция проверки должна быть зарегистрирована как принимающая два аргумента, текстовый массив, содержащий проверяемые параметры, и OID, представляющий тип объекта, с которым связаны эти параметры (в виде OID системного каталога объект будет сохранен: ForeignDataWrapperRelationId, ForeignServerRelationId, UserMappingRelationId или ForeignTableRelationId). Если функция проверки отсутствует, параметры не проверяются ни во время создания объекта, ни во время его изменения.

Подпрограммы обратного вызова обертки сторонних данных

Функция обработки FDW возвращает выделенную palloc структуру FdwRoutine, содержащую указатели на описанные ниже функции обратного вызова. Функции, связанные со сканированием, являются обязательными, остальные можно использовать по желанию.

Подпрограммы FDW для сканирования сторонних таблиц

void
GetForeignRelSize(PlannerInfo *root,
                  RelOptInfo *baserel,
                  Oid foreigntableid);

Получить оценку размера отношения для сторонней таблицы. Эта функция вызывается в начале планирования запроса, который сканирует стороннюю таблицу. Аргумент root — глобальная информация планировщика о запросе, baserel — информация планировщика об этой таблице, а foreigntableid — это OID сторонней таблицы в каталоге pg_class. (foreigntableid можно получить из структур данных планировщика, но он передается явно, чтобы сэкономить усилия.)

Эта функция должна изменить значение baserel->rows на ожидаемое количество строк, возвращаемых при сканировании таблицы, с учетом фильтра, заданного ограничительными отборами. Начальное значение baserel->rows — некая константа по умолчанию, которую следует заменить, если это вообще возможно. Также функция может изменять baserel->width, если есть возможность улучшить оценку средней ширины строки результата. (Начальное значение зависит от типов данных и значений среднего размера столбцов, измененных при последнем ANALYZE.) Кроме того, эта функция может изменить значение baserel->tuples, если она может вычислить лучшую оценку общего количества строк в сторонней таблице. (Начальное значение берется из поля pg_class.reltuples, представляющего общее количество строк, выведенное при последнем ANALYZE; оно будет равно -1, если для этой сторонней таблицы еще не выполнялась команда ANALYZE.)

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных

void
GetForeignPaths(PlannerInfo *root,
                RelOptInfo *baserel,
                Oid foreigntableid);

Создать возможные пути доступа для сканирования сторонней таблицы. Эта функция вызывается во время планирования запроса. Аргументы такие же, как и для GetForeignRelSize, которая к тому моменту уже будет вызвана.

Эта функция должна генерировать по крайней мере один путь доступа (узел ForeignPath) для сканирования сторонней таблицы и вызвать add_path, чтобы добавить каждый такой путь к baserel->pathlist. Для построения узлов ForeignPath рекомендуется использовать create_foreignscan_path. Эта функция может генерировать несколько путей доступа, например путь, который имеет допустимый pathkeys для представления предварительно отсортированного результата. Каждый путь доступа должен содержать оценки стоимости и может содержать любую внутреннюю информацию FDW, необходимую для определения предполагаемого метода сканирования.

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.

ForeignScan *
GetForeignPlan(PlannerInfo *root,
               RelOptInfo *baserel,
               Oid foreigntableid,
               ForeignPath *best_path,
               List *tlist,
               List *scan_clauses,
               Plan *outer_plan);

Создать узел плана ForeignScan из выбранного стороннего пути доступа. Эта функция вызывается в конце планирования запроса. Аргументы такие же, как для GetForeignRelSize, плюс выбранный путь ForeignPath (ранее созданный GetForeignPaths, GetForeignJoinPaths или GetForeignUpperPaths), целевой список, который должен быть выдан этим узлом плана, ограничивающие предложения, которые должны применяться этим узлом плана, и внешний вложенный план узла ForeignScan, который используется для перепроверок, выполняемых RecheckForeignScan. (Если путь предназначен для соединения, а не для базового отношения, аргумент foreigntableid равен InvalidOid.)

Эта функция должна создавать и возвращать узел плана ForeignScan; для построения узла ForeignScan рекомендуется использовать make_foreignscan.

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.

void
BeginForeignScan(ForeignScanState *node,
                 int eflags);

Начать выполнять внешнее сканирование. Эта функция вызывается во время запуска исполнителя запроса. Она должна выполнить всю необходимую подготовительную работу перед началом сканирования, но не начать выполнение собственно сканирования (это должно быть сделано при первом вызове IterateForeignScan). Узел ForeignScanState уже был создан, но его поле fdw_state по-прежнему имеет значение NULL. Информация о сканируемой таблице доступна через узел ForeignScanState (в частности, из нижележащего узла плана ForeignScan, содержащего всю внутреннюю информацию FDW, предоставляемую функцией GetForeignPlan). Поле eflags содержит флаговые биты, описывающие режим работы исполнителя для данного узла плана.

Обратите внимание, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY) равно true, эта функция не должна выполнять никаких видимых извне действий; она должна сделать только необходимый минимум, чтобы состояние узла стало допустимым для ExplainForeignScan и EndForeignScan.

TupleTableSlot *
IterateForeignScan(ForeignScanState *node);

Извлечь одну строку из внешнего источника, возвращая ее в слоте таблицы кортежей (для этой цели следует использовать узел ScanTupleSlot). Если больше нет доступных строк, возвращает NULL. Инфраструктура слотов таблицы кортежей позволяет возвращать либо физический, либо виртуальный кортеж; в большинстве случаев последний вариант предпочтительнее с точки зрения производительности. Обратите внимание, что эта функция вызывается в контексте кратковременной памяти, который будет сбрасываться между вызовами. Если вам нужно более долговечное хранение, создайте контекст памяти в BeginForeignScan или используйте es_query_cxt из принадлежащей узлу структуры EState.

Возвращаемые строки должны соответствовать целевому списку fdw_scan_tlist, если тот был предоставлен, в противном случае они должны соответствовать типу строки сканируемой внешней таблицы. Если в целях оптимизации вы решите исключить выборку ненужных столбцов, следует вставить в позиции этих столбцов NULL или же сгенерировать список fdw_scan_tlist, где эти столбцы опущены.

Обратите внимание, что исполнителю QHB все равно, нарушают ли возвращенные строки какие-либо ограничения, определенные для внешней таблицы, — но это важно для планировщика, который может неправильно оптимизировать запросы, если во внешней таблице есть строки, не удовлетворяющие объявленному ограничению. Если ограничение нарушается, когда пользователь объявил, что оно должно выполняться, возможно, будет целесообразно вызвать ошибку (так же, как это необходимо сделать в случае несоответствия типа данных).

void
ReScanForeignScan(ForeignScanState *node);

Перезапустить сканирование с самого начала. Обратите внимание, что значения тех параметров, от которых зависит сканирование, могут измениться, поэтому новое сканирование необязательно возвращает точно такие же строки.

void
EndForeignScan(ForeignScanState *node);

Завершить сканирование и освободить ресурсы. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить.

Подпрограммы FDW для сканирования сторонних соединений

Если FDW поддерживает выполнение сторонних соединений удаленно (а не путем извлечения данных обеих таблиц и выполнения соединения локально), она должна предоставить эту функцию обратного вызова:

void
GetForeignJoinPaths(PlannerInfo *root,
                    RelOptInfo *joinrel,
                    RelOptInfo *outerrel,
                    RelOptInfo *innerrel,
                    JoinType jointype,
                    JoinPathExtraData *extra);

Создать возможные пути доступа для объединения двух (или более) сторонних таблиц, принадлежащих одному и тому же стороннему серверу. Эта необязательная функция вызывается во время планирования запроса. Как и в случае с GetForeignPaths, эта функция должна сгенерировать путь(и) ForeignPath для предоставляемого joinrel (для их построения используйте create_foreign_join_path), и вызвать add_path, чтобы добавить эти пути к набору путей, подходящих для этого соединения. Но, в отличие от GetForeignPaths, нет необходимости, чтобы эта функция успешно создавала хотя бы один путь, поскольку всегда доступны пути с использованием локального соединения.

Обратите внимание, что эта функция будет повторно вызываться для одного и того же отношения соединения при различных сочетаниях внутренних и внешних отношений; ответственность за сведение к минимуму двойной работы лежит на FDW.

Если для соединения выбирается путь ForeignPath, он будет представлять весь процесс соединения; пути, созданные для задействованных таблиц и дочерних соединений, использоваться не будут. Последующая обработка пути соединения выполняется во многом так же, как и для пути сканирования одной сторонней таблицы. Единственное отличие заключается в том, что поле scanrelid результирующего узла плана ForeignScan должно быть установлено в ноль, так как он не представляет никакого отношения; вместо этого набор соединенных отношений представляет поле fs_relids узла ForeignScan. (Последнее поле автоматически устанавливается кодом ядра планировщика и не должно заполняться FDW). Другое отличие состоит в том, что поскольку список столбцов для удаленного соединения нельзя найти в системных каталогах, FDW должна заполнить поле fdw_scan_tlist соответствующим перечнем узлов TargetEntry, представляющих набор столбцов, которые она будет выдавать во время выполнения в возвращаемых ею кортежах.

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.

Подпрограммы FDW для планирования обработки после сканирования/соединения

Если FDW поддерживает выполнение удаленной обработки после сканирования/соединения, например, удаленную агрегацию, она должна предоставить эту функцию обратного вызова:

void
GetForeignUpperPaths(PlannerInfo *root,
                     UpperRelationKind stage,
                     RelOptInfo *input_rel,
                     RelOptInfo *output_rel,
                     void *extra);

Создать возможные пути доступа для обработки верхних отношений (это термин планировщика для всех процессов обработки запросов после сканирования/объединения, таких как агрегация, оконные функции, сортировка и изменения таблиц). Эта необязательная функция вызывается во время планирования запроса. В настоящее время она вызывается только в том случае, если все базовые отношения, участвующие в запросе, принадлежат одной FDW. Эта функция должна сгенерировать путь(и) ForeignPath для любой обработки после сканирования/объединения, которую FDW умеет выполнять удаленно (для их построения используйте create_foreign_upper_path), и вызвать add_path чтобы добавить эти пути к указанному верхнему отношению. Как и в случае с GetForeignJoinPaths, нет необходимости, чтобы эта функция успешно создавала какие-либо пути, поскольку всегда доступны пути с использованием локального соединения.

Аргумент stag определяет, какой шаг после сканирования/объединения в настоящее время рассматривается. Аргумент output_rel — верхнее отношение, которое должно принимать пути, представляющие вычисление этого шага, а input_rel — отношение, представляющее входные данные для этого шага. Аргумент extra предоставляет дополнительную информацию, в настоящее время он устанавливается только для UPPERREL_PARTIAL_GROUP_AGG или UPPERREL_GROUP_AGG, и тогда указывает на структуру GroupPathExtraData, либо для UPPERREL_FINAL, и тогда указывает на структуру FinalPathExtraData. (Обратите внимание, что пути ForeignPath, добавляемые в output_rel обычно не имеют прямой зависимости от путей input_rel, поскольку ожидается, что их обработка будет осуществляться извне. Однако изучение путей, ранее сгенерированных для предыдущего шага обработки, может помочь избежать лишней работы при планировании.)

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.

Подпрограммы FDW для изменения сторонних таблиц

Если FDW поддерживает записываемые сторонние таблицы, она должна предоставлять некоторые или все из нижеперечисленных функций обратного вызова, в зависимости от потребностей и возможностей FDW:

void
AddForeignUpdateTargets(Query *parsetree,
                        RangeTblEntry *target_rte,
                        Relation target_relation);

Операции UPDATE и DELETE выполняются для строк, ранее извлеченных функциями сканирования таблиц. FDW может потребоваться дополнительная информация, например идентификатор строки или значения столбцов первичного ключа, чтобы гарантированно определить нужную строку для изменения или удаления. Чтобы поспособствовать этому, данная функция может добавить дополнительные скрытые или «бросовые» целевые столбцы в список столбцов, которые должны быть получены из сторонней таблицы во время UPDATE или DELETE.

Чтобы сделать это, создайте структуру Var, представляющую нужное лишнее значение, и передайте ее в add_row_identity_var вместе с именем для бросового столбца. (Если требуется несколько столбцов, эту процедуру можно повторить несколько раз.) Для каждой необходимой структуры Var следует выбрать уникальное имя бросового столбца, но у структур Var, отличающихся только значением поля varno, имена столбцов могут и должны быть одинаковыми. Основная система использует следующие имена бросовых столбцов: tableoid для столбца tableoid таблицы, ctid или ctidN для ctid, wholerow для Var, содержащей целую строку и отмеченной vartype = RECORD, и wholerowN для Var, содержащей целую строку, с vartype, равным объявленному типу строки таблицы. По возможности используйте эти имена повторно (планировщик будет комбинировать дублирующиеся запросы для идентичных бросовых столбцов). Если в дополнение к этим вам нужен другой тип бросового столбца, возможно, будет разумно выбрать имя с префиксов вашего имени расширения во избежание конфликтов с другими FDW.

Если указатель AddForeignUpdateTargets имеет значение NULL, никакие дополнительные целевые выражения не добавляются. (Это сделает невозможным выполнение операций DELETE, хотя UPDATE все еще можно будет осуществить, если FDW идентифицирует строки с помощью неизменяемого первичного ключа.)

List *
PlanForeignModify(PlannerInfo *root,
                  ModifyTable *plan,
                  Index resultRelation,
                  int subplan_index);

Выполнить все дополнительные действия планирования, необходимые для добавления изменения или удаления в сторонней таблице. Эта функция генерирует внутреннюю информацию FDW, которая будет присоединена к узлу плана ModifyTable, выполняющему действие по изменению. Эта внутренняя информация должна иметь форму List и будет передана BeginForeignModify на стадии выполнения.

Аргумент root — это глобальная информация планировщика о запросе. Аргумент plan — это узел плана ModifyTable, который является полным за исключением поля fdwPrivLists. Аргумент resultRelation идентифицирует целевую стороннюю таблицу по индексу в диапазоне таблиц. Аргумент subplan_index определяет, какая это цель узла плана ModifyTable, считая от нуля; используйте это, если хотите индексировать во вложенных структурах узла plan, соответствующих целевым отношениям.

Дополнительную информацию см. в разделе Планирование запросов с оберткой сторонних данных.

Если указатель PlanForeignModify имеет значение NULL, никакие дополнительные действия во время планирования не предпринимаются, а список fdw_private, передаваемый в BeginForeignModify, будет равен NIL.

void
BeginForeignModify(ModifyTableState *mtstate,
                   ResultRelInfo *rinfo,
                   List *fdw_private,
                   int subplan_index,
                   int eflags);

Начать выполнение операции изменения сторонней таблицы. Эта подпрограмма вызывается во время запуска исполнителя запроса. Она должна выполнить всю необходимую подготовительную работу перед фактическими модификациями таблицы. Впоследствии для кортежей, которые будут добавляться, изменяться или удаляться, будет вызываться ExecForeignInsert, ExecForeignUpdate или ExecForeignDelete.

Аргумент mtstate — это общее состояние выполняемого узла плана ModifyTable; через эту структуру доступны глобальные данные о плане и состоянии выполнения. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. (Для сохранения любого внутреннего состояния, требуемого для этой операции, FDW доступно поле ri_FdwState структуры ResultRelInfo.) Аргумент fdw_private содержит внутренние данные, если они были сгенерированы подпрограммой PlanForeignModify. Аргумент subplan_index определяет, какая это цель узла плана ModifyTable. Аргумент eflags содержит флаговые биты, описывающие рабочий режим исполнителя для данного узла плана.

Обратите внимание, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY) равно true, эта функция не должна выполнять никаких видимых извне действий; она должна сделать только необходимый минимум, чтобы состояние узла стало допустимым для ExplainForeignModify и EndForeignModify.

Если указатель BeginForeignModify имеет значение NULL, никакие действия во время запуска исполнителя не предпринимаются.

TupleTableSlot *
ExecForeignInsert(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

Добавить один кортеж в стороннюю таблицу. Аргумент estate — это глобальное состояние выполнения запроса. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. Аргумент slot содержит добавляемый кортеж; он будет соответствовать определению типа строки сторонней таблицы. Аргумент planSlot содержит кортеж, сгенерированный вложенным планом узла плана ModifyTable; он отличается от slot тем, что может содержать дополнительные «бросовые» столбцы. (Обычно planSlot не особенно интересен в случаях INSERT, но предоставляется для полноты.)

Возвращаемое значение является либо слотом, содержащим фактически добавленные данные (они могут отличаться от предоставленных данных, например, в результате действий триггеров), либо NULL, если на самом деле никакая строка не была вставлена (опять же, как правило, в результате действий триггеров). Для этой цели можно повторно использовать переданное на вход значение slot.

Данные в возвращаемом слоте используются только в том случае, если оператор INSERT содержит предложение RETURNING или задействует представление WITH CHECK OPTION, либо если сторонняя таблица имеет триггер AFTER ROW. Триггерам требуются все столбцы, но в целях оптимизации FDW может выбрать не возвращать некоторые или все столбцы, в зависимости от содержимого предложения RETURNING или ограничений WITH CHECK OPTION. Независимо от этого, какой-то слот должен быть возвращен, чтобы подтвердить успешное выполнение, иначе число выданных запросом строк будет неверным.

Если указатель ExecForeignInsert имеет значение NULL, попытки добавления данных в стороннюю таблицу провалятся, и будет выдано сообщение об ошибке.

Обратите внимание, что эта функция также вызывается при добавлении перенаправляемых кортежей в партицию сторонней таблицы или выполнении команды COPY FROM для сторонней таблицы, и в этом случае она вызывается иначе, чем в случае INSERT. Функции обратного вызова, позволяющие FDW поддерживать это, описываются ниже.

TupleTableSlot **
ExecForeignBatchInsert(EState *estate,
                       ResultRelInfo *rinfo,
                       TupleTableSlot **slots,
                       TupleTableSlot **planSlots,
                       int *numSlots);

Массово вставить несколько кортежей в стороннюю таблицу. Аргументы те же, что и у функции ExecForeignInsert, за исключением того, что в slots и planSlots содержится несколько кортежей и в *numSlots задается количество кортежей в этих массивах.

Возвращаемое значение является массивом слотов, содержащих фактически добавленные данные (они могут отличаться от предоставленных данных, например, в результате действий триггеров). Для этой цели можно повторно использовать переданное на вход значение slots. Количество успешно добавленных кортежей возвращается в *numSlots.

Данные в возвращаемом слоте используются, только если оператор INSERT задействует представление WITH CHECK OPTION либо если сторонняя таблица имеет триггер AFTER ROW. Триггерам требуются все столбцы, но в целях оптимизации FDW может выбрать не возвращать некоторые или все столбцы, в зависимости от содержимого ограничений WITH CHECK OPTION.

Если указатель ExecForeignBatchInsert или GetForeignModifyBatchSize имеет значение NULL, при попытках добавления в стороннюю таблицу будет использоваться функция ExecForeignInsert. Также эта функция не используется, если INSERT имеет предложение RETURNING.

Обратите внимание, что эта функция также вызывается при добавлении перенаправляемых кортежей в партицию сторонней таблицы. Функции обратного вызова, позволяющие FDW поддерживать это, описываются ниже.

int
GetForeignModifyBatchSize(ResultRelInfo *rinfo);

Выдать максимальное количество кортежей, которое можно обработать за один вызов ExecForeignBatchInsert для указанной сторонней таблицы. Исполнитель передает в ExecForeignBatchInsert не более заданного количества кортежей. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. Ожидается, что FDW предоставит пользователю параметр стороннего сервера и/или сторонней таблицы для установки этого значения или использует некоторое жестко закодированное значение.

Если указатель ExecForeignBatchInsert или GetForeignModifyBatchSize имеет значение NULL, при попытках добавления в стороннюю таблицу будет использоваться функция ExecForeignInsert.

TupleTableSlot *
ExecForeignUpdate(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

Добавить один кортеж в стороннюю таблицу. Аргумент estate — это глобальное состояние выполнения запроса. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую внешнюю таблицу. Аргумент slot содержит новые данные для кортежа; он будет соответствовать определению типа строки сторонней таблицы. Аргумент planSlot содержит кортеж, сгенерированный вложенным планом узла плана ModifyTable. В отличие от slot, этот кортеж содержит только новые значения для изменяемых запросом столбцов, поэтому при индексации в planSlot не следует полагаться на номера атрибутов сторонней таблицы. Кроме того planSlot обычно содержит дополнительные «бросовые» столбцы. В частности, из этого слота будут доступны все бросовые столбцы, запрошенные функцией AddForeignUpdateTargets.

Возвращаемое значение является либо слотом, содержащим строку в том виде, в каком она фактически была обновлена (она может отличаться от предоставленных данных, например, в результате действий триггеров), или NULL, если на самом деле никакая строка не была изменена (опять же, как правило, в результате действий триггеров). Для этой цели можно повторно использовать переданное на вход значение slot.

Данные в возвращаемом слоте используются только в том случае, если оператор INSERT содержит предложение RETURNING или задействует представление WITH CHECK OPTION, либо если сторонняя таблица имеет триггер AFTER ROW. Триггерам требуются все столбцы, но в целях оптимизации FDW может выбрать не возвращать некоторые или все столбцы, в зависимости от содержимого предложения RETURNING или ограничений WITH CHECK OPTION. Независимо от этого, какой-то слот должен быть возвращен, чтобы подтвердить успешное выполнение, иначе число выданных запросом строк будет неверным.

Если указатель ExecForeignUpdate имеет значение NULL, попытки изменить стороннюю таблицу провалятся, и будет выдано сообщение об ошибке.

TupleTableSlot *
ExecForeignDelete(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

Удалить один кортеж из сторонней таблицы. Аргумент estate — это глобальное состояние выполнения запроса. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. Аргумент slot при вызове не содержит ничего полезного, но может использоваться для сохранения возвращаемого кортежа. Аргумент planSlot содержит кортеж, который был сгенерировать вложенным планом узла плана ModifyTable; в частности, он будет содержать все бросовые столбцы, запрошенные функцией AddForeignUpdateTargets. Эти бросовые столбцы нужны для идентификации удаляемого кортежа.

Возвращаемое значение является либо слотом, содержащим удаленную строку, либо NULL, если ни одна строка не была удалена (как правило, в результате действий триггеров). Для сохранения возвращаемого кортежа можно использовать передаваемое на вход значение slot.

Данные в возвращаемом слоте используются только в том случае, если запрос DELETE содержит предложение RETURNING или сторонняя таблица имеет триггер AFTER ROW. Триггерам требуются все столбцы, но в целях оптимизации FDW может выбрать не возвращать некоторые или все столбцы, в зависимости от содержимого предложения RETURNING. Независимо от этого, какой-то слот должен быть возвращен, чтобы подтвердить успешное выполнение, иначе число выданных запросом строк будет неверным.

Если указатель ExecForeignDelete имеет значение NULL, попытки удаления из сторонней таблицы провалятся, и будет выдано сообщение об ошибке.

void
EndForeignModify(EState *estate,
                 ResultRelInfo *rinfo);

Завершить изменение таблицы и освободить ресурсы. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить.

Если указатель EndForeignModify имеет значение NULL, во время завершения работы исполнителя никакие действия не предпринимаются.

Кортежи, добавленные в партиционированную таблицу с помощью INSERT или COPY FROM, перенаправляются в партиции. Если FDW поддерживает перенаправляемые партиции сторонних таблиц, она должна также предоставлять следующие функции обратного вызова. Кроме того, эти функции вызываются, когда в сторонней таблице выполняется COPY FROM.

void
BeginForeignInsert(ModifyTableState *mtstate,
                   ResultRelInfo *rinfo);

Начать выполнение операции добавления в стороннюю таблицу. Эта подпрограмма вызывается непосредственно перед добавлением в стороннюю таблицу первого кортежа и в случае, когда это партиция, выбранная для перенаправления кортежа, и когда это цель, указанная в команде COPY FROM. Она должен выполнить всю необходимую подготовительную работу перед фактическим добавлением данных. Впоследствии для кортежей, добавляемых в стороннюю таблицу, будет вызвана функция ExecForeignInsert или ExecForeignBatchInsert.

Аргумент mtstate — это общее состояние выполняемого узла плана ModifyTable; через эту структуру доступны глобальные данные о плане и состоянии выполнения. Аргумент rinfo — это структура ResultRelInfo, описывающая целевую стороннюю таблицу. (Для сохранения любого внутреннего состояния, требуемого для этой операции, FDW доступно поле ri_FdwState структуры ResultRelInfo.)

Когда эта функция вызывается командой COPY FROM, в mtstate не передаются связанные с планом глобальные данные, а аргумент planSlot функции ExecForeignInsert впоследствии вызываемой для каждого добавляемого кортежа, равен NULL, независимо от того является ли сторонняя таблица партицией, выбранной для перенаправления кортежа, или заданной в команде целью.

Если указатель Beginforeignsert имеет значение NULL, никаких действий для инициализации не предпринимается.

Обратите внимание, что если FDW не поддерживает перенаправляемые партиции сторонних таблиц и/или выполнение COPY FROM для сторонних таблиц, эта функция или впоследствии вызванная ExecForeignInsert/ExecForeignBatchInsert должна выдать соответствующую ошибку.

void
EndForeignInsert(EState *estate,
                 ResultRelInfo *rinfo);

Завершить операцию добавления и освободить ресурсы. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить.

Если указатель Endforeignsert имеет значение NULL, никакие действия для завершения не предпринимаются.

int
IsForeignRelUpdatable(Relation rel);

Выдать отчет о том, какие операции добавления поддерживает указанная сторонняя таблица. Возвращаемое значение должно быть битовой маской номеров событий правила, указывающих, какие операции поддерживаются сторонней таблицей, с помощью нумерации CmdType; то есть (1 << CMD_UPDATE) = 4 для UPDATE, (1 << CMD_INSERT) = 8 для INSERT, и (1 << CMD_DELETE) = 16 для DELETE.

Если указатель IsForeignRelUpdatable имеет значение NULL, сторонние таблицы считаются добавляемыми, изменяемыми или удаляемыми, если FDW предоставляет ExecForeignInsert, ExecForeignUpdate, или ExecForeignDelete соответственно. Эта функция необходима только в том случае, если некоторые поддерживаемые FDW таблицы можно обновить, а некоторые — нет. (Даже тогда вместо проверки этой функции допустимо выдать ошибку в подпрограмме выполнения. Однако эта функция используется для определения возможности изменения для отображения в представлениях information_schema.)

Некоторые добавления, изменения и удаления в сторонних таблицах можно оптимизировать, реализовав альтернативный набор интерфейсов. Обычные интерфейсы для добавления, изменения и удаления извлекают строки с удаленного сервера, а потом модифицируют их по одной за раз. В некоторых случаях такой пошаговый подход необходим, но он может быть неэффективен. Если сторонний сервер способен определить, какие строки следует изменить, в действительности не извлекая их, и если нет локальных структур, которые могли бы повлиять на операцию (локальные триггеры уровня строк, сохраненные сгенерированные столбцы или ограничения WITH CHECK OPTION из родительских представлений), то можно организовать эту операцию так, чтобы она целиком выполнялась на удаленном сервере. Это позволяют осуществить описанные ниже интерфейсы.

bool
PlanDirectModify(PlannerInfo *root,
                 ModifyTable *plan,
                 Index resultRelation,
                 int subplan_index);

Решить, безопасно ли выполнять прямую модификацию на удаленном сервере. Если да, вернуть true после выполнения необходимых для этого операций планирования. В противном случае вернуть false. Эта необязательная функция вызывается во время планирования запроса. Если она выполняется успешно, то на этапе выполнения будут вызываться BeginDirectModify, IterateDirectModify и EndDirectModify. В противном случае модификация таблицы будет выполняться с помощью вышеописанных функций изменения таблиц. Аргументы такие же, как и для PlanForeignModify.

Чтобы выполнить прямую модификацию на удаленном сервере, эта функция должна записать в целевой вложенный план узел плана ForeignScan, выполняющий прямую модификацию на удаленном сервере. Значения полей operation и resultRelation узла ForeignScan должны быть установлены соответственно: в поле operation должно быть задано перечисление CmdType, соответствующее виду оператора перечисление соответственно (то есть, CMD_UPDATE для UPDATE, CMD_INSERT для INSERT и CMD_DELETE для DELETE), а в поле resultRelation должен быть скопирован аргумент resultRelation.

Дополнительную информацию смотрите в разделе Планирование запросов с оберткой сторонних данных.

Если указатель PlanDirectModify имеет значение NULL, никакие попытки выполнить прямую модификацию на удаленном сервере не предпринимаются.

void
BeginDirectModify(ForeignScanState *node,
                  int eflags);

Подготовить выполнение прямой модификации на удаленном сервере. Эта функция вызывается во время запуска исполнителя. Она должен выполнить всю необходимую подготовительную работу перед прямой модификацией (это должно быть сделано при первом вызове IterateDirectModify). Узел ForeignScanState уже был создан, но его поле fdw_state по-прежнему содержит NULL. Информация о модифицируемой таблице доступна через узел ForeignScanState (в частности, из нижележащего узла плана ForeignScan, содержащего всю внутреннюю информацию FDW, предоставляемую функцией PlanDirectModify). Аргумент eflags содержит флаговые биты, описывающие рабочий режим исполнителя для этого узла плана.

Обратите внимание, что когда (eflags & EXEC_FLAG_EXPLAIN_ONLY) равно true, эта функция не должна выполнять никаких видимых извне действий; она должна сделать только необходимый минимум, чтобы состояние узла стало допустимым для ExplainDirectModify и EndDirectModify.

Если указатель BeginDirectModify имеет значение NULL, никакие попытки выполнить прямую модификацию на удаленном сервере не предпринимаются.

TupleTableSlot *
IterateDirectModify(ForeignScanState *node);

Когда запрос INSERT, UPDATE или DELETE не содержит предложение RETURNING, просто вернуть NULL после прямой модификации на удаленном сервере. Когда запрос содержит это предложение, извлечь один результат, содержащий данные, необходимые для вычисления RETURNING, возвращая его в слоте таблицы кортежей (для этой цели следует использовать принадлежащую узлу структуру ScanTupleSlot). Данные, которые были фактически добавлены, изменены или удалены, следует сохранить в node->resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple. Вернуть NULL, если не осталось доступных строк. Обратите внимание, что эта функция вызывается в контексте кратковременной памяти, который будет сбрасываться между вызовами. Если вам нужно более долговечное хранение, создайте контекст памяти в BeginDirectModify или используйте es_query_cxt из принадлежащей узлу структуры EState.

Возвращаемые строки должны соответствовать целевому списку fdw_scan_tlist, если тот был предоставлен, в противном случае они должны соответствовать типу строки сканируемой внешней таблицы. Если в целях оптимизации вы решите исключить выборку столбцов, которые не нужны для вычисления RETURNING, следует вставить в позиции этих столбцов NULL или же сгенерировать список fdw_scan_tlist, где эти столбцы опущены.

Независимо от того, содержит ли запрос предложение или нет, число выданных запросом строк должно быть увеличено самой FDW. Если запрос не содержит предложение, то FDW должна также увеличить число строк для узла ForeignScanState в случае EXPLAIN ANALYZE.

Если указатель IterateDirectModify имеет значение NULL, никакие попытки выполнить прямую модификацию на удаленном сервере не предпринимаются.

void
EndDirectModify(ForeignScanState *node);

Освободить ресурсы после прямой модификации на удаленном сервере. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить.

Если указатель EndDirectModify установлен в значение NULL, никакие попытки выполнить прямую модификацию на удаленном сервере не предпринимаются.

Подпрограммы FDW для TRUNCATE

void
ExecForeignTruncate(List *rels,
                    DropBehavior behavior,
                    bool restart_seqs);

Опустошить сторонние таблицы. Эта функция вызывается, когда для сторонней таблицы выполняется TRUNCATE. Аргумент rels — это список структур Relation, содержащих информацию об опустошаемых сторонних таблицах.

Аргумент behavior имеет значение DROP_RESTRICT или DROP_CASCADE, указывающее, что в оригинальной команде TRUNCATE был задан параметр RESTRICT или CASCADE соответственно.

Если аргумент restart_seqs равен true, исходная команда TRUNCATE запросила поведение RESTART IDENTITY, в противном случае было запрошено поведение CONTINUE IDENTITY.

Обратите внимание, что параметры ONLY, заданные в исходной команде TRUNCATE, не передаются в ExecForeignTruncate. Это поведение сходно с таковым функций обратного вызова команд SELECT, UPDATE и DELETE для сторонней таблицы.

Функция ExecForeignTruncate вызывается отдельно для каждого стороннего сервера, на котором будут опустошаться сторонние таблицы. Это означает, что все сторонние таблицы, включенные в rels, должны принадлежать одному серверу.

Если указатель ExecForeignTruncate имеет значение NULL, попытки опустошить сторонние таблицы провалятся, и будет выдано сообщение об ошибке.

Подпрограммы FDW для блокировки строк

Если FDW желает поддержать позднюю блокировку строк (как описывается в разделе Блокировка строк в обертках сторонних данных), она должна предоставить следующие функции обратного вызова:

RowMarkType
GetForeignRowMarkType(RangeTblEntry *rte,
                      LockClauseStrength strength);

Сообщить, какой вариант маркировки строк следует использовать для сторонней таблицы. Аргумент rte — это узел RangeTblEntry для таблицы, а аргумент strength описывает силу блокировки, запрошенную соответствующим предложением FOR UPDATE/SHARE, если таковое имеется. Результат должен быть членом перечислимого типа RowMarkType.

Эта функция вызывается во время планирования запроса для каждой сторонней таблицы, которая фигурирует в запросе UPDATE, DELETE или SELECT FOR UPDATE/SHARE и не является целью UPDATE или DELETE.

Если указатель GetForeignRowMarkType имеет значение NULL, всегда используется вариант ROW_MARK_COPY. (Это подразумевает, что функция RefetchForeignRow никогда не будет вызываться, поэтому предоставлять еще и ее нет необходимости.)

Дополнительную информацию см. в разделе Блокировка строк в обертках сторонних данных.

void
RefetchForeignRow(EState *estate,
                  ExecRowMark *erm,
                  Datum rowid,
                  TupleTableSlot *slot,
                  bool *updated);

Повторно извлечь один слот кортежа из сторонней таблицы после блокировки, если она требуется. Аргумент EState — это глобальное состояние выполнения запроса. Аргумент erm — это структура ExecRowMark, описывающая целевую стороннюю таблицу и тип запрашиваемой блокировки строк (если таковой имеется). В аргументе rowid задается извлекаемый кортеж. Аргумент slot не содержит ничего полезного при вызове, но может использоваться для хранения возвращаемого кортежа. Аргумент updated является выходным.

Эта функция должна сохранить Кортеж в предоставленном слоте или очистить его, если блокировку строк нельзя получить. Тип запрашиваемой блокировки строк определяется с помощью значения erm->markType, который ранее было возвращенным функцией GetForeignRowMarkType. (Вариант ROW_MARK_REFERENCE означает просто повторное извлечение кортежа, без запроса какой-либо блокировки, а ROW_MARK_COPY эта подпрограмма никогда не увидит.)

Кроме того, аргумент *updated должен иметь значение true, если извлеченный кортеж был обновленной версией, а не той, что уже была получена ранее. (Если FDW не в состоянии знать это наверняка, рекомендуется всегда возвращать true).

Обратите внимание, что по умолчанию неудачная попытка получить блокировку строк должна привести к возникновению ошибки; возврат пустого слота подходит только в том случае, если в erm->waitPolicy указывается вариант SKIP LOCKED.

Аргумент rowid — это значение ctid, ранее прочитанное для строки, которая должна быть повторно извлечена. Хотя значение rowid передается в виде Datum, в настоящее время оно может быть только tid. Такой API функции выбран в надежде, что в будущем для идентификаторов строк могут стать допустимы и другие типы данных.

Если указатель RefetchForeignRow имеет значение NULL, попытки повторно извлечь строки провалятся, и будет выдано сообщение об ошибке.

Дополнительную информацию см. в разделе Блокировка строк в обертках сторонних данных.

bool
RecheckForeignScan(ForeignScanState *node,
                   TupleTableSlot *slot);

Повторно проверить, что ранее возвращенный кортеж все еще соответствует подходящим квалификаторам сканирования и соединения и, возможно, предоставит измененную версию кортежа. Для оберток сторонних данных, которые не осуществляют вытеснение соединений, обычно будет удобнее установить в этом указателе NULL и вместо этого задать соответствующее значение fdw_recheck_quals. Однако при вытеснении внешних соединений недостаточно повторно применить к результирующему кортежу проверки, относящиеся ко всем базовым таблицам, даже если присутствуют все необходимые атрибуты, потому что из-за несоответствия некоторому квалификатору не только кортеж е будет возвращен, но и некоторые атрибуты могут получить значение NULL. Функция RecheckForeignScan может перепроверить квалификаторы и вернуть true, если они по-прежнему удовлетворяются, и false в противном случае, но кроме того она может сохранить в предоставляемом слоте кортеж, заменяющий предыдущий.

Для реализации вытеснения соединения обертка сторонних данных обычно создает альтернативный локальный план соединения, который используется только для повторных проверок; он становится внешним подпланом узла ForeignScan. Когда требуется перепроверка, этот подплан может быть выполнен, и результирующий кортеж может быть сохранен в слоте. Этот план не должен быть эффективным, поскольку ни одна базовая таблица не будет возвращать более одной строки; например, он может реализовать все соединения как вложенные циклы. Для поиска в существующих путях подходящего локального пути соединения, который можно использовать в качестве альтернативного локального плана соединения, можно вызвать функцию GetExistingLocalJoinPath. Эта функция ищет непараметризованный путь в списке путей указанного отношения соединения. (Если она не находит такой путь, то возвращает NULL, и в этом случае обертка сторонних данных может построить локальный путь самостоятельно или решить не создавать пути доступа для этого соединения.)

Подпрограммы FDW для EXPLAIN

void
ExplainForeignScan(ForeignScanState *node,
                   ExplainState *es);

Вывести дополнительные данные EXPLAIN для сканирования сторонней таблицы. Эта функция может вызывать ExplainPropertyText и связанные функции для добавления полей в вывод EXPLAIN. Для определения того, что именно нужно выводить, можно использовать поля флагов в es, а для предоставления статистики времени выполнения в случае EXPLAIN ANALYZE можно проверить состояние узла ForeignScanState.

Если указатель ExplainForeignScan имеет значение NULL, во время EXPLAIN не выводится никакая дополнительная информация.

void
ExplainForeignModify(ModifyTableState *mtstate,
                     ResultRelInfo *rinfo,
                     List *fdw_private,
                     int subplan_index,
                     struct ExplainState *es);

Вывести дополнительные данные EXPLAIN для изменения сторонней таблицы. Эта функция может вызывать ExplainPropertyText и связанные функции для добавления полей в вывод EXPLAIN. Для определения того, что именно нужно выводить, можно использовать поля флагов в es, а для предоставления статистики времени выполнения в случае EXPLAIN ANALYZE можно проверить состояние узла ModifyTableState. Первые четыре аргумента такие же, как и у функции BeginForeignModify.

Если указатель ExplainForeignModify имеет значение NULL, во время EXPLAIN не выводится никакая дополнительная информация.

void
ExplainDirectModify(ForeignScanState *node,
                    ExplainState *es);

Вывести дополнительные данные EXPLAIN для прямой модификации на удаленном сервере. Эта функция может вызывать ExplainPropertyText и связанные функции для добавления полей в вывод EXPLAIN. Для определения того, что именно нужно выводить, можно использовать поля флагов в es, а для предоставления статистики времени выполнения в случае EXPLAIN ANALYZE можно проверить состояние узла ForeignScanState.

Если указатель ExplainDirectModify имеет значение NULL, во время EXPLAIN не выводится никакая дополнительная информация.

Подпрограммы FDW для ANALYZE

bool
AnalyzeForeignTable(Relation relation,
                    AcquireSampleRowsFunc *func,
                    BlockNumber *totalpages);

Эта функция вызывается, когда в сторонней таблице выполняется ANALYZE. Если FDW может собирать статистику для этой сторонней таблицы, она должна вернуть true и передать в func указатель на функцию, которая будет собирать выборку строк из таблицы, плюс предполагаемый размер таблицы в страницах в totalpages. В противном случае вернуть false.

Если FDW не поддерживает сбор статистических данных ни для каких таблиц, то в указателе AnalyzeForeignTable можно установить NULL.

Если функция сбора выборки предоставляется, она должна иметь следующую сигнатуру:

int
AcquireSampleRowsFunc(Relation relation,
                      int elevel,
                      HeapTuple *rows,
                      int targrows,
                      double *totalrows,
                      double *totaldeadrows);

Из таблицы должна быть собрана случайная выборка количеством не более targrows строк, которая сохраняется в предоставленном вызывающей функцией массиве rows. Должно быть возвращено фактическое количество собранных строк. Кроме того, эта функция сохраняет предполагаемое общее количество активных и неиспользуемых строк в таблице в выходных аргументах totalrows и totaldeadrows. (Если для этой FDW нет концепта неиспользуемых строк, следует установить в totaldeadrows ноль.)

Подпрограммы FDW для IMPORT FOREIGN SCHEMA

List *
ImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);

Получить список команд для создания сторонней таблицы. Эта функция вызывается при выполнении IMPORT FOREIGN SCHEMA и передает дерево синтаксического анализа для этого оператора, а также OID стороннего сервера для работы. Она должна возвращать список строк на C/RUST, каждая из которых должна содержать команду CREATE FOREIGN TABLE. Эти строки будут проанализированы и выполнены основным сервером.

Внутри структуры ImportForeignSchemaStmt поле remote_schema — это имя удаленной схемы, из которой должны быть импортированы таблицы. Поле list_type определяет способ фильтрации имен таблиц: FDW_IMPORT_SCHEMA_ALL означает, что должны быть импортированы все таблицы в удаленной схеме (в этом случае поле table_list пустое), FDW_IMPORT_SCHEMA_LIMIT_TO означает, что будут включены только таблицы, перечисленные в table_list, а FDW_IMPORT_SCHEMA_EXCEPT — что таблицы, перечисленные в table_list, будут исключены. Поле options — это список параметров, используемых для процесса импорта. Значения этих параметров зависят от FDW. Например, FDW может использовать параметр для определения того, следует ли импортировать атрибуты NOT NULL столбцов. Этим параметрам необязательно иметь какое-либо отношение к тем, которые FDW поддерживает в качестве параметров объектов базы данных.

FDW может игнорировать поле local_schema структуры ImportForeignSchemaStmt, поскольку основной сервер автоматически вставит это имя в анализируемые команды CREATE FOREIGN TABLE.

Также FDW не нужно заниматься реализацией фильтрации, заданной в list_type и table_list, поскольку основной сервер автоматически пропустит все возвращенные команды для таблиц, исключенных согласно этим параметрам. Однако часто бывает полезно сразу избежать работы по созданию команд для исключенных таблиц. Для проверки того, пройдет ли фильтр заданное имя сторонней таблицы, может быть полезна функция IsImportableForeignTable().

Если FDW не поддерживает импорт определений таблиц, то в указателе ImportForeignSchema можно установить NULL.

Подпрограммы FDW для параллельного выполнения

При необходимости узел ForeignScan может поддерживать параллельное выполнение. Параллельный ForeignScan будет выполняться в нескольких процессах и должен возвращать каждую строку ровно один раз для всех взаимодействующих процессы. Для этого процессы могут координировать свои действия через блоки фиксированного размера в динамической разделяемой памяти. Не факт, что эта разделяемая память будет сопоставляться с одним и тем же адресом в каждом процессе, поэтому она не должна содержать указателей. Следующие функции являются необязательными, но большинство из них необходимы для поддержки параллельного выполнения.

bool
IsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel,
                          RangeTblEntry *rte);

Проверить, может ли сканирование выполняться в параллельном рабочем процессе. Эта функция будет вызываться только тогда, когда планировщик считает, что параллельный план может быть осуществим, и должна возвращать true, если выполнять это сканирование в параллельном рабочем процессе безопасно. Как правило, это не так, если удаленный источник данных имеет транзакционную семантику, за исключением случаев, когда соединение рабочего процесса с данными можно каким-то образом заставить совместно использовать тот же транзакционный контекст, что и в ведущем процессе.

Если эта функция не определена, предполагается, что сканирование должно выполняться в параллельном ведущем процессе. Обратите внимание, что возврат true не означает, что само сканирование можно выполнить параллельно, — только то, что сканирование может выполняться в параллельном рабочем процессе. Поэтому может быть полезно определить этот метод, даже если параллельное выполнение не поддерживается.

Size
EstimateDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt);

Оценить объем динамической разделяемой памяти, который потребуется для параллельной операции. Он может быть больше объема, который будет фактически использован, но не должен быть меньше. Возвращаемое значение задается в байтах. Эта функция является необязательной и может быть опущена, если в ней нет необходимости, но если она опускается, то следующие три функции также должны быть опущены, поскольку для использования FDW не будет выделена разделяемая память.

void
InitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
                         void *coordinate);

Инициализировать динамическую разделяемую память, которая потребуется для параллельной операции. Аргумент coordinate указывает на область разделяемой памяти с размером, равным значению, возвращаемому EstimateDSMForeignScan. Эта функция является необязательной и может быть опущена, если в ней нет необходимости.

void
ReInitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
                           void *coordinate);

Повторно инициализировать динамическую разделяемую память, необходимую для параллельной операции, когда узел плана стороннего сканирования готовится к повторному сканированию. Эта функция является необязательной и может быть опущена, если в ней нет необходимости. Рекомендуемый подход заключается в том, чтобы эта функция сбрасывала только разделяемое состояние, а функция ReScanForeignScan сбрасывала только локальное состояние. В настоящее время эта функция будет вызываться раньше ReScanForeignScan, но лучше всего на такой порядок не рассчитывать.

void
InitializeWorkerForeignScan(ForeignScanState *node, shm_toc *toc,
                            void *coordinate);

Инициализировать локальное состояние параллельного рабочего процесса на основе разделяемого состояния, установленного рабочим процессом во время выполнения InitializeDSMForeignScan. Эта функция является необязательной и может быть опущена, если в ней нет необходимости.

void
ShutdownForeignScan(ForeignScanState *node);

Высвободить ресурсы, если ожидается, что узел не будет выполнен до конца. Эта функция вызывается не всегда; иногда EndForeignScan можно вызвать без предварительного вызова ShutdownForeignScan. Поскольку сегмент DSM, используемый параллельным запросом, уничтожается сразу же после вызова этой функции обратного вызова, внешние обертки данных, желающие предпринять некоторые действия, прежде чем сегмент DSM исчезнет, должны реализовать этот метод.

Подпрограммы FDW для асинхронного выполнения

При необходимости узел ForeignScan может поддерживать асинхронное выполнение.

Следующие функции являются необязательными, но большинство из них необходимы для поддержки параллельного выполнения.

bool
IsForeignPathAsyncCapable(ForeignPath *path);

Проверить, может ли данный путь ForeignPath асинхронно сканировать нижележащее стороннее отношение. Эта функция будет вызываться только в конце планирования запроса, если данный путь является прямым потомком пути AppendPath и когда планировщик уверен, что асинхронное выполнение повысит производительность, и должна возвращать true, если данный путь способен асинхронно сканировать нижележащее отношение.

Если эта функция не определяется, предполагается, что данный путь сканирует стороннее отношение с помощью функции IterateForeignScan. (Это подразумевает, что описанные ниже функции обратного вызова никогда не будут вызываться, поэтому их тоже не нужно предоставлять.)

void
ForeignAsyncRequest(AsyncRequest *areq);

Асинхронно выдать один кортеж из узла ForeignScan. Аргумент areq — это структура AsyncRequest, описывающая узел ForeignScan и родительский узел Append, запросивший у него кортеж. Эта функция должна сохранить кортеж в слот, заданный в areq->result, и установить в areq->request_complete значение true. Или же, если она не может выдать кортеж сразу и ей необходимо дождаться внешнего по отношению к основному серверу события, например сетевого ввода/вывода, ForeignAsyncRequest должна установить для в этом флаге false и true в areq->callback_pending, чтобы вызвать для уза ForeignScan описанные ниже функции обратного вызова. Если кортежей больше нет, нужно передать в качестве слота NULL или пустой слот и установить во флаге areq->request_complete значение true. Чтобы задать в поле areq выходные параметры, рекомендуется воспользоваться функцией ExecAsyncRequestDone или ExecAsyncRequestPending.

void
ForeignAsyncConfigureWait(AsyncRequest *areq);

Сконфигурировать событие файлового дескриптора, которого желает дождаться узел ForeignScan. Эта функция будет вызываться, только когда узел ForeignScan установил флаг areq->callback_pending, и должна добавить событие в поле as_eventset родительского узла Append, описываемого аргументом areq.

Когда произойдет

событие файлового дескриптора, будет вызвана функция ForeignAsyncNotify.

void
ForeignAsyncNotify(AsyncRequest *areq);

Обработать соответствующее произошедшее событие, а затем асинхронно выдать один кортеж из узла ForeignScan. Эта функция должна установить выходные параметры в areq так же, как функция ForeignAsyncRequest.

Подпрограммы FDW для репараметризации путей

List *
ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
                                 RelOptInfo *child_rel);

Эта функция вызывается при преобразовании пути, параметризованного самым верхним родителем данного дочернего отношения child_rel, в путь, параметризованный дочерним отношением. Эта функция используется для репараметризации любых путей или преобразования любых узлов выражения, сохраненных в данном члене fdw_private структуры ForeignPath*. При необходимости обратный вызов может использовать функцию reparameterize_path_by_child, adjust_appendrel_attrs или adjust_appendrel_attrs_multilevel.

Вспомогательные функции обертки сторонних данных

С основного сервера экспортируется несколько вспомогательных функций, чтобы авторы оберток сторонних данных могли легко получить доступ к атрибутам объектов, связанных с FDW, например параметрам FDW. Чтобы использовать любую из этих функций, нужно включить файл заголовка foreign/foreign.h в исходный файл. Этот заголовок также определяет типы структур, возвращаемые этими функциями.

ForeignDataWrapper *
GetForeignDataWrapperExtended(Oid fdwid, bits16 flags);

Эта функция возвращает объект ForeignDataWrapper для обертки сторонних данных с заданным OID. Объект ForeignDataWrapper содержит свойства FDW (подробности см. в foreign/foreign.h). Аргумент flags — это побитовая маска, указывающая на дополнительный набор параметров. Он может принять значение FDW_MISSING_OK, и в этом случае для неопределенного объекта вызывающей функции вместо ошибки возвращается результат NULL.

ForeignDataWrapper *
GetForeignDataWrapper(Oid fdwid);

Эта функция возвращает объект ForeignDataWrapper для обертки сторонних данных с заданным OID. Объект ForeignDataWrapper содержит свойства FDW (подробности см. в foreign/foreign.h).

ForeignServer *
GetForeignServerExtended(Oid serverid, bits16 flags);

Эта функция возвращает объект ForeignServer для стороннего сервера с заданным OID. Объект ForeignDataWrapper содержит свойства сервера (подробности см. в foreign/foreign.h). Аргумент flags — это побитовая маска, указывающая на дополнительный набор параметров. Он может принять значение FSW_MISSING_OK, и в этом случае для неопределенного объекта вызывающей функции вместо ошибки возвращается результат NULL.

ForeignServer *
GetForeignServer(Oid serverid);

Эта функция возвращает объект ForeignServer для стороннего сервера с заданным OID. Объект ForeignDataWrapper содержит свойства сервера (подробности см. в foreign/foreign.h).

UserMapping *
GetUserMapping(Oid userid, Oid serverid);

Эта функция возвращает объект UserMapping для сопоставления пользователя для данной роли на данном сервере. (Если нет сопоставления для конкретного пользователя, она вернет сопоставление для PUBLIC, или выдаст ошибку, если его нет). Объект UserMapping содержит свойства сопоставления пользователя (подробности см. в foreign/foreign.h).

ForeignTable *
GetForeignTable(Oid relid);

Эта функция возвращает объект ForeignTable для сторонней таблицы с заданным OID. Объект ForeignTable содержит свойства сторонней таблицы (подробности см. в foreign/foreign.h).

List *
GetForeignColumnOptions(Oid relid, AttrNumber attnum);

Эта функция возвращает параметры FDW уровня столбцов для столбца с заданными OID сторонней таблицы и номером атрибута в виде списка DefElem. Если столбец не имеет параметров, возвращается NIL.

Некоторые типы объектов в дополнение к функциям поиска на основе OID имеют функции на основе имен:

ForeignDataWrapper *
GetForeignDataWrapperByName(const char *name, bool missing_ok);

Эта функция возвращает объект ForeignDataWrapper для обертки сторонних данных с заданным именем. Если обертка не найдена, возвращает NULL, если аргумент missing_ok равен true, и выдает ошибку в противном случае.

ForeignServer *
GetForeignServerByName(const char *name, bool missing_ok);

Эта функция возвращает объект ForeignServer для стороннего сервера с заданным именем. Если сервер не найден, возвращает NULL, если аргумент missing_ok равен true, и выдает ошибку в противном случае.

Планирование запросов с оберткой сторонних данных

Функции обратного вызова FDW GetForeignRelSize, GetForeignPaths, GetForeignPlan, PlanForeignModify, GetForeignJoinPaths, GetForeignUpperPaths, и PlanDirectModify должны вписываться в работу планировщика QHB. Здесь представлены некоторые замечания о том, что они должны делать.

Для уменьшения объема информации, которая должна быть извлечена из сторонней таблицы (и, как следствие, снижения затрат), можно использовать информацию в root и baserel. Особый интерес представляет поле baserel->baserestrictinfo, поскольку оно содержит ограничительные условия (предложения WHERE), которые должны использоваться для фильтрации извлекаемых строк. (Самой FDW необязательно применять эти условия, поскольку их может проверить и основной исполнитель.) Для определения того, какие столбцы нужно извлечь, можно использовать поле baserel->reltarget->exprs, но обратите внимание, что оно только перечисляет столбцы, которые должен выдать узел плана ForeignScan, а не столбцы, которые используются в вычислении условий, но не выводятся запросом.

Для хранения информации функциям планирования FDW доступны различные внутренние поля. Как правило, все структуры, которые вы сохраняете в внутренние поля FDW, должны выделяться palloc, чтобы они освобождались в конце планирования.

Поле baserel->fdw_private — это указатель void, предоставляемый функциям планирования FDW для хранения информации, относящейся к конкретной сторонней таблице. Основной планировщик его не трогает, за исключением того, что записывает в него NULL при создании узла RelOptInfo. Это полезно для передачи информации из GetForeignRelSize в GetForeignPaths и/или из GetForeignPaths в GetForeignPlan во избежание повторных вычислений.

Функция GetForeignPaths может определить значение различных путей доступа, сохраняя внутреннюю информацию в поле fdw_private узлов ForeignPath. fdw_private объявляется как указатель List, но на самом деле может содержать что угодно, поскольку основной планировщик его не трогает. Тем не менее рекомендуется использовать представление, которое может выгрузить функция nodeToString, для использования с поддержкой отладки, доступной на сервере.

Функция GetForeignPlan может проверить поле fdw_private выбранного узла ForeignPath и сгенерировать списки fdw_exprs и fdw_private, подлежащие размещению в узле плана ForeignScan, где они будут доступны во время выполнения. Оба эти списка должны быть представлены в форме, которую умеет копировать функция copyObject. Список fdw_private не имеет никаких других ограничений и никак не интерпретируется основным сервером. Список fdw_exprs (если не содержит NIL) должен содержать деревья выражений, предназначенные для вычисления во время выполнения. Затем планировщик обработает эти деревья, чтобы сделать их полностью исполняемыми.

Обычно в функции GetForeignPlan переданный целевой список можно скопировать в узел плана как есть. Передаваемый список scan_clauses содержит те же предложения, что и baserel->baserestrictinfo, но может быть переупорядочен для для повышения эффективности выполнения. В простых случаях FDW может просто удалить узлы RestrictInfo из списка scan_clauses (с помощью функции extract_actual_clauses) и поместить все предложения в список условий узла плана, что означает, что все предложения будут проверены исполнителем во время выполнения. Более сложные FDW могут проверить некоторые предложения внутренне, и в этом случае эти предложения могут быть удалены из списка условий узла плана, чтобы исполнитель не тратил время на их повторную проверку.

К примеру, FDW может идентифицировать некоторые ограничительные предложения вида сторонняя_переменная = подвыражение, которые, по ее определению, могут быть выполнены на удаленном сервере с учетом локально вычисленного значения подвыражения. Фактическая идентификация такого предложения должна произойти во время выполнения функции GetForeignPaths, так как это может повлиять на оценку стоимости пути. Поле fdw_private этого пути, вероятно, будет содержать указатель на узел RestrictInfo идентифицированного предложения. Затем GetForeignPlan удалила бы это предложение из scan_clauses, но добавила бы подвыражение в fdw_exprs чтобы гарантированно получать сообщения в исполняемой форме. Вероятно, она также поместит управляющую информацию в поле fdw_private узла плана, чтобы указать исполняющим функциям, что следует делать во время выполнения. Запрос, передаваемый на удаленный сервер, будет включать что-то вроде WHERE сторонняя_переменная = $1 со значением параметра, полученным во время выполнения в результате вычисления дерева выражения fdw_exprs.

Все предложения, удаленные из списка условий узла плана, должны быть добавлены в fdw_recheck_quals или перепроверены с помощью функции RecheckForeignScan, чтобы обеспечить правильное поведение на уровне изоляции READ COMMITTED. Когда осуществляется параллельное изменение в некоторой другой таблицы, фигурирующей в запросе, исполнителю может потребоваться проверить, что все исходные условия все еще удовлетворяются для кортежа, возможно, относительно другого набора значений параметров. Обычно использовать fdw_recheck_quals проще, чем реализовать проверки в RecheckForeignScan, но этот метод будет недостаточным при вытеснении внешних соединений, так как в этом случае некоторые поля кортежей соединения могут получить значение NULL, но сами кортежи не будут исключаться.

Другим полем узла ForeignScan, которое могут заполнить FDW, является fdw_scan_tlist, которое описывает кортежи, возвращенные FDW для этого узла плана. Для простых сканирований сторонних таблиц в нем можно установить значение NIL, подразумевая, что возвращенные кортежи имеют тип строки, объявленный для сторонней таблицы. Значение, отличное от NIL, должно быть целевым списком (списком TargetEntry), содержащим переменные и/или выражения, представляющие возвращаемые столбцы. Это можно использовать, например, чтобы показать, что FDW опустила некоторые столбцы, которые по ее наблюдению не понадобятся для запроса. Кроме того, если FDW может вычислять выражения, используемые запросом, менее затратно, чем это можно сделать локально, она может добавить эти выражения в fdw_scan_tlist. Обратите внимание, что планы соединения (созданные из путей, построенных функцией GetForeignJoinPaths) должны всегда предоставлять fdw_scan_tlist, чтобы описать набор возвращаемых ими столбцов.

FDW всегда должна создавать по крайней мере один путь, который зависит только от ограничительных предложений таблицы. В запросах соединения она тоже может предпочесть создавать пути, зависящие от предложений соединения, например сторонняя_переменная = локальная_переменная. Такие предложения нельзя найти в baserel->baserestrictinfo но следует искать в списках соединения отношения. Путь, использующий такое предложение, называется «параметризованным путем». Он должен идентифицировать другие отношения, используемые в выбранных предложениях соединения, с подходящим значением param_info; для вычисления этого значения нужно использовать get_baserel_parampathinfo. В GetForeignPlan часть локальная_переменная предложения соединения будет добавлена в fdw_exprs, и тогда во время выполнения это будет работать так же, как для обычного ограничительного предложения.

Если FDW поддерживает удаленные соединения, функция GetForeignJoinPaths должна создавать ForeignPath для потенциально удаленных соединений во многом таким же образом, как функция GetForeignPaths работает для базовых таблиц. Информацию о предполагаемом соединении можно передать в GetForeignPlan так же, как описано выше. Однако поле baserestrictinfo не подходит для отношений соединения; вместо этого соответствующие предложения соединения для конкретного соединения передаются в GetForeignJoinPaths в виде отдельного параметра (extra->restrictlist).

FDW может дополнительно поддерживать прямое выполнение некоторых действий плана, находящихся выше уровня сканирования и соединения, например группировок или агрегации. Чтобы предложить такие варианты, FDW должна генерировать пути и добавлять их в соответствующее верхнее отношение. Например, путь, представляющий удаленную агрегацию, должен быть вставлен в отношение UPPERREL_GROUP_AGG с помощью add_path. Этот путь будет сравниваться по стоимости с локальной агрегацией, выполненной путем считывания простого пути сканирования для стороннего отношения (обратите внимание, что такой путь тоже должен быть предоставлен, иначе во время планирования возникнет ошибка ). Если путь удаленной агрегации выигрывает, что обычно и происходит, он будет преобразован в план обычным способом, путем вызова функции GetForeignPlan. Рекомендуемое место для создания таких путей находится в функции обратного вызова GetForeignUpperPaths, которая вызывается для каждого верхнего отношения (т. е. для каждого шага обработки после сканирования/соединения), если все базовые отношения запроса исходят из одной FDW.

PlanForeignModify и другие функции обратного вызова, описанные в подразделе Подпрограммы FDW для изменения сторонних таблиц, разрабатываются исходя из предположения, что стороннее отношение будет сканироваться обычным способом, а затем отдельные изменения строк будут проводиться локальным планом узла ModifyTable. Этот подход необходим для общего случая, когда для изменения требуется прочтение как локальных, так и сторонних таблиц. Тем не менее если операция может быть полностью выполнена сторонним сервером, FDW может создать путь, представляющий эту возможность, и вставить его в верхнее отношение UPPERREL_FINAL, где он будет конкурировать с подходом ModifyTable. Этот подход также можно использовать для реализации удаленной команды SELECT FOR UPDATE вместо функций обратного вызова для блокировки строк, описанных в подразделе Подпрограммы FDW для блокировки строк. Имейте в виду, что путь, добавляемый в UPPERREL_FINAL, отвечает за реализацию всего поведения запроса.

При планировании команд UPDATE или DELETE функции PlanForeignModify и PlanDirectModify могут искать структуру RelOptInfo для сторонней таблицы и использовать данные baserel->fdw_private, ранее созданные функциями планирования сканирования. Однако в INSERT целевая таблица не сканируется, поэтому для нее RelOptInfo не заполняется. Список List, возвращаемый функцией PlanForeignModify, имеет те же ограничения, что и список fdw_private узла плана ForeignScan, то есть он должен содержать только те структуры, которые умеет копировать copyObject.

КомандаINSERT с предложением ON CONFLICT не поддерживает указание целевого объекта конфликта, поскольку ограничения уникальности или исключения для удаленных таблиц неизвестны локально. Это, в свою очередь, подразумевает, что не поддерживается ON CONFLICT DO UPDATE, поскольку там это указание является обязательным.

Блокировка строк в обертках сторонних данных

Если нижележащий механизм хранения FDW обладает концепцией блокировки отдельных строк для предотвращения их одновременного изменения, обычно имеет смысл, чтобы FDW выполняла блокировку на уровне строк с максимально близким приближением к семантике, используемой в обычных таблицах QHB. В связи с этим имеется несколько факторов.

Одним из ключевых решений, которое необходимо принять, это нужно ли проводить раннюю блокировку или позднюю блокировку. При ранней блокировке строка блокируется при первом извлечении из базового хранилища, тогда как при поздней блокировке строка блокируется, только когда известно, что она должна быть заблокирована. (Разница возникает из-за того, что некоторые строки могут быть отброшены локально проверяемым ограничением или условиями соединения). Ранняя блокировка намного проще и позволяет избежать дополнительных обращений к удаленному хранилищу, но она может привести к блокировке строк, которые было необязательно блокировать, что приводит к снижению параллелизма или даже неожиданным взаимоблокировкам. Кроме того, поздняя блокировка возможна только в том случае, если блокируемую строку позднее можно однозначно повторно идентифицировать. Поэтому предпочтительно, чтобы конкретную версию строки определял идентификатор строки, как это делают TID в QHB.

По умолчанию при взаимодействии с FDW QHB игнорирует факторы блокировки, но FDW может выполнять раннюю блокировку без какой-либо явной поддержки со стороны кода ядра. Функции API, описанные в подразделе Подпрограммы FDW для блокировки строк, позволяют FDW при желании использовать позднюю блокировку.

Дополнительным фактором является то, что в режиме изоляции READ COMMITTED QHB может потребоваться повторно проверить ограничение и условия соединения с обновленной версией некоторого целевого кортежа. Повторная проверка условий соединения требует повторного получения копий нецелевых строк, которые ранее были присоединены к целевому кортежу. При работе со стандартными таблицами QHB это делается путем включения TID нецелевых таблиц в список столбцов, проходящих через соединение, а затем, при необходимости, повторной выборки нецелевых строк. Этот подход сохраняет компактность набора данных соединения, но требует недорогой возможности повторного извлечения строк, а также TID, который может однозначно определить версию строки, подлежащую повторному извлечению. Поэтому по умолчанию метод, используемый для сторонних таблиц, заключается в том, чтобы включить в список столбцов, проходящих через соединение, копию всей строки, извлеченной из сторонней таблицы. Это не накладывает на FDW никаких особых требований, но может привести к снижению производительности соединений слиянием и по хешу. FDW, способная удовлетворить требованиям повторной выборки, может выбрать для этого первый способ.

Для команд UPDATE или DELETE в сторонней таблице рекомендуется, чтобы операция ForeignScan с целевой таблицей выполняла раннюю блокировку извлекаемых ею строк, возможно, посредством эквивалента SELECT FOR UPDATE. FDW может определить, является ли таблица целью UPDATE/DELETE, во время планирования, сравнивая ее идентификатор отношения с root->parse->resultRelation, или во время выполнения с помощью функции ExecRelationIsTargetRelation(). Альтернативной возможностью является выполнение поздней блокировки в функции обратного вызова ExecForeignUpdate или ExecForeignDelete, но никакой специальной поддержки для этого не предусмотрено.

Для блокируемых сторонних таблиц, заданных в команде SELECT FOR UPDATE/SHARE, операция ForeignScan, опять же, может выполнить раннюю блокировку путем выборки кортежей посредством эквивалента SELECT FOR UPDATE/SHARE. Для выполнения вместо этого поздней блокировки предоставьте функции обратного вызова, определенные в подразделе Подпрограммы FDW для блокировки строк. Выберите в функции GetForeignRowMarkType параметр маркировки строк ROW_MARK_EXCLUSIVE, ROW_MARK_NOKEYEXCLUSIVE, ROW_MARK_SHARE или ROW_MARK_KEYSHARE, в зависимости от запрашиваемого режима блокировки. (Код ядра будет действовать одинаково независимо от того, какой из этих четырех вариантов вы выберете). Далее вы можете определить, была ли сторонняя таблица заблокирована этим типом команды, с помощью get_plan_rowmark во время планирования или с помощью ExecFindRowMark во время выполнения; следует проверить не только то, отлична ли от NULL возвращенная структура маркировки строк, но и то, что ее поле strength не содержит LCS_NONE.

Наконец, для сторонних таблиц, задействованных в команде UPDATE, DELETE или SELECT FOR UPDATE/SHARE, но не заданных для блокировки строк, можно переопределить выбор по умолчанию, то есть копирование строк целиком, выбрав в функции GetForeignRowMarkType вариант ROW_MARK_REFERENCE, чтобы сила блокировки стала LCS_NONE. Это приведет к тому, что функция RefetchForeignRow будет вызвана с этим значением для markType; затем она должна повторно извлечь строку, не не запрашивая новую блокировку. (Если у вас есть функция GetForeignRowMarkType, но вы не хотите повторно извлекать незаблокированные строки, выберите для LCS_NONE параметр ROW_MARK_COPY.)

Написание метода отбора выборки из таблицы

В дополнение к методам BERNOULLI и SYSTEM, требуемым стандартом SQL, реализация предложения TABLESAMPLE в QHB поддерживает пользовательские методы отбора выборки из таблицы. Метод отбора выборки определяет, какие строки таблицы будут выбраны при использовании предложения TABLESAMPLE.

На уровне SQL метод отбора выборки представлен единственной функцией SQL (как правило, реализованной на C/RUST) с сигнатурой

method_name(internal) RETURNS tsm_handler

Имя функции совпадает с именем метода, фигурирующим в предложении TABLESAMPLE. Аргумент internal является фиктивным (всегда принимающим нулевое значение) и просто предотвращает вызов этой функции непосредственно из команды SQL. Результатом функции должна быть выделенная palloc структура типа TsmRoutine, содержащая указатели на вспомогательные функции для метода отбора выборки. Эти вспомогательные функции представляют собой простые функции на C/RUST, которые не видны и не могут вызываться на уровне SQL. Эти вспомогательные функции описаны в разделе Вспомогательные функции метода отбора выборки.

Кроме указателей на функций структура TsmRoutine должна содержать следующие поля:

List *parameterTypes
Это список OID типов данных параметров, которые будет принимать предложение TABLESAMPLE при использовании данного метода отбора выборки. Например, у встроенных методов этот список содержит один элемент со значением FLOAT4OID, представляющим собой процент выборки. Пользовательские методы могут иметь дополнительные или иные параметры.

bool repeatable_across_queries
Если true, то этот метод отбора выборки может возвращать идентичные выборки в последовательных запросах, если каждый раз предоставляются одни и те же параметры и начальное значение REPEATABLE, а содержимое таблицы не изменяется. Если же значение равно false, предложение REPEATABLE не принимается для использования с этим методом отбора выборки.

bool repeatable_across
Если true, то метод отбора выборки может возвращать идентичные выборки при последовательных сканированиях в одном запросе (при неизменных параметрах, начальном значении и снимке). Если же значение равно false, то планировщик не будет выбирать планы, требующие более одного сканирования таблицы с выборкой, так как это может привести к несогласованному результату запроса.

Методы отбора выборки из таблицы, входящие в стандартный дистрибутив, могут послужить хорошим примером при реализации собственных методов.

Вспомогательные функции метода отбора выборки

Функция обработки TSM возвращает аллоцированную palloc структуру TsmRoutine, содержащую указатели на вспомогательные функции, описанные ниже. Большинство этих функций являются обязательными, однако некоторые — нет, и соответствующие им указатели могут иметь значение NULL.

void
SampleScanGetSampleSize (PlannerInfo *root,
                         RelOptInfo *baserel,
                         List *paramexprs,
                         BlockNumber *pages,
                         double *tuples);

Эта функция вызывается во время планирования. Она должна оценить количество страниц отношения, которые будут прочитаны во время сканирования выборки, и количество кортежей, которые будут выбраны при сканировании. (Например, их можно определить, оценив процент выборки и умножив baserel->pages и baserel->tuples на этот процент с округлением до целых.) Список paramexprs содержит выражения, являющиеся параметрами к предложению TABLESAMPLE. Если значения этих выражений нужны для целей оценки, рекомендуется использовать функцию estimate_expression_value(), чтобы преобразовать их в константы, однако функция должна предоставить оценку размера и в случае, когда выражения нельзя преобразовать, и она не должна выдавать ошибку, даже если эти значения кажутся некорректными (помните, что это только приблизительные оценки значений, которые будут получены во время выполнения). pages и tuples являются выходными параметрами.

void
InitSampleScan (SampleScanState *node,
                int eflags);

Выполнить инициализацию для выполнения узла плана SampleScan. Эта функция вызывается во время запуска исполнителя. Она должна выполнить всю необходимую подготовительную работу до начала обработки. Узел SampleScanState уже создан, но его поле tsm_state равно NULL. Функция InitSampleScan может выделить с помощью palloc область для любых данных внутреннего состояния, требуемых для метода отбора выборки, и сохранить указатель на нее в node->tsm_state. Информация о сканируемой таблице доступна через иные поля узла SampleScanState (но обратите внимание, что дескриптор сканирования node->ss.ss_currentScanDesc еще не установлен). Аргумент eflags содержит битовые флаги, описывающие рабочий режим исполнителя для данного узла плана.

Когда (eflags & EXEC_FLAG_EXPLAIN_ONLY) равно true, сканирование фактически не будет выполняться, и функция должна сделать только необходимый минимум, чтобы состояние узла стало допустимым для EXPLAIN и EndSampleScan.

Эту функцию можно опустить (присвоить указателю NULL), тогда всю подготовительную работу, необходимую для метода отбора выборки, должна выполнять функция BeginSampleScan.

void
BeginSampleScan (SampleScanState *node,
                 Datum *params,
                 int nparams,
                 uint32 seed);

Начать выполнение сканирования выборки. Это функция вызывается непосредственно перед первой попыткой извлечения кортежа и может быть вызвана снова, если сканирование необходимо перезапустить. Информация о сканируемой таблице доступна через поля узла SampleScanState (но обратите внимание, что дескриптор сканирования node->ss.ss_currentScanDesc еще не установлен). Массив params длиной nparams содержит значения параметров, переданных предложению TABLESAMPLE. Их количество и тип будут определяться списком parameterTypes метода отбора выборки, и они были проверены на отличие от NULL. Аргумент seed содержит начальное значение для всех случайных чисел, сгенерированных внутри метода отбора выборки; это либо хеш на основе значения REPEATABLE (если таковое имеется), либо результат вызова random() (в противном случае).

Эта функция может корректировать значения полей node->use_bulkread и node->use_pagemode. Если node->use_bulkread равен true (по умолчанию), то при сканирование будет использоваться стратегия доступа к буферу, предусматривающая переработку буферов после использования. Может иметь смысл установить в этом поле false, если при сканировании будет просматриваться лишь малая часть страниц таблицы. Если node->use_bulkread равен true (по умолчанию), то при сканировании проверка видимости будет выполняться за один проход для всех кортежей на каждой просмотренной странице. Может иметь смысл установить в этом поле false, если при сканировании будет выбираться лишь малая часть кортежей на каждой просмотренной странице. Это снизит число выполняемых проверок видимости кортежей, хотя каждая из них будет дороже, так как потребует больше блокировок.

Если метод отбора выборки помечен как repeatable_across_scans, он должен быть способен выбирать во время повторного сканирования тот же набор кортежей, что и в первый раз, то есть вызов BeginSampleScan должен приводить к выбору тех же кортежей, что и раньше (если параметры TABLESPACE и начальное значение не изменились).

BlockNumber
NextSampleBlock (SampleScanState *node, BlockNumber nblocks);

Возвращает номер блока следующей сканируемой страницы или InvalidBlockNumber, если не осталось страниц для сканирования.

Эту функцию можно опустить (присвоив указателю NULL), тогда код ядра выполнит последовательное сканирование всего отношения. Такое сканирование может быть синхронизированным, поэтому метод отбора выборки не может предполагать, что при каждом сканировании страницы отношения просматриваются в одном порядке.

OffsetNumber
NextSampleTuple (SampleScanState *node,
                 BlockNumber blockno,
                 OffsetNumber maxoffset);

Возвращает номер смещения следующего кортежа, отбираемого с заданной страницы, или InvalidOffsetNumber, если не осталось кортежей для отбора. Аргумент maxoffset является наибольшим номером смещения, возможным на этой странице.

Примечание
NextSampleTuple не говорит явно, в каких номерах смещения в диапазоне 1 .. maxoffset действительно содержатся допустимые кортежи. Как правило, это не является проблемой, поскольку код ядра игнорирует запросы на отбор отсутствующих или скрытых кортежей; это не должно привести к отклонениям в выборке. Однако при необходимости эта функция может использовать node->donetuples для проверки того, сколько из возвращенных кортежей были допустимы и видимы.

Примечание
NextSampleTuple не должна предполагать, что blockno — это тот же номер страницы, что был возвращен при последнем вызове NextSampleBlock. Он точно был возвращен при каким-то предыдущем вызове NextSampleBlock, однако код ядра может вызывать NextSampleBlock до фактического сканирования страниц, для поддержки упреждающего извлечения. Допустимо предположить, что когда начнется отбор с данной страницы, все последующие вызовы NextSampleTuple будут обращаться к этой же странице, пока не будет возвращен InvalidOffsetNumber.

void
EndSampleScan (SampleScanState *node);

Завершить сканирование и освободить ресурсы. Как правило, нет нужды освобождать память, выделенную palloc, но, например, открытые файлы и соединения с удаленными серверами следует очистить. В рядовых случаях, когда таких ресурсов нет, эту функцию можно опустить (присвоив указателю NULL).

Написание провайдера индивидуализированного сканирования

QHB поддерживает набор экспериментальных средств, позволяющих модулям расширения добавлять в систему новые типы сканирования. В отличие от обертки сторонних данных, которая должна знать только, как сканировать собственные сторонние таблицы, провайдер индивидуализированного сканирования может предоставить альтернативный метод сканирования любого отношения в системе. Как правило, мотив для написания провайдера индивидуализированного сканирования состоит в возможности использования некоторой оптимизации, не поддерживаемой основной системой, например кэширования или некоторой формы аппаратного ускорения. В этой главе описывается, как написать новый провайдер индивидуализированного сканирования.

Реализация нового типа индивидуализированного сканирования — это трехэтапный процесс. Во-первых, во время планирования необходимо сгенерировать пути доступа, представляющие сканирование с использованием предложенной стратегии. Во-вторых, если планировщик выбрал один из этих путей доступа в качестве оптимальной стратегии для сканирования конкретного отношения, такой путь доступа нужно преобразовать в план. Наконец, следует предусмотреть возможность выполнения плана и получения тех же результатов, которые получились бы с любым другим путем доступа, направленным на то же отношение.

Создание путей индивидуализированного сканирования

Провайдер индивидуализированного сканирования обычно добавляет пути для базового отношения, устанавливая следующий обработчик, который вызывается после того, как код ядра создал все возможные для него пути доступа для отношения (за исключением путей Gather, которые создаются после этого вызова с тем, чтобы они могли использовать частичные пути, добавленные этим обработчиком):

typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root,
                                            RelOptInfo *rel,
                                            Index rti,
                                            RangeTblEntry *rte);
extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook;

Хотя эту функцию обработки можно использовать для проверки, изменения или удаления путей, созданных основной системой, провайдер индивидуализированного сканирования обычно ограничивается созданием объектов CustomPath и добавлением их к rel с помощью add_path. Провайдер индивидуализированного сканирования отвечает за инициализацию объекта CustomPath, который объявляется следующим образом:

typedef struct CustomPath
{
    Path      path;
    uint32    flags;
    List     *custom_paths;
    List     *custom_private;
    const CustomPathMethods *methods;
} CustomPath;

Поле path необходимо инициализировать, как и для любого другого пути, включая оценку количества строк, начальную и общую стоимость, а также порядок сортировки, предоставляемый этим путем. Поле flags — это битовая маска, которая должна включать в себя CUSTOMPATH_SUPPORT_BACKWARD_SCAN, если индивидуализированный путь может поддерживать обратное сканирование, и CUSTOMPATH_SUPPORT_MARK_RESTORE, если он может поддерживать маркировку и восстановление позиции. Обе эти возможности являются необязательными. Необязательный параметр custom_paths — это список узлов Path, используемых этим узлом индивидуализированного пути; они будут преобразованы планировщиком в узлы Plan. Поле custom_private можно использовать для хранения частных данных индивидуализированного пути. Частные данные должны храниться в форме, которую может обработать функция nodeToString, чтобы отладочные подпрограммы, пытающиеся вывести индивидуализированный путь, работали как задумано. Поле methods должно указывать на объект (обычно статически выделенный), реализующий требуемые методы индивидуализированного пути, из которых в настоящее время существует только один.

Кроме того, провайдер индивидуализированного сканирования может предоставить пути соединения. Так же, как и для базовых отношений, такой путь должен выдавать тот же результат, что и соединение, которое он заменяет. Для этого провайдер соединения должен установить следующий обработчик, а затем создать в этой функции обработки путь(и) CustomPath для отношения соединения.

typedef void (* set_join_pathlist_hook_type) (PlannerInfo * root,
                                             RelOptInfo * joinrel,
                                             RelOptInfo * externalrel,
                                             RelOptInfo * innerrel,
                                             JoinType jointype,
                                             JoinPathExtraData * extra);
extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook;

Этот обработчик будет вызываться повторно для одного и того же отношения соединения, с различными комбинациями внутренних и внешних отношений; обработчик отвечает за минимизацию лишней работы.

Функции обратного вызова пути индивидуализированного сканирования

Plan *(*PlanCustomPath) (PlannerInfo *root,
                         RelOptInfo *rel,
                         CustomPath *best_path,
                         List *tlist,
                         List *clauses,
                         List *custom_plans);

Преобразовать индивидуализированный путь в готовый план. Возвращаемым значением обычно будет объект CustomScan, который эта функция обратного вызова должна аллоцировать и инициализировать. Более подробную информацию см. в разделе Создание планов индивидуализированного сканирования.

Создание планов индивидуализированного сканирования

Индивидуализированное сканирование представляется в конечном дереве планов в виде следующей структуры:

typedef struct CustomScan
{
    Scan      scan;
    uint32    flags;
    List     *custom_plans;
    List     *custom_exprs;
    List     *custom_private;
    List     *custom_scan_tlist;
    Bitmapset *custom_relids;
    const CustomScanMethods *methods;
} CustomScan;

Поле scan необходимо инициализировать, как и для любого другого сканирования, включая оценки стоимости, списки целевых объектов, условия и т. д. Поле flags — это битовая маска, имеющая тот же смысл, что и в CustomPath. Поле custom_plans можно использовать для хранения дочерних узлов Plan. Поле custom_exprs следует использовать для хранения деревьев выражений, которые необходимо будет исправить с помощью setrefs.c и subselect.c, тогда как custom_private следует использовать для хранения других частных данных, используемых только самим провайдером индивидуализированного сканирования. Поле custom_scan_tlist может иметь значение NIL при сканировании базового отношения, указывая, что индивидуализированное сканирование возвращает кортежи сканирования, соответствующие типу строки базового отношения. В противном случае это список целевых объектов, описывающий фактические кортежи сканирования. Список custom_scan_tlist должен быть предоставлен для соединений и может быть предоставлен для сканирования, если провайдер индивидуализированного сканирования может вычислить некоторые выражения без переменных. Поле custom_relids устанавливается кодом ядра для набора отношений (индексов таблицы диапазонов), которые обрабатывает этот узел сканирования, но когда это сканирование заменяет соединение, в этом поле будет только один элемент. Поле methods должно указывать на объект (обычно статически выделенный), реализующий необходимые методы индивидуализированного сканирования, которые более подробно описаны ниже.

Когда CustomScan сканирует отношение, scan.scanrelid должен быть индексом таблицы диапазонов сканируемой таблицы. Когда этот объект заменяет соединение, scan.scanrelid должен быть нулевым.

Деревья плана должны иметь возможность дублироваться с помощью copyObject, поэтому все данные, хранящиеся в «индивидуализированных» полях, должны состоять из узлов, которые эта функция может обрабатывать. Кроме того, провайдеры индивидуализированного сканирования не могут заменить структуру CustomScan на содержащую ее более крупную структуру, как это было бы возможно для CustomPath или CustomScanState.

Функции обратного вызова плана индивидуализированного сканирования

Node *(*CreateCustomScanState) (CustomScan *cscan);

Выделяет CustomScanState для этого объекта CustomScan. Фактический размер выделенной памяти зачастую будет больше, чем требуется для обычной структуры CustomScanState, потому что многие провайдеры захотят встроить этот объект в качестве первого поля более крупной структуры. Возвращаемое значение должно иметь соответствующим образом заполненные тег узла и поле methods, но другие поля на этом этапе все еще должны содержать нули; после того, как ExecInitCustomScan выполнит базовую инициализацию, будет вызвана функция обратного вызова BeginCustomScan, которая позволит провайдеру индивидуализированного сканирования осуществить все остальные требуемые действия.

Выполнение индивидуализированного сканирования

Когда выполняется CustomScan, его состояние выполнения представляется структурой CustomScanState, которая определяется следующим образом:

typedef struct CustomScanState
{
    ScanState ss;
    uint32    flags;
    const CustomExecMethods *methods;
} CustomScanState;

Поле ss инициализируется, как и для любого другого состояния сканирования, за исключением того, что если сканирование выполняется для соединения, а не для базового отношения, ss.ss_currentRelation остается равным NULL. Поле flags — это битовая маска, имеющая тот же смысл, что и в CustomPath и CustomScan. Поле methods должно указывать на объект (обычно статически выделенный), реализующий требуемые методы состояния индивидуализированного сканирования, более подробно описанные ниже. Как правило, структура CustomScanState, которой не нужно поддерживать copyObject, на самом деле будет включена в более крупную структуру в качестве первого ее члена.

Функции обратного вызова выполнения индивидуализированного сканирования

void (*BeginCustomScan) (CustomScanState *node,
                         EState *estate,
                         int eflags);

Завершить инициализацию предоставленной структуры CustomScanState. Стандартные поля были инициализированы функцией ExecInitCustomScan, но все частные поля должны инициализироваться здесь.

TupleTableSlot *(*ExecCustomScan) (CustomScanState *node);

Извлечь следующий кортеж сканирования. Если какие-либо кортежи остаются, эта функция должна заполнить ps_ResultTupleSlot следующим кортежем в текущем направлении сканирования, а затем вернуть слот кортежа. Если же кортежи закончились, должен быть возвращен NULL или пустой слот.

void (*EndCustomScan) (CustomScanState *node);

Очистить данные, связанные со структурой CustomScanState. Этот метод является обязательным, но ему необязательно что-то делать, если связанные данные отсутствуют или будут очищены автоматически.

void (*ReScanCustomScan) (CustomScanState *node);

Переместить позицию текущего сканирования в начало и подготовить повторное сканирование отношения.

void (*MarkPosCustomScan) (CustomScanState *node);

Сохранить текущую позицию сканирования, чтобы впоследствии ее можно было восстановить с помощью функции обратного вызова RestrPosCustomScan. Эта функция является необязательной и должна предоставляться только в том случае, если установлен флаг CUSTOMPATH_SUPPORT_MARK_RESTORE.

void (*RestrPosCustomScan) (CustomScanState *node);

Восстановить предыдущую позицию сканирования, сохраненную с помощью функции обратного вызова MarkPosCustomScan. Эта функция является необязательной и должна предоставляться только в том случае, если установлен флаг CUSTOMPATH_SUPPORT_MARK_RESTORE.

Size (*EstimateDSMCustomScan) (CustomScanState *node,
                               ParallelContext *pcxt);

Оценить объем разделяемой динамической памяти, которая потребуется для параллельной операции. Это значение может быть больше, чем фактически использованный объем сумма, но не должно быть меньше. Возвращаемое значение представлено в байтах. Эта функция является необязательной и должна предоставляться только в том случае, если этот провайдер индивидуализированного сканирования поддерживает параллельное выполнение.

void (*InitializeDSMCustomScan) (CustomScanState *node,
                                 ParallelContext *pcxt,
                                 void *coordinate);

Инициализировать разделяемую динамическую память, которая потребуется для параллельной операции. Поле coordinate указывает на область разделяемой памяти размером, равным значению, возвращаемому EstimateDSMCustomScan. Эта функция является необязательной и должна предоставляться только в том случае, если этот провайдер индивидуализированного сканирования поддерживает параллельное выполнение.

void (*ReInitializeDSMCustomScan) (CustomScanState *node,
                                   ParallelContext *pcxt,
                                   void *coordinate);

Повторно инициализировать разделяемую динамическую память, требуемую для параллельной операции, перед тем как узел плана индивидуализированного сканирования будет повторно сканироваться. Эта функция является необязательной и должна предоставляться только в том случае, если этот провайдер индивидуализированного сканирования поддерживает параллельное выполнение. Рекомендуемый подход заключается в том, чтобы эта функция сбрасывала только разделяемое состояние, а функция ReScanCustomScan сбрасывала только локальное состояние. В настоящее время эта функция обратного вызова будет вызываться перед ReScanCustomScan, но лучше всего на такой порядок не рассчитывать.

void (*InitializeWorkerCustomScan) (CustomScanState *node,
                                    shm_toc *toc,
                                    void *coordinate);

Инициализировать локальное состояние параллельного рабочего процесса на основе разделяемого состояния, заданного ведущим процессом во время выполнения InitializeDSMCustomScan. Эта функция является необязательной и должна предоставляться только в том случае, если этот провайдер индивидуализированного сканирования поддерживает параллельное выполнение.

void (*ShutdownCustomScan) (CustomScanState *node);

Освободить ресурсы, когда ожидается, что узел не будет выполнен до конца. Эта функция вызывается не всегда; иногда EndCustomScan можно вызвать без предварительного вызова ShutdownCustomScan. Поскольку сегмент DSM, используемый параллельным запросом, уничтожается сразу же после вызова этой функции обратного вызова, провайдеры индивидуализированного сканирования, желающие предпринять некоторые действия, прежде чем сегмент DSM исчезнет, должны реализовать этот метод.

void (*ExplainCustomScan) (CustomScanState *node,
                           List *ancestors,
                           ExplainState *es);

Вывести дополнительную информацию для узла EXPLAIN плана индивидуализированного сканирования. Эта функция является необязательной. Разделяемые данные, сохраненные в ScanState, например список целевых объектов и отношение сканирования, будут показаны даже без этой функции обратного вызова, но она позволяет отображать дополнительное, внутреннее состояние.

Генетический оптимизатор запросов

Примечание
Данный оптимизатор разработал Мартин Утеш (Martin Utesch, utesch@aut.tu-freiberg.de) для Института автоматического управления в Техническом университете Фрайбергская горная академия, Германия.

Обработка запроса как сложная задача оптимизации

Среди всех операторов отношений самым сложным для обработки и оптимизации является соединение. Количество возможных планов запроса растет экспоненциально по мере увеличения количества соединений в запросе. Дополнительные затраты на оптимизацию возникают как следствие поддержки различных методов соединения (например, в QHB это вложенный цикл, соединение по хешу и соединение слиянием) для обработки отдельных соединений и разнообразия индексов (например, в QHB это B-дерево, хеш, GiST и GIN), выступающих в роли путей доступа к отношениям.

Обычный оптимизатор запросов QHB выполняет почти исчерпывающий поиск по всему пространству стратегических альтернатив. Этот алгоритм, впервые появившийся в СУБД IBM System R, выдает близкий к оптимальному порядок соединений, но может потребовать огромного объема времени и памяти, когда количество соединений в запросе оказывается большим. В итоге обычный оптимизатор QHB оказывается неподходящим для запросов, соединяющих большое количество таблиц.

Далее мы опишем реализацию генетического алгоритма, решающего проблему порядка соединений способом, который эффективен для запросов с большим количеством соединений.

Генетические алгоритмы

Генетический алгоритм (ГА) — это метод эвристической оптимизации, работающий по принципу случайного поиска. Здесь множество возможных решений проблемы оптимизации рассматривается как популяция особей. Степень адаптации особи к среде определяется ее приспособленностью.

Координаты особи в пространстве поиска представляются хромосомами, которые по сути являются набором символьных строк. Фрагмент хромосомы, кодирующий значение одного оптимизируемого параметра, называется геном. Обычно ген кодируется в виде двоичного или целочисленного значения.

В результате симуляции эволюционных операций — рекомбинации, мутации и селекции — выявляются новые поколения точек поиска, которые в среднем демонстрируют более высокую приспособленность, чем их предшественники. Схема этих этапов отображена на Рисунке 1.

Рисунок 1. Структура генетического алгоритма

Согласно FAQ в группе comp.ai.genetic, нельзя в полной мере утверждать, что ГА реализует чисто случайный поиск решения проблемы. ГА использует стохастические процессы, но результат получается определенно не случайным (лучше случайного).

Генетическая оптимизация запросов (GEQO) в QHB

Модуль GEQO (Genetic Query Optimizer, генетический оптимизатор запросов) подходит к проблеме оптимизации запросов, как будто это широко известная задача коммивояжера (TSP, traveling salesman problem). Возможные планы запроса кодируются в виде строк целых чисел. Каждая строка представляет порядок соединения одного отношения из запроса со следующим. Например, дерево соединения

   /\
  /\ 2
 /\ 3
4  1

кодируется целочисленной строкой '4-1-3-2', которая означает: сначала соединить отношения '4' и '1', потом добавить '3', а затем '2', где 1, 2, 3, 4 — это идентификаторы отношений внутри оптимизатора QHB.

Реализация GEQO в QHB имеет следующие особые характеристики:

Использование ГА с устойчивым состоянием (заменяются наименее приспособленные особи популяции, а не все поколение) способствует быстрой сходимости к улучшенным планам запроса. Это критично для обработки запроса в оптимальное время;

Использование скрещивания с краевой рекомбинацией, которое особенно подходит для минимизации краевых потерь при решении TSP посредством ГА;

Мутация как генетический оператор считается устаревшей, поэтому для генерирования допустимых маршрутов TSP не требуются механизмы исправления.

Части модуля GEQO взяты из алгоритма Genitor, разработанного Д. Уитли (D. Whitley).

Модуль GEQO позволяет оптимизатору запросов QHB эффективно поддерживать запросы с большим количеством соединений, обходясь без исчерпывающего поиска.

Генерирование возможных планов с GEQO

В процессе планирования GEQO используется код стандартного планировщика, который формирует планы сканирования отдельных отношений. Затем с применением генетического подхода разрабатываются планы соединений. Как показано выше, каждый рассматриваемый план соединения представляется последовательностью, в которой должны соединяться базовые отношения. На начальном этапе код GEQO просто случайным образом генерирует несколько возможных последовательностей соединения. Для каждой такой последовательности вызывается код стандартного планировщика, оценивающий стоимость выполнения запроса с этим порядком соединения. (Для каждого этапа последовательности рассматриваются все три возможные стратегии соединения и все изначально определенные планы сканирования отношений. Итоговой оценкой стоимости будет минимальная из всех возможных.) Последовательности соединения с меньшей оценкой стоимости считаются «более приспособленными», чем последовательности с большей стоимостью. Генетический алгоритм отбрасывает наименее приспособленных кандидатов. Затем генерируются новые кандидаты путем комбинирования генов более приспособленных кандидатов — то есть из случайно выбранных частей известных последовательностей соединения с низкой стоимостью создаются новые последовательности для рассмотрения. Этот процесс повторяется, пока не будет рассмотрено некоторое предопределенное количество последовательностей соединения; затем для генерирования окончательного плана используется наилучшая последовательность, найденная за все время поиска.

Этот процесс заведомо недетерминирован вследствие случайных выборов при формировании начальной популяции и последующей «мутации» лучших кандидатов. Во избежание неожиданных изменений выбранного плана, при каждом проходе алгоритм GEQO перезапускает свой генератор случайных чисел с текущим значением параметра geqo_seed. Пока geqo_seed и другие параметры GEQO остаются неизменными, для определенного запроса (и других входных данных планировщика, таких как статистика) будет генерироваться один и тот же план. Чтобы поэкспериментировать с разными путями поиска, попробуйте изменить geqo_seed.

Задачи будущих реализаций GEQO в QHB

Наше внимание по-прежнему сосредоточено на поисках пути оптимизации значений параметров генетического алгоритма. В файле /usr/local/qhb/ backend/optimizer/geqo/geqo_main.c, подпрограммах gimme_pool_size и gimme_number_generations, мы должны найти компромиссные значения параметров, удовлетворяющие двум разнонаправленным требованиям:

Оптимальность плана запроса

Время вычисления

В текущей реализации приспособленность каждой рассматриваемой последовательности соединений рассчитывается в стандартном планировщике путем выполнения каждый раз с самого начала кода выбора соединения и оценки стоимости. Учитывая, что разные кандидаты могут использовать схожие подпоследовательности соединений, большая часть работы будет повторяться. Этот процесс можно значительно ускорить, сохраняя оценки стоимости для внутренних соединений. Проблема состоит в том, чтобы не занять этим сохраненным состоянием чрезмерные объемы памяти.

На более общем уровне не вполне понятно, насколько уместно оптимизировать запросы с помощью алгоритма ГА, разработанного для решения TSP. В случае TSP стоимость, связанная с любой подстрокой (частью маршрута), не зависит от остального маршрута, но это определенно не так для оптимизации запросов. Таким образом, остается под вопросом, является ли скрещивание с краевой рекомбинацией наиболее эффективной процедурой мутации.

Дополнительные материалы

Дополнительную информацию о генетических алгоритмах можно найти в следующих источниках:

The Hitch-Hiker's Guide to Evolutionary Computation (FAQ в группе news://comp.ai.genetic)

Evolutionary Computation and its application to art and design, Craig Reynolds

Fundamentals of Database Systems. Sixth Edition. Ramez Elmasri and Shamkant B. Navathe. Addison-Wesley. 2011.

The design and implementation of the POSTGRES query optimizer. Zelaine Fong. University of California, Berkeley, Computer Science Department.

Часть III. Язык SQL

В этой части описывается использование языка SQL в QHB. Мы начнем с общего синтаксиса SQL, затем объясним, как создавать структуры для хранения данных, как наполнять базу данных и как выполнять к ней запросы. Далее перечислим существующие типы данных и функции, применяемые с командами SQL. И закончим рассмотрением нескольких важных для оптимальной производительности аспектов.

Главы, представленные в этой части упорядочены определенным образом, чтобы
даже новичок мог проработать информацию последовательно, от начала до конца. При этом главы самодостаточны, так что опытные пользователи могут читать их так, как им будет удобно. Полное описание конкретных команд представлено в главе Команды SQL.

Читатели этой части должны знать, как подключиться к базе данных QHB и выполнять команды SQL. Если вы этого еще не знаете, то рекомендуется сначала прочитать первую и вторую часть. Команды SQL обычно вводятся с использованием интерактивного терминала QHB psql, но также можно использовать и другие программы с аналогичной функциональностью.

Язык SQL. Общие сведения

Введение

Основные положения

Создание таблицы

Наполнение таблицы строками

Запросы к таблице

Соединения таблиц

Агрегатные функции

Изменение строк

Удаление строк

Расширенная функциональность

Введение

Представления

Внешние ключи

Транзакции

Оконные функции

Наследование

Расширенный SQL

Синтаксис SQL

Лексическая структура

Выражения значения

Вызов функции

Определение данных

Базовая информация по таблицам

Значения по умолчанию

Генерируемые столбцы

Ограничения

Системные столбцы

Изменение таблиц

Права

Политики защиты строк

Схемы

Наследование

Партиционирование таблиц

Сторонние данные

Другие объекты базы данных

Отслеживание зависимостей

Манипулирование данными

Добавление данных

Изменение данных

Удаление данных

Возврат данных из измененных строк

Запросы

Обзор

Табличные выражения

Списки выборки

Сочетание запросов (UNION, INTERSECT, EXCEPT)

Сортировка строк (ORDER BY)

LIMIT и OFFSET

Списки VALUES

Запросы WITH (общие табличные выражения)

Типы данных

Числовые типы

Денежные типы

Символьные типы

Двоичные типы данных

Типы даты/времени

Логический тип

Перечислимые типы

Геометрические типы

Типы сетевых адресов

Типы битовых строк

Типы для текстового поиска

Тип UUID

Тип XML

Типы JSON

Массивы

Составные типы

Диапазонные типы

Типы доменов

Типы идентификаторов объектов

Тип pg_lsn

Псевдотипы

Функции и операторы

Логические операторы

Функции и операторы сравнения

Математические функции и операторы

Строковые функции и операторы

Функции и операторы для двоичных строк

Функции и операторы для битовых строк

Сопоставление с шаблоном

Функции для форматирования типов данных

Функции и операторы даты/времени

Функции поддержки перечислений

Геометрические функции и операторы

Функции и операторы для сетевых адресов

Функции и операторы текстового поиска

Функции генерирования UUID

Функции XML

Функции и операторы JSON

Функции для управления последовательностями

Условные выражения

Функции и операторы для массивов

Диапазонные/мультидиапазонные функции и операторы

Агрегатные функции

Оконные функции

Выражения подзапросов

Сравнение строк и массивов

Функции, возвращающие множества

Системные информационные функции и операторы

Функции для системного администрирования

Триггерные функции

Функции событийных триггеров

Функции статистической информации

Преобразование типов

Обзор

Операторы

Функции

Хранимое значение

UNION, CASE и связанные конструкции

Выходные столбцы SELECT

Параллельный запрос

Как работают параллельные запросы

Когда может использоваться параллельное выполнение запросов?

Параллельные планы

Безопасность параллельного выполнения

PL/pgSQL — процедурный язык SQL

Обзор

Структура PL/pgSQL

Объявления

Выражения

Основные операторы

Управляющие структуры

Курсоры

Управление транзакциями

Ошибки и сообщения

Триггерные функции

Внутренняя структура PL/pgSQL

Советы по разработке на PL/pgSQL

Портирование из Oracle PL/SQL

Команды SQL

ABORT

ALTER AGGREGATE

ALTER COLLATION

ALTER CONVERSION

ALTER DATABASE

ALTER DEFAULT PRIVILEGES

ALTER DOMAIN

ALTER EVENT TRIGGER

ALTER EXTENSION

ALTER FOREIGN DATA WRAPPER

ALTER FOREIGN TABLE

ALTER FUNCTION

ALTER GROUP

ALTER INDEX

ALTER LANGUAGE

ALTER LARGE OBJECT

ALTER MATERIALIZED VIEW

ALTER OPERATOR

ALTER OPERATOR CLASS

ALTER OPERATOR FAMILY

ALTER POLICY

ALTER PROCEDURE

ALTER PUBLICATION

ALTER ROLE

ALTER ROUTINE

ALTER RULE

ALTER SCHEMA

ALTER SEQUENCE

ALTER SERVER

ALTER STATISTICS

ALTER SUBSCRIPTION

ALTER SYSTEM

ALTER TABLE

ALTER TABLESPACE

ALTER TEXT SEARCH CONFIGURATION

ALTER TEXT SEARCH DICTIONARY

ALTER TEXT SEARCH PARSER

ALTER TEXT SEARCH TEMPLATE

ALTER TRIGGER

ALTER TYPE

ALTER USER

ALTER USER MAPPING

ALTER VIEW

ANALYZE

BEGIN

CALL

CHECKPOINT

CLOSE

CLUSTER

COMMENT

COMMIT

COMMIT PREPARED

COPY

CREATE ACCESS METHOD

CREATE AGGREGATE

CREATE CAST

CREATE COLLATION

CREATE CONVERSION

CREATE DATABASE

CREATE DOMAIN

CREATE EVENT TRIGGER

CREATE EXTENSION

CREATE FOREIGN DATA WRAPPER

CREATE FOREIGN TABLE

CREATE FUNCTION

CREATE GROUP

CREATE INDEX

CREATE LANGUAGE

CREATE MATERIALIZED VIEW

CREATE OPERATOR

CREATE OPERATOR CLASS

CREATE OPERATOR FAMILY

CREATE POLICY

CREATE PROCEDURE

CREATE PUBLICATION

CREATE ROLE

CREATE RULE

CREATE SCHEMA

CREATE SEQUENCE

CREATE SERVER

CREATE STATISTICS

CREATE SUBSCRIPTION

CREATE TABLE

CREATE TABLE AS

CREATE TABLESPACE

CREATE TEXT SEARCH CONFIGURATION

CREATE TEXT SEARCH DICTIONARY

CREATE TEXT SEARCH PARSER

CREATE TEXT SEARCH TEMPLATE

CREATE TRANSFORM

CREATE TRIGGER

CREATE TYPE

CREATE USER

CREATE USER MAPPING

CREATE VIEW

DEALLOCATE

DECLARE

DELETE

DISCARD

DO

DROP ACCESS METHOD

DROP AGGREGATE

DROP CAST

DROP COLLATION

DROP CONVERSION

DROP DATABASE

DROP DOMAIN

DROP EVENT TRIGGER

DROP EXTENSION

DROP FOREIGN DATA WRAPPER

DROP FOREIGN TABLE

DROP FUNCTION

DROP GROUP

DROP INDEX

DROP LANGUAGE

DROP MATERIALIZED VIEW

DROP OPERATOR

DROP OPERATOR CLASS

DROP OPERATOR FAMILY

DROP OWNED

DROP POLICY

DROP PROCEDURE

DROP PUBLICATION

DROP ROLE

DROP ROUTINE

DROP RULE

DROP SCHEMA

DROP SEQUENCE

DROP SERVER

DROP STATISTICS

DROP SUBSCRIPTION

DROP TABLE

DROP TABLESPACE

DROP TEXT SEARCH CONFIGURATION

DROP TEXT SEARCH DICTIONARY

DROP TEXT SEARCH PARSER

DROP TEXT SEARCH TEMPLATE

DROP TRANSFORM

DROP TRIGGER

DROP TYPE

DROP USER

DROP USER MAPPING

DROP VIEW

END

EXECUTE

EXPLAIN

FETCH

GRANT

IMPORT FOREIGN SCHEMA

INSERT

LISTEN

LOAD

LOCK

MOVE

NOTIFY

PREPARE

PREPARE TRANSACTION

REASSIGN OWNED

REFRESH MATERIALIZED VIEW

REINDEX

RELEASE SAVEPOINT

RESET

REVOKE

ROLLBACK

ROLLBACK PREPARED

ROLLBACK TO SAVEPOINT

SAVEPOINT

SECURITY LABEL

SELECT

SELECT INTO

SET

SET CONSTRAINTS

SET ROLE

SET SESSION AUTHORIZATION

SET TRANSACTION

SHOW

START TRANSACTION

TRUNCATE

UNLISTEN

UPDATE

VACUUM

VALUES

Поддержка SQL-стандарта

Ключевые слова SQL

Коды ошибок QHB

Поддержка даты и времени

Соответствие стандарту SQL

Язык SQL. Общие сведения

Введение

В этой главе рассматривается применение SQL для выполнения простых операций. Это руководство предназначено только для ознакомления и никоим образом не является полным справочным руководством по SQL. Следует также учитывать, что некоторые возможности языка QHB являются расширениями стандарта.

В следующих примерах мы предполагаем, что вы создали базу данных mydb, как описано в главе Начало работы, и смогли запустить psql, как описано в разделе psql.

Основные положения

QHB — это реляционная система управления базами данных (РСУБД). Это означает, что это система управления данными, представленными в виде отношений. По сути, отношение — это математическое обозначение таблицы. Хранение данных в таблицах на сегодняшний день распространено настолько, что это может показаться заведомо очевидным, но все же существует ряд других способов организации баз данных. К примеру, файлы и каталоги в Unix-подобных операционных системах образуют иерархическую базу данных. К более современным разработкам относятся объектно- ориентированные базы данных.

Любая таблица представляет собой именованный набор строк. Каждая строка данной таблицы имеет одинаковый набор именованных столбцов, а каждому столбцу присваивается определенный тип данных. Несмотря на то, что порядок столбцов во всех строках фиксирован, важно помнить, что SQL никоим образом не гарантирует порядок строк в таблице (хотя их можно явно отсортировать при выводе).

Таблицы объединены в базы данных, а набор баз данных, управляемых одним экземпляром сервера QHB, составляет кластер баз данных.

Создание таблицы

Вы можете создать таблицу, указав ее имя вместе с именами всех столбцов и их типами:

CREATE TABLE weather (
    city            varchar(80),
    temp_lo         int,           -- минимальная температура воздуха днем
    temp_hi         int,           -- максимальная температура воздуха днем
    prcp            real,          -- выпадение атмосферных осадков
    date            date
);

Этот текст можно также ввести в psql вместе с переводами строк. psql поймет, что команда продожается до точки с запятой.

В командах SQL можно свободно использовать пробельные символы (т. е. пробелы, табуляции и переводы строк). Это означает, что вы можете ввести команду с другим выравниванием, нежели в примере выше, или даже уместить ее в одной строке. Два минуса («--») обозначают начало комментария. Все, что идет за ними до конца строки игнорируется. SQL нечувствителен к регистру ключевых слов и идентификаторов, за исключением случаев, когда идентификаторы заключены в кавычки (в примере выше это не сделано).

varchar(80) определяет тип данных, который может хранить произвольные символьные строки длиной до 80 символов. int — обычный целочисленный тип. real — тип для хранения чисел с плавающей запятой одинарной точности. date — тип для хранения даты. (Да, столбец типа date тоже называется date. Это может быть удобно или сбивать с толку — как вам больше нравится.)

QHB поддерживает стандартные типы SQL int, smallint, real, double precision, char(N), varchar(N), date, time, timestamp и interval, а также другие типы общего назначения и обширный набор геометрических типов. Кроме того, QHB можно расширить произвольным числом пользовательских типов данных. Поэтому в записях имена типов не являются ключевыми словами, кроме ситуаций, когда это требуется для поддержки особых случаев в стандарте SQL.

Во второй таблице будут храниться города и их географическое местоположение:

CREATE TABLE cities (
    name            varchar(80),
    location        point
);

Тип point — пример специфического типа данных QHB.

Наконец, следует упомянуть, что если вам больше не нужна таблица или вы хотите пересоздать ее по-другому, ее можно удалить с помощью следующей команды:

DROP TABLE имя_таблицы;

Наполнение таблицы строками

Для наполнения таблицы строками используется команда INSERT:

INSERT INTO weather VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27');

Обратите внимание, что для всех типов данных используются довольно очевидные форматы ввода. Константы, за исключением простых числовых значений, обычно должны быть в апострофах ('), как в примере выше. Тип date на самом деле очень гибок и принимает разные форматы, но лучше придерживаться однозначного формата, показанного здесь.

Тип point требует ввода пары координат, как показано здесь:

INSERT INTO cities VALUES ('San Francisco', '(-194.0, 53.0)');

Используемый до сих пор синтаксис требует, чтобы вы помнили порядок столбцов. Альтернативная запись позволяет перечислить столбцы явно:

INSERT INTO weather (city, temp_lo, temp_hi, prcp, date)
    VALUES ('San Francisco', 43, 57, 0.0, '1994-11-29');

Также при желании можно перечислить столбцы в другом порядке или даже пропустить некоторые столбцы, например, если осадки неизвестны:

INSERT INTO weather (date, city, temp_hi, temp_lo)
    VALUES ('1994-11-29', 'Hayward', 54, 37);

Многие разработчики предпочитают перечислять столбцы явно, не полагаясь на их порядок в таблице.

Пожалуйста, введите все представленные выше команды, чтобы у вас были данные, с которыми можно будет работать в последующих разделах.

Чтобы загрузить большой объем данных из текстовых файлов можно также воспользоваться командой COPY. Обычно такая операция проходит быстрее, потому что команда COPY оптимизирована для этого применения, хотя и менее гибка, чем INSERT. Пример ее применения может быть таким:

COPY weather FROM '/home/user/weather.txt';

где имя исходного файла должно быть доступно на компьютере, где запущен серверный процесс, а не на клиенте, поскольку серверный процесс читает этот файл напрямую. Подробнее о команде COPY можно узнать на соответствующей справочной странице.

Запросы к таблице

Для получения данных из таблицы нужно выполнить запрос. Для этого применяется оператор SQL SELECT. Этот оператор делится на список выборки (часть, в которой перечисляются столбцы, которые должны быть возвращены), список таблиц (часть, в которой перечислены таблицы, из которых необходимо получить данные), и необязательное уточнение (часть, в которой указываются ограничения). Например, чтобы получить все строки из таблицы weather, напишите:

SELECT * FROM weather;

Здесь * — это краткое обозначение для «всех столбцов».¹ Этот же результат можно получить с помощью запроса:

SELECT city, temp_lo, temp_hi, prcp, date FROM weather;

Результат должен быть следующим:

city           | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
San Francisco  |      46 |      50 | 0.25 | 1994-11-27
San Francisco  |      43 |      57 |    0 | 1994-11-29
Hayward        |      37 |      54 |      | 1994-11-29
(3 rows)

В списке выборки можно записать выражения, а не просто ссылки на столбцы. Например, можно сделать так:

SELECT city, (temp_hi+temp_lo)/2 AS temp_avg, date FROM weather;

Результат должен быть:

city           | temp_avg |    date
---------------+----------+------------
San Francisco  |       48 | 1994-11-27
San Francisco  |       50 | 1994-11-29
Hayward        |       45 | 1994-11-29
(3 rows)

Обратите внимание, что для переименования выходного столбца используется необязательное предложение AS.

Запрос можно уточнить, добавив предложение WHERE, указывающее, какие строки необходимо вернуть. Предложение WHERE содержит логическое выражение (проверку истинности), и возвращаются только те строки, для которых это логическое выражение имеет значение true. В этом уточнении можно использовать обычные логические операторы (AND, OR и NOT). Например, следующий запрос вернет температуру города Сан-Франциско в дождливые дни:

SELECT * FROM weather
    WHERE city = 'San Francisco' AND prcp > 0.0;

Результат:

city           | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
San Francisco  |      46 |      50 | 0.25 | 1994-11-27
(1 row)

Можно сделать так, чтобы результаты запроса возвращались в определенном порядке:

SELECT * FROM weather
    ORDER BY city;

city           | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
Hayward        |      37 |      54 |      | 1994-11-29
San Francisco  |      43 |      57 |    0 | 1994-11-29
San Francisco  |      46 |      50 | 0.25 | 1994-11-27

В этом примере порядок сортировки задан не полностью, поэтому вы можете получить строки Сан-Франциско в любом порядке. Но вы всегда получите показанные выше результаты, если введете:

SELECT * FROM weather
    ORDER BY city, temp_lo;

Можно потребовать, чтобы из результата запроса были удалены повторяющиеся строки:

SELECT DISTINCT city
    FROM weather;

city
---------------
Hayward
San Francisco
(2 rows)

И в этом случае порядок результирующих строк может варьироваться. Чтобы гарантировать согласующиеся результаты, следует использовать одновременно DISTINCT и ORDER BY:²

SELECT DISTINCT city
    FROM weather
    ORDER BY city;

Соединения таблиц

До сих пор наши запросы обращались только к одной таблице за раз. Однако запросы могут обращаться сразу к нескольким таблицам или к одной таблице, но так, что одновременно будут обрабатываться несколько наборов ее строк. Запросы, обращающиеся к нескольким таблицам (или нескольким экземплярам одной таблицы) одновременно, называются соединениями (JOIN). Они объединяют строки из одной таблицы со строками из второй таблицы с помощью выражения, указывающего, какие пары строк должны образоваться. Например, чтобы вернуть все погодные записи вместе с координатами соответствующего города, базе данных нужно сравнить столбец city каждой строки таблицы weather со столбцом name всех строк таблицы cities и выбрать пары строк, где эти значения совпадают.³ Это можно сделать с помощью следующего запроса:

SELECT * FROM weather JOIN cities ON city = name;

city          | temp_lo | temp_hi | prcp |    date    |     name      | location
--------------+---------+---------+------+------------+---------------+-----------
San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)
(2 rows)

Обратите внимание на два факта в результирующем наборе:

Отсутствует результирующая строка для города Хейвард (Hayward). Это связано с тем, что в таблице cities для Хейварда нет соответствующей записи, поэтому соединение игнорирует эту строку в таблице weather. Вскоре мы увидим, как это можно исправить.

Присутствуют два столбца с названием города. Это правильно, так как списки столбцов из таблиц weather и cities конкатенируются. Однако на практике такое поведение нежелательно, поэтому лучше перечислять результирующие столбцы явно, а не с помощью *:

SELECT city, temp_lo, temp_hi, prcp, date, location
    FROM weather JOIN cities ON city = name;

Поскольку у всех столбцов разные имена, анализатор автоматически обнаружил, какой таблице они принадлежат. Если же в двух таблицах имена столбцов повторяются, то необходимо дополнить имена столбцов, чтобы показать, какой конкретно имеется в виду:

SELECT weather.city, weather.temp_lo, weather.temp_hi,
       weather.prcp, weather.date, cities.location
    FROM weather JOIN cities ON weather.city = cities.name;

В целом, считается хорошим тоном дополнять все имена столбцов в запросе с соединениями, чтобы запрос не завершился ошибкой, если позднее в одну из таблиц будет добавлен столбец с повторяющимся именем.

Запросы соединения, которые были показаны ранее, также можно записать в таком виде:

SELECT *
    FROM weather, cities
    WHERE city = name;

Этот синтаксис предшествует синтаксису JOIN/ON введенному в SQL-92. Таблицы просто перечисляются в предложении FROM, а выражение сравнения добавляется в предложение WHERE. Результаты, получаемые из этого старого неявного синтаксиса и нового явного синтаксиса JOIN/ON, одинаковы. Но читающему запрос проще понять смысл явного синтаксиса: условие соединения вводится с помощью собственного ключевого слова, тогда как раньше это условие включалось в предложение WHERE вместе с другими условиями.

Теперь мы выясним, как вернуть записи по Хейварду. Мы хотим, чтобы запрос просканировал таблицу weather и для каждой строки в ней нашел соответствующие строки cities. Если подходящей строки не найдено, мы хотим, чтобы вместо столбцов таблицы cities были выведены некоторые «пустые значения». Этот вид запроса называется внешним соединением. (Соединения, которые мы видели до сих пор, являются внутренними.) Команда выглядит так:

SELECT *
    FROM weather LEFT OUTER JOIN cities ON weather.city = cities.name;

     city      | temp_lo | temp_hi | prcp |    date    |     name      | location
---------------+---------+---------+------+------------+---------------+-----------
 Hayward       |      37 |      54 |      | 1994-11-29 |               |
 San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
 San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)
(3 rows)

Этот запрос называется левым внешним соединением, поскольку в вывод хотя бы по разу попадут все строки таблицы, указанной слева от оператора соединения, тогда как из таблицы справа туда попадут только строки, соответствующие какой-либо строке в левой таблице. При выводе строки левой таблицы, для которой нет совпадения в правой, вместо столбцов правой таблицы подставляются пустые значения (NULL).

Существуют также правые и полные внешние соединения.

Кроме того, можно соединить таблицу саму с собой. Это называется самосоединением. К примеру, предположим, что мы хотим найти все записи о погоде, находящиеся в диапазоне температур других записей о погоде. Поэтому нам нужно сравнить столбцы temp_lo и temp_hi каждой строки weather со столбцами temp_lo и temp_hi всех остальных строк weather. Это можно сделать с помощью следующего запроса:

SELECT w1.city, w1.temp_lo AS low, w1.temp_hi AS high,
       w2.city, w2.temp_lo AS low, w2.temp_hi AS high
    FROM weather w1 JOIN weather w2
        ON w1.temp_lo < w2.temp_lo AND w1.temp_hi > w2.temp_hi;

     city      | low | high |     city      | low | high
---------------+-----+------+---------------+-----+------
 San Francisco |  43 |   57 | San Francisco |  46 |   50
 Hayward       |  37 |   54 | San Francisco |  46 |   50
(2 rows)

Здесь мы переименовали таблицу погоды на w1 и w2 чтобы можно было различить левую и правую стороны соединения. Подобные псевдонимы можно использовать и в других запросах, чтобы сэкономить время на ввод, например:

SELECT *
    FROM weather w JOIN cities c ON w.city = c.name;

Такой тип сокращения встречается довольно часто.

Агрегатные функции

Как и большинство других СУБД для реляционных баз данных, QHB поддерживает агрегатные функции. Агрегатная функция вычисляет единственный результат из множества входных строк. Например, есть агрегаты для вычисления количества (count), суммы (sum), среднего (avg), максимума (max) и минимума (min) из набора строк.

Например, мы можем найти максимальную из всех минимальных дневных температур воздуха:

SELECT max(temp_lo) FROM weather;

max
-----
 46
(1 row)

Если мы хотим узнать, в каком городе (или городах) было это значение температуры, можно попробовать написать:

SELECT city FROM weather WHERE temp_lo = max(temp_lo);   --  НЕВЕРНО

но это не сработает, поскольку агрегат max нельзя использовать в предложении WHERE. (Это ограничение существует из-за того, что WHERE определяет, какие строки будут рассчитаны с помощью агрегатной функции, поэтому очевидно, что оно должно вычисляться раньше агрегатных функций). Однако, как это часто бывает, для получения желаемого результата можно перезапустить этот запрос, использовав подзапрос:

SELECT city FROM weather
    WHERE temp_lo = (SELECT max(temp_lo) FROM weather);

city
---------------
San Francisco
(1 row)

Теперь все работает, поскольку подзапрос является независимой операцией и вычисляет свою агрегатную функцию отдельно от внешнего запроса.

Агрегатные функции также очень полезны в сочетании с предложениями GROUP BY. Например, мы можем получить максимальную из минимальных дневных температур для каждого города:

SELECT city, max(temp_lo)
    FROM weather
    GROUP BY city;

city          | max
--------------+-----
Hayward       |  37
San Francisco |  46
(2 rows)

В итоге мы получаем по одной выходной строке для каждого города. Каждый результат агрегатной функции вычисляется по строкам таблицы, соответствующим этому городу. Теперь можно отфильтровать эти сгруппированные строки с помощью HAVING:

SELECT city, max(temp_lo)
    FROM weather
    GROUP BY city
    HAVING max(temp_lo) < 40;

city   | max
---------+-----
Hayward |  37
(1 row)

получив те же результаты, но только для тех городов, у которых значения temp_lo ниже 40. Наконец, если нам нужны только города, названия которых начинаются с «S», можно написать:

SELECT city, max(temp_lo)
    FROM weather
    WHERE city LIKE 'S%'            -- (1)
    GROUP BY city
    HAVING max(temp_lo) < 40;

(1): Синтаксис оператора LIKE, выполняющего сравнение по шаблону, рассматривается в разделе Функции и операторы.

Важно понимать взаимодействие между агрегатными функциями и предложениями SQL WHERE и HAVING. Принципиальное различие между WHERE и HAVING заключается в том, что WHERE сначала выбирает строки, а потом группирует их и вычисляет агрегатные функции (таким образом, оно определяет, какие строки пойдут в вычисление агрегатов), тогда как HAVING выбирает группы строк после группировки и вычисления агрегатных функций. Таким образом, WHERE не должно содержать агрегатных функций; нет смысла использовать агрегатную функцию для определения входных строк для агрегатных функций. С другой стороны, предложение HAVING всегда содержит агрегатные функции. (Строго говоря, вы можете написать HAVING, в котором нет агрегатных функций, но едва ли это будет полезно. То же самое условие с большей эффективностью можно использовать на стадии WHERE).

В предыдущем примере мы смогли применить в WHERE ограничение по названию города, так как это не агрегатная функция. Это более эффективно, чем добавлять ограничение к HAVING, поскольку так мы избегаем группировок и вычисления агрегатов для всех строк, не удовлетворяющих условию WHERE.

Изменение строк

Вы можете изменять существующие строки с помощью команды UPDATE. Предположим, вы обнаружили, что показания температуры после 28 ноября завышены на 2 градуса. Вы можете исправить данные следующим образом:

UPDATE weather
    SET temp_hi = temp_hi - 2,  temp_lo = temp_lo - 2
    WHERE date > '1994-11-28';

Взгляните на новое состояние данных:

SELECT * FROM weather;

     city      | temp_lo | temp_hi | prcp |    date
---------------+---------+---------+------+------------
 San Francisco |      46 |      50 | 0.25 | 1994-11-27
 San Francisco |      41 |      55 |    0 | 1994-11-29
 Hayward       |      35 |      52 |      | 1994-11-29
(3 rows)

Удаление строк

Строки можно удалить из таблицы с помощью команды DELETE. Предположим, вас больше не интересует погода в Хейварде. Вы можете удалить эти строки из таблицы следующим образом:

DELETE FROM weather WHERE city = 'Hayward';

Все записи погоды для города Хейвард удалены.

SELECT * FROM weather;

city          | temp_lo | temp_hi | prcp |    date
--------------+---------+---------+------+------------
San Francisco |      46 |      50 | 0.25 | 1994-11-27
San Francisco |      41 |      55 |    0 | 1994-11-29
(2 rows)

Остерегайтесь использовать операторы такого вида:

DELETE FROM имя_таблицы;

Без уточнения DELETE удалит все строки из заданной таблицы, опустошив ее. При этом система не запросит предварительное подтверждение операции!

¹

Хотя SELECT * полезен для спонтанных запросов, в рабочем коде это повсеместно считается дурным тоном, поскольку добавление столбца в таблицу приведет к изменению результатов таких запросов.

²

В некоторых базах данных реализация DISTINCT автоматически упорядочивает строки, поэтому в ORDER BY нет необходимости. Но это не требуется стандартом SQL, и QHB не гарантирует, что DISTINCT отсортирует выводимые строки.

³

Это просто концептуальная модель. Обычно соединение выполняется более эффективным способом, чем фактическое сравнение всех возможных пар строк, но это скрыто от пользователя.

Расширенная функциональность

Введение

В главе Язык SQL. Общие сведения были рассмотрены основы использования SQL для хранения и обращения к данным в QHB. Теперь мы обсудим некоторые более продвинутые функции SQL, которые упрощают управление и предотвращают потерю или повреждение данных. Кроме того, мы рассмотрим некоторые расширения QHB.

В этой главе иногда приводятся ссылки на примеры из главы Язык SQL. Общие сведения, которые будут изменены или улучшены, поэтому будет полезно прочитать эту главу. Некоторые примеры также можно найти далее в разделе Расширенный SQL. Также там содержатся некоторые примеры данных и таблицы, которые повторно в этой главе не описываются.

Представления

В разделе Соединения таблиц мы рассматривали запросы из нескольких таблиц. Предположим, что комбинированный список записей о погоде и местоположении городов представляет особый интерес для вашего приложения, но вы не хотите заново вводить весь запрос каждый раз, когда он понадобится. Вы можете создать представление запроса, дающее этому запросу имя, к которому можно обращаться как к обычной таблице:

CREATE VIEW myview AS
    SELECT city, temp_lo, temp_hi, prcp, date, location
        FROM weather, cities
        WHERE city = name;

SELECT * FROM myview;

Активное использование представлений является ключевым аспектом хорошего проекта базы данных SQL. Представления позволяют скрыть элементы структуры таблиц, которые могут меняться по мере развития приложения, за согласованными интерфейсами.

Представления можно использовать практически везде, где можно использовать настоящую таблицу. Также можно создавать представления из других представлений.

Внешние ключи

Вспомните таблицы weather и cities из главы Язык SQL. Общие сведения. Рассмотрим следующую проблему: Вы хотите убедиться, что никто не сможет вставить в таблицу weather строки, для которых нет соответствующей записи в таблице cities. Это ограничение называется поддержанием ссылочной целостности данных. В упрощенных СУБД это будет реализовано (если вообще будет) путем просмотра таблицы cities с целью проверки, существует ли соответствующая запись, а затем будут добавлены или отклонены новые записи из weather. Этот подход имеет ряд недостатков и очень неудобен, поэтому QHB может сделать это за вас.

Новое объявление таблиц будет выглядеть так:

CREATE TABLE cities (
        city     varchar(80) primary key,
        location point
);

CREATE TABLE weather (
        city      varchar(80) references cities(city),
        temp_lo   int,
        temp_hi   int,
        prcp      real,
        date      date
);

Теперь попробуйте вставить неверную запись:

INSERT INTO weather VALUES ('Berkeley', 45, 53, 0.0, '1994-11-28');

ERROR:  insert or update on table "weather" violates foreign key constraint "weather_city_fkey"
DETAIL:  Key (city)=(Berkeley) is not present in table "cities".
-- ОШИБКА: INSERT или UPDATE в таблице "weather" нарушает ограничение внешнего ключа "weather_city_fkey"
-- ДЕТАЛИЗАЦИЯ: Ключ (city)=(Berkeley) отсутствует в таблице "cities".

Поведение внешних ключей можно настроить под ваше приложение. В данном руководстве мы не зайдем дальше этого простого примера, но дополнительную информацию можно найти в главе Определение данных. Правильное использование внешних ключей, несомненно, улучшит качество ваших приложений для базы данных, поэтому мы настоятельно рекомендуем изучить их.

Транзакции

Транзакции являются фундаментальной концепцией всех СУБД. Ключевая особенность транзакции состоит в том, что она объединяет несколько последовательных действий в одну операцию «все или ничего». Промежуточные состояния между действиями не видны другим, параллельно выполняющимся, транзакциям, и если происходит какой-либо сбой, препятствующий завершению конкретной транзакции, то ни одно из ее действий никак не влияет на базу данных.

Например, рассмотрим банковскую базу данных, которая содержит остатки по различным счетам клиентов, а также общие остатки по депозитам для филиалов. Предположим, что мы хотим перевести со счета Алисы 100 долларов на счет Боба. Если сильно упростить, команды SQL для этого могут выглядеть так:

UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
UPDATE branches SET balance = balance - 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice');
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
UPDATE branches SET balance = balance + 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');

Подробности этих команд здесь не важны; важно отметить, что для выполнения этой довольно простой операции требуется несколько отдельных обновлений. Сотрудники банка хотят быть уверены, что либо все эти обновления произойдут, либо ни одно из них не произойдет. Конечно, будет нехорошо, если из-за системного сбоя Боб получит 100 долларов, но со счета Алисы они списаны не будут. Да и Алиса не останется счастливым клиентом, если у нее со счета будет списана сумма без зачисления Бобу. Нам нужна гарантия того, что если во время операции что-то пойдет не так, ни одно из выполненных до сих пор действий не вступит в силу. Группировка всех изменений в одну транзакцию дает нам эту гарантию. Транзакция считается атомарной: с точки зрения других транзакций она либо выполняется полностью, либо вообще не выполняется.

Также нам нужна гарантия, что после завершения и подтверждения транзакции в СУБД она действительно надежно сохранится и не будет утеряна, даже если вскоре после этого произойдет сбой системы. Например, если мы записываем снятие наличных Бобом, мы хотим исключить всякую возможность того, что списание с его счета исчезнет в результате сбоя сразу после того, как он выйдет за дверь банка. Транзакционная база данных гарантирует, что все сделанные транзакцией изменения будут записаны в постоянном хранилище (то есть на диске) до сообщения о завершении транзакции.

Еще одно важное свойство транзакционных баз данных тесно связано с понятием атомарных изменений: когда одновременно выполняется несколько транзакций, каждая из них не должна видеть неполные изменения, внесенные другими. Например, если одна транзакция занята суммированием всех остатков в филиалах, не годится, чтобы она включала списание из филиала Алисы, но не включала пополнение из филиала Боба, и наоборот. Таким образом, транзакции должны выполнять «все или ничего» не только с точки зрения их необратимого воздействия на базу данных, но и с точки зрения их видимости во время исполнения. Изменения, сделанные открытой транзакцией, невидимы для других транзакций вплоть до завершения этой транзакция, после чего становятся видимыми сразу все изменения.

В QHB транзакция устанавливается путем заключения команд SQL этой транзакции в команды BEGIN и COMMIT. Таким образом, наша банковская транзакция будет выглядеть так:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
-- и т. д.
COMMIT;

Если в ходе транзакции мы решаем, что не хотим ее фиксировать (возможно, мы только что заметили, что баланс Алисы стал отрицательным), вместо COMMIT можно выполнить команду ROLLBACK, и все изменения будут отменены.

На самом деле QHB обрабатывает каждый оператор SQL как выполняемый в транзакции. Если вы не добавите команду BEGIN, то каждый отдельный оператор будет неявно окружен командами BEGIN и (в случае успешного завершения) COMMIT. Группу операторов, окруженную BEGIN и COMMIT, иногда называют блоком транзакций.

Примечание
Некоторые клиентские библиотеки добавляют команды BEGIN и COMMIT автоматически, поэтому можно, не ведая того, получить эффект блоков транзакций. Проверьте документацию по используемому вами прикладному интерфейсу.

Можно управлять транзакциями более детально, используя точки сохранения. Точки сохранения позволяют выборочно отменять отдельные части транзакции, в то же время фиксируя остальные. После определения точки сохранения командой SAVEPOINT при необходимости можно откатиться до этой точки с помощью ROLLBACK TO. Все изменения базы данных, сделанные транзакцией между определением точки сохранения и откатом к ней отменяются, но изменения, сделанные раньше этой точки, остаются.

После отката к точке сохранения она продолжает оставаться определенной, поэтому к ней можно откатиться несколько раз. И наоборот, если вы уверены, что вам не понадобится снова возвращаться к определенной точке сохранения, ее можно удалить, чтобы освободить ресурсы системы. Имейте в виду, что удаление или откат к точке сохранения автоматически сбросит все точки сохранения, которые были определены после нее.

Все это происходит внутри блока транзакции, поэтому ни одно из изменений не видно другим сеансам базы данных. Когда и если вы фиксируете блок транзакции, зафиксированные действия становятся видимыми для других сеансов как единое целое, тогда как отмененные действия вообще никогда не становятся видимыми.

Вспоминая банковскую базу данных, предположим, что мы списываем 100 долларов со счета Алисы и зачисляем их на счет Боба, а потом выясняем, что нужно было зачислить их на счет Уолли. Это можно исправить, используя точки сохранения, например:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
-- ой ... забудем об этом и используем счет Уолли
ROLLBACK TO my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Wally';
COMMIT;

Этот пример, конечно, чрезмерно упрощен, но блоком транзакции можно активно управлять с помощью точек сохранения. Более того, команда ROLLBACK TO — это единственный, не считая полного отката и повторного запуска, способ восстановить контроль над блоком транзакции, из-за ошибки переведенным системой в прерванное состояние.

Оконные функции

Оконная функция выполняет различные вычисления для набора строк таблицы, которые так или иначе связаны с текущей строкой. Это сопоставимо с вычислением, которое можно выполнить с помощью агрегатной функции. Однако оконные функции не группируют сроки в одну выходную строку, как это сделали бы обычные, не оконные агрегатные функции. Вместо этого строки остаются отдельными элементами. За кадром же оконная функция может обращаться не только к текущей строке результата запроса.

Вот пример, который показывает, как сравнить зарплату каждого сотрудника со средней зарплатой в его или ее отделе:

SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;

depname  | empno | salary |          avg
-----------+-------+--------+-----------------------
develop   |    11 |   5200 | 5020.0000000000000000
develop   |     7 |   4200 | 5020.0000000000000000
develop   |     9 |   4500 | 5020.0000000000000000
develop   |     8 |   6000 | 5020.0000000000000000
develop   |    10 |   5200 | 5020.0000000000000000
personnel |     5 |   3500 | 3700.0000000000000000
personnel |     2 |   3900 | 3700.0000000000000000
sales     |     3 |   4800 | 4866.6666666666666667
sales     |     1 |   5000 | 4866.6666666666666667
sales     |     4 |   4800 | 4866.6666666666666667
(10 rows)

Первые три выходных столбца взяты непосредственно из таблицы empsalary, и для каждой строки в этой таблице есть одна выходная строка. Четвертый столбец представляет среднее значение по всем строкам таблицы, которые имеют то же значение depname что и текущая строка. (На самом деле это та же функция, что и обычный, не оконный агрегат avg, но из-за предложения OVER она обрабатывается как оконная функция и производит вычисление в пределах рамки окна.)

Вызов оконной функции всегда содержит предложение OVER, идущее сразу после ее имени и аргумента(ов). Это синтаксически отличает ее от обычной функции или не оконной агрегатной функции. Предложение OVER точно определяет, как строки запроса разделяются для обработки оконной функцией. Предложение PARTITION BY внутри OVER делит строки на группы, или партиции, имеющие одинаковые значения выражений PARTITION BY. Для каждой строки оконная функция вычисляется по строкам, попадающим в тот же раздел, что и текущая строка.

Кроме того, используя в OVER предложение ORDER BY, можно управлять порядком, в котором строки обрабатываются оконными функциями. (Окну ORDER BY даже необязательно соответствовать порядку, в котором выводятся строки). Например:

SELECT depname, empno, salary,
       rank() OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;

depname  | empno | salary | rank
-----------+-------+--------+------
develop   |     8 |   6000 |    1
develop   |    10 |   5200 |    2
develop   |    11 |   5200 |    2
develop   |     9 |   4500 |    4
develop   |     7 |   4200 |    5
personnel |     2 |   3900 |    1
personnel |     5 |   3500 |    2
sales     |     1 |   5000 |    1
sales     |     4 |   4800 |    2
sales     |     3 |   4800 |    2
(10 rows)

Как видно из результата, функция rank формирует номер по ранжиру для каждого отдельного значения ORDER BY в партиции текущей строки, используя порядок, определенный в предложении ORDER BY. Для rank не требуется явный параметр, поскольку ее поведение полностью определяется предложением OVER.

Строки, рассматриваемые оконной функцией, являются строками «виртуальной таблицы», созданной из предложения FROM путем отсеивания его результата предложениями WHERE, GROUP BY и HAVING, если таковые имеются. Например, строка, удаленная из результата из-за несоответствия условию WHERE, не видна ни одной оконной функции. Запрос может содержать несколько оконных функций, которые по-разному разбивают данные на части с использованием разных предложений OVER, но все они действуют на одну и ту же коллекцию строк, определенных этой виртуальной таблицей.

Мы уже видели, что ORDER BY можно опустить, если порядок строк не важен. Также можно опустить и PARTITION BY, и в этом случае получится одна партиция, содержащая все строки.

Есть еще одно важное понятие, связанное с оконными функциями: для каждой строки в ее партиции есть набор строк, называемый рамкой окна. Некоторые оконные функции действуют только на строки рамки окна, а не на всю партицию. По умолчанию, если задано ORDER BY, то рамка состоит из всех строк от начала партиции до текущей строки, а также любых последующих строк, которые, согласно предложению ORDER BY, равны текущей строке. Если ORDER BY опущено, рамка по умолчанию состоит из всех строк в партиции.¹ Вот пример использования sum:

SELECT salary, sum(salary) OVER () FROM empsalary;

salary |  sum
--------+-------
  5200 | 47100
  5000 | 47100
  3500 | 47100
  4800 | 47100
  3900 | 47100
  4200 | 47100
  4500 | 47100
  4800 | 47100
  6000 | 47100
  5200 | 47100
(10 rows)

Мы видим, что, поскольку в предложении OVER нет ORDER BY, рамка окна совпадает с партицией, которая в отсутствие PARTITION BY представляет собой всю таблицу; другими словами, каждая сумма вычисляется по всей таблице, и поэтому мы получаем одинаковый результат для каждой выходной строки. Но если добавить предложение ORDER BY, получатся совсем другие результаты:

SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary;

salary |  sum
--------+-------
   3500 |  3500
   3900 |  7400
   4200 | 11600
   4500 | 16100
   4800 | 25700
   4800 | 25700
   5000 | 30700
   5200 | 41100
   5200 | 41100
   6000 | 47100
(10 rows)

Здесь сумма вычисляется из первого (самого низкого) оклада до текущего, включая любые его дубликаты (обратите внимание на результаты для повторяющихся окладов).

Оконные функции разрешены только в списке SELECT и предложении ORDER BY запроса. Они запрещены в других местах, например в предложениях GROUP BY, HAVING и WHERE. Это потому, что они логически выполняются после обработки этих предложений. Кроме того, оконные функции выполняются после не оконных агрегатных функций. Это означает, что разрешается включать вызов агрегатной функции в аргументы оконной, но не наоборот.

Если есть необходимость отфильтровать или сгруппировать строки после вычислений оконной функции, можно воспользоваться подзапросами на выборку. Например:

SELECT depname, empno, salary, enroll_date
FROM
  (SELECT depname, empno, salary, enroll_date,
          rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
     FROM empsalary
  ) AS ss
WHERE pos < 3;

Приведенный выше запрос показывает только строки из внутреннего запроса с результатом rank (номером по ранжиру) менее 3.

Когда запрос включает несколько оконных функций, можно записать каждую из них с отдельным предложением OVER, но это будет дублированием и будет приводить к ошибкам, если потребуется одинаковое определение окна для нескольких функций. Вместо этого каждое определение окна можно указать в предложении WINDOW, а затем ссылаться на него в OVER. Например:

SELECT sum(salary) OVER w, avg(salary) OVER w
  FROM empsalary
  WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);

Более подробную информацию об оконных функциях можно найти в подразделах Вызовы оконных функций и Обработка оконных функций, разделе Оконные функции и на справочной странице команды SELECT.

Наследование

Наследование — это понятие, взятое из объектно-ориентированных баз данных. Оно открывает новые интересные возможности при проектировании баз данных.

Давайте создадим две таблицы: cities (города) и capitals (столицы). Естественно, столицы тоже являются городами, поэтому нужно каким-то образом неявно показать их при перечислении всех городов. При должной сноровке можно изобрести, например, такую схему:

CREATE TABLE capitals (
  name       text,
  population real,
  elevation  int,    -- (высота в футах)
  state      char(2)
);

CREATE TABLE non_capitals (
  name       text,
  population real,
  elevation  int     -- (высота в футах)
);

CREATE VIEW cities AS
  SELECT name, population, elevation FROM capitals
    UNION
  SELECT name, population, elevation FROM non_capitals;

Она работает нормально, пока идет запрос данных, но если понадобится, к примеру, изменить несколько строк, все кончится плохо.

Есть и другое, лучшее решение:

CREATE TABLE cities (
  name       text,
  population real,
  elevation  int     -- (высота в футах)
);

CREATE TABLE capitals (
  state      char(2) UNIQUE NOT NULL
) INHERITS (cities);

В этом случае строка таблицы capitals наследует все столбцы (name (название), population (население) и elevation (высота над уровнем моря)) от родительской таблицы cities. Столбец name имеет тип text, собственный тип QHB для символьных строк переменной длины. В таблице capitals имеется дополнительных столбец state, показывающий аббревиатуру штата. В QHB таблица может наследоваться от ноля и более других таблиц.

Например, следующий запрос найдет названия всех городов, включая столицы штатов, расположенные выше 500 футов над уровнем моря:

SELECT name, elevation
  FROM cities
  WHERE elevation > 500;

и вернет:

   name    | elevation
-----------+-----------
 Las Vegas |      2174
 Mariposa  |      1953
 Madison   |       845
(3 rows)

С другой стороны, следующий запрос найдет все города, не являющиеся столицами штатов и расположенные выше 500 футов над уровнем моря:

SELECT name, elevation
    FROM ONLY cities
    WHERE elevation > 500;
   name    | elevation
-----------+-----------
 Las Vegas |      2174
 Mariposa  |      1953
(2 rows)

Здесь слово ONLY перед cities показывает, что запрос должен выполняться только по таблице cities и не спускаться ниже ее в иерархии наследования. Многие уже рассмотренные здесь команды — SELECT, UPDATE и DELETE — поддерживают запись с указанием ONLY.

Примечание
Хотя наследование зачастую бывает полезно, оно не было интегрировано с ограничениями уникальности или внешних ключей, что ограничивает его полезность. Подробную информацию см. в разделе Наследование.

Расширенный SQL

-----------------------------
-- Наследование:
--	Таблица может наследоваться от нескольких таблиц. Запрос может получить как записи из таблицы
--	так дополнительно и записи из всех ее наследников.
-----------------------------
-- Для примера, таблица capitals (столицы) наследуется от таблицы  cities (города), при этом будут унаследованы все поля.
CREATE TABLE cities (
	name		text,
	population	float8,
	altitude	int		-- (в футах)
);
CREATE TABLE capitals (
	state		char(2)
) INHERITS (cities);
-- Теперь заполним таблицы:
INSERT INTO cities VALUES ('San Francisco', 7.24E+5, 63);
INSERT INTO cities VALUES ('Las Vegas', 2.583E+5, 2174);
INSERT INTO cities VALUES ('Mariposa', 1200, 1953);
INSERT INTO capitals VALUES ('Sacramento', 3.694E+5, 30, 'CA');
INSERT INTO capitals VALUES ('Madison', 1.913E+5, 845, 'WI');
SELECT * FROM cities;
SELECT * FROM capitals;
-- Можно найти все города, включая столицы, которые расположены на высоте 500 футов и выше.
SELECT c.name, c.altitude
FROM cities c
WHERE c.altitude > 500;
-- Для того чтобы в запросе участвовала только родительская таблица, используйте ключевое слово ONLY:
SELECT name, altitude
FROM ONLY cities
WHERE altitude > 500;
-- Очистка таблиц (сначала надо удалить дочерние таблицы):
DROP TABLE capitals;
DROP TABLE cities;

¹

Есть возможность определить рамку окна и другими способами, но в этом руководстве они не рассматриваются. Подробную информацию об этих способах см. в подразделе Вызовы оконных функций.

Синтаксис SQL

В этой главе описывается синтаксис языка SQL. Он формирует основу для понимания следующих глав, в которых подробно рассматриваются способы применения команд SQL для определения и изменения данных.

Мы советуем внимательно прочитать эту главу и тем пользователям, которые уже знакомы с SQL, поскольку она содержит несколько правил и концепций, которые по-разному реализованы в базах данных SQL или относятся к QHB.

Лексическая структура

Вводимый на языке SQL текст состоит из последовательности команд. Команда же состоит из последовательности синтаксических единиц, оканчивающейся точкой с запятой («;»). Конец входного потока также завершает команду. Какие именно синтаксические единицы допустимы, зависит от синтаксиса конкретной команды.

Синтаксическая единица может быть ключевым словом, идентификатором, идентификатором в кавычках, литералом (или константой) или специальным символом. Обычно синтаксические единицы разделяются пробельными символами (пробел, табуляция, перевод строки), но это необязательно, если нет неоднозначности (что обычно бывает только в том случае, если специальный символ соседствует с каким-либо другим типом синтаксической единицы).

Например, следующий текст является допустимым (синтаксически) вводом SQL:

SELECT * FROM MY_TABLE;
UPDATE MY_TABLE SET A = 5;
INSERT INTO MY_TABLE VALUES (3, 'hi there');

Это последовательность из трех команд, по одной на строку (хотя это и не требуется; в одной строке может быть несколько команд, и наоборот, команды можно для удобства разбиты на несколько строк).

Кроме того, во входных данных SQL могут находиться комментарии. Это не синтаксические единицы; фактически они равнозначны пробельным символам.

Синтаксис SQL не очень согласован относительно того, какие синтаксические единицы определяются как команды, а какие являются операндами или параметрами. Первые несколько синтаксических единиц обычно являются именем команды, поэтому в приведенном выше примере мы обычно говорим о командах «SELECT», «UPDATE» и «INSERT». Но, например, команда UPDATE всегда требует, чтобы синтаксическая единица SET находилась в определенной позиции, а этому конкретному варианту INSERT для завершения требуется VALUES. Точные правила синтаксиса для каждой команды описаны в главе Команды SQL.

Идентификаторы и ключевые слова

Такие синтаксические единицы, как SELECT, UPDATE или VALUES в приведенном выше примере, являются примерами ключевых слов, то есть слов, имеющих в языке SQL фиксированное значение. Синтаксические единицы MY_TABLE и A являются примерами идентификаторов. Они определяют имена таблиц, столбцов или других объектов базы данных в зависимости от команды, в которой они используются. Поэтому иногда их называют просто «именами». Ключевые слова и идентификаторы имеют одинаковую лексическую структуру, и это означает, что, не зная языка, невозможно понять, является ли синтаксическая единица идентификатором или ключевым словом. Полный список ключевых слов, используемых стандарте SQL и в QHB, можно найти в разделе Ключевые слова SQL.

Идентификаторы SQL и ключевые слова должны начинаться с буквы (a-z, но также могут начинаться с букв с диакритическими знаками и нелатинских букв) или подчеркивания (_). Последующие символы в идентификаторе или ключевом слове могут быть буквами, подчеркиванием, цифрами (0-9) или знаками доллара ($). Обратите внимание, что в соответствии с буквой стандарта SQL в идентификаторах знаки доллара не допускаются, поэтому их использование может уменьшить переносимость приложений. В стандарте SQL точно не будет ключевых слов, содержащих цифры или начинающихся или заканчивающихся подчеркиванием, поэтому идентификаторы такого вида защищены от возможного конфликта с будущими расширениями стандарта.

Система использует не более NAMEDATALEN-1 байт идентификатора; в командах можно написать более длинные имена, но они будут усечены. По умолчанию NAMEDATALEN равен 64, поэтому максимальная длина идентификатора составляет 63 байта.

Ключевые слова и идентификаторы без кавычек не чувствительны к регистру. Следовательно, команду

UPDATE MY_TABLE SET A = 5;

можно с тем же успехом записать как:

uPDaTE my_TabLE SeT a = 5;

Часто используемое соглашение заключается в написании ключевых слов в верхнем регистре, а имен в нижнем регистре, например:

UPDATE my_table SET a = 5;

Существует второй тип идентификатора: идентификатор с разделителями или идентификатор в кавычках. Он формируется путем включения произвольной последовательности символов в кавычки ("). Идентификатор с разделителями всегда является идентификатором, а не ключевым словом. Таким образом, "select" можно использовать для обращения к столбцу или таблице с именем «select», тогда как select без кавычек воспринимался бы как ключевое слово и тем самым вызвал ошибку синтаксического анализа при использовании там, где ожидается имя таблицы или столбца. Приведенный выше пример можно написать с помощью идентификаторов в кавычках примерно так:

UPDATE "my_table" SET "a" = 5;

Идентификаторы в кавычках могут содержать любой символ, кроме символа с кодом ноль. (Чтобы включить кавычки в сам идентификатор, продублируйте их). Это позволяет создавать имена таблиц или столбцов, которые в противном случае были бы невозможны, например, с пробелами или амперсанды. Ограничение длины при этом все равно действует.

Кроме того, заключение идентификатора в кавычки делает его чувствительным к регистру, тогда как имена без кавычек всегда переводятся в нижний регистр. Например, идентификаторы FOO, foo и "foo" считаются в QHB одинаковыми, но "Foo" и "FOO" отличаются от этих трех и друг от друга. (Перевод имен без кавычек в нижний регистр, как это происходит в QHB, несовместим со стандартом SQL, где говорится, что имена без кавычек должны переводиться в верхний регистр. То есть, согласно стандарту, foo должно быть равнозначно "FOO", а не "foo". Если вы хотите создавать переносимые приложения, рекомендуется писать конкретное имя либо всегда в кавычках, либо всегда без кавычек.)

Еще один вариант идентификаторов в кавычках позволяет использовать экранированные символы Unicode, идентифицируемые по их кодам. Этот вариант начинается с U& (строчная или прописная U, потом амперсанд), а за ними сразу без пробелов идет кавычка, например U&"foo". (Обратите внимание, что это создает неоднозначность с оператором &. Чтобы избежать этой проблемы, ставьте вокруг этого оператора пробелы.) Внутри кавычек можно писать символы Unicode, экранированные либо обратным слэшем, за которым идет код символа из четырех шестнадцатеричных цифр, либо, как вариант, обратным слэшем, за которым идет знак плюс и код из шести шестнадцатеричных цифр. Например, идентификатор "data" можно записать как

U&"d\0061t\+000061"

В следующем, менее тривиальном примере, закодировано русское слово «слон» на кириллице:

U&"\0441\043B\043E\043D"

Если вы хотите использовать вместо обратного слэша какой-то другой управляющий символ, его можно указать, добавив после строки предложение UESCAPE, например:

U&"d!0061t!+000061" UESCAPE '!'

Управляющим символом может быть любой одиночный символ, кроме шестнадцатеричной цифры, знака плюс, апострофа, кавычки или пробельного символа. Обратите внимание, что управляющий символ после UESCAPE заключается в апострофы, а не в кавычки.

Чтобы включить управляющий символ в идентификатор буквально, продублируйте его.

Чтобы указать суррогатные пары UTF-16 для составления символов с кодами больше чем U+FFFF, можно использовать либо четырех-, либо шестизначную форму, хотя доступность шестизначной формы технически делает это ненужным. (Суррогатные пары не сохраняются напрямую, а объединяются в один код UTF-8.)

Если кодировка сервера не UTF-8, символ с кодом Unicode, указанным одной из этих управляющих последовательностей, преобразуется в фактическую кодировку сервера; если это невозможно, выдается ошибка.

Константы

В QHB есть три типа неявно типизированных констант: строки, битовые строки и числа. Кроме того, константы можно указать с явными типами, которые могут обеспечить более точное представление и более эффективную обработку системой. Эти варианты обсуждаются в следующих подразделах.

Строковые константы

Строковая константа в SQL — это произвольная последовательность символов, заключенная в апострофы ('), например 'This is a string'. Чтобы включить символ апострофа в строковую константу, продублируйте его, например, 'Dianne''s horse'. Обратите внимание, что это не то же самое, что символ кавычки (").

Две строковые константы, разделенные только пробельными символами с как минимум одним переводом строки, объединяются и, по сути, обрабатываются, как если бы строка была записана как одна константа. Например, запись:

SELECT 'foo'
'bar';

равнозначна:

SELECT ’foobar’;

но:

SELECT 'foo'      'bar';

считается недопустимым синтаксисом. (Это немного странное поведение определяется SQL; QHB просто следует стандарту.)

Строковые константы с управляющими последовательностями в стиле C

QHB также принимает «управляющие» строковые константы, являющиеся расширением стандарта SQL. При указании строки экранированной константой сразу перед открывающим апострофом пишется буква E (в верхнем или нижнем регистре), например E'foo'. (Если управляющая строковая константа занимает несколько строк, E пишется только перед первым открывающим апострофом). Внутри управляющей строки обратного слэша (\) начинается C-подобная управляющая последовательность с обратным слэшем, в которой сочетание обратного слэша и последующего символа (или символов) представляет специальное байтовое значение, как показано в Таблице 1:

Таблица 1. Управляющие последовательности с обратным слэшем

Управляющая последовательность с обратным слэшем | Описание
\b | возврат на символ
\f | перевод страницы
\n | перевод строки
\r | возврат каретки
\t | табуляция
\o, \oo, \ooo (o = 0-7) | восьмеричное значение байта
\xh, \xhh (h = 0-9, A-F) | шестнадцатеричное значение байта
\uxxxx, \Uxxxxxxxx (x = 0-9, A-F) | 16- или 32-битное шестнадцатеричное значение символа Unicode

Любой другой символ после обратного слэша воспринимается буквально. Таким образом, чтобы включить символ обратного слэша в строку, продублируйте его (\\). Кроме того, в управляющую строку можно включить апостроф, написав \', в дополнение к обычному способу ''.

Вы должны сами позаботиться о том, чтобы создаваемые вами последовательности байтов, особенно при использовании восьмеричного или шестнадцатеричного экранирования, формировали допустимые символы в кодировке сервера. Полезным вариантом может быть использование управляющих последовательностей Unicode или альтернативной записи Unicode, о которой рассказывается в подразделе Строковые константы с управляющими последовательностями Unicode; тогда сервер будет проверять, возможно ли преобразование такого символа.

ВНИМАНИЕ!
Если параметр конфигурации standard_conforming_strings имеет значение off, то QHB распознает обратный слэш как управляющий символ и в обычных строках и в управляющих строковых константах. Однако по умолчанию этот параметр имеет значение on, а это означает, что обратный слэш распознается только в управляющих строковых константах. Это поведение больше соответствует стандарту, хотя может нарушить работу приложений, полагающихся на поведение, когда обратный слэш распознается всегда. В качестве обходного решения можно установить этот параметр в off, но лучше просто отказаться от использования обратного слэша в качестве управляющего символа. Если вам нужно, чтобы обратный слэш представлял специальный символ, напишите строковую константу с E.

В дополнение к standard_conforming_strings обращением с обратными слэшами в строковых константах управляют параметры конфигурации escape_string_warning и backslash_quote.

Символ с кодом ноль не может быть строковой константой.

Строковые константы с управляющими последовательностями Unicode

QHB также поддерживает другой тип синтаксиса управляющих последовательностей для строк, позволяющий указывать произвольные символы Unicode по их кодам. Управляющая строковая константа Unicode начинается с U& (заглавная или строчная буква U, потом амперсанд) а за ними сразу без пробелов идет апостроф, например U&'foo'. (Обратите внимание, что это создает неоднозначность с оператором &. Чтобы избежать этой проблемы, ставьте вокруг этого оператора пробелы.) Внутри апострофов можно писать символы Unicode, экранированные либо обратным слэшем, за которым идет код символа из четырех шестнадцатеричных цифр, либо, как вариант, обратным слэшем, за которым идет знак плюс и код из шести шестнадцатеричных цифр. Например, строку 'data' можно записать как

U&'d\0061t\+000061'

В следующем, менее тривиальном примере, закодировано русское слово «слон» на кириллице:

U&'\0441\043B\043E\043D'

Если вы хотите использовать вместо обратного слэша какой-то другой управляющий символ, его можно указать, добавив после строки предложение UESCAPE, например:

U&'d!0061t!+000061' UESCAPE '!'

Управляющим символом может быть любой одиночный символ, кроме шестнадцатеричной цифры, знака плюс, апострофа, кавычки или пробельного символа.

Чтобы включить управляющий символ в строку буквально, продублируйте его.

Чтобы указать суррогатные пары UTF-16 для составления символов с кодами больше чем U+FFFF, можно использовать либо четырех-, либо шестизначную форму, хотя доступность шестизначной формы технически делает это ненужным. (Суррогатные пары не сохраняются напрямую, а объединяются в один код UTF-8.)

Если кодировка сервера не UTF-8, символ с кодом Unicode, указанным одной из этих управляющих последовательностей, преобразуется в фактическую кодировку сервера; если это невозможно, выдается ошибка.

Кроме того, синтаксис управляющих последовательностей Unicode для строковых констант работает только при включенном параметре конфигурации standard_conforming_strings. Это связано с тем, что в противном случае этот синтаксис может запутать клиентов, анализирующих операторы SQL, до такой степени, что это может привести к инъекциям SQL и подобным проблемам безопасности. Если этот параметр выключен, такой синтаксис будет отклонен с сообщением об ошибке.

Строковые константы, заключенные в знаки доллара

Хотя стандартный синтаксис для указания строковых констант обычно удобен, в нем бывает трудно разобраться, когда нужная строка содержит много апострофов или обратных слэшей, поскольку каждый из них должен быть удвоен. Чтобы обеспечить в таких ситуациях более удобочитаемые запросы, QHB предоставляет другой способ записи строковых констант, называемый «заключение в знаки доллара». Строковая константа, заключенная в такие «кавычки», состоит из знака доллара ($), необязательного «тега», состоящего из нуля или более символов, еще одного знака доллара, произвольной последовательности символов, составляющих содержимое строки, знака доллара, того же тега, которым началась эта цитата, и знака доллара. Например, вот два разных способа указать строку «Dianne's horse» с использованием знаков доллара:

$$Dianne's horse$$
$SomeTag$Dianne's horse$SomeTag$

Обратите внимание, что внутри строки, заключенной в знаки доллара, апострофы можно использовать без экранирования. Действительно, ни один символ внутри строки в долларовых кавычках не экранируется: содержимое строки всегда пишется буквально. Обратные слэши, как и знаки доллара, являются управляющими, только когда входят в состав последовательности, соответствующей открывающему тегу.

Строковые константы в долларовых кавычках можно вкладывать друг в друга, выбирая на каждом уровне вложенности разные теги. Чаще всего это используется при написании определений функций. Например:

$function$
BEGIN
    RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
END;
$function$

Здесь последовательность $q$[\t\r\n\v\\]$q$ представляет заключенную в знаки доллара текстовую строку [\t\r\n\v\\], которая будет распознаваться при выполнении QHB тела этой функции. Но поскольку последовательность не соответствует внешнему разделителю долларовых кавычек $function$, с точки зрения внешней строки это просто еще несколько символов внутри константы.

Тэг строки в долларовых кавычках, если таковой имеется, следует тем же правилам, что и идентификатор без кавычек, за исключением того, что он не может содержать знак доллара. Теги чувствительны к регистру, поэтому $tag$String content$tag$ — правильная строка, а $TAG$String content$tag$ — нет.

Строка в знаках доллара, следующая за ключевым словом или идентификатором, должна отделяться от него пробельными символами; в противном случае разделитель долларовых кавычек будет принят за часть предыдущего идентификатора.

Заключение в знаки доллара не являются частью стандарта SQL, но зачастую это более удобный способ написания сложных строковых литералов, чем соответствующий стандарту синтаксис с апострофами. Это особенно полезно при представлении строковых констант внутри других констант, что часто требуется в определениях процедурных функций. В синтаксисе с апострофами каждый обратный слэш в приведенном выше примере пришлось бы записывать как четыре обратных слэша, которые будут уменьшены до двух обратных слэшей при анализе исходной строковой константы, а затем до одного, когда внутренняя строковая константа будет повторно проанализирована во время выполнения функции.

Константы битовых строк

Константы битовых строк выглядят как обычные строковые константы с буквой B (в верхнем или нижнем регистре) сразу перед открывающим апострофом (без промежуточных пробельных символом), например B'1001'. В битовых строковых константах допускаются только символы 0 и 1.

В качестве альтернативы константы битовой строки можно указать в шестнадцатеричном формате с использованием начального X (в верхнем или нижнем регистре), например X'1FF'. Это обозначение равнозначно константе битовой строки с четырьмя двоичными цифрами для каждой шестнадцатеричной цифры.

Обе формы константы битовой строки можно разносить на несколько строк так же, как и обычные строковые константы. Заключать в знаки доллара константы битовых строк нельзя.

Числовые константы

Числовые константы принимаются в следующих общих формах:

цифры
цифры.[цифры][e[+-]цифры]
[цифры].цифры[e[+-]цифры]
цифры[+-]цифры

где цифры — это одна или несколько десятичных цифр (от 0 до 9 включительно). Как минимум одна цифра должна стоять до или после десятичной точки (при ее наличии). По крайней мере одна цифра должна следовать за обозначением экспоненциального формата (e), если таковой имеется. В константе не должно быть пробелов или других символов. Обратите внимание, что любой ведущий знак плюс или минус на самом деле не считается частью константы; это применяемый к константе оператор.

Вот несколько примеров допустимых числовых констант:

42
3.5
4.
.001
5e2
1.925e-3

Числовая константа, которая не содержит ни десятичной точки, ни экспоненты, изначально считается типом integer, если ее значение соответствует типу integer (32 бита); иначе предполагается, что это тип bigint, если его значение соответствует типу bigint (64 бита); иначе он считается типом numeric. Константы, содержащие десятичные точки и/или экспоненты, всегда изначально считаются numeric.

Изначально назначенный тип данных числовой константы является лишь отправной точкой для алгоритмов разрешения типов. В большинстве случаев константа будет автоматически приведена к наиболее подходящему типу в зависимости от контекста. При необходимости можно принудительно интерпретировать числовое значение как определенный тип данных, приведя его к нужному типу. Например, можно сделать так, чтобы числовое значение воспринималось как тип real (float4), написав:

REAL '1.23'  -- строковый стиль
1.23::REAL   -- стиль PostgreSQL (недействующий)

На самом деле это просто частные случаи рассматриваемых далее общих нотаций приведения типов.

Константы других типов

Константу произвольного типа можно ввести с помощью любой из следующих нотаций:

тип 'строка'
'строка'::тип
CAST ( 'строка' AS тип )

Текст строковой константы передается на вход процедуры преобразования для заданного типа. Результатом является константа указанного типа. Если нет неопределенности относительно требуемого типа константы (например, когда она присваивается столбцу таблицы напрямую), явное приведение типов можно опустить, и в этом случае константа будет автоматически приведена к нужному типу.

Строковую константу можно записать как обычной нотацией SQL, так и заключив ее в знаки доллара.

Также можно указать приведение типа с использованием синтаксиса, как у функций:

имя_типа ( 'строка' )

но таким образом можно задавать не все имена типов; подробную информацию см. в подразделе Приведение типов.

Символы ::, CAST(), синтаксис функций также можно применять для указания преобразований типов во время выполнения произвольных выражений, как описывается в подразделе Приведение типов. Чтобы избежать синтаксической неоднозначности, синтаксис тип 'строка' можно использовать только для указания типа простой литеральной константы. Другое ограничение синтаксиса тип 'строка' заключается в том, что он не работает для типов-массивов; для указания констант этого типа используйте :: или CAST().

Синтаксис CAST() соответствует SQL. Синтаксис тип 'строка' является обобщением стандарта: SQL определяет этот синтаксис только для нескольких типов данных, но QHB допускает его для всех типов. Синтаксис с :: является историческим, равно как и синтаксис в виде вызова функции.

Операторы

Имя оператора — это последовательность символов длиной до NAMEDATALEN-1 (по умолчанию 63) символов из следующего списка:

+ - * / < > = ~ ! @ # % ^ & | ` ?

Однако на имена операторов есть несколько ограничений:

В имени оператора не могут фигурировать -- и /*, поскольку они будут приняты за начало комментария.

Имя оператора из нескольких символов может заканчиваться на + или -, только если это имя содержит хотя бы один из следующих символов:

~ ! @ # % ^ & | ` ?

Например, @- это разрешенное имя оператора, а *- — нет. Это ограничение позволяет QHB анализировать запросы, соответствующие SQL, без пробелов между синтаксическими единицами.

При работе с именами нестандартных операторов SQL скорее всего понадобится разделять соседние операторы пробелами, чтобы избежать неоднозначности. Например, если вы определили префиксный оператор с именем @, нельзя написать X*@Y; следует написать X* @Y, чтобы QHB прочитал его как имена двух операторов, а не одного.

Специальные символы

Некоторые символы, не относящиеся к буквенно-цифровым, имеют особое значение, отличное от обозначения оператора. Подробную информацию об их использовании можно найти там, где описан соответствующий элемент синтаксиса. Этот раздел включен сюда, только чтобы перечислить имеющиеся символы и обобщить их назначение.

Знак доллара ($), за которым следуют цифры, используется для представления позиционного параметра в теле определения функции или подготовленного оператора. В других контекстах знак доллара может быть частью идентификатора или строковой константы в долларовых кавычках.

Круглые скобки (()) имеют обычное значение и применяются для группирования выражений и обеспечения приоритета. В некоторых случаях скобки требуются как часть фиксированного синтаксиса конкретной команды SQL.

Квадратные скобки ([]) используются для выбора элементов массива. Дополнительную информацию о массивах см. в разделе Массивы.

Запятые (,) используются в некоторых синтаксических конструкциях для разделения элементов списка.

Точка с запятой (;) завершает команду SQL. Она не может находиться где-либо внутри команды, кроме как строковых констант или идентификаторов в кавычках.

Двоеточие (:) используется для выбора «срезов» из массивов. (См. раздел Массивы. В некоторых диалектах SQL (например в Embedded SQL) двоеточие используется в качестве префиксов в именах переменных.

Звездочка (*) используется в некоторых контекстах для обозначения всех полей табличной строки или составного значения. Она также имеет особое значение при использовании в качестве аргумента агрегатной функции, а именно, указывает, что агрегат не требует какого-либо явного параметра.

Точка (.) Используется в числовых константах и для разделения имен схем, таблиц и столбцов.

Комментарии

Комментарий — это последовательность символов, начинающаяся с двойного дефиса и продолжающаяся до конца строки, например:

-- Это стандартный комментарий SQL

В качестве альтернативы можно использовать комментарии блока в стиле C:

/* многострочный комментарий
 * с вложенностью: /* вложенный блочный комментарий */
 */

где комментарий начинается с /* и продолжается до соответствующего вхождения */. Эти блочные комментарии вложены, как указано в стандарте SQL, но, в отличие от C, так, что можно закомментировать большие блоки кода, которые могут содержать уже существующие блочные комментарии.

Комментарий удаляется из входного потока перед дальнейшим синтаксическим анализом и фактически заменяется пробельным символом.

Приоритеты операторов

В Таблице 2 показаны приоритет и ассоциативность операторов в QHB. Большинство операторов имеют одинаковый приоритет и являются левоассоциативными. Приоритет и ассоциативность операторов встроены в синтаксический анализатор. Если вы хотите, чтобы выражение с несколькими операторами анализировалось иначе, чем предписано правилами приоритетов, заключите его в круглые скобки.

Таблица 2. Приоритеты операторов (от высшего к низшему)

Оператор/Элемент | Ассоциативность | Описание
. | слева направо | разделитель имени таблицы/столбца
:: | слева направо | приведение типа в стиле QHB
[ ] | слева направо | выбор элемента массива
+ - | справа налево | унарный плюс, унарный минус
^ | слева направо | возведение в степень
* / % | слева направо | умножение, деление, остаток от деления
+ - | слева направо | сложение, вычитание
(любой другой оператор) | слева направо | все остальные собственные и пользовательские операторы
BETWEEN IN LIKE ILIKE SIMILAR |  | включение в диапазон, наличие во множестве, сопоставление строк
< > = <= >= <> |  | операторы сравнения
IS ISNULL NOT NULL |  | IS TRUE, IS FALSE, IS NULL, IS DISTINCT FROM и т. д.
NOT | справа налево | логическое отрицание
AND | слева направо | логическое конъюнкция
OR | слева направо | логическая дизъюнкция

Обратите внимание, что правила приоритета операторов также применяются к пользовательским операторам, имена которых совпадают с именами вышеперечисленных встроенных операторов. Например, если вы определите оператор «+» для некоторого пользовательского типа данных, он будет иметь тот же приоритет, что и встроенный оператор «+», независимо от того, что делает ваш.

Когда в синтаксисе OPERATOR используется имя оператора, дополненное схемой, как, например, в:

SELECT 3 OPERATOR(pg_catalog.+) 4;

конструкция OPERATOR имеет приоритет по умолчанию, показанный в Таблице 2 для «любого другого оператора». Это верно независимо от того, какой именно оператор появляется внутри OPERATOR().

Выражения значения

Выражение значения используются в различных контекстах, например, в списке целей команды SELECT, в качестве новых значений столбцов в INSERT или UPDATE или в условиях поиска в ряде команд. Результат вычисления выражения значений иногда называют скаляром, чтобы отличить его от результата табличного выражения (которое является таблицей). Поэтому выражения значений также называют скалярными выражениями (или даже просто выражениями). Синтаксис выражения позволяет вычислять значения из примитивных частей, используя арифметические, логические операции, а также операции присвоения и т. п.

Выражением значения может быть:

Постоянное или буквальное значение

Ссылка на столбец

Ссылка на позиционный параметр в теле определения функции или подготовленного оператора

Выражение с индексом

Выражение выбора поля

Запуск оператора

Вызов функции

Агрегатное выражение

Вызов оконной функции

Приведение типа

Выражение правила сортировки

Скалярный подзапрос

Конструктор массива

Конструктор строки

Другое выражение значения в скобках (используется для группировки подвыражений и переопределения приоритета)

В дополнение к этому списку существует ряд конструкций, которые могут быть классифицированы как выражения, но не следуют никаким общим правилам синтаксиса. Как правило, они имеют семантику функции или оператора и объясняются в соответствующем месте в главе Функции и операторы. Примером является предложение IS NULL.

Константы уже обсуждались в разделе Константы. В следующих подразделах обсуждаются остальные варианты.

Ссылки на столбец

На столбец можно ссылаться в виде:

отношение.имя_столбца

Здесь отношение — это имя таблицы (возможно, дополненное именем схемы) или ее псевдоним, определенный с помощью предложения FROM. Имя таблицы и разделяющую точку можно опустить, если имя столбца уникально среди всех таблиц, используемых в текущем запросе. (См. также главу Запросы).

Позиционные параметры

Ссылка на позиционный параметр используется для указания значения, поступающего в оператор SQL извне. Параметры используются в определениях функций SQL и в подготовленных запросах. Кроме того, некоторые клиентские библиотеки поддерживают указание значений данных отдельно от командной строки SQL, и в этом случае параметры используются для ссылки на внешние значения данных. Форма ссылки на параметр:

$число

Например, рассмотрим определение функции dept как:

CREATE FUNCTION dept(text) RETURNS dept
    AS $$ SELECT * FROM dept WHERE name = $1 $$
    LANGUAGE SQL;

Здесь $1 ссылается на значение первого аргумента функции при каждом ее вызове.

Индексы массивов

Если выражение выдает значение типа массива, то конкретный элемент значения этого массива можно извлечь, написав

выражение[индекс]

или можно извлечь несколько смежных элементов («срез массива»), написав

выражение[нижний_индекс:верхний_индекс]

(Здесь квадратные скобки [ ] должны фигурировать буквально). Каждый индекс сам по себе является выражением, которое будет округляться до ближайшего целого значения.

В общем случае массив выражение должен быть заключен в круглые скобки, но когда индексируемое выражение является просто ссылкой на столбец или позиционным параметром, их можно опустить. Кроме того, если исходный массив многомерный, можно конкатенировать несколько индексов. Например:

mytable.arraycolumn[4]
mytable.two_d_column[17][34]
$1[10:42]
(arrayfunction(a,b))[42]

В последнем примере круглые скобки обязательны. Подробную информацию о массивах см. в разделе Массивы.

Выбор поля

Если выражение возвращает значение составного типа (тип строки), то конкретное поле строки можно извлечь, написав

выражение.имя_поля

В общем случае выражение строки должно быть заключено в круглые скобки, но если выбираемое выражение является просто ссылкой на таблицу или позиционным параметром, их можно опустить. Например:

mytable.mycolumn
$1.somecolumn
(rowfunction(a,b)).col3

(Таким образом, полная ссылка на столбец на самом деле представляет собой просто частный случай синтаксиса выбора поля). Важный частный случай здесь — это извлечение поля из столбца составного типа:

(compositecol).somefield
(mytable.compositecol).somefield

Здесь круглые скобки нужны, чтобы показать, что compositecol — это имя столбца, а не таблицы, или, во втором случае, что mytable — это имя таблицы, а не схемы.

Можно запросить все поля составного значения, написав .*:

(compositecol).*

Эта запись ведет себя по-разному в зависимости от контекста; подробную информацию см. в подразделе Использование составных типов в запросах.

Вызовы операторов

Существует три возможных синтаксиса для вызова оператора:

выражение оператор выражение (бинарный инфиксный оператор)
оператор выражение (унарный префиксный оператор)

где синтаксическая единица оператор следует синтаксическим правилам подраздела Операторы, или является одним из ключевых слов AND, OR и NOT, или является полным именем оператора в форме:

OPERATOR(схема.имя_оператора)

Какие конкретные операторы существуют и являются ли они унарными или бинарными зависит от того, какие операторы были определены системой или пользователем. Встроенные операторы описываются в главе Функции и операторы.

Вызовы функций

Синтаксис для вызова функции — это имя функции (возможно, дополненное именем схемы), за которым следует список аргументов в круглых скобках:

имя_функции ([выражение [, выражение ... ]] )

Например, вычисление квадратного корня из 2:

sqrt(2)

Список всех встроенных функций см. в главе Функции и операторы. Пользователь может добавить к ним и другие необходимые функции.

При отправке запросов в базу данных, где одни пользователи не доверяют другим, при написании вызовов таких функций соблюдайте меры безопасности, описанные в разделе Функции.

К аргументам можно привязать необязательные имена. Подробную информацию см. в разделе Вызов функции.

Примечание
Функцию, принимающую один аргумент составного типа, можно при желании вызывать с использованием синтаксиса выбора поля, и наоборот, выбор поля можно написать в функциональном стиле. То есть обозначения col(table) и table.col взаимозаменяемы. Такое поведение не включено в стандарт SQL, но предоставляется в QHB, поскольку это позволяет использовать функции для эмуляции «вычисляемых полей». Подробную информацию см. в подразделе Синтаксис вводимых и выводимых значений массива

Агрегатные выражения

Агрегатное выражение представляет собой применение агрегатной функции для строк, выбранных запросом. Агрегатная функция сводит несколько входных значений к одному результирующему, например сумме или среднему. Синтаксис агрегатного выражения может быть одним из следующих:

имя_агрегата (выражение [ , ... ] [ предложение_order_by ] ) [ FILTER ( WHERE условие_фильтра ) ]
имя_агрегата (ALL выражение [ , ... ] [ предложение_order_by ] ) [ FILTER ( WHERE условие_фильтра ) ]
имя_агрегата (DISTINCT выражение [ , ... ] [ предложение_order_by ] ) [ FILTER ( WHERE условие_фильтра ) ]
имя_агрегата ( * ) [ FILTER ( WHERE условие_фильтра ) ]
имя_агрегата ( [ выражение [ , ... ] ] ) WITHIN GROUP ( предложение_order_by ) [ FILTER ( WHERE условие_фильтра ) ]

где имя_агрегата — это имя ранее определенной агрегатной функции (возможно, дополненное имени схемы), а выражение — это любое выражение значения, которое само не содержит агрегатное выражение или вызов оконной функции. Необязательные предложение_order_by и условие_фильтра описываются ниже.

Первая форма агрегатного выражения вызывает агрегат для каждой входной строки. Вторая форма такая же, как и первая, поскольку по умолчанию используется ALL. Третья форма вызывает агрегат для каждого отдельного выражения значения (или отдельного набора значений для нескольких выражений), найденного во входных строках. Четвертая форма вызывает агрегат для каждой входной строки; поскольку конкретное входное значение не указано, эта форма обычно используется только для агрегатной функции count(*). Последняя форма используется для сортирующих агрегатных функций, которые описываются ниже.

Большинство агрегатных функций игнорируют входные значения NULL, поэтому строки, в которых одно или несколько выражений выдают значение NULL, отбрасываются. Это можно считать верным для всех встроенных агрегатов, если не указано иное.

Например, count(*) выдает общее количество входных строк; count(f1) выдает количество входных строк, в которых f1 не NULL, поскольку count игнорирует NULL, а count(distinct f1) выдает количество уникальных значений f1, отличных от NULL.

Обычно входные строки подаются в агрегатную функцию в неопределенном порядке. Во многих случаях это не имеет значения; например, min выдает одинаковый результат независимо от того, в каком порядке она принимает входные данные. Однако некоторые агрегатные функции (например array_agg и string_agg) выдают результаты, зависящие от порядка входных строк. При использовании такого агрегата для указания желаемого порядка можно добавить необязательное предложение_order_by. У предложения_order_by тот же синтаксис, что и у предложения ORDER BY уровня запроса, как описано в разделе Сортировка строк (ORDER BY), за исключением того, что его выражения всегда являются просто выражениями и не могут быть именами или числами выходных столбцов. Например:

SELECT array_agg(a ORDER BY b DESC) FROM table;

При работе с агрегатными функциями с несколькими аргументами обратите внимание, что предложение ORDER BY идет после всех аргументов агрегации. Например, пишите так:

SELECT string_agg(a, ',' ORDER BY a) FROM table;

но не это:

SELECT string_agg(a ORDER BY a, ',') FROM table;  -- неверно

Последний пример синтаксически допустим, но представляет собой вызов агрегатной функции с одним аргументом и двумя ключами ORDER BY (второй довольно бесполезен, поскольку является константой).

Если в дополнение к предложению_order_by указывается DISTINCT, то все выражения ORDER BY должны соответствовать обычным аргументам агрегата; то есть нельзя сортировать выражения, которые не включены в список DISTINCT.

Примечание
Возможность указывать в агрегатной функции одновременно DISTINCT и ORDER BY является расширением QHB.

Помещение ORDER BY в обычный список аргументов агрегата, как описано выше, используется при упорядочении входных строк для универсальных и статистических агрегатов, для которых упорядочение необязательно. Существует подкласс агрегатных функций, называемых сортирующими агрегатами, для которых предложение_order_by является обязательным, обычно потому что вычисление такого агрегата имеет смысл только при условии конкретного порядка его входных строк. Типичными примерами сортирующих агрегатов являются вычисления ранга и процентиля. Для сортирующего агрегата предложение_order_by записывается внутри WITHIN GROUP (...), как показано выше в последнем варианте синтаксиса. Выражения в предложении_order_by вычисляются для каждой входной строки, как обычные агрегатные аргументы, сортируются в соответствии с требованиями предложения_order_by и передаются в агрегатную функцию в качестве входных аргументов. (Это не работает, если предложение_order_by находится не в WITHIN GROUP, поскольку тогда оно не рассматривается как аргумент(ы) для агрегатной функции). Выражения аргумента, перед WITHIN GROUP, если таковые имеются, называются прямыми аргументами, чтобы отличать их от агрегатных аргументов, перечисленных в предложении_order_by. В отличие от обычных агрегатных аргументов, прямые аргументы вычисляются только один раз за вызов агрегата, а для каждой входной строки. Это означает, что они могут содержать переменные, только если эти переменные сгруппированы GROUP BY; это ограничение такое же, как если бы прямые аргументы вообще были вне агрегатного выражения. Прямые аргументы обычно используются для таких элементов, как процентильные дроби, которые имеют смысл только как единственное значение для всего расчета агрегации. Список прямых аргументов может быть пустым; в этом случае просто напишите () вместо (*). (На самом деле QHB принимает любое написание, но только первый вариант соответствует стандарту SQL).

Пример вызова сортирующего агрегата:

SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY income) FROM households;
 percentile_cont
-----------------
           50489

который получает 50-й процентиль, или медиану, значения столбца income из таблицы households. Здесь 0.5 — прямой аргумент; если бы дробь процентиля была значением, меняющимся от строки к строке, это бы не имело смысла.

Если указано предложение FILTER, то в агрегатную функцию передаются только те входные строки, для которых условие_фильтра вычисляется как истинное; другие строки отбрасываются. Например:

SELECT
    count(*) AS unfiltered,
    count(*) FILTER (WHERE i < 5) AS filtered
FROM generate_series(1,10) AS s(i);
 unfiltered | filtered
------------+----------
         10 |        4
(1 row)

Предопределенные агрегатные функции описаны в разделе Агрегатные функции. Пользователь может добавить и другие агрегатные функции.

Агрегатное выражение может находиться только в результирующем списке или в предложении HAVING команды SELECT. В других предложениях, например WHERE, оно запрещено, поскольку эти предложения логически вычисляются до формирования результатов агрегатов.

Когда агрегатное выражение фигурирует в подзапросе (см. подразделы Скалярные подзапросы и Выражения подзапросов), агрегат обычно вычисляется по строкам подзапроса. Но если агрегатные аргументы (и условие_фильтра, если имеется) содержат только переменные внешнего уровня, возникает исключение: в этом случае агрегат относится к ближайшему внешнему уровню и вычисляется для всех строк этого запроса. Тогда все агрегатное выражение является внешней ссылкой для подзапроса, в котором оно фигурирует, и действует как константа для всех вычислений этого подзапроса. Ограничение относительно нахождения только в результирующем списке или предложении HAVING действует на уровне запроса, к которому принадлежит агрегат.

Вызовы оконных функций

Вызов оконной функции представляет собой применение агрегатоподобной функции к некоторой части строк, выбранной запросом. В отличие от вызовов агрегатных функций, это не связано с группировкой выбранных строк в одну выходную строку — в выходе запроса каждая строка остается отдельной. Однако оконная функция имеет доступ ко всем строкам, которые будут частью группы текущей строки в соответствии с указанием группировки (список PARTITION BY) вызова оконной функции. Синтаксис вызова оконной функции может быть одним из следующих:

имя_функции ([выражение [, выражение ... ]]) [ FILTER ( WHERE условие_фильтра ) ] OVER имя_окна
имя_функции ([выражение [, выражение ... ]]) [ FILTER ( WHERE условие_фильтра ) ] OVER ( определение_окна )
имя_функции ( * ) [ FILTER ( WHERE условие_фильтра ) ] OVER имя_окна
имя_функции ( * ) [ FILTER ( WHERE условие_фильтра ) ] OVER ( определение_окна )

где определение_окна имеет синтаксис:

[ имя_существующего_окна ]
[ PARTITION BY выражение [, ...] ]
[ ORDER BY выражение [ ASC | DESC | USING оператор ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ определение_рамки ]

Необязательное определение_рамки может иметь вид:

{ RANGE | ROWS | GROUPS } начало_рамки [ исключение_рамки ]
{ RANGE | ROWS | GROUPS } BETWEEN начало_рамки AND конец_рамки [ исключение_рамки ]

где начало_рамки и конец_рамки могут иметь вид:

UNBOUNDED PRECEDING
смещение PRECEDING
CURRENT ROW
смещение FOLLOWING
UNBOUNDED FOLLOWING

а исключение_рамки может иметь вид:

EXCLUDE CURRENT ROW
EXCLUDE GROUP
EXCLUDE TIES
EXCLUDE NO OTHERS

Здесь выражение представляет любое выражение значения, которое само не содержит вызовов оконных функций.

имя_окна является ссылкой на указание именованного окна, определенного в предложении WINDOW этого запроса. Как вариант, можно указать в скобках полное определение_окна, используя тот же синтаксис, что и для определения именованного окна в предложении WINDOW; подробную информацию см. на справочной странице команды SELECT. Стоит отметить, что OVER имя_окна не совсем равнозначно OVER (имя_окна ...); последнее подразумевает копирование и изменение определения окна и будет отклонено, если указание этого окна включает определение рамки.

Предложение PARTITION BY группирует строки запроса в партиции, которые обрабатываются оконной функцией по отдельности. PARTITION BY работает аналогично предложению GROUP BY уровня запроса за исключением того, что его выражения всегда являются просто выражениями и не могут быть именами или числами выходных столбцов. Без PARTITION BY все созданные запросом строки обрабатываются как одна партиция. Предложение ORDER BY определяет порядок, в котором строки партиции обрабатываются оконной функцией. Оно работает аналогично предложению ORDER BY уровня запроса, но тоже не может использовать имена или номера выходных столбцов. Без ORDER BY строки обрабатываются в неопределенном порядке.

В определении_рамки задается набор строк, составляющих рамку окна, являющуюся подмножеством текущей партиции, для тех оконных функций, которые действуют на рамку, а не на всю партицию. Набор строк в рамке может варьировать в зависимости от того, какая строка является текущей. Рамку можно задать в режиме RANGE, ROWS или GROUPS; в каждом случае она простирается от начала_рамки до конца_рамки. Если конец_рамки опущен, по умолчанию подразумевается текущая строка (CURRENT ROW).

Вместе с UNBOUNDED PRECEDING начало_рамки означает, что эта рамка начинается с первой строки партиции, а конец_рамки с UNBOUNDED FOLLOWING аналогичным образом означает, что рамка заканчивается последней строкой партиции.

В режиме RANGE или GROUPS начало_рамки с CURRENT ROW означает, что рамка начинается с первой одноранговой строки для текущей (это строка, которую предложение ORDER BY окна сортирует как равнозначную текущей строке), тогда как конец_рамки с CURRENT ROW означает, что рамка заканчивается последней одноранговой строкой для текущей. В режиме ROWS CURRENT ROW просто означает текущую строку.

В вариантах определения рамки смещение PRECEDING и смещение FOLLOWING смещение должно быть выражением, не содержащим никаких переменных, агрегатных или оконных функций. Значение смещения зависит от режима рамки:

В режиме ROWS смещение должно выдавать отличное от NULL неотрицательное целое число, и этот вариант означает, что рамка начинается за указанное число строк перед текущей строкой или заканчивается через указанное число строк после нее.

В режиме GROUPS смещение, опять же, должно выдавать отличное от NULL неотрицательное целое число, и этот вариант означает, что рамка начинается за указанное число одноранговых групп перед одноранговой группой текущей строки или заканчивается через указанное число одноранговых групп после нее. Здесь одноранговые группы представляют собой набор строк, равнозначных при упорядочивании ORDER BY. (Для использования режима GROUPS в определении окна должно быть предложение ORDER BY).

В режиме RANGE для этих вариантов требуется, чтобы в предложении ORDER BY был задан ровно один столбец. смещение определяет максимальную разницу между значением этого столбца в текущей строке и его значением в предыдущих или последующих строках рамки. Тип данных выражения смещения варьируется в зависимости от типа данных столбца упорядочивания. Для числовых упорядочивающих столбцов это обычно тот же тип, что и у упорядочивающего столбца, но для упорядочивающих столбцов даты и времени это interval. Например, если столбец заказа имеет тип date или timestamp, можно написать RANGE BETWEEN '1 day' PRECEDING AND '10 days' FOLLOWING. смещение по-прежнему должно быть отличным от NULL и неотрицательным, хотя что считать «неотрицательным», зависит от его типа данных.

В любом случае расстояние до конца рамки ограничено расстоянием до конца партиции, поэтому для строк рядом с концом партиции рамка может содержать меньше строк, чем в других местах.

Обратите внимание, что в режимах ROWS и GROUPS указания 0 PRECEDING и 0 FOLLOWING равнозначны CURRENT ROW. Это обычно выполняется и в режиме RANGE для подходящего конкретному типу данных значения «нуля».

Параметр исключение_рамки позволяет исключить из рамки строки вокруг текущей, даже если они были бы включены в соответствии с определениями начала и конца рамки. EXCLUDE CURRENT ROW исключает из рамки текущую строку. EXCLUDE GROUP исключает из рамки текущую строку и одноранговые ей в порядке сортировки EXCLUDE TIES исключает из рамки все одноранговые строки для текущей, но не ее саму. EXCLUDE NO OTHERS просто явно указывает поведение по умолчанию — не исключать текущую или одноранговые ей строки.

Вариант определения рамки по умолчанию — RANGE UNBOUNDED PRECEDING, который равнозначен RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. С ORDER BY это задает рамку, включающую все строки от начала партиции до последней строки, одноранговой для текущей (согласно ORDER BY). Без ORDER BY это означает, что в рамку окна включены все строки партиции, поскольку все строки становятся одноранговыми для текущей.

Ограничения состоят в том, что начало_рамки не может быть UNBOUNDED FOLLOWING, конец_рамки не может быть UNBOUNDED PRECEDING и конец_рамки в приведенном выше списке указаний начала_рамки и конца_рамки не может идти перед началом_рамки — например, RANGE BETWEEN CURRENT ROW AND смещение PRECEDING не допускается. Но, например, ROWS BETWEEN 7 PRECEDING AND 8 PRECEDING допустимо, даже если оно никогда не выберет никакие строки.

Если указано предложение FILTER, то оконной функции передаются только те входные строки, для которых условие_фильтра вычисляется как истинное; другие строки отбрасываются. Предложение FILTER принимают только агрегирующие оконные функции.

Встроенные оконные функции описаны в таблице Оконные функции. Пользователь может добавить и другие оконные функции. Кроме того, в качестве оконной можно использовать любую встроенную или пользовательскую универсальную или статистическую агрегатную функцию. (Упорядочивающие и гипотезирующие агрегаты в настоящее время использовать в качестве оконных функций нельзя.)

Синтаксисы с * используются для вызова агрегатных функций без параметров в качестве оконных, например count(*) OVER (PARTITION BY x ORDER BY y). Для исключительно оконных функций звездочка (*) обычно не используется. Также исключительно оконные функции не позволяют использовать в списке аргументов предложения DISTINCT или ORDER BY.

Вызовы оконных функций разрешены только в списке SELECT и предложении ORDER BY запроса.

Более подробную информацию об оконных функциях можно также найти в разделе Оконные функции главы «Расширенная функциональность» и в подразделе Обработка оконных функций.

Приведение типов

Приведение типов определяет преобразование из одного типа данных в другой. QHB принимает два равнозначных синтаксиса для приведения типов:

CAST ( выражение AS тип )
выражение::тип

Синтаксис CAST соответствует SQL; синтаксис с :: является историческим наследием QHB.

Когда приведение применяется к выражению значения известного типа, оно представляет преобразование типа во время выполнения. Приведение будет успешным, только если определена подходящая операция преобразования типа. Обратите внимание, что это немного отличается от использования приведений с константами, описанных в разделе Константы других типов. Приведение, примененное к обычному строковому литералу, представляет собой начальное присвоение типа значению литеральной константы и поэтому выполнится успешно для любого типа (если содержимое строкового литерала имеет приемлемый входной синтаксис для этого типа данных).

Явное приведение типов обычно можно опустить, если нет неоднозначности относительно типа, который должно выдавать выражение значения (например, когда оно назначено столбцу таблицы); в таких случаях система автоматически применяет приведение типа. Однако автоматическое преобразование выполняется только для приведений, помеченных в системных каталогах как «OK to apply implicitly». Другие приведения следует вызывать с явным синтаксисом приведения. Это ограничение предназначено для того, чтобы предотвратить неожиданные неявные преобразования.

Также возможно указать приведение типа с использованием синтаксиса функции:

имя_типа (выражение)

Однако это работает только для типов, имена которых также допустимы в качестве имен функций. Например, double precision таким образом использовать нельзя, а равнозначный float8 — можно. Кроме того, имена interval, time и timestamp из-за синтаксических конфликтов можно использовать таким образом, только если они заключены в кавычки. Таким образом, использование синтаксиса приведений в форме вызова функции приводит к несоответствиям, и его, вероятно, стоит избегать.

Примечание
Приведение типа с синтаксисом функции на самом деле является просто вызовом функции. Когда для выполнения преобразования во время выполнения используется один из двух стандартных синтаксисов приведения, он будет внутренне вызывать зарегистрированную функцию, выполняющую преобразование. По соглашению эти функции преобразования имеют имя своего выходного типа, и поэтому «синтаксис функции» является не чем иным, как прямым вызовом нижележащей функции преобразования. Очевидно, что портативным приложениям на это полагаться не следует. Дополнительную информации см. на справочной странице команды CREATE CAST.

Выражения правил сортировки

Предложение COLLATE переопределяет правило сортировки выражения. Оно добавляется после выражения, к которому относится:

выражение COLLATE правило_сортировки

где правило_сортировки — это имя идентификатора, которое может быть дополнено схемой. Предложение COLLATE связывает крепче, чем операторы; при необходимости можно использовать круглые скобки.

Если правило сортировки не указано явно, СУБД либо получит его из столбцов, используемых в выражении, либо (если в выражении нет столбцов) использует правило сортировки базы данных по умолчанию.

Два типовых варианта использования предложения COLLATE: переопределение порядка сортировки в предложении ORDER BY, например:

SELECT a, b, c FROM tbl WHERE ... ORDER BY a COLLATE "C";

и переопределение правила сортировки при вызове функции или оператора, результаты которых зависят от локали, например:

SELECT * FROM tbl WHERE a > 'foo' COLLATE "C";

Обратите внимание, что в последнем случае предложение COLLATE добавляется после входного аргумента оператора, на который необходимо повлиять. Неважно, к какому аргументу при вызове оператора или функции присоединяется предложение COLLATE, поскольку правило сортировки, применяемое оператором или функцией, выводится с учетом всех аргументов, и явное предложение COLLATE будет переопределять правила сортировки всех остальных аргументов. (Однако присоединение несоответствующих предложений COLLATE к более чем одному аргументу является ошибкой. Более подробную информацию см. в разделе Поддержка правил сортировки.) Таким образом, это дает тот же результат, что и в предыдущем примере:

SELECT * FROM tbl WHERE a COLLATE "C" > 'foo';

Но этот вариант будет ошибкой:

SELECT * FROM tbl WHERE (a > 'foo') COLLATE "C";

потому что он пытается применить правило сортировки к результату оператора >, который имеет несопоставляемый тип данных boolean.

Скалярные подзапросы

Скалярный подзапрос — это обычный запрос SELECT в круглых скобках, который возвращает ровно одну строку с одним столбцом. (Информацию о написании запросов см. в главе Запросы.) Запрос SELECT выполняется, и единственное возвращаемое значение используется в окружающем его выражении значения. Ошибочно использовать в качестве скалярного подзапроса запрос, возвращающий более одной строки или одного столбца. (Но если во время конкретного выполнения подзапрос не возвращает строк, ошибки нет; скалярный результат считается равным NULL.) Подзапрос может ссылаться на переменные из окружающего запроса, которые будут действовать как константы во время любого разового вызова подзапроса. Другие выражения, включающие подзапросы, рассматриваются в разделе Выражения подзапросов.

Например, следующий запрос находит самый населенный город в каждом штате:

SELECT name, (SELECT max(pop) FROM cities WHERE cities.state = states.name)
    FROM states;

Конструкторы массивов

Конструктор массива — это выражение, которое создает значение массива, используя значения для его элементов-членов. Простой конструктор массива состоит из ключевого слова ARRAY, левой квадратной скобки [, списка выражений (разделенных запятыми) для значений элементов массива и в конце правой квадратной скобки ]. Например:

SELECT ARRAY[1,2,3+4];
  array
---------
 {1,2,7}
(1 row)

По умолчанию тип элемента массива является общим типом выражений-членов, определенный с использованием тех же правил, что и для конструкций UNION или CASE (см. раздел UNION, CASE и связанные конструкции). Вы можете переопределить это, явно приведя конструктор массива к желаемому типу, например:

SELECT ARRAY[1,2,22.7]::integer[];
  array
----------
 {1,2,23}
(1 row)

Это имеет тот же эффект, что и приведение каждого выражения к типу элемента массива по отдельности. Более подробную информацию о приведении см. в подразделе Приведение типов.

Значения многомерного массива можно построить с помощью вложенных конструкторов массива. Во внутренних конструкторах ключевое слово ARRAY можно опустить. Например, следующие варианты дают один и тот же результат:

SELECT ARRAY[ARRAY[1,2], ARRAY[3,4]];
     array
---------------
 {{1,2},{3,4}}
(1 row)

SELECT ARRAY[[1,2],[3,4]];
     array
---------------
 {{1,2},{3,4}}
(1 row)

Поскольку многомерные массивы должны быть прямоугольными, внутренние конструкторы на одном уровне должны создавать вложенные массивы одинаковых размеров. Любое приведение, примененное к внешнему конструктору ARRAY, автоматически распространяется на все внутренние конструкторы.

Элементы конструктора многомерного массива могут быть чем угодно, выдающим массив правильного вида, а не только вложенной конструкцией ARRAY. Например:

CREATE TABLE arr(f1 int[], f2 int[]);

INSERT INTO arr VALUES (ARRAY[[1,2],[3,4]], ARRAY[[5,6],[7,8]]);

SELECT ARRAY[f1, f2, '{{9,10},{11,12}}'::int[]] FROM arr;
                     array
------------------------------------------------
 {{{1,2},{3,4}},{{5,6},{7,8}},{{9,10},{11,12}}}
(1 row)

Вы можете создать и пустой массив, но так как невозможно иметь массив без типа, этот пустой массив следует явно привести к желаемому типу. Например:

SELECT ARRAY[]::integer[];
 array
-------
 {}
(1 row)

Также возможно построить массив из результатов подзапроса. В этой форме конструктор массива записывается с ключевым словом ARRAY, за которым следует подзапрос в круглых (а не квадратных) скобках. Например:

SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%');
                                 array
-----------------------------------------------------------------------
 {2011,1954,1948,1952,1951,1244,1950,2005,1949,1953,2006,31,2412,2413}
(1 row)

SELECT ARRAY(SELECT ARRAY[i, i*2] FROM generate_series(1,5) AS a(i));
              array
----------------------------------
 {{1,2},{2,4},{3,6},{4,8},{5,10}}
(1 row)

Подзапрос должен возвращать один столбец. Если выходной столбец подзапроса не является типом массива, результирующий одномерный массив будет иметь элемент для каждой строки в результате подзапроса, причем тип элемента соответствует типу выходного столбца подзапроса. Если выходной столбец подзапроса имеет тип массива, результатом будет массив того же типа, но на одну размерность больше; в этом случае все строки подзапроса должны выдавать массивы одинаковой размерности, иначе результат не будет прямоугольным.

Индексы значения массива, построенного с помощью ARRAY, всегда начинаются с единицы. Дополнительную информацию о массивах см. в разделе Массивы.

Конструкторы строк

Конструктор строки — это выражение, которое создает значение строки (также называемое составным значением), используя значения для его полей-членов. Конструктор строки состоит из ключевого слова ROW, левой круглой скобки, нуля или более выражений (разделенных запятыми) для значений поля строки и в конце правой круглой скобки. Например:

SELECT ROW(1,2.5,'this is a test');

Если в списке более одного выражения, ключевое слово ROW необязательно.

Конструктор строки может включать синтаксис значение_строки.*, который будет разворачиваться в список элементов значения строки, как это происходит, когда синтаксис .* используется на верхнем уровне списка SELECT (см. подраздел Использование составных типов в запросах). Например, если таблица t имеет столбцы f1 и f2, следующие записи выдают идентичные результаты:

SELECT ROW(t.*, 42) FROM t;
SELECT ROW(t.f1, t.f2, 42) FROM t;

По умолчанию значение, созданное выражением ROW, относится к анонимному типу записи. При необходимости его можно привести к именованному составному типу — либо к типу строки таблицы, либо к составному типу, созданному с помощью CREATE TYPE AS. Чтобы избежать двусмысленности, может потребоваться явное приведение. Например:

CREATE TABLE mytable(f1 int, f2 float, f3 text);

CREATE FUNCTION getf1(mytable) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;

-- В приведении нет необходимости, поскольку существует только getf1()
SELECT getf1(ROW(1,2.5,'this is a test'));
 getf1
-------
     1
(1 row)

CREATE TYPE myrowtype AS (f1 int, f2 text, f3 numeric);

CREATE FUNCTION getf1(myrowtype) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;

-- Теперь приведение необходимо, чтобы указать, какую функцию вызвать:
SELECT getf1(ROW(1,2.5,'this is a test'));
ERROR:  function getf1(record) is not unique
-- ОШИБКА: функция getf1(record) не уникальна

SELECT getf1(ROW(1,2.5,'this is a test')::mytable);
 getf1
-------
     1
(1 row)

SELECT getf1(CAST(ROW(11,'this is a test',2.5) AS myrowtype));
 getf1
-------
    11
(1 row)

Конструкторы строк можно использовать для построения составных значений, которые будут храниться в столбце таблицы составного типа или передаваться в функцию, принимающую составной параметр. Кроме того, можно сравнить два значения строки или проверить строку с помощью IS NULL или IS NOT NULL, например:

SELECT ROW(1,2.5,'this is a test') = ROW(1, 3, 'not the same');

SELECT ROW(table.*) IS NULL FROM table;  -- выбрать строки, содержащие только NULL

Подробную информацию см. в разделе Сравнение строк и массивов. Кроме того, конструкторы строк можно использовать в связке с подзапросами, что рассматривается в разделе Выражения подзапросов.

Правила вычисления выражений

Порядок вычисления подвыражений не определен. В частности, входные данные оператора или функции не обязательно вычисляются слева направо или в любом другом фиксированном порядке.

Кроме того, если результат выражения можно определить путем вычисления только некоторых его частей, то другие подвыражения могут вообще не вычисляться. Например, в выражении:

SELECT true OR somefunc();

функция somefunc() (вероятно) не будет вызвана вообще. То же самое будет, если написать:

SELECT somefunc() OR true;

Обратите внимание, что это не то же самое, что «оптимизация» логических операторов слева направо, встречающаяся в некоторых языках программирования.

Как следствие, неразумно использовать в сложных выражениях функции с побочными эффектами. Особенно опасно полагаться на побочные эффекты или порядок вычисления в предложениях WHERE и HAVING, поскольку эти предложения тщательно перерабатываются при построении плана выполнения. Логические выражения (комбинации AND/OR/NOT) в этих предложениях могут быть реорганизованы любым способом, разрешенным законами алгебры логики.

Когда необходимо принудительно задать порядок вычислений, можно воспользоваться конструкцией CASE (см. раздел Условные выражения). Например, это ненадежный способ попытаться избежать деления на ноль в предложении WHERE:

SELECT ... WHERE x > 0 AND y/x > 1.5;

А это безопасный:

SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;

Используемая таким образом конструкция CASE будет препятствовать попыткам оптимизации, так что это следует делать только в случае необходимости. (В этом конкретном примере было бы лучше обойти проблему, написав y > 1.5*x.)

Однако CASE — не панацея от подобных проблем. Одним из ограничений показанного выше метода является то, что он не препятствует раннему вычислению константных подвыражений. Как описано в разделе Категории изменчивости функций, функции и операторы, помеченные как IMMUTABLE, могут быть вычислены при планировании запроса, а не при его выполнении. Так например:

SELECT CASE WHEN x > 0 THEN x ELSE 1/0 END FROM tab;

скорее всего, приведет к ошибке деления на ноль из-за того, что планировщик пытается упростить константное подвыражение, даже если в каждой строке таблицы есть x > 0 поэтому ветвь ELSE никогда не будет обработана во время выполнения.

Хотя этот конкретный пример может показаться глупым, в запросах, выполняемых внутри функций, могут возникать связанные ситуации, когда константы включены неявно, поскольку значения аргументов функции и локальных переменных могут быть вставлены в запросы в качестве констант для целей планирования. Например, в функциях PL/pgSQL использование оператора IF-THEN-ELSE для защиты рискованных вычислений намного безопаснее, чем просто вложить ее в выражение CASE.

Другое ограничение того же типа состоит в том, что CASE не может предотвратить вычисление содержащегося в нем агрегатного выражения, потому что агрегатные выражения вычисляются раньше, чем рассматриваются другие выражения в списке SELECT или предложении HAVING. Например, следующий запрос может вызвать ошибку деления на ноль, несмотря на то, что он вроде бы от нее защищен:

SELECT CASE WHEN min(employees) > 0
            THEN avg(expenses / employees)
       END
    FROM departments;

Агрегаты min() и avg() вычисляются одновременно по всем входным строкам, поэтому если любая строка имеет поле employees, равное нулю, то ошибка деления на ноль будет происходить до того, как появится возможность проверить результат min(). Вместо этого, чтобы предотвратить попадание проблемных входных строк в агрегатную функцию, воспользуйтесь предложением WHERE или FILTER.

Вызов функции

QHB позволяет вызывать функции с именованными параметрами с использованием позиционной или именованной нотации. Именованная нотация особенно полезна для функций с большим числом параметров, поскольку она делает связи между параметрами и фактическими аргументами более явными и надежными. В позиционной нотации вызов функции записывается со значениями аргументов в том же порядке, в котором они определены в объявлении функции. В именованной нотации аргументы сопоставляются с параметрами функции по имени и могут быть записаны в любом порядке. Для каждого варианта вызова также следует учитывать влияние типов аргументов функций, описанное в разделе Функции.

В любой нотации заданные в объявлении функции параметры, имеющие значения по умолчанию, вообще не должны записываться в вызове. Но это особенно полезно в именованной нотации, поскольку можно опустить любое сочетание параметров; тогда как в позиционной нотации параметры можно опустить только справа налево.

Кроме того, QHB поддерживает смешанную нотацию, объединяющую позиционную и именованную нотации. В этом случае позиционные параметры записываются первыми, а именованные добавляются после них.

Следующие примеры проиллюстрируют использование всех трех нотаций, используя это определение функции:

CREATE FUNCTION concat_lower_or_upper(a text, b text, uppercase boolean DEFAULT false)
RETURNS text
AS
$$
 SELECT CASE
        WHEN $3 THEN UPPER($1 || ' ' || $2)
        ELSE LOWER($1 || ' ' || $2)
        END;
$$
LANGUAGE SQL IMMUTABLE STRICT;

Функция concat_lower_or_upper имеет два обязательных параметра, a и b. Кроме того, есть один необязательный параметр uppercase, который по умолчанию равен false. Входные значения a и b будут конкатенированы и переведены в верхний или нижний регистр в зависимости от параметра uppercase. Остальные детали этого определения функции здесь не важны (дополнительную информацию см. в главе Расширение SQL).

Использование позиционной нотации

Позиционная нотация — это традиционный механизм передачи аргументов функциям в QHB. Пример:

SELECT concat_lower_or_upper('Hello', 'World', true);
 concat_lower_or_upper
-----------------------
 HELLO WORLD
(1 row)

Все аргументы указаны по порядку. Результат — верхний регистр, поскольку uppercase задан как true. Другой пример:

SELECT concat_lower_or_upper('Hello', 'World');
 concat_lower_or_upper
-----------------------
 hello world
(1 row)

Здесь параметр uppercase опущен, поэтому он получает значение по умолчанию false, что приводит к выводу в нижнем регистре. В позиционной нотации аргументы, имеющие значения по умолчанию, можно опустить справа налево.

Использование именованной нотации

В именованной нотации имя каждого аргумента указывается с помощью =>, чтобы отделить его от выражения аргумента. Например:

SELECT concat_lower_or_upper(a => 'Hello', b => 'World');
 concat_lower_or_upper
-----------------------
 hello world
(1 row)

Опять же, аргумент uppercase был опущен, поэтому он неявно установлен в значение false. Одно из преимуществ использования именованной нотации заключается в том, что аргументы можно указать в любом порядке, например:

SELECT concat_lower_or_upper(a => 'Hello', b => 'World', uppercase => true);
 concat_lower_or_upper
-----------------------
 HELLO WORLD
(1 row)

SELECT concat_lower_or_upper(a => 'Hello', uppercase => true, b => 'World');
 concat_lower_or_upper
-----------------------
 HELLO WORLD
(1 row)

В целях обратной совместимости поддерживается более старый синтаксис, основанный на ":=":

SELECT concat_lower_or_upper(a := 'Hello', uppercase := true, b := 'World');
 concat_lower_or_upper
-----------------------
 HELLO WORLD
(1 row)

Использование смешанной нотации

Смешанная нотация объединяет позиционную и именованную нотации. Однако, как уже упоминалось, именованные аргументы не могут предшествовать позиционным. Например:

SELECT concat_lower_or_upper('Hello', 'World', uppercase => true);
 concat_lower_or_upper
-----------------------
 HELLO WORLD
(1 row)

В приведенном выше запросе аргументы a и b указываются позиционно, а uppercase — по имени. В данном примере это мало что добавляет, кроме более удобного оформления. С более сложной функцией, имеющей множество параметров со значениями по умолчанию, именованная или смешанная нотация может сэкономить много времени на написание и уменьшить вероятность ошибок.

Примечание
Именованные и смешанные нотации вызовов в настоящее время нельзя использовать при вызове агрегатной функции (но они работают, когда агрегатная функция используется в качестве оконной).

Определение данных

В этой главе описывается, как создавать структуры базы данных, в которых будут храниться данные. В реляционной базе данных необработанные данные хранятся в таблицах, поэтому большая часть этой главы посвящена объяснению того, как создаются и изменяются таблицы и какие функциональные возможности доступны для управления данными, хранящимися в этих таблицах. Затем мы обсудим, как таблицы могут быть собраны в схемы и как могут быть назначены права для таблиц. Наконец, мы кратко рассмотрим другие функциональные возможности, влияющие на хранение данных, такие как наследование, партиционирование таблиц, представления, функции и триггеры.

Базовая информация по таблицам

Таблица в реляционной базе данных очень похожа на таблицу на бумаге: она состоит из строк и столбцов. Количество и порядок столбцов фиксированы, и у каждого столбца есть имя. Количество строк переменно — оно отражает, сколько данных хранится в данный момент. SQL не дает никаких гарантий относительно порядка строк в таблице. Когда таблица прочитывается, строки появятся в произвольном порядке, если только сортировка не задана явно. Подробнее это освещается в главе Запросы. Кроме того, SQL не присваивает строкам уникальные идентификаторы, поэтому в таблице может быть несколько полностью идентичных строк. Это следствие математической модели, лежащей в основе реляционных СУБД, но обычно такое дублирование нежелательно. Далее в этой главе мы увидим, как бороться с этой проблемой.

Каждый столбец имеет тип данных. Тип данных ограничивает набор возможных значений, которые могут быть назначены столбцу, и определяет семантику хранящихся в столбце данных, чтобы их можно было использовать для вычислений. Например, столбец, объявленный как имеющий числовой тип, не будет принимать произвольные текстовые строки, а данные, хранящиеся в таком столбце, можно использовать для математических вычислений. И наоборот, столбец с типом символьной строки будет принимать практически любые данные, но не подлежит математическим вычислениям, хотя другие операции, например, конкатенация строк, для него доступны.

QHB включает в себя значительный набор встроенных типов данных, которые подходят для многих приложений. Кроме того, пользователи могут определять свои собственные типы данных. Большинство встроенных типов данных имеет очевидные имена и семантику, поэтому подробное их рассмотрение отложим до главы Типы данных. Некоторые из часто используемых типов данных: integer для целых чисел, numeric для возможно дробных чисел, text для символьных строк, date для дат, time для значений времени суток и timestamp для значений, содержащих и дату, и время.

Для создания таблицы используется, соответственно, команда CREATE TABLE. В этой команде следует указать как минимум имя новой таблицы, имена столбцов и тип данных каждого столбца. Например:

CREATE TABLE my_first_table (
    first_column text,
    second_column integer
);

Это создает таблицу с именем my_first_table с двумя столбцами. Первый столбец называется first_column и имеет тип данных text, второй — second_column с типом integer. Имена таблиц и столбцов соответствуют синтаксису идентификатора, описанному в подразделе Идентификаторы и ключевые слова. Имена типов обычно также являются идентификаторами, но есть некоторые исключения. Обратите внимание, что список столбцов разделен запятыми и заключен в скобки.

Конечно, предыдущий пример был крайне надуманным. Обычно таблицам и столбцам дают имена, отражающие вид данных, которые они хранят. Итак, давайте взглянем на более реалистичный пример:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric
);

(тип numeric может хранить дробные компоненты, что типично для денежных сумм).

Совет
Когда создается много взаимосвязанных таблиц, разумно выбрать единый шаблон именования таблиц и столбцов. Например, можно решить, использовать ли для имен таблиц существительные в единственном или множественном числе (у обоих вариантов есть свои сторонники).

Существует ограничение на количество столбцов в таблице. В зависимости от типов столбцов оно колеблется от 250 до 1600. Однако определение таблицы с таким количеством столбцов весьма необычно и часто бывает признаком сомнительного дизайна.

Если таблица больше не нужна, ее можно удалить с помощью команды DROP TABLE. Например:

DROP TABLE my_first_table;
DROP TABLE products;

Попытка удалить несуществующую таблицу является ошибкой. Тем не менее в файлах скриптов SQL принято удалять каждую таблицу перед ее созданием, игнорируя любые сообщения об ошибках, чтобы скрипт работал независимо от того, существует таблица или нет. (При необходимости можно воспользоваться вариантом DROP TABLE IF EXISTS, чтобы избежать сообщений об ошибках, но это не стандартный SQL).

Если нужно изменить таблицу, которая уже существует, это рассматривается далее в этой главе в разделе Изменение таблиц.

Используя описанные выше инструменты, можно создавать полностью функциональные таблицы. Остальная часть этой главы посвящена дополнительным возможностям в определении таблицы, обеспечивающим целостность, безопасность или удобство. Если вы жаждете заполнить ваши таблицы данными прямо сейчас, можете сразу перейти к главе Манипулирование данными и прочитать остальную часть этой главы позже.

Значения по умолчанию

Столбцу может быть назначено значение по умолчанию. Когда создается новая строка и для некоторых столбцов не указаны значения, эти столбцы будут заполнены соответствующими значениями по умолчанию. Команда манипулирования данными тоже может явно запросить, чтобы для столбца было установлено значение по умолчанию, и ей необязательно знать, что это за значение. (Подробнее команды манипулирования данными рассматриваются в главе Манипулирование данными.

Если значение по умолчанию не объявлено явно, им является значение NULL. Обычно это имеет смысл, поскольку значение NULL может рассматриваться как представляющее неизвестные данные.

В определении таблицы значения по умолчанию перечисляются после типа данных столбца. Например:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric DEFAULT 9.99
);

Значением по умолчанию может быть выражение, которое будет вычисляться всякий раз, когда вставляется значение по умолчанию (а не при создании таблицы). Типичный пример: для столбца timestamp по умолчанию задано значение CURRENT_TIMESTAMP, поэтому для него устанавливается время добавления строки. Другим распространенным примером является генерирование «серийного номера» для каждой строки. В QHB это обычно делается примерно так:

CREATE TABLE products (
    product_no integer DEFAULT nextval('products_product_no_seq'),
    ...
);

где функция nextval() предоставляет последовательные значения из объекта последовательности (см. раздел Функции для управления последовательностями). Такое объявление достаточно распространено, поэтому для него есть специальное сокращение:

CREATE TABLE products (
    product_no SERIAL,
    ...
);

Сокращение SERIAL рассматривается в подразделе Последовательные типы.

Генерируемые столбцы

Генерируемый столбец — это специальный столбец, который всегда вычисляется из других столбцов. Таким образом, для столбцов это то же самое, что представление для таблиц. Существует два вида генерируемых столбцов: хранимые и виртуальные. Хранимый генерируемый столбец вычисляется, когда он записывается (вставляется или обновляется) и занимает пространство в хранилище, как если бы это был обычный столбец. Виртуальный генерируемый столбец не занимает места в хранилище и вычисляется при чтении. Таким образом, виртуальный генерируемый столбец похож на представление, а хранимый — на материализованное представление (за исключением того, что он всегда обновляется автоматически). В настоящее время QHB реализует только хранимые генерируемые столбцы.

Чтобы создать генерируемый столбец, используйте предложение GENERATED ALWAYS AS в команде CREATE TABLE, например:

CREATE TABLE people (
    ...,
    height_cm numeric,
    height_in numeric GENERATED ALWAYS AS (height_cm / 2.54) STORED
);

Чтобы выбрать хранимый вариант генерируемого столбца, следует указать ключевое слово STORED. Более подробную информацию см. на справочной странице команды CREATE TABLE.

Генерируемый столбец нельзя записать напрямую. В командах INSERT или UPDATE для генерируемого столбца нельзя указать значение, но можно указать ключевое слово DEFAULT.

Рассмотрим различия между столбцом со значением по умолчанию и генерируемым столбцом. Значение столбца по умолчанию вычисляется один раз при первом добавлении строки, если не было предоставлено никакого другого значения; генерируемый столбец обновляется при каждом изменении строки и не может быть переопределен. Значение столбца по умолчанию может не ссылаться на другие столбцы таблицы; генерирующее выражение обычно имеет такие ссылки. В значении по умолчанию могут использоваться изменчивые функции, например random(), или функции, относящиеся к текущему времени; для генерируемых столбцов это не допускается.

К определению генерируемых столбцов и таблиц, включающих генерируемые столбцы, применяется несколько ограничений:

В генерирующем выражении могут вызываться только постоянные функции и никак не могут использоваться подзапросы или ссылки на что-либо, кроме текущей строки.

Генерирующее выражение не может ссылаться на другой сгенерированный столбец.

Генерирующее выражение не может ссылаться на системный столбец, за исключением tableoid.

Генерируемый столбец не может иметь значение по умолчанию или определение идентификации.

Генерируемый столбец не может быть частью ключа разбиения.

Сторонние таблицы могут иметь сгенерированные столбцы. Подробную информацию см. на справочной странице команды CREATE FOREIGN TABLE.

Применительно к наследованию:

Если родительский столбец является генерируемым столбцом, дочерний столбец тоже должен быть генерируемым, причем тем же генерирующим выражением. В определении дочернего столбца следует опустить предложение GENERATED, поскольку оно будет скопировано из родительского.

В случае множественного наследования, если один родительский столбец является генерируемым, то все родительские столбцы должны быть генерируемыми, причем тем же генерирующим выражением.

Если родительский столбец не является генерируемым, дочерний столбец может быть определен и как генерируемый, и как обычный.

Дополнительные ограничения относятся к использованию генерируемых столбцов.

Генерируемые столбцы хранят права доступа отдельно от прав нижележащих базовых столбцов. Таким образом, можно расположить их так, чтобы определенная роль могла читать данные из генерируемого столбца, но не из нижележащих базовых столбцов.

Согласно концепции, генерируемые столбцы обновляются после запуска триггеров BEFORE. Поэтому изменения, внесенные в базовые столбцы в триггере BEFORE, будут отражены в генерируемых столбцах. Однако обращение к генерируемым столбцам в триггерах BEFORE, наоборот, запрещено.

Ограничения

Типы данных представляют собой способ ограничения возможных значений данных, которые могут храниться в таблице. Однако для многих приложений предоставляемое ими ограничение слишком грубое. Например, столбец, содержащий цену товара, вероятно, должен принимать только положительные значения. Но стандартного типа данных, который принимает только положительные числа, нет. Другая проблема заключается в том, что может потребоваться ограничить данные столбца относительно других столбцов или строк. Например, в таблице, содержащей информацию о товаре, должна быть только одна строка для каждого кода товара.

Исходя из этого, SQL позволяет определять ограничения для столбцов и таблиц. Ограничения дают возможность сколько угодно управлять данными в таблицах. Если пользователь пытается сохранить в столбце данные, нарушающие ограничение, возникает ошибка. Это происходит, даже если значение пришло из определения значения по умолчанию.

Проверочные ограничения

Проверочное ограничение является наиболее общим типом ограничений. Оно позволяет указать, что значение в определенном столбце должно удовлетворять логическому выражению (значению истинности). Например, чтобы предписать только положительные цены на товары, можно использовать:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0)
);

Как можно видеть, определение ограничения идет после типа данных, аналогично определениям значений по умолчанию. Значения по умолчанию и ограничения можно перечислить в любом порядке. Проверочное ограничение состоит из ключевого слова CHECK, за которым следует выражение в скобках. Выражение проверочного ограничения должно включать столбец, для которого задается выражение, иначе в ограничении не будет особого смысла.

Также можно задать ограничению отдельное имя. Это уточняет сообщения об ошибках и позволяет ссылаться на ограничение, когда нужно его изменить. Синтаксис при этом следующий:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CONSTRAINT positive_price CHECK (price > 0)
);

Итак, чтобы указать именованное ограничение, используйте ключевое слово CONSTRAINT, за которым следует идентификатор, а затем — определение ограничения. (Если не указать имя ограничения таким образом, система выберет имя за вас.)

Также проверочное ограничение может ссылаться на несколько столбцов. Допустим, вы храните обычную цену и цену со скидкой и хотите, чтобы цена со скидкой была ниже обычной цены:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0),
    discounted_price numeric CHECK (discounted_price > 0),
    CHECK (price > discounted_price)
);

Первые два ограничения должны выглядеть знакомо. В третьем используется новый синтаксис. Он не привязан к определенному столбцу, а отображается как отдельный элемент в списке столбцов, разделенных запятыми. Определения столбцов и эти определения ограничений можно перечислить в смешанном порядке.

Можно сказать, что первые два ограничения являются ограничениями столбца, тогда как третье ограничение является ограничением таблицы, потому что оно написано отдельно от любого определения одного столбца. Ограничения столбца тоже можно записать как ограничения таблицы, а вот обратное не всегда возможно, поскольку предполагается, что ограничение столбца относится только к тому столбцу, к которому оно прикреплено. (QHB не применяет это правило, но вы должны следовать ему, если хотите, чтобы определения таблиц работали с другими системами баз данных). Приведенный выше пример можно записать и так:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric,
    CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0),
    CHECK (price > discounted_price)
);

или даже:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0 AND price > discounted_price)
);

Это дело вкуса.

Ограничениям таблицы, как и ограничениям столбцов, можно назначить имена:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric,
    CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0),
    CONSTRAINT valid_discount CHECK (price > discounted_price)
);

Важно отметить, что проверочное ограничение удовлетворяется, если проверочное выражение имеет значение true или NULL. Поскольку большинство выражений будут иметь значение NULL, если любой из операндов равен NULL, они не будут препятствовать записи значений NULL в ограниченные столбцы. Чтобы гарантировать, что столбец не содержит значений NULL, можно воспользоваться ограничением NOT NULL, описанным в следующем разделе.

Примечание
QHB не поддерживает ограничения CHECK, ссылающиеся на данные таблицы, кроме проверки новой или измененной строки. Хотя проверочное ограничение нарушающее это правило, может показаться работающим в простых тестах, это не гарантирует, что база данных не достигнет состояния, в котором условие ограничения ложно (из-за последующих изменений другой строки (строк)). Это может привести к сбою резервного копирования и перезагрузке базы данных. Сбой при перезагрузке может произойти, даже если полное состояние базы данных согласуется с ограничением, из-за того, что строки загружаются не в том порядке, который будет удовлетворять этому ограничению. Для выражения ограничений между строками и между таблицами, по возможности, используйте ограничения UNIQUE, EXCLUDE или FOREIGN KEY.

Если требуется одноразовая проверка относительно других строк при добавлении строки, а не постоянно поддерживаемая гарантия согласованности, для ее реализации можно использовать пользовательский триггер. (Этот подход позволяет избежать проблемы выгрузки/перезагрузки, поскольку qhb_dump переустанавливает триггеры уже после перезагрузки данных, поэтому во время выполнения выгрузки/перезагрузки проверка выполняться не будет.)

Примечание
QHB предполагает, что условия ограничений CHECK неизменны, то есть они всегда будут давать один и тот же результат для одной и той же входной строки. Это предположение оправдывает выполнение проверки на это ограничение только при добавлении или изменении строки, но не в других случаях. (Вышеприведенное предупреждение о том, что не следует ссылаться на другие данные таблицы является частным случаем этого ограничения).

Примером распространенного способа нарушить это предположение является ссылка на пользовательскую функцию в выражении CHECK, а затем изменение поведения этой функции. QHB не запрещает этого, но не заметит, есть ли в таблице строки, которые теперь нарушают ограничение CHECK. Это может привести к сбою последующего резервного копирования и перезагрузки базы данных. Рекомендуемый способ проведения такого изменения состоит в том, чтобы удалить ограничение (командой ALTER TABLE), скорректировать определение функции и повторно добавить ограничение, тем самым перепроверив его для всех строк таблицы.

Ограничения NOT NULL

Ограничение NOT NULL просто указывает, что столбец не должен принимать значение NULL. Пример синтаксиса:

CREATE TABLE products (
    product_no integer NOT NULL,
    name text NOT NULL,
    price numeric
);

Ограничение NOT NULL всегда записывается как ограничение столбца. Ограничение NOT NULL функционально равнозначно созданию проверочного ограничения CHECK (имя_столбца IS NOT NULL), но в QHB создание явного ограничения NOT NULL более эффективно. Недостатком является то, что дать явные имена ограничениям NOT NULL, созданным таким образом, невозможно.

Столбец может иметь более одного ограничения. Просто напишите ограничения одно за другим:

CREATE TABLE products (
    product_no integer NOT NULL,
    name text NOT NULL,
    price numeric NOT NULL CHECK (price > 0)
);

Порядок не имеет значения — он никак не влияет на то, в каком порядке будут проверяться ограничения.

Ограничение NOT NULL имеет обратное ограничение NULL. Это не означает, что столбец должен иметь значение NULL — в таком случае он бесполезен. Вместо этого просто выбирается поведение по умолчанию, согласно которому столбец может иметь значение NULL. Ограничение NULL отсутствует в стандарте SQL и не должно использоваться в переносимых приложениях. (Оно было добавлено только в QHB для совместимости с некоторыми другими СУБД.) Однако некоторым пользователям оно нравится, так как позволяет легко переключать ограничение в файле скрипт. Например, можно начать с:

CREATE TABLE products (
    product_no integer NULL,
    name text NULL,
    price numeric NULL
);

а затем добавить ключевое слово NOT, где это необходимо.

Совет
В основном в базах данных при проектировании большинство столбцов должно быть помечено как NOT NULL.

Ограничения уникальности

Ограничения уникальности гарантируют, что данные, содержащиеся в столбце или группе столбцов, являются уникальными среди всех строк в таблице. Синтаксис следующий:

CREATE TABLE products (
    product_no integer UNIQUE,
    name text,
    price numeric
);

когда оно задается как ограничение столбца, и такой:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric,
    UNIQUE (product_no)
);

когда оно задается как ограничение таблицы.

Чтобы определить ограничение уникальности для группы столбцов, запишите его как ограничение таблицы с именами столбцов, разделенными запятыми:

CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    UNIQUE (a, c)
);

Это указывает на то, что комбинация значений в указанных столбцах уникальна для всей таблицы, хотя любой отдельный столбец не должен быть (и обычно не является) уникальным.

Ограничению уникальности можно назначить собственное имя обычным способом:

CREATE TABLE products (
    product_no integer CONSTRAINT must_be_different UNIQUE,
    name text,
    price numeric
);

Добавление ограничения уникальности автоматически создаст уникальный индекс B-дерево для столбца или группы столбцов, перечисленных в этом ограничении. Условие уникальности, охватывающее только некоторые строки, нельзя записать как ограничение уникальности, но можно обеспечить такое условие, создав уникальный частичный индекс.

Как правило, ограничения уникальности нарушается, если в таблице имеется более одной строки, в которой значения всех входящих в ограничение столбцов равны. Однако в этом сравнении никогда не считаются равными два значения NULL. Это означает, что даже при наличии уникального ограничения можно хранить повторяющиеся строки, содержащие значение NULL как минимум в одном из ограниченных столбцов. Такое поведение соответствует стандарту SQL, но другие базы данных SQL могут не следовать этому правилу. Поэтому будьте осторожны при разработке переносимых приложений.

Ограничение первичного ключа

Ограничение первичного ключа указывает, что столбец или группа столбцов могут использоваться в качестве уникального идентификатора для строк в таблице. Необходимо, чтобы значения были уникальными и не равными NULL. Так что следующие два определения таблицы принимают одинаковые данные:

CREATE TABLE products (
    product_no integer UNIQUE NOT NULL,
    name text,
    price numeric
);

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

Первичные ключи могут занимать более одного столбца; в этом случае синтаксис похож на таковой ограничений уникальности:

CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    PRIMARY KEY (a, c)
);

Добавление первичного ключа автоматически создаст уникальный индекс B-дерево для столбца или группы столбцов, перечисленных в первичном ключе, и ставит на столбец (или столбцы) метку NOT NULL.

Таблица не может иметь более одного первичного ключа. (Может быть сколько угодно уникальных ограничений и ограничений NOT NULL, которые функционально почти одинаковы, но идентифицировать как первичный ключ можно только одно.) Теория реляционных баз данных требует, чтобы у каждой таблицы был первичный ключ. QHB не настаивает на применении этого правила, но обычно лучше его соблюдать.

Первичные ключи полезны как для целей документирования, так и для клиентских приложений. Например, приложение с графическим интерфейсом, позволяющее изменять значения строк, скорее всего должно знать первичный ключ таблицы, чтобы иметь возможность уникально идентифицировать строки. У СУБД существуют различные способы использования первичного ключа, если он был объявлен; например, первичный ключ определяет целевой столбец (или столбцы) по умолчанию для внешних ключей, ссылающихся на его таблицу.

Ограничение внешнего ключа

Ограничение внешнего ключа указывает, что значения в столбце или группе столбцов должны соответствовать значениям, появляющимся в некоторой строке другой таблицы. Это ограничение поддерживает ссылочную целостность между двумя связанными таблицами.

Допустим, у вас есть таблица товаров, которую мы уже использовали несколько раз:

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

Предположим также, что у вас есть таблица с заказами этих товаров. Мы хотим убедиться, что таблица заказов содержит заказы только тех товаров, которые действительно существуют. Для этого мы определяем ограничение внешнего ключа в таблице заказов, ссылающейся на таблицу товаров:

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    product_no integer REFERENCES products (product_no),
    quantity integer
);

Теперь невозможно создавать заказы с отличными от NULL записями product_no, отсутствующими в таблице товаров.

В этой ситуации мы называем таблицу заказов дочерней (или ссылающейся) таблицей, а таблицу товаров — родительской (или ссылочной). Аналогичным образом именуются дочерние и родительские (или ссылающиеся и ссылочные) столбцы.

Указанную выше команду можно сократить до:

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    product_no integer REFERENCES products,
    quantity integer
);

потому что в отсутствие списка столбцов первичный ключ родительской таблицы используется в качестве ссылочного столбца (или столбцов).

Ограничению внешнего ключа можно назначить собственное имя обычным способом.

Кроме того, внешний ключ может ограничивать и ссылаться на группу столбцов. Как обычно, в этом случае его нужно записать в виде табличного ограничения. Вот пример надуманного синтаксиса:

CREATE TABLE t1 (
  a integer PRIMARY KEY,
  b integer,
  c integer,
  FOREIGN KEY (b, c) REFERENCES other_table (c1, c2)
);

Разумеется, количество и тип ограниченных столбцов должны соответствовать количеству и типу ссылочных столбцов.

Иногда полезно задавать в качестве «другой таблицы» ограничения внешнего ключа ту же самую таблицу; это называется самоссылающимся внешним ключом. Например, если вы хотите, чтобы строки таблицы представляли узлы древовидной структуры, можно написать:

CREATE TABLE tree (
    node_id integer PRIMARY KEY,
    parent_id integer REFERENCES tree,
    name text,
    ...
);

Для узла верхнего уровня parent_id будет равен NULL, тогда как записи с отличным от NULL parent_id окажутся ограничены и будут ссылаться только на допустимые строки таблицы.

Таблица может иметь более одного ограничения внешнего ключа. Это используется для реализации отношений «многие ко многим» между таблицами. Скажем, у вас есть таблицы с товарами и заказами, но теперь вы хотите, чтобы один заказ мог содержать много товаров (что не разрешено в приведенной выше структуре). Можно использовать следующую структуру таблиц:

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    shipping_address text,
    ...
);

CREATE TABLE order_items (
    product_no integer REFERENCES products,
    order_id integer REFERENCES orders,
    quantity integer,
    PRIMARY KEY (product_no, order_id)
);

Обратите внимание, что в последней таблице первичный ключ перекрывает внешние ключи.

Мы знаем, что внешние ключи запрещают создание заказов, не относящихся ни к каким товарам. Но что, если товар будет удален после создания заказа, который на него ссылается? SQL позволяет справиться и с этим. На первый взгляд, у нас есть несколько вариантов:

Запретить удаление ссылочного товара

Удалить вместе с ним связанные заказы;

Что-то другое?

Чтобы проиллюстрировать это, давайте реализуем следующую политику в приведенном выше примере отношения «многие ко многим»: когда кто-то хочет удалить товар, на который все еще ссылается заказ (через order_items), мы это запрещаем. Если кто-то удаляет заказ, его элементы также удаляются:

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    shipping_address text,
    ...
);

CREATE TABLE order_items (
    product_no integer REFERENCES products ON DELETE RESTRICT,
    order_id integer REFERENCES orders ON DELETE CASCADE,
    quantity integer,
    PRIMARY KEY (product_no, order_id)
);

Ограничивающие и каскадные удаления являются двумя наиболее распространенными вариантами. RESTRICT предотвращает удаление ссылочной строки. NO ACTION означает, что если при проверке ограничения все еще существуют какие-либо ссылающиеся строки, то возникает ошибка; если ничего не указать, это поведение по умолчанию. (Существенное различие между этими двумя вариантами заключается в том, что NO ACTION позволяет отложить проверку до более поздней стадии транзакции, тогда как RESTRICT этого не позволяет.) CASCADE указывает, что при удалении ссылочной строки ссылающаяся строка (или строки) также должны автоматически удаляться. Есть еще два варианта: SET NULL и SET DEFAULT. При удалении ссылочной строки они устанавливают в ссылающихся столбцах ссылающихся строк значения NULL или их значения по умолчанию соответственно. Обратите внимание, что это не освобождает вас от соблюдения ограничений. Например, если действие указывает SET DEFAULT, но значение по умолчанию не удовлетворяет ограничению внешнего ключа, эта операция завершится ошибкой.

Аналогично указанию ON DELETE также существует ON UPDATE который вызывается при изменении (обновлении) ссылочного столбца. Возможные действия одинаковы. В этом случае CASCADE означает, что обновленные значения ссылочных столбцов должны быть скопированы в ссылающиеся строки.

Как правило, ссылающаяся строка не должна удовлетворять ограничению внешнего ключа, если какой-либо из ее ссылающихся столбцов равен NULL. Если к объявлению внешнего ключа добавляется MATCH FULL, то ссылающаяся строка экранируется, удовлетворяя ограничению, только если все ее ссылающиеся столбцы равны NULL (таким образом, сочетание значений NULL и не-NULL гарантированно приведет к сбою ограничения MATCH FULL). Если вы хотите, чтобы ссылающиеся строки не могли не удовлетворять ограничению внешнего ключа, объявите ссылающиеся столбцы как NOT NULL.

Внешний ключ должен ссылаться на столбцы, которые являются первичным ключом или образуют ограничение уникальности. Это означает, что для ссылочных столбцов всегда существует индекс (обусловливающий первичный ключ или ограничение уникальности), поэтому проверка на соответствие ссылающейся строки будет эффективной. Поскольку команды DELETE для строки из ссылочной таблицы или UPDATE для ссылочного столбца потребуют сканирование ссылающейся таблицы на наличие строк, соответствующих старому значению, часто рекомендуется индексировать и ссылающиеся столбцы. Поскольку это не всегда необходимо и существует множество вариантов индексации, объявление ограничения внешнего ключа не создает автоматически индекс по ссылающимся столбцам.

Более подробная информация об обновлении и удалении данных приведена в главе Манипулирование данными. Также см. описание синтаксиса ограничения внешнего ключа на справочной странице команды CREATE TABLE.

Исключающие ограничения

Исключающие ограничения гарантируют, что если любые две строки сравниваются по заданным столбцам или выражениям с использованием заданных операторов, то по крайней мере один из этих операторов сравнения вернет false или NULL. Синтаксис при этом такой:

CREATE TABLE circles (
    c circle,
    EXCLUDE USING gist (c WITH &&)
);

Подробную информацию см. на справочной странице команды CREATE TABLE ... CONSTRAINT ... EXCLUDE.

Добавление исключающего ограничения автоматически создаст индекс того типа, который указан в объявлении этого ограничения.

Системные столбцы

Каждая таблица имеет несколько системных столбцов, которые неявно определяются системой. Следовательно, эти имена нельзя использовать в качестве имен пользовательских столбцов. (Обратите внимание, что эти ограничения не зависят от того, является ли имя ключевым словом или нет; заключение имени в кавычки все равно не позволит обойти эти ограничения). На самом деле нет нужды беспокоиться об этих столбцах; достаточно знать, что они существуют.

tableoid
OID таблицы, содержащей эту строку. Этот столбец особенно удобен для запросов, которые делают выборку из партиционированных таблиц (см. раздел Партиционирование таблиц или иерархий наследования (см. раздел Наследование), поскольку без него трудно определить, из какой конкретной таблицы получена строка. Столбец tableoid можно соединить со столбцом oid из таблицы pg_class, чтобы получить имя таблицы.

xmin
Идентификатор (код операции) добавляющей транзакции для этой версии строки. (Версия строки - это отдельное состояние строки; каждое изменение создает новую версию той же логической строки).

cmin
Идентификатор команды (начиная с нуля) внутри добавляющей транзакции.

xmax
Идентификатор (код операции) удаляющей транзакции или ноль для неудаленной версии строки. Этот столбец может быть ненулевым в видимой версии строки. Обычно это означает, что удаляющая транзакция еще не зафиксирована или что произошел откат попытки удаления.

cmax
Идентификатор команды в удаляющей транзакции или ноль.

ctid
Физическое местоположение версии строки в ее таблице. Обратите внимание, что хотя ctid можно использовать для очень быстрого поиска версии строки, ctid строки изменится, если та будет изменена или перемещена командой VACUUM FULL. Поэтому столбец ctid бесполезен в качестве долгосрочного идентификатора строки. Для логической идентификации строк должен использоваться первичный ключ.

Идентификаторы транзакций также являются 32-битными величинами. В долгоживущей базе данных нумерация идентификаторов транзакций может пойти по кругу. При подходящих процедурах обслуживания эта проблема не критична; подробную информацию см. в главе Регулярные задачи обслуживания базы данных. Однако в долгосрочной перспективе (более одного миллиарда транзакций) полагаться на уникальность идентификаторов транзакций неразумно.

Идентификаторы команд также являются 32-битными величинами. Это создает жесткий предел в 2^32 (примерно 4 миллиарда) команд SQL в одной транзакции. На практике это ограничение не является проблемой — обратите внимание, что это ограничение на количество команд SQL, а не количество обработанных строк. Кроме того, идентификатор будут использовать только те команды, которые действительно изменяют содержимое базы данных.

Изменение таблиц

Когда вы создаете таблицу и понимаете, что допустили ошибку или что изменились требования приложения, вы можете удалить таблицу и создать ее заново. Но это не самый удобный вариант, если таблица уже заполнена данными или если на нее ссылаются другие объекты базы данных (например ограничение внешнего ключа). Поэтому QHB предоставляет семейство команд для внесения изменений в существующие таблицы. Обратите внимание, что это принципиально отличается от изменения содержащихся в таблице данных: здесь мы заинтересованы в изменении определения, или структуры, этой таблицы.

В структуру таблицы можно вносить следующие изменения:

Добавить столбцы

Удалить столбцы

Добавить ограничения

Удалить ограничения

Изменить значения по умолчанию

Изменить типы данных столбцов

Переименовать столбцы

Переименовать таблицы

Все эти действия выполняются с помощью команды ALTER TABLE, справочная страница которой содержит более подробную информацию, чем представленная ниже.

Добавление столбца

Чтобы добавить столбец, можно воспользоваться такой командой:

ALTER TABLE products ADD COLUMN description text;

Новый столбец изначально заполняется любым заданным значением по умолчанию (или значением NULL, если не указано предложение DEFAULT).

Совет
В настоящее время добавление столбца с постоянным значением по умолчанию больше не означает, что при выполнении команды ALTER TABLE должны быть изменены все строки таблицы. Вместо этого значение по умолчанию будет возвращено при следующем обращении к строке и сохранится в ней при перезаписи таблицы, тем самым делая выполнение ALTER TABLE очень быстрым даже с большими таблицами.

Однако если значение по умолчанию является изменчивым (как, например, clock_timestamp()), каждую строку нужно будет изменить, записав в нее значение, вычисленное во время выполнения ALTER TABLE. Во избежание потенциально длительной операции изменения, в частности, если вы в любом случае намереваетесь заполнить столбец в основном не значениями по умолчанию, возможно, будет лучше добавить столбец без значения по умолчанию, вставить верные значения с помощью UPDATE, а затем добавить любое желаемое значение по умолчанию, как описано ниже.

Можно также одновременно установить ограничения для этого столбца, используя обычный синтаксис:

ALTER TABLE products ADD COLUMN description text CHECK (description <> '');

На самом деле в этой команде можно использовать все параметры, применимые к описанию столбца в CREATE TABLE. Однако имейте в виду, что значение по умолчанию должно удовлетворять заданным ограничениям, иначе операция ADD завершится с ошибкой. Как вариант, можно добавить ограничения позже (см. ниже), после правильного заполнения нового столбца.

Удаление столбца

Чтобы удалить столбец, можно воспользоваться такой командой:

ALTER TABLE products DROP COLUMN description;

Какие бы данные ни были в столбце, они исчезнут. Ограничения таблицы, касающиеся этого столбца, тоже удаляются. Однако если на столбец ссылается ограничение внешнего ключа другой таблицы, QHB не станет молча удалять это ограничение. Разрешить удаление всего, что зависит от столбца, можно, добавив указание CASCADE:

ALTER TABLE products DROP COLUMN description CASCADE;

Общий механизм, стоящий за этим, описан в разделе Отслеживание зависимостей.

Добавление ограничения

Чтобы добавить ограничение, используется синтаксис ограничения таблицы. Например:

ALTER TABLE products ADD CHECK (name <> '');
ALTER TABLE products ADD CONSTRAINT some_name UNIQUE (product_no);
ALTER TABLE products ADD FOREIGN KEY (product_group_id) REFERENCES product_groups;

Чтобы добавить ограничение NOT NULL, которое нельзя записать как ограничение таблицы, используйте этот синтаксис:

ALTER TABLE products ALTER COLUMN product_no SET NOT NULL;

Ограничение будет проверено немедленно, поэтому его можно будет добавить, только если данные таблицы ему удовлетворяют.

Удаление ограничения

Чтобы удалить ограничение, нужно знать его имя. Если имя задано, это легко. В противном случае система присваивает сгенерированное имя, которое необходимо выяснить. Здесь может быть полезна команда psql \d имя_таблицы; другие интерфейсы тоже могут обеспечить способ проверки информации о таблице. После этого можно воспользоваться следующей командой:

ALTER TABLE products DROP CONSTRAINT some_name;

(Если вы имеете дело со сгенерированным именем ограничения, например $2, не забывайте, что его нужно будет заключить в кавычки, чтобы сделать действительным идентификатором.)

Как и в случае удаления столбца, если нужно удалить ограничение, от которого зависит что-то еще, следует добавить указание CASCADE. Примером служит ограничение внешнего ключа, зависящее от ограничения уникальности или первичного ключа на ссылочных столбцах.

Это работает одинаково для всех типов ограничений, кроме ограничений NOT NULL. Чтобы удалить ограничение NOT NULL, используйте:

ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL;

(Вспомните, что у ограничений NOT NULL нет имен.)

Изменение значения столбца по умолчанию

Чтобы установить для столбца новое значение по умолчанию, используйте следующую команду:

ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77;

Обратите внимание, что выполнение этой команды не влияет на существующие строки в таблице, она просто меняет значение по умолчанию для будущих команд INSERT.

Чтобы удалить любое значение по умолчанию, используйте:

ALTER TABLE products ALTER COLUMN price DROP DEFAULT;

Это практически то же самое, что установить значением по умолчанию NULL. Как следствие, сброс значения по умолчанию, если оно не было определено, не будет ошибкой, потому что значение по умолчанию неявно равно значению NULL.

Изменение типа данных столбца

Чтобы преобразовать столбец в другой тип данных, используйте такую команду:

ALTER TABLE products ALTER COLUMN price TYPE numeric(10,2);

Команда завершится успешно, только если каждую существующую запись в столбце можно преобразовать в новый тип путем неявного приведения. Если требуется более сложное преобразование, можно добавить предложение USING, в котором указано, как вычислить новые значения из старых.

QHB будет пытаться преобразовать значение столбца по умолчанию (если оно есть) в новый тип, и все ограничения, связанные с этим столбцом. Но эти преобразования могут завершиться ошибкой или привести к неожиданным результатам. Зачастую перед изменением типа столбца лучше удалить любые его ограничения, а затем снова их добавить, но уже измененными подходящим образом.

Переименование столбца

Чтобы переименовать столбец, выполните:

ALTER TABLE products RENAME COLUMN product_no TO product_number;

Переименование таблицы

Чтобы переименовать таблицу, выполните:

ALTER TABLE products RENAME TO items;

Права

При создании объекта ему назначается владелец. Обычно владельцем является роль, выполнившая создающую команду. Для большинства видов объектов начальное состояние таково, что только владелец (или суперпользователь) может что-либо делать с этим объектом. Чтобы другие роли могли его использовать, необходимо предоставить им права.

Существуют различные виды прав: SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, CREATE, CONNECT, TEMPORARY, EXECUTE и USAGE. Права, применимые к конкретному объекту, различаются в зависимости от типа объекта (таблица, функция и т. д.). Более подробная информация о значениях этих прав приведена ниже. В следующих разделах и главах также будет описано, как используются эти права.

Право изменять или удалять объект является неотъемлемым правом владельца объекта и не может быть назначено или отозвано само по себе. (Однако, как и все права, это право может быть унаследовано членами роли-владельца; см. раздел Членство в роли.)

Объекту можно назначить нового владельца с помощью команды ALTER соответствующего объекту вида, например:

ALTER TABLE имя_таблицы OWNER TO новый_владелец;

Суперпользователи всегда могут это сделать; обычные роли могут делать это только в том случае, если они являются одновременно текущим владельцем объекта (или членом роли-владельца) и членом новой роли-владельца.

Для назначения прав используется команда GRANT. Например, если joe является существующей ролью, а accounts — существующей таблицей, право на изменение таблицы можно предоставить так:

GRANT UPDATE ON accounts TO joe;

Запись ALL вместо определенного права предоставляет все права, которые относятся к типу объекта.

Для предоставления права каждой роли в системе можно использовать специальное имя «роли» PUBLIC. Кроме того, когда пользователей базы данных много, можно установить «групповые» роли, что поможет управлять правами, — подробности см. в главе Роли в базе данных.

Чтобы отозвать право, используйте подходящую команду REVOKE:

REVOKE ALL ON accounts FROM PUBLIC;

Обычно только владелец объекта (или суперпользователь) может предоставить или отозвать права для объекта. Тем не менее можно предоставить право «с функцией передачи», которая дает получателю возможность в свою очередь предоставлять его другим лицам. Если функция предоставления впоследствии отзывается, то все, кто получил право от этого получателя (напрямую или через цепочку предоставлений), потеряют это право. Подробную информацию см. на справочных страницах команд GRANT и REVOKE.

Владелец объекта может отозвать собственные обычные права, например, сделать таблицу доступной только для чтения не только для остальных, но и для себя. Но владельцы всегда воспринимаются как имеющие возможность предоставлять права, поэтому они всегда могут заново предоставить себе собственные права.

Доступные права:

SELECT
Позволяет выбирать с помощью SELECT любой или заданный столбец (столбцы) таблицы, представления, материализованного представления или другого табличного объекта. Также позволяет использовать COPY TO. Это право также необходимо для обращения по ссылке на существующие значения столбца в UPDATE или DELETE. Кроме того для последовательностей это право позволяет использовать функцию currval. Для больших объектов это право позволяет прочитать данные в объекте.

INSERT
Разрешает добавлять с помощью INSERT новую строку в таблицу, представление и т. д. Может быть предоставлено на заданные столбцы, и тогда в команде INSERT можно присваивать значения только этим столбцам (поэтому другие столбцы будут получать значения по умолчанию). Также позволяет использовать COPY FROM.

UPDATE
Разрешает изменять с помощью UPDATE любой или заданный столбец (столбцы) таблицы, представления и т. д. (На практике любой нетривиальной команде UPDATE также потребуется право SELECT, поскольку она должна обращаться к столбцам таблицы, чтобы определить, какие строки изменять, и/или чтобы вычислить новые значения для столбцов.) Для команды SELECT ... FOR UPDATE и SELECT ... FOR SHARE, в дополнение к праву SELECT, также требуется это право как минимум для одного столбца. Для последовательностей это право позволяет использовать функции nextval и setval. Для больших объектов это право позволяет записывать данные в объект или опустошать его.

DELETE
Позволяет удалять с помощью DELETE строки из таблицы, представления и т. д. (На практике любой нетривиальной команде DELETE также потребуется право SELECT, поскольку она должна обращаться к столбцам таблицы, чтобы определить, какие строки нужно удалить).

TRUNCATE
Позволяет опустошать таблицу с помощью TRUNCATE.

REFERENCES
Позволяет создать ограничение внешнего ключа, ссылающегося на таблицу или определенные столбцы таблицы.

TRIGGER
Позволяет создать триггер для таблицы, представления и т. д.

CREATE
Для баз данных позволяет создавать в них новые схемы и публикации, а также устанавливать доверенные расширения.
Для схем позволяет создавать в них новые объекты. Чтобы переименовать существующий объект, нужно быть его владельцем и иметь это право для содержащей его схемы.
Для табличных пространств позволяет создавать в них таблицы, индексы и временные файлы, а также создавать базы данных, в которых заданное табличное пространство является табличным пространством по умолчанию.
Обратите внимание, что отзыв этого права не изменит существование или расположение существующих объектов.

CONNECT
Позволяет правополучателю подключаться к базе данных. Это право проверяется при установлении соединения (в дополнение к проверке любых ограничений, налагаемых qhb_hba.conf).

TEMPORARY
Позволяет создавать временные таблицы при использовании конкретной базы данных.

EXECUTE
Позволяет вызывать функцию или процедуру, включая использование любых операторов, реализованных поверх функции. Это единственный тип прав, применимый к функциям и процедурам.

USAGE
Для процедурных языков позволяет использовать язык для создания функций на этом языке. Это единственный тип прав, применимый к процедурным языкам.
Для схем разрешает доступ к содержащимся в них объектам (при условии, что собственные требования к правам объектов также выполнены). По сути, это позволяет правополучателю «искать» объекты в схеме. Без этого разрешения имена объектов все равно можно увидеть, например, запросив системные каталоги. Кроме того, после отзыва этого разрешения в существующих сеансах могут быть операторы, ранее выполнявшие этот поиск, так что этот способ предотвращения доступа к объекту не вполне безопасен.
Для последовательностей позволяет использовать функции currval и nextval.
Для типов и доменов позволяет использовать их при создании таблиц, функций и других объектов схемы. (Обратите внимание, что это право не управляет всем «использованием» данного типа, например, значениями типа, появляющимися в запросах. Оно только предотвращает создание объектов, зависящих от этого типа. В основном это право нужно для управления тем, какие пользователи могут создать зависимости от типа, которые впоследствии могут помешать владельцу типа его изменить.)
Для оберток сторонних данных позволяет создавать с их помощью новые серверы.
Для сторонних серверов позволяет создавать с их помощью сторонние таблицы. Кроме того, правополучатели могут создавать, изменять или удалять свои собственные сопоставления пользователей, связанные с этим сервером.

Права, требуемые для других команд, перечислены на страницах справок по соответствующим командам.

QHB по умолчанию предоставляет роли PUBLIC права для некоторых типов объектов при создании этих объектов. Для таблиц, столбцов таблиц, последовательностей, оберток сторонних данных, сторонних серверов, больших объектов, схем или табличных пространств PUBLIC никаких прав по умолчанию не получает. Для других типов объектов правами по умолчанию, предоставляемыми PUBLIC, являются: CONNECT и TEMPORARY (создание временных таблиц) для баз данных; право EXECUTE для функций и процедур; право USAGE для языков и типов данных(включая домены). Разумеется, владелец объекта может отозвать с помощью REVOKE как права по умолчанию, так и права, предоставленные явно. (Для обеспечения максимальной безопасности выполните команду REVOKE в той же транзакции, в которой создается объект; тогда не образуется промежуток, во время которого другой пользователь может использовать объект). Кроме того, эти настройки прав по умолчанию можно изменить с помощью команды ALTER DEFAULT PRIVILEGES.

В Таблице 1 перечислены однобуквенные сокращения, которые используются для этих типов прав в значениях ACL (Access Control List, список управления доступом). Эти буквы можно увидеть в выходных данных перечисленных ниже команд psql или при просмотре столбцов ACL системных каталогов.

Таблица 1. Сокращения для прав в ACL

Право | Сокращение | Применимые типы объектов
SELECT | r («read», читать) | LARGE OBJECT, SEQUENCE, TABLE (и табличные объекты), столбец таблицы
INSERT | a («append», добавить) | TABLE, столбец таблицы
UPDATE | w («write», записать) | LARGE OBJECT, SEQUENCE, TABLE, столбец таблицы
DELETE | d | TABLE
TRUNCATE | D | TABLE
REFERENCES | x | TABLE, столбец таблицы
TRIGGER | t | TABLE
CREATE | C | DATABASE, SCHEMA, TABLESPACE
CONNECT | c | DATABASE
TEMPORARY | T | DATABASE
EXECUTE | X | FUNCTION, PROCEDURE
USAGE | U | DOMAIN, FOREIGN DATA WRAPPER, FOREIGN SERVER, LANGUAGE, SCHEMA, SEQUENCE, TYPE

В Таблице 2 сведены права, доступные для каждого типа объекта SQL, с использованием вышеприведенных сокращений. Кроме того, здесь перечислены команды psql, с помощью которых можно проверить настройки прав для каждого типа объекта.

Таблица 2. Сводка прав доступа

Тип объекта | Все права | Права PUBLIC по умолчанию | Команда psql
DATABASE | CTc | Tc | \l
DOMAIN | U | U | \dD+
FUNCTION или PROCEDURE | X | X | \df+
FOREIGN DATA WRAPPER | U | нет | \dew+
FOREIGN SERVER | U | нет | \des+
LANGUAGE | U | U | \dL+
LARGE OBJECT | rw | нет |
SCHEMA | UC | нет | \dn+
SEQUENCE | rwU | нет | \dp
TABLE (и табличные объекты) | arwdDxt | нет | \dp
Столбец таблицы | arwx | нет | \dp
TABLESPACE | C | нет | \db+
TYPE | U | U | \dT+

Права, которые были предоставлены для определенного объекта, отображаются в виде списка записей aclitem, где каждая запись aclitem описывает разрешения одного правополучателя, которые были предоставлены конкретным праводателем. Например, calvin=r*w/hobbes указывает, что роль calvin имеет право SELECT (r) с возможностью его передачи (*), а также не подлежащее передаче право UPDATE (w), и оба они предоставлены ролью hobbes. Если calvin также имеет некоторые права на тот же объект, предоставленные другим праводателем, они будут отображаться в виде отдельной записи aclitem. Пустое поле правополучателя в aclitem означает роль PUBLIC.

В качестве примера предположим, что пользователь miriam создает таблицу mytable и выполняет:

GRANT SELECT ON mytable TO PUBLIC;
GRANT SELECT, UPDATE, INSERT ON mytable TO admin;
GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;

Тогда команда psql \dp покажет:

=> \dp mytable
                                  Access privileges
 Schema |  Name   | Type  |   Access privileges   |   Column privileges   | Policies
--------+---------+-------+-----------------------+-----------------------+----------
 public | mytable | table | miriam=arwdDxt/miriam+| col1:                +|
        |         |       | =r/miriam            +|   miriam_rw=rw/miriam |
        |         |       | admin=arw/miriam      |                       |
(1 row)

Если столбец «Access privileges» для данного объекта пуст, это означает, что на объект имеются права по умолчанию (то есть запись его прав в соответствующем системном каталоге равна NULL). Права по умолчанию всегда включают все права для владельца и могут включать некоторые права для PUBLIC в зависимости от типа объекта, как описано выше. Первая команда GRANT или REVOKE реализует права по умолчанию (создав, например, miriam=arwdDxt/miriam), а затем изменит их согласно указанному запросу. Аналогично записи в «Column privileges» отображаются только для столбцов с правами не по умолчанию. (Примечание: для этой цели «права по умолчанию» всегда означают встроенные права по умолчанию для типа объекта. Объект, у которого права были изменены командой ALTER DEFAULT PRIVILEGES, всегда будет отображаться с явной записью прав, включающей эффект ALTER).

Обратите внимание, что неявные возможности владельца передавать права в отображении прав доступа не отмечаются. А знак * появится, только когда возможности передачи прав были кому-либо предоставлены явно.

Политики защиты строк

В дополнение к стандартной системе прав SQL, доступной посредством команды GRANT, таблицы могут иметь политики защиты строк, ограничивающие для пользователей строки, которые могут быть возвращены обычными запросами или добавлены, изменены или удалены с помощью команд изменения данных. Эта функция также известна как защита на уровне строк (Row-Level Security, RLS). По умолчанию у таблиц нет никаких политик, поэтому, если у пользователя есть права доступа к таблице в соответствии с системой прав SQL, все строки в ней одинаково доступны для запроса или изменения.

Когда в таблице включена защита строк (с помощью ALTER TABLE ... ENABLE ROW LEVEL SECURITY), все обычные обращения к таблице для выбора или изменения строк должны быть разрешены политикой защиты строк. (Однако на владельца таблицы политики защиты строк обычно не распространяются.) Если для таблицы не существует политики, по умолчанию используется политика запрета, означающая, что строки не видны или не могут быть изменены. На операции, которые применяются ко всей таблице, например TRUNCATE и REFERENCES, защита строк не действует.

Политики защиты строк могут применяться к конкретным командам, ролям или тому и другому. Можно указать политику как применяемую ко всем командам (ALL) либо к SELECT, INSERT, UPDATE или DELETE. Заданную политику можно связать с несколькими ролями, и при этом применяются обычные правила членства и наследования ролей.

Чтобы указать, какие строки являются видимыми или изменяемыми в соответствии с политикой, требуется выражение, возвращающее логический результат. Это выражение будет вычисляться для каждой строки перед проверкой любых условий или выполнении функций, поступающих из пользовательского запроса. (Единственным исключением из этого правила являются функции leakproof, которые гарантированно не допускают утечки информации; оптимизатор может выбрать применение таких функций перед проверкой защиты строк). Строки, для которых это выражение не возвращает true, обрабатываться не будут. Для обеспечения независимого управления видимыми строками и строками, которые можно изменять, возможно задать отдельные выражения. Выражения политики выполняются как часть запроса с правами выполняющего его пользователя, хотя для доступа к данным, недоступным вызывающему пользователю, можно использовать функции определения настроек защиты.

Суперпользователи и роли с атрибутом BYPASSRLS при доступе к таблице всегда обходят систему защиты строк. Владельцы таблиц обычно тоже обходят защиту строк, хотя они могут включить ее для себя с помощью ALTER TABLE ... FORCE ROW LOWEL SECURITY.

Право на включение и выключение защиты строк, а также добавление политик в таблицу всегда имеет только владелец таблицы.

Политики создаются с помощью команды CREATE POLICY, изменяются с помощью команды ALTER POLICY и удаляются с помощью команды DROP POLICY. Чтобы включать и выключать защиту строк для конкретной таблицы, воспользуйтесь командой ALTER TABLE.

У каждой политики есть имя, и для таблицы можно определить несколько политик. Поскольку политики привязаны к таблице, каждая такая политика должна иметь уникальное имя. Разные таблицы могут иметь политики с одинаковым именем.

Когда к данному запросу применяется несколько политик, они объединяются либо с помощью OR (для разрешающих политик, которые используются по умолчанию), либо с помощью AND (для ограничительных политик). Это похоже на правило, согласно которому отдельно взятая роль обладает правами всех ролей, членом которых она является. Разрешительные и ограничительные политики рассматриваются ниже.

В качестве простого примера далее показано, как создать политику в отношении account, чтобы разрешить доступ к строкам только членам роли managers и при этом только к строкам их собственных учетных записей:

CREATE TABLE accounts (manager text, company text, contact_email text);

ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;

CREATE POLICY account_managers ON accounts TO managers
    USING (manager = current_user);

Приведенная выше политика неявно предоставляет предложение WITH CHECK, идентичное ее предложению USING, поэтому ограничение применяется как к строкам, выбранным командой (таким образом, менеджер не может выполнять SELECT, UPDATE или DELETE для существующих строк, принадлежащих другому менеджеру), так и к строкам, измененным командой (таким образом, строки, принадлежащие другому менеджеру, нельзя создать с помощью INSERT или UPDATE).

Если роль не указана или используется специальное имя пользователя PUBLIC, то политика применяется ко всем пользователям в системе. Чтобы разрешить всем пользователям доступ только к своей строке в таблице users, можно использовать простую политику:

CREATE POLICY user_policy ON users
    USING (user_name = current_user);

Это работает аналогично предыдущему примеру.

Чтобы использовать для строк, добавляемых в таблицу, политику, отличную от политики для для видимых строк, можно объединить несколько политик. Эта пара политик позволит всем пользователям просматривать все строки в таблице users, но изменять только свои собственные:

CREATE POLICY user_sel_policy ON users
    FOR SELECT
    USING (true);
CREATE POLICY user_mod_policy ON users
    USING (user_name = current_user);

В команде SELECT эти две политики объединяются с использованием OR, в результате чего можно выбрать все строки. В других типах команд применяется только вторая политика, поэтому эффекты те же, что и раньше.

Защиту строк также можно выключить с помощью команды ALTER TABLE. Выключение защиты строк не приводит к удалению политик, определенных для таблицы; они просто игнорируются. Затем все строки в таблице становятся видимыми и изменяемыми в соответствии с системой прав стандарта SQL.

Ниже приведен развернутый пример того, как эту функциональность можно использовать в производственных средах. Таблица passwd эмулирует файл паролей Unix:

-- Простой пример на основе файла passwd
CREATE TABLE passwd (
  user_name             text UNIQUE NOT NULL,
  pwhash                text,
  uid                   int  PRIMARY KEY,
  gid                   int  NOT NULL,
  real_name             text NOT NULL,
  home_phone            text,
  extra_info            text,
  home_dir              text NOT NULL,
  shell                 text NOT NULL
);

CREATE ROLE admin;  -- Администратор
CREATE ROLE bob;    -- Обычный пользователь
CREATE ROLE alice;  -- Обычный пользователь

-- Наполнение таблицы
INSERT INTO passwd VALUES
  ('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/dash');
INSERT INTO passwd VALUES
  ('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/zsh');
INSERT INTO passwd VALUES
  ('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/bin/zsh');

-- Обязательно нужно включить для этой таблицы защиту на уровне строк
ALTER TABLE passwd ENABLE ROW LEVEL SECURITY;

-- Создание политик
-- Администратор может видеть и добавлять любые строки
CREATE POLICY admin_all ON passwd TO admin USING (true) WITH CHECK (true);
-- Обычные пользователи могут видеть все строки
CREATE POLICY all_view ON passwd FOR SELECT USING (true);
-- Обычные пользователи могут изменять свои записи, но
-- не могут задавать любую произвольную оболочку
CREATE POLICY user_mod ON passwd FOR UPDATE
  USING (current_user = user_name)
  WITH CHECK (
    current_user = user_name AND
    shell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/tcsh')
  );

-- Администратор получает все обычные права
GRANT SELECT, INSERT, UPDATE, DELETE ON passwd TO admin;
-- Пользователи получают доступ на чтение только к общедоступным столбцам
GRANT SELECT
  (user_name, uid, gid, real_name, home_phone, extra_info, home_dir, shell)
  ON passwd TO public;
-- Пользователям разрешается изменять определенные столбцы
GRANT UPDATE
  (pwhash, real_name, home_phone, extra_info, shell)
  ON passwd TO public;

Как и при любых настройках защиты, важно проверить политики и убедиться, что эта система работает должным образом. В приведенном выше примере показано, что система разрешений работает правильно.

-- администратор может видеть все строки и поля
qhb=> set role admin;
SET
qhb=> table passwd;
 user_name | pwhash | uid | gid | real_name |  home_phone  | extra_info | home_dir    |   shell
-----------+--------+-----+-----+-----------+--------------+------------+-------------+-----------
 admin     | xxx    |   0 |   0 | Admin     | 111-222-3333 |            | /root       | /bin/dash
 bob       | xxx    |   1 |   1 | Bob       | 123-456-7890 |            | /home/bob   | /bin/zsh
 alice     | xxx    |   2 |   1 | Alice     | 098-765-4321 |            | /home/alice | /bin/zsh
(3 rows)

-- Проверка того, что может делать Элис
qhb=> set role alice;
SET
qhb=> table passwd;
ERROR:  permission denied for relation passwd
-- ОШИБКА: отсутствие прав доступа для отношения passwd
qhb=> select user_name,real_name,home_phone,extra_info,home_dir,shell from passwd;
 user_name | real_name |  home_phone  | extra_info | home_dir    |   shell
-----------+-----------+--------------+------------+-------------+-----------
 admin     | Admin     | 111-222-3333 |            | /root       | /bin/dash
 bob       | Bob       | 123-456-7890 |            | /home/bob   | /bin/zsh
 alice     | Alice     | 098-765-4321 |            | /home/alice | /bin/zsh
(3 rows)

qhb=> update passwd set user_name = 'joe';
ERROR:  permission denied for relation passwd
-- Элис разрешено изменить свое имя (поле real_name), но не другие имена
qhb=> update passwd set real_name = 'Alice Doe';
UPDATE 1
qhb=> update passwd set real_name = 'John Doe' where user_name = 'admin';
UPDATE 0
qhb=> update passwd set shell = '/bin/xx';
ERROR:  new row violates WITH CHECK OPTION for "passwd"
-- ОШИБКА: новая роль нарушает ПАРАМЕТР WITH CHECK для "passwd"
qhb=> delete from passwd;
ERROR:  permission denied for relation passwd
qhb=> insert into passwd (user_name) values ('xxx');
ERROR:  permission denied for relation passwd
-- Элис может изменить свой пароль; изменение других строк RLS молча предотвращает
qhb=> update passwd set pwhash = 'abc';
UPDATE 1

Все созданные до сих пор политики были разрешающими, то есть при применении нескольких политик они объединяются с помощью логического оператора «ИЛИ». Хотя разрешающие политики могут создаваться так, чтобы разрешать доступ к строкам только в желаемых случаях, может быть проще объединить разрешающие политики с ограничительными (которым должны удовлетворять записи и которые объединяются с помощью логического оператора «И»). На основе вышеприведенного примера мы добавляем ограничительную политику, требующую, чтобы для доступа к записям таблицы passwd администратора подключался через локальный сокет домена Unix:

CREATE POLICY admin_local_only ON passwd AS RESTRICTIVE TO admin
    USING (pg_catalog.inet_client_addr() IS NULL);

Затем мы можем удостовериться, что администратор, подключающийся по сети, не увидит никаких записей из-за этой ограничительной политики:

=> SELECT current_user;
 current_user
--------------
 admin
(1 row)

=> select inet_client_addr();
 inet_client_addr
------------------
 127.0.0.1
(1 row)

=> SELECT current_user;
 current_user
--------------
 admin
(1 row)

=> TABLE passwd;
 user_name | pwhash | uid | gid | real_name | home_phone | extra_info | home_dir | shell
-----------+--------+-----+-----+-----------+------------+------------+----------+-------
(0 rows)

=> UPDATE passwd set pwhash = NULL;
UPDATE 0

Проверки ссылочной целостности, такие как ограничения уникальности или первичных ключей, а также ссылки на внешние ключи, всегда обходят защиту строк для обеспечения целостности данных. При разработке схем и политик на уровне строк необходимо соблюдать осторожность во избежание утечек информации по «скрытому каналу» посредством таких проверок ссылочной целостности.

В некоторых случаях важно сделать так, чтобы защита строк не применялась. Например, если при создании резервной копии вследствие политики безопасности строк некоторые строки окажутся неявно из нее исключены, это может привести к катастрофическим последствиям. В такой ситуации можно выключить (установить значение off) параметр конфигурации row_security. Само по себе это не обходит защиту строк; если результаты какого-либо запроса окажутся отфильтрованы политикой, будет выдана ошибка. Затем можно будет выяснить и устранить причину этой ошибки.

В приведенных выше примерах выражения политик учитывают только текущие значения в запрашиваемой или изменяемой строке. Это самый простой и наиболее эффективный вариант; лучше всего по возможности разрабатывать приложения для защиты строк именно таким образом. Если для принятия решения о политике необходимо обратиться к другим строкам или таблицам, это можно делать в выражениях политик с помощью подзапросов SELECT или функций, содержащих SELECT. Однако имейте в виду, что такой доступ может создать состояния гонки, чреватые утечкой информации, если не принять меры предосторожности. В качестве примера рассмотрим следующую конструкцию таблицы:

-- определение групп прав
CREATE TABLE groups (group_id int PRIMARY KEY,
                     group_name text NOT NULL);

INSERT INTO groups VALUES
  (1, 'low'),
  (2, 'medium'),
  (5, 'high');

GRANT ALL ON groups TO alice;  -- alice является администратором
GRANT SELECT ON groups TO public;

-- определение уровней прав пользователей
CREATE TABLE users (user_name text PRIMARY KEY,
                    group_id int NOT NULL REFERENCES groups);

INSERT INTO users VALUES
  ('alice', 5),
  ('bob', 2),
  ('mallory', 2);

GRANT ALL ON users TO alice;
GRANT SELECT ON users TO public;

-- таблица, содержащая требующую защиты информацию
CREATE TABLE information (info text,
                          group_id int NOT NULL REFERENCES groups);

INSERT INTO information VALUES
  ('barely secret', 1),
  ('slightly secret', 2),
  ('very secret', 5);

ALTER TABLE information ENABLE ROW LEVEL SECURITY;

-- строка должна быть доступна для чтения/изменения пользователям с group_id защиты
-- большим или равным group_id данной строки
CREATE POLICY fp_s ON information FOR SELECT
  USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user));
CREATE POLICY fp_u ON information FOR UPDATE
  USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user));

-- мы защищаем таблицу с информацией, полагаясь только на RLS
GRANT ALL ON information TO public;

Теперь предположим, что alice хочет изменить «слегка секретную» информацию, но решает, что mallory не следует показывать новое содержимое этой строки, поэтому она делает так:

BEGIN;
UPDATE users SET group_id = 1 WHERE user_name = 'mallory';
UPDATE information SET info = 'secret from mallory' WHERE group_id = 2;
COMMIT;

Это выглядит безопасно — нет промежутка между выполнением команд, в котором mallory смогла бы увидеть строку «secret from mallory». Тем не менее здесь присутствует состояние гонки. Если mallory одновременно делает, скажем,

SELECT * FROM information WHERE group_id = 2 FOR UPDATE;

и ее транзакция находится в режиме READ COMMITTED, она может видеть «secret from mallory». Это происходит, если ее транзакция достигает строки information сразу после транзакции alice. Она блокируется, ожидая фиксации транзакции alice, а затем извлекает измененное содержимое строки благодаря предложению FOR UPDATE. Однако она не извлекает измененную строку из users для неявного SELECT, потому что этот подзапрос SELECT не содержал FOR UPDATE; вместо этого строка users читается из снимка, сделанного в начале запроса. Поэтому выражение политики проверяет старое значение уровня прав mallory и позволяет ей видеть эту измененную строку.

Есть несколько способов обойти эту проблему. Одним простым ответом является использование SELECT ... FOR SHARE в подзапросах SELECT в политиках защиты строк. Однако для этого необходимо предоставить заинтересованным пользователям право UPDATE для ссылочной таблицы (в данном случае users), что может быть нежелательным. (Но можно применить другую политику защиты строк, чтобы не дать им фактически использовать это право, или встроить подзапрос SELECT в функцию определения настроек защиты.) Кроме того, интенсивное одновременное использование совместных блокировок строки в ссылочной таблице может привести к снижению производительности, особенно если строку часто изменяют. Другое решение, имеющее смысл, если изменения ссылочной таблицы происходят нечасто, — установить блокировку ссылочной таблицы в режиме ACCESS EXCLUSIVE при ее изменении, чтобы никакие параллельные транзакции не могли проверять старые значения строк. Или можно просто дождаться завершения всех параллельных транзакций после фиксации изменения в ссылочной таблице, прежде чем вносить изменения, рассчитанные на новую ситуацию в области безопасности.

Дополнительную информацию см. на справочных страницах команд CREATE POLICY и ALTER TABLE.

Схемы

Кластер баз данных QHB содержит одну или несколько именованных баз данных. Роли и некоторые другие типы объектов являются общими для всего кластера. Клиентское соединение с сервером может получить доступ только к объектам в одной базе данных, указанной в запросе на соединение.

Примечание
Пользователи кластера не обязательно имеют право доступа к каждой базе данных в кластере. Общие имена ролей означают, что в двух базах данных одного кластера не может быть разных ролей с именем, скажем, joe, но систему можно настроить так, чтобы разрешить joe доступ только к некоторым базам данных.

База данных содержит одну или несколько именованных схем, которые, в свою очередь, содержат таблицы. Схемы содержат и другие виды именованных объектов, включая типы данных, функции и операторы. Одно и то же имя объекта можно без конфликтов использовать в разных схемах; например, и schema1 и myschema могут содержать таблицы с именем mytable. В отличие от баз данных, схемы разделены не жестко: пользователь может обращаться к объектам в любой схеме в базе данных, к которой он подключен, если у него есть для этого права.

Существует несколько причин, по которым стоит использовать схемы:

Чтобы позволить многим пользователям использовать одну базу данных, не мешая друг другу.

Чтобы организовать объекты базы данных в логические группы для лучшей управляемости.

Сторонние приложения можно поместить в отдельные схемы, чтобы они не конфликтовали с именами других объектов.

Схемы аналогичны каталогам на уровне операционной системы, за исключением того, что схемы не могут быть вложенными.

Создание схемы

Чтобы создать схему, используйте команду CREATE SCHEMA. Дайте схеме имя по вашему выбору. Например:

CREATE SCHEMA myschema;

Чтобы создать или получить доступ к объектам в схеме, напишите полное имя, состоящее из имени схемы и имени таблицы, разделенных точкой:

схема.таблица

Это работает везде, где ожидается имя таблицы, включая команды изменения таблицы и команды обращения к данным, обсуждаемые в следующих главах. (Для краткости мы будем говорить только о таблицах, но те же идеи применимы и к другим видам именованных объектов, таким как типы и функции.)

На самом деле, можно использовать даже более общий синтаксис:

база_данных.схема.таблица

но в настоящее время он существует только для формального соответствия стандарту SQL. Если вы напишете имя базы данных, оно должно совпадать с именем базы, к которой вы подключены.

Итак, чтобы создать таблицу в новой схеме, используйте:

CREATE TABLE myschema.mytable (
 ...
);

Чтобы удалить схему, если она пуста (все объекты в ней удалены), используйте:

DROP SCHEMA myschema;

Чтобы удалить схему, включая все содержащиеся в ней объекты, используйте:

DROP SCHEMA myschema CASCADE;

Описание стоящего за этим общего механизма см. в разделе Отслеживание зависимостей.

Часто бывает необходимо создать схему, принадлежащую кому-то другому (поскольку это один из способов ограничить активность пользователей четко определенными пространствами имен). Синтаксис для этого следующий:

CREATE SCHEMA имя_схемы AUTHORIZATION имя_пользователя;

Можно даже опустить имя схемы, и в этом случае оно будет совпадать с именем пользователя. Возможности применения этого см. в подразделе Шаблоны использования.

Имена схем, начинающиеся с pg_, зарезервированы для системных целей и не могут быть созданы пользователями.

Схема Public

В предыдущих разделах мы создавали таблицы без указания имен схем. По умолчанию такие таблицы (и другие объекты) автоматически помещаются в схему с именем «public». Каждая новая база данных содержит такую схему. Таким образом, команда

CREATE TABLE products (...);

равнозначна команде

CREATE TABLE public.products (...);

Путь поиска схемы

Полные имена загромождают текст, и, в любом случае, зачастую лучше не связывать конкретное имя схемы с приложениями. Поэтому таблицы часто называются неполными именами, состоящими только из имени таблицы. Система определяет, какая таблица имеется в виду, следуя пути поиска, который представляет собой список схем для поиска нужного объекта. Первая подходящая таблица в пути поиска считается искомой. Если в пути поиска нет совпадений, сообщается об ошибке, даже если совпадающие имена таблиц существуют в других схемах в базе данных.

Возможность создавать объекты с одинаковыми именами в разных схемах усложняет написание запроса, который каждый раз ссылается на одни и те же объекты. Это также открывает возможность одним пользователям злонамеренно или случайно изменять поведение запросов других пользователей. Из-за преобладания неполных имен в запросах и их использования во внутренних структурах QHB, добавление схемы в search_path по сути доверяет всем пользователям, имеющим право CREATE для этой схемы. Когда вы запускаете обычный запрос, злонамеренный пользователь, способный создавать объекты в схеме вашего пути поиска, может взять на себя управление и выполнять произвольные функции SQL, как если бы их выполняли вы.

Первая схема, названная в пути поиска, называется текущей схемой. Это не только первая искомая схема — в ней будут создаваться новые таблицы, если в команде CREATE TABLE не задано имя схемы.

Чтобы показать текущий путь поиска, используйте следующую команду:

SHOW search_path;

В настройках по умолчанию это возвращает:

search_path
--------------
"$user", public

Первый элемент указывает, что следует искать схему с именем текущего пользователя. Если такой схемы не существует, эта запись игнорируется. Второй элемент ссылается на схему public, которую мы уже видели.

Первая существующая схема в пути поиска является местоположением по умолчанию для создания новых объектов. По этой причине по умолчанию объекты создаются в схеме public. Когда на объекты ссылаются в любом другом контексте без указания схемы (команды изменения таблицы, изменения данных или запросов), путь поиска просматривается до тех пор, пока не будет найден соответствующий объект. Следовательно, в конфигурации по умолчанию любое обращение с неполным именем, опять же, может ссылаться только на схему public.

Чтобы поместить нашу новую схему в путь, необходимо использовать:

SET search_path TO myschema,public;

(Мы опускаем здесь $user, потому что прямо сейчас оно не нужно.) И тогда можно обратиться к таблице без указания схемы:

DROP TABLE mytable;

Кроме того, поскольку myschema является первым элементом в пути, новые объекты будут по умолчанию создаваться именно в этой схеме.

Также можно написать:

SET search_path TO myschema;

Теперь у нас больше нет доступа к схеме public без ее явного указания. В схеме public нет ничего особенного, за исключением того, что она существует по умолчанию. Ее тоже можно удалить.

Другие способы управления путем поиска схемы см. также в разделе Системные информационные функции и операторы.

Для имен типов данных, функций и операторов путь поиска работает тем же образом, что и для имен таблиц. Имена типов данных и функций можно дополнить схемой полностью аналогично именам таблиц. Если нужно записать в выражение полное имя оператора, существует специальное условие — следует написать:

OPERATOR(схема.оператор)

Это необходимо, чтобы избежать синтаксической неоднозначности. Пример:

SELECT 3 OPERATOR(pg_catalog.+) 4;

На практике все обычно полагаются на путь поиска, чтобы избежать необходимости писать нечто столь же замысловатое.

Схемы и права

По умолчанию пользователи не могут получить доступ к объектам в схемах, которыми они не владеют. Для этого владелец схемы должен предоставить право USAGE для этой схемы. Чтобы разрешить пользователям использовать объекты в схеме, могут потребоваться дополнительные права, подходящие для этого объекта.

Пользователю также может быть разрешено создавать объекты в чужой схеме. Для этого необходимо предоставить право CREATE для этой схемы. Обратите внимание, что по умолчанию все имеют права CREATE и USAGE в схеме public. Это позволяет всем пользователям, которые могут подключаться к указанной базе данных, создавать объекты в ее схеме public. Некоторые шаблоны использования требуют отзыва этого права:

REVOKE CREATE ON SCHEMA public FROM PUBLIC;

(Первое слово «public» означает схему, второе — «каждый пользователь». В первом случае это идентификатор, во втором — ключевое слово, отсюда и другой регистр; см. разъяснения в подразделе Идентификаторы и ключевые слова.)

Схема системного каталога

В дополнение к public и созданным пользователями схемам каждая база данных содержит схему pg_catalog, содержащую системные таблицы и все встроенные типы данных, функции и операторы. В сущности, pg_catalog всегда является частью пути поиска. Если она не указана в пути явно, то она неявно просматривается перед поиском в остальных схемах пути. Это гарантирует, что встроенные имена всегда будет можно найти. Тем не менее если вы предпочитаете, чтобы имена, определяемые пользователем, переопределяли встроенные имена, можно явно разместить pg_catalog в конце пути поиска.

Поскольку имена системных таблиц начинаются с pg_, лучше не использовать подобные имена во избежание конфликта, если в какой-то будущей версии будет определена системная таблица с тем же именем, что и ваша. (С путем поиска по умолчанию неполная ссылка на имя вашей таблицы будет восприниматься как обращение к системной таблице.) Системные таблицы будут и дальше следовать соглашению об именах, начинающихся с pg_, поэтому они не будут конфликтовать с неполными именами пользовательских таблиц, если пользователи не используют префикс pg_.

Шаблоны использования

Схемы можно применять для организации данных различными способами. Безопасный шаблон использования схемы не позволяет ненадежным пользователям изменять поведение запросов других пользователей. Когда база данных не применяет безопасный шаблон использования схемы, пользователям, желающим безопасно запросить объекты этой базы данных, придется предпринимать в начале каждого сеанса защитные меры. В частности, они будут начинать каждый сеанс, устанавливая в search_path пустую строку или иным образом удаляя оттуда схемы, доступные для записи обычным пользователям. Существует несколько шаблонов использования, с легкостью поддерживаемых конфигурацией по умолчанию:

Ограничить обычных пользователей частными схемами. Для этого выполните REVOKE CREATE ON SCHEMA public FROM PUBLIC и создайте для каждого пользователя схему с его же именем. Напомним, что путь поиска по умолчанию начинается с имени $user, которое преобразуется в имя пользователя. Поэтому, если у каждого пользователя есть отдельная схема, они по умолчанию получают доступ к своим схемам. После принятия этого шаблона в базе данных, куда уже вошли ненадежные пользователи, рассмотрите возможность проверки схемы public на предмет наличия объектов с именами как у объектов в схеме pg_catalog. Этот шаблон является безопасным шаблоном использования схемы, за исключением случаев, когда недоверенный пользователь является владельцем базы данных или имеет право CREATEROLE — тогда никакой безопасный шаблон использования схемы невозможен.

Удалить схему public из пути поиска по умолчанию, изменив qhb.conf или введя команду ALTER ROLE ALL SET search_path = "$user". При этой все пользователи сохраняют возможность создавать объекты в схеме public, но выбирать эти объекты возможно, только используя полные имена. Тогда как обращения к таблицам по полному имени вполне допустимы, вызовы функций в схеме public будут небезопасными или ненадежными. Если вы создаете в схеме public функции или расширения, используйте вместо этого первый шаблон. Иначе, как и в первом шаблоне, это безопасно за исключением случаев, когда ненадежный пользователь является владельцем базы данных или имеет право CREATEROLE.

Сохранить поведение по умолчанию. Все пользователи неявно имеют доступ к схеме public. Это моделирует ситуацию, когда схемы вообще недоступны, обеспечивая плавный переход из среды без схем. Однако это не безопасный шаблон. Он допустим только в том случае, если в базе данных имеется всего один или несколько доверяющих друг другу пользователей.

Какой бы шаблон вы не выбрали, при установке разделяемых приложений (таблиц, которые будут использоваться всеми, дополнительных функции, предоставленных третьими лицами, и т. д.) поместите их в отдельные схемы. Не забудьте предоставить соответствующие права, чтобы открыть доступ к ним другим пользователям. Тогда пользователи смогут обращаться к этим дополнительным объектам, указывая в их именах имя схемы, или при желании помещать эти дополнительные схемы в свой путь поиска.

Возможность переноса

В стандарте SQL не существует концепции, что объекты в одной схеме могут принадлежать разным пользователям. Более того, некоторые реализации СУБД не позволяют создавать схемы, имена которых отличаются от имен их владельцев. Фактически в СУБД, реализующей только базовую поддержку схемы, указанную в стандарте, понятия схемы и пользователя почти равнозначны. Поэтому многие пользователи считают, что в действительности полные имена записываются как имя_пользователя.имя_таблицы. Именно так будет вести себя QHB, если вы создадите схемы для каждого пользователя.

Кроме того, в стандарте SQL отсутствует понятие схемы public. Для максимального соответствия стандарту использовать схему public не следует.

Конечно, некоторые СУБД SQL могут вообще не реализовывать схемы или предоставлять поддержку пространства имен, разрешая (возможно, с ограничениями) перекрестный доступ к базам данных. Если вам потребуется работать с этими системами, максимальной переносимости можно будет достичь, если вообще не использовать схемы.

Наследование

QHB реализует наследование таблиц, что может быть полезным инструментом для разработчиков баз данных. (SQL:1999 и более поздние версии определяют возможность наследования типов, которая во многих отношениях отличается от описанных здесь функциональностей.)

Давайте начнем с примера: предположим, что мы пытаемся построить модель данных для городов. В каждом штате много городов, но только одна столица. Мы хотим иметь возможность быстро получить столицу для любого конкретного штата. Это можно сделать, создав две таблицы: одну для столиц штатов, другую для городов, которые не являются столицами. Но что происходит, когда мы хотим запросить данные о городе, независимо от того, является он столицей или нет? Возможность наследования может помочь решить эту проблему. Мы определяем таблицу capitals так, чтобы она наследовала cities:

CREATE TABLE cities (
    name            text,
    population      float,
    altitude        int     -- в футах
);

CREATE TABLE capitals (
    state           char(2)
) INHERITS (cities);

В этом случае таблица capitals наследует все столбцы своей родительской таблицы cities. Столицы штатов также имеют дополнительный столбец state, в котором указан их штат.

В QHB таблица может наследоваться от нуля и более других таблиц, а запрос может ссылаться либо на все строки таблицы, либо на все строки таблицы плюс всех ее потомков. Последнее является поведением по умолчанию. Например, следующий запрос находит названия всех городов, включая столицы штатов, которые расположены на высоте более 500 футов:

SELECT name, altitude
    FROM cities
    WHERE altitude > 500;

Учитывая пример данных из руководства QHB (см. раздел Введение главы «Язык SQL. Общие сведения»), этот запрос возвращает:

name      | altitude
----------+----------
Las Vegas |     2174
Mariposa  |     1953
Madison   |      845

С другой стороны, следующий запрос находит все города, которые не являются столицами штатов и расположены на высоте более 500 футов:

SELECT name, altitude
    FROM ONLY cities
    WHERE altitude > 500;

   name    | altitude
-----------+----------
 Las Vegas |     2174
 Mariposa  |     1953

Здесь ключевое слово ONLY указывает, что запрос должен применяться только к cities, но не к таблицам ниже ее в иерархии наследования. Многие из команд, которые мы уже обсуждали — SELECT, UPDATE и DELETE — поддерживают ключевое слово ONLY.

Кроме того, можно написать имя таблицы с завершающим *, чтобы явно указать, что включены и дочерние таблицы:

SELECT name, altitude
    FROM cities*
    WHERE altitude > 500;

Запись * не обязательна, так как по умолчанию всегда подразумевается это поведение.

В некоторых случаях можно узнать, из какой таблицы поступила конкретная строка. В каждой таблице есть системный столбец с именем tableoid, в котором можно посмотреть идентификатор исходной таблицы:

SELECT c.tableoid, c.name, c.altitude
FROM cities c
WHERE c.altitude > 500;

этот запрос возвращает:

tableoid |   name    | altitude
----------+-----------+----------
   139793 | Las Vegas |     2174
   139793 | Mariposa  |     1953
   139798 | Madison   |      845

(Если вы попытаетесь воспроизвести этот пример, вы, вероятно, получите в OID другие числа.) Объединив oid с pg_class, можно увидеть фактические имена таблиц:

SELECT p.relname, c.name, c.altitude
FROM cities c, pg_class p
WHERE c.altitude > 500 AND c.tableoid = p.oid;

этот запрос возвращает:

relname  |   name    | altitude
----------+-----------+----------
cities   | Las Vegas |     2174
cities   | Mariposa  |     1953
capitals | Madison   |      845

Другой способ получить тот же эффект — использовать псевдоним типа regclass, который выведет OID таблицы в символьном виде:

SELECT c.tableoid::regclass, c.name, c.altitude
FROM cities c
WHERE c.altitude > 500;

Наследование не распространяет автоматически данные из команд INSERT или COPY в другие таблицы в иерархии наследования. В нашем примере следующий оператор INSERT завершится ошибкой:

INSERT INTO cities (name, population, altitude, state)
VALUES ('Albany', NULL, NULL, 'NY');

Можно надеяться, что данные будут каким-то образом перенаправлены в таблицу capitals, но этого не происходит: INSERT всегда вставляет данные точно в указанную таблицу. В некоторых случаях возможно перенаправить добавление с помощью правила перезаписи запросов. Однако в приведенном выше примере это не поможет, поскольку таблица cities не содержит столбец state, и поэтому команда будет отвергнута до того, как можно будет применить это правило.

Все проверочные ограничения и ограничения NOT NULL родительской таблицы автоматически наследуются ее дочерними таблицами, если явно не указано какое-либо предложение NO INHERIT. Другие типы ограничений (уникальности, первичного и внешнего ключей) не наследуются.

Таблица может наследоваться от нескольких родительских таблиц, и в этом случае она будет объединять в себе столбцы, определенные родительскими таблицами. К ним добавляются все столбцы, объявленные в определении дочерней таблицы. Если в нескольких родительских таблицах или в определении как родительской, так и дочерней таблицы встречаются столбцы с одинаковым именем, то эти столбцы «сливаются» таким образом, чтобы в дочерней таблице был только один такой столбец. Сливаемые столбцы должны иметь одинаковые типы данных, иначе возникает ошибка. Наследуемые проверочные ограничения и ограничения NOT NULL сливаются аналогичным образом. Так, например, слитный столбец будет помечен как NOT NULL, если любое из определений столбцов, из которых он получен, помечено как NOT NULL. Проверочные ограничения сливаются, если они имеют одинаковые имена, но если их условия различаются, слияние завершится ошибкой.

Отношение наследования между таблицами обычно устанавливается при создании дочерней таблицы с помощью предложения INHERITS оператора CREATE TABLE. Как вариант, к таблице, которая уже определена сообразным способом, можно добавить новое родительское отношение, используя вариант ALTER TABLE с предложением INHERIT. Для этого новая дочерняя таблица уже должна включать столбцы с теми же именами и типами, что и у столбцов родительской таблицы. Кроме того она должна включать проверочные ограничения с теми же именами и проверочными выражениями, что и у родительской. Схожим образом можно удалить из дочернего элемента наследственную связь, используя вариант ALTER TABLE с предложением NO INHERIT. Динамическое добавление и удаление таких наследственных связей может быть полезно, когда отношение наследования используется для партиционирования таблиц (см. раздел Партиционирование таблиц).

Одним из удобных способов создания совместимой таблицы, которая впоследствии станет новым потомком, является использование предложения LIKE в команде CREATE TABLE. Это создает новую таблицу с теми же столбцами, что и у исходной таблицы. Если в исходной таблице есть какие-либо проверочные ограничения, следует указать в LIKE параметр INCLUDING CONSTRAINTS, поскольку чтобы считаться совместимым, новый потомок должен иметь ограничения, соответствующие родительским.

Родительскую таблицу нельзя удалить, пока существуют ее потомки. Также нельзя удалить или изменить столбцы или проверочные ограничения дочерних таблиц, если они унаследованы от каких-либо родительских таблиц. Если требуется удалить таблицу и всех ее потомков, это легко сделать, удалив родительскую таблицу с параметром CASCADE (см. раздел Отслеживание зависимостей).

Команда ALTER TABLE будет распространять вниз по иерархии наследования любые изменения в определениях данных столбцов и в проверочных ограничениях. Опять же, удаление столбцов, зависящих от других таблиц, возможно только с помощью параметра CASCADE. ALTER TABLE следует тем же правилам слияния и отклонения дубликатов столбцов, которые применяются при выполнении CREATE TABLE.

Унаследованные запросы выполняют проверки прав доступа только для родительской таблицы. Так, например, предоставление разрешения UPDATE для таблицы cities подразумевает также разрешение на изменение строк в таблице capitals при обращении к ним через cities. Это сохраняет видимость того, что данные находятся (также) в родительской таблице. Но напрямую таблицу capitals нельзя изменить без дополнительных прав. Аналогичным образом политики защиты строк родительской таблицы (см. раздел Политики защиты строк) применяются к строкам, приходящим из дочерних таблиц при выполнении унаследованного запроса. Политики дочерней таблицы, если таковые имеются, применяются только в том случае, если ее имя явно указано в запросе; при этом любые политики, связанные с его родителем (или родителями), игнорируются.

Внешние таблицы (см. раздел Сторонние данные) также могут быть частью иерархий наследования как родительские или дочерние таблицы, так же, как и обычные. Если сторонняя таблица является частью иерархии наследования, то любые операции, не поддерживаемые сторонней таблицей, не поддерживаются и во всей иерархии.

Ограничения применимости

Обратите внимание, что не все команды SQL способны работать в иерархиях наследования. Команды, используемые для запроса или изменения данных либо изменения схемы (например, SELECT, UPDATE, DELETE, большинство вариантов ALTER TABLE, но не INSERT или ALTER TABLE ... RENAME), обычно по умолчанию включают дочерние таблицы и поддерживают указание ONLY, чтобы исключить их. Команды, выполняющие обслуживание и настройку базы данных (например REINDEX, VACUUM), обычно работают только с отдельными физическими таблицами и не поддерживают рекурсию по иерархиям наследования. Соответствующее поведение каждой отдельной команды описывается на ее справочной странице (глава Команды SQL).

Серьезным ограничением возможности наследования является то, что индексы (включая ограничения уникальности) и ограничения внешних ключей применяются только к отдельным таблицам, но не к их потомкам. Это верно как для ссылающихся, так и для ссылочных сторон ограничения внешнего ключа. Таким образом, применительно к вышеописанному примеру:

Если мы объявляем cities.name с ограничением UNIQUE или PRIMARY KEY, это не помешает таблице capitals иметь строки с именами, дублирующими строки в cities. И эти дублирующиеся строки по умолчанию будут отображаться в запросах из cities. На самом деле, по умолчанию таблица capitals вообще не имеет ограничения уникальности, и поэтому может содержать несколько строк с одинаковыми именами. Можно добавить для capitals ограничение уникальности, но это не предотвратит дублирование относительно cities.

Точно так же, если бы мы указали, что cities.name ссылается (REFERENCES) на какую-то другую таблицу, это ограничение не стало бы автоматически распространяться на capitals. В этом случае ситуацию можно исправить, вручную добавив такое же ограничение REFERENCES в capitals.

Указание на то, что столбец другой таблицы ссылается (REFERENCES) на cities(name), позволит этой другой таблице содержать названия городов, но не столиц. Для этого случая хорошего решения нет.

Некоторая функциональность, не реализованная для иерархий наследования, реализована для декларативного партиционирования. Тщательно взвесьте все за и против, прежде чем принять решение о том, подойдет ли для вашего приложения партиционирование с помощью устаревшего наследования.

Партиционирование таблиц

QHB поддерживает базовое партиционирование таблиц. В этом разделе описывается, почему и как нужно реализовать партиционирование при проектировании вашей базы данных.

Обзор

Партиционирование позволяет логически разделить одну большую таблицу на более мелкие физические части. Партиционирование может дать несколько преимуществ:

В определенных ситуациях можно значительно улучшить производительность запросов, особенно когда большинство часто используемых строк таблицы находятся в одной партиции или небольшом числе партиций. По сути партиционирование заменяет верхние уровни деревьев индексов, повышая вероятность того, что интенсивно используемые части этих индексов поместятся в памяти.

Когда запросы или изменения обращаются к значительной доле одной партиции, производительность можно улучшить, воспользовавшись преимуществом последовательного сканирования этой партиции вместо поиска по индексу, которое потребовало бы чтения с произвольным обращением к данным, разбросанным по всей таблице.

Массовые загрузки и удаления можно выполнить путем добавления или удаления партиций, если такой шаблон использования был предусмотрен при проектировании партиционирования. Выполнение команды ALTER TABLE DETACH PARTITION или удаление отдельной партиции с помощью DROP TABLE происходят намного быстрее, чем массовая операция. Кроме того, эти команды полностью исключают издержки, связанные с выполнением VACUUM после массовой операции DELETE.

Редко используемые данные можно перенести на более дешевые и медленные носители.

Эти преимущества обычно имеют смысл, только если без разбиения таблица будет очень большой. Точный размер, по достижении которого таблица выиграет от партиционирования, зависит от приложения, хотя практическое правило заключается в том, что размер таблицы должен превысить объем физической памяти сервера баз данных.

QHB предлагает встроенную поддержку для следующих форм партиционирования:

Партиционирование по диапазону
Таблица разбивается на «диапазоны», определяемые ключевым столбцом или набором столбцов, без перекрытия диапазонов значений, назначенных различным партициям. Например, можно разделить по диапазонам дат или идентификаторов для определенных бизнес-объектов. Границы каждого диапазона считаются включающими нижнее значение и исключающими верхнее. Например, если у одной партиции задан диапазон значений от 1 до 10, а у следующей — от 10 до 20, то значение 10 относится ко второй партиции, а не к первой.

Партиционирование по списку
Таблица разбивается путем явного перечисления, какие значения ключа должны фигурировать в каждой партиции.

Партиционирование по хешу
Таблица разбивается путем указания модуля и остатка для каждой партиции. Каждая партиция будет содержать строки, для которых при делении значения хеша ключа разбиения на указанный модуль получится заданный остаток.

Если вашему приложению нужно использовать другие, не перечисленные выше, формы партиционирования, вместо них можно использовать альтернативные методы, например наследование и представления UNION ALL. Такие методы предлагают гибкость, но, в сравнении с партиционированием, не дают некоторых преимуществ в производительности.

Декларативное партиционирование

QHB позволяет декларировать, как поделить таблицу на партиции. Такая разделенная таблица называется партиционированной таблицей. Эта декларация включает метод партиционирования (один из описанных выше) и список столбцов или выражений, которые будут использоваться в качестве ключа разбиения.

Сама партиционированная таблица является «виртуальной» и как таковая не хранится. Вместо этого хранилище относится к ее партициям, которые во всем остальном являются обычными таблицами, просто связанными с партиционированной. В каждой партиции хранится подмножество данных, определенное ее границами партиции. Все строки, добавленные в партиционированную таблицу, будут перенаправлены в соответствующие партиции в зависимости от значений столбца(ов) ключа разбиения. Если при изменении ключа разбиения строки она перестанет удовлетворять значениям границ исходной партиции, эта строка будет перемещена в другую партицию.

Сами партиции могут быть определены как партиционированные таблицы, обусловливая вложенное партиционирование. Хотя у всех партиций должны быть те же столбцы, что и у их партиционированной родительской таблицы, они могут иметь собственные индексы, ограничения и значения по умолчанию, отличные от других партиций. Подробную информацию о создании партиционированных таблиц и партиций см. на справочной странице команды CREATE TABLE.

Невозможно превратить обычную таблицу в партиционированную или наоборот. Тем не менее, в партиционированную таблицу можно добавить в качестве партиции уже существующую обычную или партиционированную таблицу или удалить партицию из партиционированной таблицы, превратив ее в отдельную таблицу; это может упростить и ускорить многие процессы обслуживания. Более подробно вложенные команды ATTACH PARTITION и DETACH PARTITION описаны на справочной странице команды ALTER TABLE.

Кроме того, партиции могут быть сторонними таблицами, хотя при этом требуется немалая доля осмотрительности, поскольку именно пользователь отвечает за то, чтобы содержимое сторонней таблицы удовлетворяло правилу партиционирования. Также имеются и другие ограничения. Подробную информацию см. на справочной странице команды CREATE FOREIGN TABLE.

Пример декларативного партиционирования

Предположим, мы конструируем базу данных для большой компании по производству мороженого. Компания каждый день измеряет пиковые температуры, а также продажи мороженого в каждом регионе. Концептуально нам нужна таблица вроде этой:

CREATE TABLE measurement (
    city_id         int not null,
    logdate         date not null,
    peaktemp        int,
    unitsales       int
);

Мы знаем, что большинство запросов будут обращаться только к данным за последнюю неделю, месяц или квартал, поскольку основное использование этой таблицы будет заключаться в подготовке онлайн-отчетов для руководства. Чтобы уменьшить объем старых данных, которые необходимо хранить, мы решили сохранить данные за последние 3 года. В начале каждого месяца мы будем удалять данные самого старого месяца. В этой ситуации мы можем использовать партиционирование для удовлетворения всех наших требований к таблице результатов измерений.

Чтобы использовать декларативное партиционирование в этом случае, выполните следующие действия:

Создайте таблицу measurement как партиционированную таблицу, указав предложение PARTITION BY, включающее метод партиционирования (в данном случае RANGE) и список столбцов, используемых в качестве ключа разбиения.

CREATE TABLE measurement (
    city_id         int not null,
    logdate         date not null,
    peaktemp        int,
    unitsales       int
) PARTITION BY RANGE (logdate);

Создайте партиции. В определении каждой партиции должны быть заданы границы, соответствующие методу партиционирования и ключу разбиения родительской таблицы. Обратите внимание, что указание границ таким образом, при котором значения новой партиции будут пересекаться со значениями в одной или нескольких существующих партициях, приведет к ошибке.

Созданные таким образом партиции во всех смыслах являются обычными таблицами QHB (или, возможно, сторонними таблицами). Для каждой партиции можно по отдельности указать табличное пространство и параметры хранения.

Для нашего примера в каждой партиции должны храниться данные за один месяц, чтобы соответствовать требованию об удалении данных по месяцам. Таким образом, команды могут выглядеть так:

CREATE TABLE measurement_y2006m02 PARTITION OF measurement
    FOR VALUES FROM ('2006-02-01') TO ('2006-03-01');

CREATE TABLE measurement_y2006m03 PARTITION OF measurement
    FOR VALUES FROM ('2006-03-01') TO ('2006-04-01');

...
CREATE TABLE measurement_y2007m11 PARTITION OF measurement
    FOR VALUES FROM ('2007-11-01') TO ('2007-12-01');

CREATE TABLE measurement_y2007m12 PARTITION OF measurement
    FOR VALUES FROM ('2007-12-01') TO ('2008-01-01')
    TABLESPACE fasttablespace;

CREATE TABLE measurement_y2008m01 PARTITION OF measurement
    FOR VALUES FROM ('2008-01-01') TO ('2008-02-01')
    WITH (parallel_workers = 4)
    TABLESPACE fasttablespace;

(Вспомните, что у соседних партиций может быть общее значение границы, поскольку верхние границы воспринимаются как исключающие.)

Чтобы реализовать вложенное партиционирование, укажите предложение PARTITION BY в командах, используемых для создания отдельных партиций, например:

CREATE TABLE measurement_y2006m02 PARTITION OF measurement
    FOR VALUES FROM ('2006-02-01') TO ('2006-03-01')
    PARTITION BY RANGE (peaktemp);

После создания партиции measurement_y2006m02 любые данные, добавленные в таблицу measurement, которая сопоставлена с measurement_y2006m02 (или данные, непосредственно вставленные в measurement_y2006m02, при условии, что это удовлетворяет ограничению ее партиции), будут затем перенаправлены в одну из партиций на основе столбца peaktemp. Указанный ключ разбиения может перекрываться с ключом разбиения родительской таблицы, хотя при указании границ вложенной партиции следует соблюдать осторожность, чтобы принимаемое ей множество данных входило в подмножество, допускаемое собственными границами партиции; система не пытается проверить, так ли это на самом деле.

Добавление данных в родительскую таблицу, не сопоставленную с одной из существующих партиций, вызовет ошибку; подходящую партицию следует создать вручную.

Вручную создавать табличные ограничения, описывающие границы партиций, нет необходимости. Подобные ограничения будут созданы автоматически.

Создайте для партиционированной таблицы индекс по ключевому столбцу (или столбцам), а также любые другие индексы, которые могут понадобиться. (В ключевом индексе нет особой необходимости, но в большинстве сценариев он полезен). Это автоматически создает соответствующий индекс для каждой партиции, и все партиции, которые вы создаете или присоединяете позже, тоже будут содержать индекс. Индекс или ограничение уникальности, декларируемые для партиционированной таблицы, являются «виртуальными» наравне с самой этой таблицей: фактические данные находятся в дочерних индексах отдельных таблиц-партиций.

CREATE INDEX ON measurement (logdate);

Убедитесь, что параметр конфигурации enable_partition_pruning в qhb.conf не выключен. Если это так, запросы не будут оптимизированы должным образом.

В приведенном выше примере мы будем ежемесячно создавать новую партицию, поэтому было бы разумно написать скрипт, автоматически генерирующий необходимый код DDL.

Обслуживание партиций

Обычно набор партиций, созданный при первоначальном определении таблицы, не предполагается оставлять статичным. Обычно требуется удалить партиции, содержащие старые данные, и периодически добавлять новые партиции для новых данных. Одним из наиболее важных преимуществ партиционирования является именно то, что оно позволяет практически мгновенно выполнить эту в противном случае трудную задачу, манипулируя структурой партиций, вместо того чтобы физически перемещать большие объемы данных.

Самый простой вариант удаления старых данных — удалить ненужную партицию:

DROP TABLE measurement_y2006m02;

Таким образом можно очень быстро удалить миллионы записей, потому что не нужно индивидуально удалять каждую запись. Однако обратите внимание, что приведенная выше команда требует установления блокировки ACCESS EXCLUSIVE на родительскую таблицу.

Другой вариант, зачастую более предпочтительный, состоит в том, чтобы удалить партицию из партиционированной таблицы, но сохранить к ней доступ как к самостоятельной таблице. Такую команду можно записать двумя способами:

ALTER TABLE measurement DETACH PARTITION measurement_y2006m02;
ALTER TABLE measurement DETACH PARTITION measurement_y2006m02 CONCURRENTLY;

Это позволяет выполнять дальнейшие операции с данными до удаления таблицы. Например, зачастую это подходящее время для проведения резервного копирования данных с помощью COPY, qhb_dump или подобных им инструментов. Также в этот момент может быть удобно агрегировать данные в более компактные форматы, выполнить с ними другие манипуляции или запустить формирование отчетов. Первая форма этой команды требует блокировки для ACCESS EXCLUSIVE родительской таблицы. Добавление квалификатора CONCURRENTLY, как во второй форме, позволяет требовать для операции отсоединения только блокировку SHARE UPDATE EXCLUSIVE для родительской таблицы, но подробнее эти ограничения описаны в [ALTER TABLE ... DETACH PARTITION].

Схожим образом мы можем добавить новую партицию для обработки новых данных. Мы можем создать пустую партицию в партиционированной таблице так же, как выше были созданы исходные партиции:

CREATE TABLE measurement_y2008m02 PARTITION OF measurement
    FOR VALUES FROM ('2008-02-01') TO ('2008-03-01')
    TABLESPACE fasttablespace;

В качестве альтернативы иногда удобнее создать новую таблицу вне структуры партиций, а затем сделать ей подходящую партицию. Это позволяет загружать, проверять и преобразовывать данные до их появления в партиционированной таблице. Избежать утомительного повторения определения родительской таблицы помогает вариант CREATE TABLE ... LIKE:

CREATE TABLE measurement_y2008m02
  (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS)
  TABLESPACE fasttablespace;

ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
   CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );

\copy measurement_y2008m02 from 'measurement_y2008m02'
-- возможно, какая-то дополнительная работа по подготовке данных

ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02
    FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' );

Команда ATTACH PARTITION требует установки блокировки SHARE UPDATE EXCLUSIVE для партиционированной таблицы.

Перед запуском команды ATTACH PARTITION рекомендуется создать ограничение CHECK для присоединяемой таблицы, соответствующее ожидаемому ограничению партиции, как показано выше. Таким образом, система сможет пропустить сканирование, которому в противном случае потребуется проверить неявное ограничение партиции. Без ограничения CHECK таблица будет сканироваться для проверки ограничения партиции, при этом удерживая блокировку ACCESS EXCLUSIVE для этой партиции. После завершения ATTACH PARTITION рекомендуется сбросить ставшее лишним ограничение CHECK. Если присоединяемая таблица сама является партиционированной, то каждая из ее вложенных партиций будет рекурсивно блокироваться и сканироваться до тех пор, пока не встретится подходящее ограничение CHECK или не будут достигнуты листовые партиции.

Аналогично, если в партиционированной таблице есть партиция DEFAULT, рекомендуется создать ограничение CHECK, исключающее ограничение присоединяемой партиции. Если этого не сделать, то партиция DEFAULT будет просканирована, чтобы убедиться, что она не содержит записей, которые должны быть расположены в присоединяемой партиции. Эта операция будет осуществляться при удержании блокировки ACCESS EXCLUSIVE на партиции DEFAULT. Если сама партиция DEFAULT является партиционированной таблицей, то каждая из ее партиций будет рекурсивно проверяться таким же образом, как и присоединяемая таблица, как упоминалось выше.

Как объяснено выше, для партиционированных таблиц можно создавать индексы так, чтобы они автоматически применялись ко всей иерархии. Это очень удобно, поскольку будут проиндексированы не только существующие партиции, но и любые партиции, которые будут созданы в будущем. Единственное ограничение заключается в том, что при создании такого партиционированного индекса невозможно использовать квалификатор CONCURRENTLY. Чтобы избежать длительных блокировок, можно использовать для партиционированной таблицы CREATE INDEX ON ONLY; такой индекс помечается как недопустимый, и партиции не получают индекс автоматически. Индексы для партиций можно создавать отдельно с помощью CONCURRENTLY, а затем присоединять к индексу на родительской таблице с помощью ALTER INDEX .. ATTACH PARTITION. Как только индексы для всех партиций присоединяются к родительскому индексу, тот автоматически помечается как допустимый. Пример:

CREATE INDEX measurement_usls_idx ON ONLY measurement (unitsales);

CREATE INDEX measurement_usls_200602_idx
    ON measurement_y2006m02 (unitsales);
ALTER INDEX measurement_usls_idx
    ATTACH PARTITION measurement_usls_200602_idx;
...

Этот метод также можно использовать с ограничениями UNIQUE и PRIMARY KEY; индексы создаются неявно при создании ограничения. Пример:

ALTER TABLE ONLY measurement ADD UNIQUE (city_id, logdate);

ALTER TABLE measurement_y2006m02 ADD UNIQUE (city_id, logdate);
ALTER INDEX measurement_city_id_logdate_key
    ATTACH PARTITION measurement_y2006m02_city_id_logdate_key;
...

Ограничения

К партиционированным таблицам применяются следующие ограничения:

Ограничения уникальности (и, следовательно, первичных ключей) для партиционированных таблиц должны включать все столбцы ключа разбиения. Это ограничение существует, потому что отдельные индексы, образующие ограничение, могут напрямую обеспечивать уникальность только в своих партициях. Поэтому сама структура партиционирования должна гарантировать отсутствие дубликатов в разных партициях.

Невозможно создать исключающее ограничение, охватывающее всю партиционированную таблицу. Такое ограничение можно только наложить на каждую листовую партицию по отдельности. Это, опять же, проистекает из невозможности установить межпартициальные ограничения.

Триггеры BEFORE ROW для INSERT не могут изменить партицию, которая в итоге станет пунктом назначения для новой строки.

Смешивание временных и постоянных отношений в одном дереве партиционирования не допускается. Следовательно, если партиционированная таблица является постоянной, то такими же должны быть и ее партиции; то же касается временных партиционированных таблиц. При использовании временных отношений все члены дерева партиционирования должны быть из одного сеанса.

Отдельные партиции связываются со своими партиционированными таблицами, негласно используя наследование. Однако с декларативно партиционированными таблицами или из партициями нельзя использовать все общие функциональные возможности наследования, как описано ниже. А именно, партиция не может иметь никаких других родителей, кроме партиционированной таблицы, к которой она относится, равно как и таблица не может наследоваться сразу и от партиционированной таблицы, и от обычной. Это означает, что партиционированные таблицы и их партиции никогда не делят иерархию наследования с обычными таблицами.

Поскольку иерархия партиционирования, состоящая из партиционированной таблицы и ее партиций, по-прежнему остается иерархией наследования, она содержит tableoid и на нее распространяются все обычные правила наследования, описанные в разделе Наследование, за некоторыми исключениями:

Партиции не могут иметь столбцы, отсутствующие в родительской таблице. Такие столбцы невозможно ни указать при создании партиций с помощью CREATE TABLE, ни добавить в партиции постфактум с помощью ALTER TABLE. Таблицы можно добавить в качестве партиций с помощью ALTER TABLE ... ATTACH PARTITION, только если их столбцы полностью соответствуют родительским.

Ограничения CHECK и NOT NULL, относящиеся к партиционированной таблице, всегда наследуются всеми ее партициями. Ограничения CHECK, помеченные как NO INHERIT, в партиционированных таблицах создавать нельзя. Кроме того, невозможно снять ограничение NOT NULL со столбца партиции, если такое же ограничение имеется в родительской таблице.

Использование указания ONLY при добавлении или удалении ограничения только в партиционированной таблице поддерживается, только пока в ней нет партиций. Если партиции существуют, применение ONLY вызовет ошибку. Вместе с тем, ограничения в самих партициях можно как добавлять, так и удалять (если они отсутствуют в родительской таблице).

Поскольку сама партиционированная таблица не содержит никаких данных, при попытках использовать для нее TRUNCATE ONLY всегда будет возвращаться ошибка.

Партиционирование через наследование

Хотя встроенное декларативное партиционирование подходит для большинства общих случаев использования, в некоторых обстоятельствах может оказаться полезным более гибкий подход. Партиционирование можно реализовать с помощью наследования таблиц, что позволяет использовать несколько особенностей, не поддерживаемых декларативным партиционированием, например:

Для декларативного партиционирования у партиций должен быть ровно тот же набор столбцов, что и у партиционированной таблицы, тогда как при наследовании таблиц в дочерних таблицах могут содержаться дополнительные столбцы, отсутствующие в родительской.

Наследование таблиц допускает множественное наследование.

Декларативное партиционирование поддерживает только партиционирование по диапазонам, спискам и хешу, тогда как наследование таблиц позволяет разделять данные по критерию, выбранному пользователем. (Тем не менее обратите внимание, что если исключающее ограничение не может эффективно отсечь дочерние таблицы из планов запросов, производительность этих запросов может быть весьма низкой).

Пример партиционирования через наследование

В этом примере формируется структура партиционирования, равнозначная структуре из примера декларативного партиционирования выше. Выполните следующие действия:

Создайте «корневую» таблицу, от которой будут наследоваться все «дочерние» таблицы. Эта таблица не будет содержать данных. Не определяйте для этой таблицы никакие проверочные ограничения, если только вы не собираетесь применять их и ко всем дочерним таблицам. Также нет смысла определять для нее какие-либо индексы или ограничения уникальности. В нашем примере корневая таблица — это таблица measurement, как она была определена изначально:

CREATE TABLE measurement (
city_id         int not null,
logdate         date not null,
peaktemp        int,
unitsales       int
);

Создайте несколько «дочерних» таблиц, каждая из которых наследуется от корневой таблицы. Обычно в этих таблицах нет добавочных столбцов, кроме набора, унаследованного от корневой. Как и при декларативном партиционировании, эти таблицы во всех отношениях являются обычными таблицами QHB (или сторонними таблицами).

CREATE TABLE measurement_y2006m02 () INHERITS (measurement);
CREATE TABLE measurement_y2006m03 () INHERITS (measurement);
...
CREATE TABLE measurement_y2007m11 () INHERITS (measurement);
CREATE TABLE measurement_y2007m12 () INHERITS (measurement);
CREATE TABLE measurement_y2008m01 () INHERITS (measurement);

Добавьте неперекрывающиеся ограничения таблиц в дочерние таблицы, чтобы определить в каждой из них допустимые значения ключей.

Типичные примеры:

CHECK ( x = 1 )
CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
CHECK ( outletID >= 100 AND outletID < 200 )

Убедитесь, что ограничения обеспечивают отсутствие пересечений между значениями ключей, разрешенными в разных дочерних таблицах. Распространенной ошибкой является установка ограничений диапазона следующим образом:

CHECK ( outletID BETWEEN 100 AND 200 )
CHECK ( outletID BETWEEN 200 AND 300 )

Это неправильно, так как неясно, к какой дочерней таблице относится значение ключа 200. Вместо этого было бы лучше создать дочерние таблицы так:

CREATE TABLE measurement_y2006m02 (
    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
) INHERITS (measurement);

CREATE TABLE measurement_y2006m03 (
    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
) INHERITS (measurement);

...
CREATE TABLE measurement_y2007m11 (
    CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
) INHERITS (measurement);

CREATE TABLE measurement_y2007m12 (
    CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
) INHERITS (measurement);

CREATE TABLE measurement_y2008m01 (
    CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
) INHERITS (measurement);

Для каждой дочерней таблицы создайте индекс по ключевому столбцу (или столбцам), а также любые другие индексы, которые могут понадобиться.

CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);

Мы хотим, чтобы приложение могло сказать INSERT INTO measurement... и данные были перенаправлены в соответствующую дочернюю таблицу. Это можно устроить, присоединив к корневой таблице подходящую триггерную функцию. Если данные будут добавляться только к последнему потомку, можно вызвать очень простую триггерную функцию:

CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
    RETURN NULL;
END;
$$
LANGUAGE plpgsql;

После создания функции мы создаем триггер, который вызывает эту триггерную функцию:

CREATE TRIGGER insert_measurement_trigger
    BEFORE INSERT ON measurement
    FOR EACH ROW EXECUTE FUNCTION measurement_insert_trigger();

Эту триггерную функцию следует переопределять ежемесячно, чтобы она всегда указывала на текущую дочернюю таблицу. Однако определение самого триггера обновлять не нужно.

Нам может понадобиться, чтобы при добавлении данных сервер автоматически находил дочернюю таблицу, в которую должна быть добавлена строка. Это можно сделать с помощью более сложной триггерной функции, например:

CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
    IF ( NEW.logdate >= DATE '2006-02-01' AND
         NEW.logdate < DATE '2006-03-01' ) THEN
         INSERT INTO measurement_y2006m02 VALUES (NEW.*);
    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
            NEW.logdate < DATE '2006-04-01' ) THEN
        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
    ...
    ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
            NEW.logdate < DATE '2008-02-01' ) THEN
        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
    ELSE
        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
    END IF;
    RETURN NULL;
END;
$$
LANGUAGE plpgsql;

Определение триггера такое же, как и раньше. Обратите внимание, что каждая проверка IF должна точно соответствовать ограничению CHECK для своей дочерней таблицы.

Хотя эта функция сложнее варианта с одним месяцем, ее не требуется так часто обновлять, поскольку при необходимости ветви можно добавить заранее.

Примечание
Возможно, на практике лучше всего будет проверять самую новую дочернюю таблицу, если большая добавлений идет именно в нее. Для простоты мы показали проверки триггера в том же порядке, что и в других частях этого примера.

Другой подход к перенаправлению добавлений данных в соответствующую дочернюю таблицу заключается в установке в корневой таблице правил вместо триггера. Например:

CREATE RULE measurement_insert_y2006m02 AS
ON INSERT TO measurement WHERE
    ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
DO INSTEAD
    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
...
CREATE RULE measurement_insert_y2008m01 AS
ON INSERT TO measurement WHERE
    ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
DO INSTEAD
    INSERT INTO measurement_y2008m01 VALUES (NEW.*);

Правило гораздо затратнее триггера, но эти затраты относятся ко всему запросу, а не к каждой строке, поэтому такой метод может быть выигрышным при массовом добавлении данных. Однако в большинстве случаев метод с триггером будет обеспечивать лучшую производительность.

Имейте в виду, что COPY игнорирует правила. Если вы хотите использовать для добавления данных эту команду, придется копировать их в правильную дочернюю таблицу, а не непосредственно в корневую. При этом COPY запускает триггеры, поэтому если вы используете вариант с триггерами, эту команду можно применять как обычно.

Другой недостаток варианта с правилами заключается в том, что не существует простого способа вызвать ошибку, если набор правил не охватывает дату добавления; вместо этого данные будут просто отправлены в корневую таблицу.

Убедитесь, что в qhb.conf не выключен параметр конфигурации constraint_exclusion; иначе дочерние таблицы могут сканироваться, когда в этом нет необходимости.

Как можно видеть, сложная иерархия таблиц может потребовать значительный объем кода DDL. В приведенном выше примере требуется ежемесячно создавать новую дочернюю таблицу, поэтому было бы разумно написать скрипт, автоматически генерирующий необходимый код DDL.

Обслуживание таблиц, партиционированных через наследование

Чтобы быстро удалить старые данные, просто удалите дочернюю таблицу, которая больше не нужна:

DROP TABLE measurement_y2006m02;

Чтобы удалить дочернюю таблицу из таблицы иерархии наследования, но сохранить доступ к ней как к отдельной таблице:

ALTER TABLE measurement_y2006m02 NO INHERIT measurement;

Чтобы добавить новую дочернюю таблицу для обработки новых данных, создайте пустую дочернюю таблицу так же, как выше были созданы исходные дочерние таблицы:

CREATE TABLE measurement_y2008m02 (
    CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' )
) INHERITS (measurement);

Как вариант, можно создать и заполнить новую дочернюю таблицу, а потом добавить ее в иерархию таблиц. Это позволит загружать, проверять и преобразовывать данные до того, как они станут видимыми для запросов в родительской таблице.

CREATE TABLE measurement_y2008m02
  (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
   CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
\copy measurement_y2008m02 from 'measurement_y2008m02'
-- возможно, какая-то дополнительная работа по подготовке данных
ALTER TABLE measurement_y2008m02 INHERIT measurement;

Предупреждения относительно партиционирования через наследование

К партиционированию, реализованному через наследование, применяются следующие предупреждения:

Не существует автоматического способа проверить, являются ли все ограничения CHECK взаимоисключающими. Безопаснее создать код, генерирующий дочерние таблицы и создающий и/или изменяющий связанные объекты, чем каждый раз делать это вручную.

Индексы и ограничения внешнего ключа применяются к отдельным таблицам, а не к наследующим от них потомкам, поэтому следует учитывать некоторые касающиеся их ограничения применимости.

Показанные здесь схемы предполагают, что значения ключевых столбцов строки никогда не меняются или, по крайней мере, не меняются настолько, чтобы требовать их перемещения в другую партицию. Команда UPDATE, которая попытается сделать это, закончится с ошибкой из-за ограничений CHECK. Если нужно обработать такие случаи, можно поместить в дочерние таблицы подходящие триггеры изменения, но это значительно усложняет управление общей структурой.

Если вы используете команды VACUUM или ANALYZE вручную, не забывайте, что их нужно запускать для каждой дочерней таблицы по отдельности. Например, подобная команда:

ANALYZE measurement;

будет обрабатывать только корневую таблицу.

Операторы INSERT с предложениями ON CONFLICT вряд ли будут работать должным образом, поскольку действие ON CONFLICT выполняется только в случае нарушений уникальности в указанном целевом отношении, но не в его дочерних отношениях.

Если приложение явно не осведомлено о схеме партиционирования, для трассировки строк в нужную дочернюю таблицу будут необходимы триггеры или правила. Триггеры могут быть сложными в написании и будут намного медленнее, чем трассировка кортежей, выполняемая внутренне посредством декларативного партиционирования.

Отсечение партиций

Отсечение партиций — это метод оптимизации запросов, повышающий производительность для декларативно партиционированных таблиц. Например:

SET enable_partition_pruning = on;                 -- по умолчанию
SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';

Без отсечения партиций вышеупомянутый запрос будет сканировать каждую из партиций таблицы measurement. При включенном отсечении партиций планировщик изучит определение каждой партиции и докажет, что ее не нужно сканировать, поскольку в ней не может быть строк, соответствующих предложению WHERE из запроса. Когда планировщик может доказать это, он исключает (отсекает) партицию из плана запроса.

Используя команду EXPLAIN и параметр конфигурации enable_partition_pruning, можно показать разницу между планом, для которого партиции были отсечены, и тем, для которого они не отсекались. Типичный неоптимизированный план для этого типа установки таблицы:

SET enable_partition_pruning = off;
EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
                                    QUERY PLAN
-----------------------------------------------------------------------------------
 Aggregate  (cost=188.76..188.77 rows=1 width=8)
   ->  Append  (cost=0.00..181.05 rows=3085 width=0)
         ->  Seq Scan on measurement_y2006m02  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)
         ->  Seq Scan on measurement_y2006m03  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)
...
         ->  Seq Scan on measurement_y2007m11  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)
         ->  Seq Scan on measurement_y2007m12  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)
         ->  Seq Scan on measurement_y2008m01  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)

Некоторые или все партиции могут использовать сканирование индекса вместо последовательного сканирования всей таблицы, но суть в том, что для ответа на этот запрос вообще нет необходимости сканировать более старые партиции. При включении отсечения партиций мы получаем куда более дешевый план, который даст тот же ответ:

SET enable_partition_pruning = on;
EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
                                    QUERY PLAN
-----------------------------------------------------------------------------------
 Aggregate  (cost=37.75..37.76 rows=1 width=8)
   ->  Seq Scan on measurement_y2008m01  (cost=0.00..33.12 rows=617 width=0)
         Filter: (logdate >= '2008-01-01'::date)

Обратите внимание, что отсечение партиций обусловлено только ограничениями, неявно определенными ключами разбиения, а не наличием индексов. Поэтому нет необходимости определять индексы по ключевым столбцам. Необходимость создания индекса для данной партиции зависит от того, какую ее часть будут сканировать обращающиеся к этой партиции запросы: значительную или малую. Индекс будет полезен в последнем случае, но не в первом.

Отсечение партиций можно провести не только во время планирования данного запроса, но и во время его выполнения. Это полезно, поскольку позволяет отсечь больше партиций, когда предложения содержат выражения, значения которых неизвестны во время планирования запроса (например параметры, определенные в операторе PREPARE), значение, полученное из подзапроса, или параметризованное значение на внутренней стороне соединения с вложенным циклом. Отсечение партиций во время выполнения запроса можно провести в любой из следующих моментов времени:

Во время инициализации плана запроса. Здесь можно выполнить отсечение партиций для значений параметров, которые становятся известны на этапе инициализации выполнения. Партиции, отсеченные на этом этапе, не будут отображаться в запросе EXPLAIN или EXPLAIN ANALYZE. Определить количество удаленных на этом этапе партиций можно, наблюдая за свойством «Subplans Removed» в выводе EXPLAIN.

Во время фактического выполнения плана запроса. Здесь также можно выполнить отсечение партиций, чтобы удалить их с помощью значений, которые становятся известны только во время фактического выполнения запроса. Это включает в себя значения из подзапросов и из параметров времени выполнения, например, значения из параметризованных соединений с вложенным циклом. Поскольку значение этих параметров во время выполнения запроса может изменяться много раз, отсечение партиции выполняется всякий раз, когда изменяется один из используемых для отсечения параметров выполнения. Определение того, были ли партиции отсечены на этом этапе, требует тщательной проверки свойства loops в выводе EXPLAIN ANALYZE. Подпланы, соответствующие различным партициям, могут иметь разные значения, в зависимости от того, сколько раз каждый из них отсекался во время выполнения. Некоторые могут быть показаны, как (never executed) (никогда не выполнялся) если они постоянно отсекались.

Отсечение партиций можно выключить, изменив значение enable_partition_pruning.

Партиционирование и исключение по ограничению

Исключение по ограничению — это метод оптимизации запросов, схожий с отсечением партиций. Хотя он в основном применяется для партиционирования, реализованного с использованием устаревшего метода наследования, он может применяться и для других целей, в том числе с декларативным партиционированием.

Исключение по ограничению работает практически так же, как отсечение партиций, за исключением того, что оно использует ограничения CHECK каждой таблицы – откуда и получило свое название — тогда как отсечение партиций использует границы партиций таблицы, которые существуют только в случае декларативного партиционирования. Другое отличие состоит в том, что исключение ограничений применяется только во время планирования; во время выполнения партиции не удаляются.

В исключении по ограничению используются ограничения CHECK, что делает его медленным по сравнению с отсечением партиций, но иногда это можно использовать как преимущество: поскольку ограничения можно определить даже для декларативно партиционированных таблиц, в дополнение к их внутренним границам партиций, исключение по ограничению может исключить из плана запроса дополнительные партиции.

Значением по умолчанию (и рекомендуемым) для параметра constraint_exclusion является не on (включен) и не off (выключен), а промежуточное значение, называемое partition, при котором метод применяется только к запросам, которые предположительно будут работать с таблицами, партиционированными через наследование. При значении on планировщик будет проверять ограничения CHECK во всех запросах, в том числе простых, в которых вышеупомянутое преимущество вряд ли проявится.

К исключению по ограничению применимы следующие предупреждения:

Исключение по ограничению применяется только при планировании запросов, в отличие от отсечения партиций, которое также может применяться и при их выполнении.

Исключение по ограничению работает только тогда, когда предложение WHERE в запросе содержит константы (или предоставленные извне параметры). Например, сравнение с изменяемой функцией, такой как CURRENT_TIMESTAMP, оптимизировать нельзя, поскольку планировщик не может знать, в какую дочернюю таблицу попадет значение функции во время выполнения.

Делайте ограничения на партиции простыми, иначе планировщик не сможет доказать, что дочерние таблицы необязательно посещать. Используйте простые условия равенства для партиционирования по списку или простые проверки диапазона для партиционирования по диапазону, как показано в предыдущих примерах. Хорошее практическое правило заключается в том, что ограничения партиционирования должны содержать только сравнения столбца(ов) разбиения с константами, где используются операторы, поддерживающие индексы B-деревья, поскольку в ключе разбиения допускаются только столбцы, индексируемые по B-дереву.

Во время исключения по ограничению проверяются все ограничения для всех потомков родительской таблицы, поэтому большое количество потомков может значительно увеличить время планирования запросов. Таким образом, устаревшее партиционирование через наследование будет хорошо работать, пока число дочерних таблиц не перевалит за сто; не пытайтесь применять его с многими тысячами дочерних таблиц.

Оптимальные методы декларативного партиционирования

Выбирать способ партиционирования таблицы следует тщательно, так как неудовлетворительный проект может негативно повлиять на производительность планирования и выполнения запросов.

Одним из наиболее важных проектных решений является выбор столбца (или столбцов), по которым вы партиционируете свои данные. Зачастую лучшим выбором будет партиционирование по столбцу или набору столбцов, которые чаще всего встречаются в предложениях WHERE запросов, выполняемых в партиционированной таблице. Те предложения WHERE, которые совместимы с ограничениями границ партиций, можно использовать для отсечения ненужных партиций. Тем не менее требования ограничений PRIMARY KEY или UNIQUE могут заставить вас выбрать другие столбцы. Удаление нежелательных данных также является фактором, который следует учитывать при планировании стратегии партиционирования. Целую партицию можно отсоединить довольно быстро, поэтому может быть полезно разработать стратегию партиционирования таким образом, чтобы все данные, удаляемые одновременно, находились в одной партиции.

Выбор целевого числа партиций, на которые следует разделить таблицу, тоже является критически важным решением. Отсутствие достаточного количества партиций может означать, что индексы остаются слишком большими, а локальность данных плохой, что может привести к низкому проценту попадания в кэш. Однако разделение таблицы на слишком большое число партиций также может вызвать проблемы. Слишком большое количество партиций может означать увеличение времени планирования запросов и более высокому потреблению памяти при планировании и выполнении запросов, что подробно описано ниже. Кроме того, при выборе способа партиционирования таблицы важно учитывать, какие изменения могут произойти в будущем. Например, если вы решили иметь по одной партицию для каждого клиента и в настоящее время у вас небольшое количество крупных клиентов, подумайте о последствиях, если через несколько лет у вас будет много мелких клиентов. Возможно, в таком случае, лучше выбрать партиционирование по хешу (HASH) и создать разумное количество партиций, вместо того чтобы пытаться партиционировать их по списку (LIST) и надеяться, что количество клиентов не превысит порог, по которому целесообразно партиционировать данные.

Вложенное партиционирование может быть полезным для дополнительного разбиения партиций, которые предположительно могут стать крупнее других партиций. Еще один вариант — использовать партиционирование по диапазону с несколькими столбцами в ключе разбиения. При каждом из этих вариантов с легкостью может получиться излишнее число партиций, поэтому использовать их рекомендуется умеренно.

Важно учитывать издержки партиционирования при планировании и выполнении запросов. Планировщик запросов обычно способен достаточно хорошо обрабатывать иерархии партиционирования с несколькими тысячами партиций при условии, что типичные запросы позволяют ему отсекать значительную часть партиций. Если после отсечения партиций планировщиком их остается еще много, время планирования увеличивается, а потребление памяти возрастает. Еще одна причина для беспокойства по поводу большого количества партиций заключается в том, что со временем потребление памяти сервером может значительно возрасти, особенно если множество сеансов обращается к огромному числу партиций. Это связано с тем, что каждая партиция требует загрузки своих метаданных в локальную память каждого сеанса, который к ней обращается.

С рабочими нагрузками типа хранилища данных имеет смысл использовать больше партиций, чем с рабочей нагрузкой типа OLTP. Как правило, в хранилищах данных планирование запросов занимает меньше времени, так как большая часть времени обработки тратится на выполнение запросов. При любом из этих двух типов рабочих нагрузок важно принимать правильные решения как можно раньше, поскольку процесс переразбиения больших объемов данных может оказаться чрезвычайно медленным. Для оптимизации стратегии партиционирования зачастую бывает полезно моделирование предполагаемой рабочей нагрузки. Никак нельзя просто исходить из предположения, что чем больше партиций, тем лучше, или наоборот.

Сторонние данные

QHB реализует части спецификации SQL/MED, позволяя получать доступ к данным, находящимся вне QHB, используя обычные запросы SQL. Такие данные называются сторонними.

Доступ к сторонним данным осуществляется с помощью обертки сторонних данных. Обертка сторонних данных представляет собой библиотеку, которая может взаимодействовать с внешним источником данных, скрывая детали подключения к нему и получения из него данных. Несколько оберток сторонних данных доступны в виде модулей из каталога share/extension; см. раздел Расширения. Другие типы оберток сторонних данных можно найти среди продуктов сторонних производителей. Если ни одна из существующих оберток сторонних данных не соответствует вашим потребностям, вы можете написать свою собственную; см. главу Написание обертки сторонних данных.

Для обращения к сторонним данным необходимо создать объект сторонний сервер, который определяет способ подключения к определенному внешнему источнику данных в соответствии с набором параметров, используемых его вспомогательной оберткой сторонних данных. Затем нужно создать одну или несколько сторонних таблиц, которые определяют структуру внешних данных. Стороннюю таблицу можно использовать в запросах совсем как обычная, но сторонняя таблица не хранит данные на сервере QHB. Всякий раз, когда такая таблицы используется, QHB запрашивает обертку сторонних данных для выборки данных из внешнего источника или передачи данных в него в случае команд изменения.

При обращении к внешним данным внешний источник данных может потребовать аутентификацию. Эту информацию можно обеспечить с помощью сопоставления пользователей, которое позволяет предоставить дополнительные данные, например имена пользователей и пароли, в зависимости от текущей роли пользователя QHB.

Дополнительную информацию см. на справочных страницах команд CREATE FOREIGN DATA WRAPPER, CREATE SERVER, CREATE USER MAPPING, CREATE FOREIGN TABLE и IMPORT FOREIGN SCHEMA.

Другие объекты базы данных

Таблицы являются центральными объектами в структуре реляционной базы данных, потому что они содержат ваши данные. Но это не единственные объекты, существующие в базе данных. Можно создать многие другие виды объектов, делающие использование и управление данными более эффективным или удобным. Они не обсуждаются в этой главе, но мы перечислим их здесь, чтобы вы знали об имеющихся возможностях:

Представления

Функции, процедуры и операторы

Типы данных и домены

Триггеры и правила перезаписи

Более подробно эти темы рассматриваются в главе Серверное программирование.

Отслеживание зависимостей

Когда вы создаете сложные структуры базы данных, включающие множество таблиц с ограничениями внешнего ключа, представления, триггеры, функции и т. д., вы неявно создаете сеть зависимостей между этими объектами. Например, таблица с ограничением внешнего ключа зависит от таблицы, на которую она ссылается.

Чтобы обеспечить целостность структуры всей базы данных, QHB делает так, чтобы вы не могли удалить объекты, от которых все еще зависят другие объекты. Например, попытка удалить таблицу товаров, которая рассматривалась в подразделе Ограничение внешнего ключа, с зависящей от нее таблицей заказов, приведет к появлению сообщения об ошибке приблизительно такого содержания:

DROP TABLE products;

ERROR:  cannot drop table products because other objects depend on it
DETAIL:  constraint orders_product_no_fkey on table orders depends on table products
HINT:  Use DROP ... CASCADE to drop the dependent objects too.
-- ОШИБКА: удалить таблицу products невозможно, потому что от нее зависят другие объекты
-- ДЕТАЛИЗАЦИЯ: ограничение orders_product_no_fkey для таблицы orders зависит от таблицы products
-- СОВЕТ: Для удаления зависимых объектов используйте DROP ... CASCADE

Сообщение об ошибке содержит полезный совет: если вы не хотите удалять все зависимые объекты по отдельности, можно выполнить:

DROP TABLE products CASCADE;

и все зависимые объекты будут удалены, равно как и любые объекты, которые рекурсивно зависят от них. В данном случае таблица заказов не удаляется, а удаляется только ограничение внешнего ключа. На этом все заканчивается, потому что от ограничения внешнего ключа ничего не зависит. (Если вы хотите проверить, что будет делать DROP ... CASCADE, запустите DROP без CASCADE и прочитайте вывод DETAIL.)

В QHB почти все команды DROP поддерживают указание CASCADE. Конечно, характер возможных зависимостей зависит от типа объекта. Кроме того, вместо CASCADE можно написать RESTRICT, чтобы получить поведение по умолчанию, которое должно предотвратить удаление объектов, от которых зависят любые другие объекты.

Примечание
Согласно стандарту SQL, в команде DROP требуется явно указывать либо RESTRICT, либо CASCADE. На самом деле ни в одной СУБД это правило не действует, но будет ли поведением по умолчанию RESTRICT или CASCADE, зависит от конкретной СУБД.

Если в команде DROP перечисляется несколько объектов, CASCADE требуется только при наличии зависимостей вне указанной группы. Например, в команде, скажем, DROP TABLE tab1, tab2 наличие внешнего ключа, ссылающегося на tab1 из tab2 не будет означать, что для успешного выполнения команды необходим CASCADE.

Для пользовательских функций QHB отслеживает зависимости, связанные с внешне видимыми свойствами функции, такими как ее аргументы и типы результатов, но не зависимости, которые могут быть обнаружены только путем анализа тела функции. В качестве примера рассмотрим следующую ситуацию:

CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow',
                             'green', 'blue', 'purple');

CREATE TABLE my_colors (color rainbow, note text);

CREATE FUNCTION get_color_note (rainbow) RETURNS text AS
  'SELECT note FROM my_colors WHERE color = $1'
  LANGUAGE SQL;

(Функции языка SQL рассматриваются в разделе Функции на языке запросов (SQL).) QHB будет знать, что функция get_color_note зависит от типа rainbow: удаление типа приведет к принудительному удалению функции, поскольку тип ее аргумента больше не определяется. Но QHB не будет считать, что get_color_note зависит от таблицы my_colors, и поэтому не будет удалять эту функцию при удалении таблицы. Хотя у этого подхода есть недостатки, но есть и преимущества. При отсутствии таблицы функция в некотором смысле все еще будет действующей, хотя ее выполнение вызовет ошибку; создание новой таблицы с тем же именем позволит функции снова заработать.

Манипулирование данными

В предыдущей главе рассказывалось, как создать таблицы и другие структуры для хранения данных. Теперь пришло время заполнить таблицы данными. В этой главе описывается, как добавлять, изменять и удалять табличные данные. Из следующей же главы наконец-то можно будет узнать, как извлекать из базы данных требуемые данные.

Добавление данных

Только что созданная таблица не содержит данных. Первое, что нужно сделать чтобы база данных начала приносить пользу, — это добавить в таблицу данные. Данные вставляются по одной строке за раз. Хотя, конечно, за одну команду можно вставить и более одной строки, но вставить менее одной строки (неполную строку) невозможно. Даже если известны только некоторые значения столбцов, необходимо создать строку целиком.

Чтобы создать новую строку, используйте команду INSERT. Для этой команды требуется задать имя таблицы и значение столбца. Например, рассмотрим таблицу товаров из главы Определение данных:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric
);

Пример команды для добавления строки:

INSERT INTO products VALUES (1, 'Cheese', 9.99);

Значения данных перечисляются через запятую, в порядке нахождения столбцов в таблице. Обычно в качестве значений указываются литералы (константы), но допустимы и скалярные выражения.

Вышеуказанный синтаксис имеет недостаток — вам нужно знать порядок столбцов в таблице. Чтобы избежать этого, можно перечислить столбцы явно. Например, обе следующие команды делают то же самое, что и приведенная выше:

INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', 9.99);
INSERT INTO products (name, price, product_no) VALUES ('Cheese', 9.99, 1);

Многие пользователи считают правильным всегда указывать имена столбцов.

Если у вас нет значений для всех столбцов, некоторые из них можно опустить. В этом случае столбцы будут заполнены значениями по умолчанию. Например:

INSERT INTO products (product_no, name) VALUES (1, 'Cheese');
INSERT INTO products VALUES (1, 'Cheese');

Вторая форма является расширением QHB. Она заполняет столбцы слева заданными значениями, а остальные получают значения по умолчанию.

Также для ясности можно запросить значения по умолчанию явно, для отдельных столбцов или для всей строки:

INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', DEFAULT);
INSERT INTO products DEFAULT VALUES;

Можно вставить несколько строк одной командой:

INSERT INTO products (product_no, name, price) VALUES
    (1, 'Cheese', 9.99),
    (2, 'Bread', 1.99),
    (3, 'Milk', 2.99);

Кроме того, можно вставить результат запроса (который может вообще не содержать строк или содержать одну или несколько строк):

INSERT INTO products (product_no, name, price)
  SELECT product_no, name, price FROM new_products
    WHERE release_date = 'today';

Это позволяет в полной мере использовать механизм запросов SQL (глава Запросы) для вычисления добавляемых строк.

Совет
При одномоментном добавлении большого объема данных имеет смысл использовать команду COPY. Она не такая гибкая, как команда INSERT, но более эффективна. Дополнительную информацию об увеличении производительности при массовой загрузке данных см. в разделе Заполнение базы данных.

Изменение данных

Модификация данных, которые уже находятся в базе данных, называется изменением. Изменять можно отдельные строки, все строки в таблице или подмножество всех строк. Каждый столбец можно изменять отдельно; другие столбцы не затрагиваются.

Чтобы изменить существующие строки, используйте команду UPDATE. Для этого требуется знать три вещи:

Имя таблицы и изменяемого столбца

Новое значение столбца

Какую строку (строки) изменять

Напоминаем, что в главе Определение данных говорилось, что SQL, как правило, не предоставляет уникальный идентификатор строк. Поэтому не всегда возможно напрямую указать, какую строку нужно изменить. Вместо этого указывается, каким условиям должна соответствовать изменяемая строка. Только если в таблице имеется первичный ключ (независимо от того, объявлен он или нет), можно с уверенностью обращаться к отдельным строкам, выбирая условие, соответствующее первичному ключу. На этот факт опираются графические инструменты доступа к базе данных, позволяя изменять строки по отдельности.

Например, эта команда увеличивает цену на все товары с ценой 5 до 10:

UPDATE products SET price = 10 WHERE price = 5;

Это может вызвать изменение нуля, одной или нескольких строк. Запрос на изменение, не соответствующий ни одной строке, не является ошибкой.

Давайте посмотрим на эту команду подробно. Сначала идет ключевое слово UPDATE, потом имя таблицы. Как обычно, имя таблицы может быть дополнено схемой; в противном случае оно ищется в пути. Далее следует ключевое слово SET, а за ним — имя столбца, знак равенства и новое значение столбца. Новое значение столбца может быть любым скалярным выражением или просто константой. Например, если нужно поднять цену на все товары на 10%, можно использовать:

UPDATE products SET price = price * 1.10;

Как видите, выражение для нового значения может ссылаться на существующие значения в строке. Кроме того, отсутствует предложение WHERE. Если оно опущено, это означает, что будут изменены все строки в таблице. Если условие WHERE указано, изменяются только те строки, которые ему соответствуют. Обратите внимание, что знак равенства в предложении SET является присваиванием, а в предложении WHERE — сравнением, но это не создает никакой двусмысленности. Конечно, условию WHERE не обязательно быть проверкой равенства. Доступны и многие другие операторы (см. главу Функции и операторы). Но выражение должно вычисляться до логического результата.

В команде UPDATE можно изменить несколько столбцов, перечислив присваиваемые им значения в предложении SET. Например:

UPDATE mytable SET a = 5, b = 3, c = 1 WHERE a > 0;

Удаление данных

До сих пор мы описывали, как добавлять данные в таблицы и как их изменять. Осталось рассмотреть, как удалить данные, которые больше не нужны. Так же, как добавление данных возможно только целыми строками, удалять из таблицы тоже можно только строки целиком. В предыдущем разделе было упоминалось, что SQL не предоставляет способ напрямую обращаться к отдельным строкам. Поэтому удаление отдельных строк можно осуществить только путем указания условий, которым должны соответствовать удаляемые строки. Если в таблице есть первичный ключ, можно указать конкретную строку. Кроме того, можно удалить группы строк, соответствующих условию, или сразу все строки в таблице.

Для удаления строк используется команда DELETE; ее синтаксис очень похож на команду UPDATE. Например, чтобы удалить все строки из таблицы товаров с ценой 10, напишите следующее:

DELETE FROM products WHERE price = 10;

Если просто написать:

DELETE FROM products;

будут удалены все строки в таблице! БУДЬТЕ ОСТОРОЖНЫ.

Возврат данных из измененных строк

Иногда полезно получить данные из измененных строк во время манипуляций с ними же. У команд INSERT, UPDATE и DELETE имеется необязательное предложение RETURNING, поддерживающее эту возможность. Использование RETURNING позволяет избежать выполнения дополнительного запроса к базе данных для сбора данных, и это особенно ценно, когда иным образом было бы трудно однозначно идентифицировать измененные строки.

Допустимое содержимое предложения RETURNING совпадает с выходным списком команды SELECT (см. раздел Списки выборки). Он может содержать имена столбцов целевой таблицы команды или выражения значений, использующие эти столбцы. Обычным сокращением является RETURNING *, выбирающее все столбцы целевой таблицы по порядку.

В команде INSERT доступные для RETURNING данные образуются из строки в том виде, в котором она была добавлена. Это не очень полезно при обычных добавлениях, поскольку возвращаются данные, предоставленные клиентом. Но это может быть очень удобно при использовании вычисленных значений по умолчанию. Например, при использовании столбца serial, предоставляющего уникальные идентификаторы, RETURNING может вернуть идентификатор, назначенный новой строке:

CREATE TABLE users (firstname text, lastname text, id serial primary key);

INSERT INTO users (firstname, lastname) VALUES ('Joe', 'Cool') RETURNING id;

Предложение RETURNING также очень полезно с INSERT ... SELECT.

В команде UPDATE доступные для RETURNING данные образуются из нового содержимого модифицированной строки. Например:

UPDATE products SET price = price * 1.10
  WHERE price <= 99.99
  RETURNING name, price AS new_price;

В команде DELETE доступные для RETURNING данные образуются из содержимого удаленной строки. Например:

DELETE FROM products
  WHERE obsoletion_date = 'today'
  RETURNING *;

Если в целевой таблице есть триггеры (глава Триггеры), доступные для RETURNING данные образуются из строки, измененной триггерами. Таким образом, еще одним распространенным вариантом использования RETURNING является проверка вычисленных триггерами столбцов.

Запросы

В предыдущих разделах описывалось, как создать таблицы, как наполнить их данными и как манипулировать этими данными. В данном разделе наконец объясняется, как получить данные из базы данных.

Обзор

Процесс или команда получения данных из базы данных называется запросом. В SQL для создания запросов используется команда SELECT. Общий синтаксис команды SELECT выглядит так:

[WITH запросы_with] SELECT список_выборки FROM табличное_выражение [определение_сортировки]

Далее будут подробно описаны список выборки, табличное выражение и определение сортировки. Запросы WITH рассматриваются в последнюю очередь, поскольку они являются расширенной функциональной возможностью.

Простой запрос имеет следующий вид:

SELECT * FROM table1;

Если предположить, что существует таблица с именем table1, то эта команда извлечет все строки и все определенные пользователем столбцы из table1. (Метод выдачи результата зависит от клиентского приложения. Например, программа psql отобразит на экране таблицу ASCII, хотя клиентские библиотеки предложат функции для извлечения отдельных значений из результата запроса). В качестве списка выборки указан символ *, означающий, что в результате выполнения запроса вернутся все столбцы табличного выражения. В списке выборки можно также указать подмножество доступных столбцов или выполнить вычисления с этими столбцами. Например, если в table1 есть столбцы с именами a, b и c (и, возможно, другие), можно выполнить следующий запрос:

SELECT a, b + c FROM table1;

(предполагая, что b и c имеют числовой тип данных). Более подробную информацию см. в разделе Списки выборки.

FROM table1 — это простейший вид табличного выражения, который читает только одну таблицу. В общем случае табличные выражения могут быть сложными комбинациями базовых таблиц, соединений и подзапросов. Но можно и полностью опустить табличное выражение и использовать команду SELECT как калькулятор:

SELECT 3 * 4;

Это будет иметь больший смысл, если выражения в списке выборки возвращают меняющиеся результаты. Например, можно вызвать функцию так:

SELECT random();

Табличные выражения

Табличное выражение вычисляет таблицу. Оно содержит предложение FROM, за которым при необходимости могут идти предложения WHERE, GROUP BY и HAVING. Простые табличные выражения просто ссылаются на таблицу на диске, также называемую базовой, но в более сложных выражениях базовые таблицы можно изменять или комбинировать различными способами.

Необязательные предложения WHERE, GROUP BY и HAVING в табличных выражениях задают последовательность преобразований, выполняемых с таблицей, полученной в предложении FROM. В результате этих преобразований создается виртуальная таблица, строки которой передаются в список выборки для вычисления выходных строк запроса.

Предложение FROM

Предложение FROM формирует таблицу из одной или нескольких ссылок на таблицы, разделенных запятыми.

FROM ссылка_на_таблицу [, ссылка_на_таблицу [, ...]]

Ссылка на таблицу может быть именем таблицы (возможно, дополненным схемой) или производной таблицей, например, подзапросом, соединением таблиц или их сложной комбинацией. Если в предложении FROM указано более одной такой ссылки, таблицы соединяются перекрестно (то есть образуется декартово произведение их строк; см. ниже). Результатом списка FROM является промежуточная виртуальная таблица, которая затем может преобразовываться с помощью предложений WHERE, GROUP BY и HAVING, и в итоге станет результатом общего табличного выражения.

Если табличная ссылка указывает на таблицу, являющуюся родительской в табличной иерархии наследования, то в результате выводятся строки не только этой таблицы, но и всех ее дочерних таблиц, если только не указать перед ее именем ключевое слово ONLY. Однако при этом ссылка выведет только столбцы именованной таблицы — дополнительные столбцы дочерних таблиц будут проигнорированы.

Вместо добавление ONLY перед именем таблицы можно написать * после имени, явно указав, что должны обрабатываться и дочерние таблицы. Однако практических причин использовать этот синтаксис больше нет, поскольку поиск в дочерних таблицах теперь производится по умолчанию.

Соединенные таблицы

Соединенная таблица — это таблица, полученная из двух других (реальных или производных) таблиц в соответствии с правилами соединения определенного типа. Допустимы внутренние, внешние и перекрестные соединения. Общий синтаксис соединения таблиц выглядит так:

T1 тип_соединения T2 [ условие_соединения ]

Соединения всех типов могут быть объединены в цепочку или вложены друг в друга: любая из таблиц или даже обе таблицы T1 и T2 могут быть уже соединенными таблицами. Для указания точного порядка соединения предложения JOIN можно заключать в скобки. При отсутствии скобок предложения JOIN обрабатываются слева направо.

Типы соединений

Перекрестное соединение

T1 CROSS JOIN T2

Для каждой возможной комбинации строк из T1 и T2 (т. е. их декартова произведения) соединенная таблица будет содержать строку, состоящую из всех столбцов T1 и следующих за ними столбцов T2. Если таблицы содержат N и M строк соответственно, соединенная таблица будет содержать N * M строк.

FROM T1 CROSS JOIN T2 равнозначно FROM T1 INNER JOIN T2 ON TRUE (см. ниже). Также эта запись равнозначна FROM T1, T2.

Примечание
Последний вариант не полностью равнозначен первым двум, если в соединении указано более двух таблиц, поскольку JOIN связывает сильнее, чем запятая. Например, FROM T1 CROSS JOIN T2 INNER JOIN T3 ON условие не равнозначно FROM T1, T2 INNER JOIN T3 ON условие так как условие может ссылаться на T1 в первом случае, но не во втором.

Соединения с сопоставлением строк

T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 ON логическое_выражение
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 USING ( список столбцов соединения )
T1 NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2

Слова INNER и OUTER необязательны во всех формах. INNER — это значение по умолчанию; LEFT, RIGHT и FULL подразумевают внешнее соединение.

Условие соединения указывается в предложении ON или USING или неявно словом NATURAL. Это условие определяет, какие строки из двух исходных таблиц считаются «совпадающими», как подробно рассматривается ниже.

Возможные типы соединений с сопоставлением строк:

INNER JOIN
Для каждой строки R1 из T1 в соединенной таблице содержится строка для каждой строки из T2, которая удовлетворяет условию соединения с R1.

LEFT OUTER JOIN
Сначала выполняется внутреннее соединение. Затем для каждой строки T1, которая не удовлетворяют условию соединения ни с одной строкой в T2, добавляется соединенная строка со значениями NULL в столбцах T2. Таким образом, в соединенной таблице всегда будет присутствовать хотя бы одна строка для каждой строки T1.

RIGHT OUTER JOIN
Сначала выполняется внутреннее соединение. Затем для каждой строки T2, которая не удовлетворяют условию соединения ни с одной строкой в T1, добавляется соединенная строка со значениями NULL в столбцах T1. Таким образом, в соединенной таблице всегда будет присутствовать хотя бы одна строка для каждой строки T2.

FULL OUTER JOIN
Сначала выполняется внутреннее соединение. Затем для каждой строки T1, которая не удовлетворяют условию соединения ни с одной строкой в T2, добавляется соединенная строка со значениями NULL в столбцах T2. А для каждой строки T2, которая не удовлетворяют условию соединения ни с одной строкой в T1, добавляется соединенная строка со значениями NULL в столбцах T1.

Предложение ON является наиболее общим видом условия соединения: в нем указываются выражения логического типа, такие же, как те, что используются в предложении WHERE. Пара строк из T1 и T2 соответствуют друг другу, если выражение ON возвращает true.

Предложение USING — это сокращенная запись, позволяющая извлечь пользу в особой ситуации, когда обе стороны соединения используют одинаковые имена столбцов. Оно принимает разделенный запятыми список имен общих столбцов и формирует условие соединения с равенством этих столбцов. Например, соединение T1 и T2 с помощью USING (a, b) создает условие ON T1.a = T2.a AND T1.b = T2.b.

Кроме того, при выводе JOIN USING исключаются избыточные столбцы: нет необходимости выводить оба сопоставленных столбца, так как они имеют одинаковые значения. Тогда как JOIN ON выводит все столбцы из T1, а затем все столбцы из T2, JOIN USING создает один столбец для каждой из перечисленных пар столбцов (в указанном порядке), затем все оставшиеся столбцы из T1 и наконец все оставшиеся столбцы из T2.

Наконец, NATURAL — это сокращенная форма USING: оно формирует список USING, состоящий из всех имен столбцов, которые появляются в обеих входных таблицах. Как и с USING, в выходной таблице эти столбцы появляются только один раз. Если нет общих имен столбцов, NATURAL JOIN ведет себя как JOIN ... ON TRUE, выводя перекрестное соединение таблиц.

Примечание
Предложение USING разумно защищено от изменений столбцов в соединяемых отношениях, поскольку соединяются только перечисленные столбцы. Использование NATURAL гораздо более рискованно, поскольку любые изменения схем в любом из отношений, вызывающие появление нового совпадающего имени столбца, приведут к соединению и этого нового столбца.

Рассмотрим вышеизложенное на примерах. Допустим, у нас есть таблицы t1:

num | name
-----+------
  1 | a
  2 | b
  3 | c

и t2:

num | value
-----+-------
  1 | xxx
  3 | yyy
  5 | zzz

Для различных типов их соединений мы получим следующие результаты:

=> SELECT * FROM t1 CROSS JOIN t2;
 num | name | num | value
-----+------+-----+-------
   1 | a    |   1 | xxx
   1 | a    |   3 | yyy
   1 | a    |   5 | zzz
   2 | b    |   1 | xxx
   2 | b    |   3 | yyy
   2 | b    |   5 | zzz
   3 | c    |   1 | xxx
   3 | c    |   3 | yyy
   3 | c    |   5 | zzz
(9 rows)

=> SELECT * FROM t1 INNER JOIN t2 ON t1.num = t2.num;
 num | name | num | value
-----+------+-----+-------
   1 | a    |   1 | xxx
   3 | c    |   3 | yyy
(2 rows)

=> SELECT * FROM t1 INNER JOIN t2 USING (num);
 num | name | value
-----+------+-------
   1 | a    | xxx
   3 | c    | yyy
(2 rows)

=> SELECT * FROM t1 NATURAL INNER JOIN t2;
 num | name | value
-----+------+-------
   1 | a    | xxx
   3 | c    | yyy
(2 rows)

=> SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num;
 num | name | num | value
-----+------+-----+-------
   1 | a    |   1 | xxx
   2 | b    |     |
   3 | c    |   3 | yyy
(3 rows)

=> SELECT * FROM t1 LEFT JOIN t2 USING (num);
 num | name | value
-----+------+-------
   1 | a    | xxx
   2 | b    |
   3 | c    | yyy
(3 rows)

=> SELECT * FROM t1 RIGHT JOIN t2 ON t1.num = t2.num;
 num | name | num | value
-----+------+-----+-------
   1 | a    |   1 | xxx
   3 | c    |   3 | yyy
     |      |   5 | zzz
(3 rows)

=> SELECT * FROM t1 FULL JOIN t2 ON t1.num = t2.num;
 num | name | num | value
-----+------+-----+-------
   1 | a    |   1 | xxx
   2 | b    |     |
   3 | c    |   3 | yyy
     |      |   5 | zzz
(4 rows)

Условие соединения, указанное с помощью ON, может также содержать условия, не имеющие прямого отношения к соединению. Это может быть полезно для некоторых запросов, но должно быть тщательно продумано. Например:

=> SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num AND t2.value = 'xxx';
 num | name | num | value
-----+------+-----+-------
   1 | a    |   1 | xxx
   2 | b    |     |
   3 | c    |     |
(3 rows)

Обратите внимание, что это же ограничение в WHERE приводит к иному результату:

=> SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num WHERE t2.value = 'xxx';
 num | name | num | value
-----+------+-----+-------
   1 | a    |   1 | xxx
(1 row)

Это связано с тем, что ограничение в предложение ON обрабатывается до соединения, а ограничение в WHERE — после. Это не имеет значения при внутренних соединениях, но очень важно при внешних.

Псевдонимы таблиц и столбцов

Таблицам и сложным табличным ссылкам можно дать временные имена, по которым можно обращаться к таблицам в запросе. Это временное имя называется псевдонимом таблицы.

Псевдоним можно создать с помощью ключевого слова AS:

FROM табличная_ссылка AS псевдоним

Или:

FROM табличная_ссылка псевдоним

Ключевое слово AS является необязательным. Вместо псевдонима здесь может быть любой идентификатор.

Обычно псевдонимы создаются в качестве коротких идентификаторов для длинных имен таблиц, чтобы сохранить читаемость запросов на соединение. Например:

SELECT * FROM некое_очень_длинное_имя_таблицы s JOIN другое_довольно_длинное_имя a ON s.id = a.num;

Псевдоним становится новым именем табличной ссылки в текущем запросе — после объявления псевдонима нельзя сослаться на таблицу по исходному имени. Поэтому следующий запрос недопустим:

SELECT * FROM my_table AS m WHERE my_table.a > 5;    -- неправильно

Псевдонимы таблиц в основном используется для улучшения читаемости, но их использование также необходимо, когда таблица соединяется сама с собой, например:

SELECT * FROM people AS mother JOIN people AS child ON mother.id = child.mother_id;

Кроме того, псевдоним обязателен, если табличная ссылка является подзапросом (см. подраздел Подзапросы).

В случае неоднозначных определений псевдонимов используются скобки. В следующем примере первый оператор назначает псевдоним b второму экземпляру my_table, а второй оператор назначает псевдоним результату соединения:

SELECT * FROM my_table AS a CROSS JOIN my_table AS b ...
SELECT * FROM (my_table AS a CROSS JOIN my_table) AS b ...

В другой форме назначения псевдонима временные имена даются не только самой таблице, но и ее столбцам:

FROM табличная_ссылка [AS] псевдоним ( столбец1 [, столбец2 [, ...]] )

Если псевдонимов столбцов меньше, чем фактически столбцов в таблице, остальные столбцы сохраняют свои исходные имена. Такой синтаксис особенно полезен для самосоединений или подзапросов.

Когда псевдоним применяется к результату JOIN, он скрывает изначальные имена таблиц внутри JOIN. Например, следующий запрос SQL возможен:

SELECT a.* FROM my_table AS a JOIN your_table AS b ON ...

а запрос:

SELECT a.* FROM (my_table AS a JOIN your_table AS b ON ...) AS c

выдаст ошибку, так как псевдоним таблицы a не виден снаружи псевдонима c.

Подзапросы

Подзапросы, образующие таблицу, должны быть заключены в круглые скобки, и им обязательно нужно назначать псевдонимы (как описано в подразделе Псевдонимы таблиц и столбцов). Например:

FROM (SELECT * FROM table1) AS псевдоним

Этот пример равнозначен записи FROM table1 AS псевдоним. Более интересные случаи, которые нельзя свести к простому соединению, возникают, когда подзапрос включает в себя группировку или агрегирующие функции.

Кроме того, подзапрос может быть списком VALUES:

FROM (VALUES ('anne', 'smith'), ('bob', 'jones'), ('joe', 'blow'))
     AS names(first, last)

Опять же, тут требуется псевдоним таблицы. Назначать псевдонимы столбцам списка VALUES необязательно, но это хорошая практика. Дополнительную информацию см. в разделе Списки VALUES.

Табличные функции

Табличные функции — это функции, выдающие набор строк, состоящий из базовых типов данных (скалярных типов) или составных типов данных (строк таблицы). Они используются как таблицы, представления или подзапросы в предложении FROM. Столбцы, возвращаемые табличными функциями, можно включать в предложения SELECT, JOIN или WHERE так же, как столбцы таблиц, представлений или подзапросов.

Табличные функции тоже можно соединять с помощью записи ROWS FROM; при этом результаты будут возвращены в параллельных столбцах. В таком случае количество строк в результате будет равно наибольшему из результатов функций, а результаты с меньшим количеством строк дополнятся значениями NULL.

вызов_функции [WITH ORDINALITY] [[AS] псевдоним_таблицы [(псевдоним_столбца [, ... ])]]
ROWS FROM( вызов_функции [, ... ] ) [WITH ORDINALITY] [[AS] псевдоним_таблицы [(псевдоним_столбца [, ... ])]]

Если указано предложение WITH ORDINALITY, то в результирующие столбцы функции будет добавлен дополнительный столбец типа bigint с пронумерованными строками результирующего набора (начиная с 1). (Это обобщение стандартного синтаксиса SQL для UNNEST ... WITH ORDINALITY). По умолчанию данный столбец называется ordinality, но с помощью предложения AS ему можно присвоить другое имя.

Специальную табличную функцию UNNEST можно вызвать с любым количеством параметров-массивов, а возвращает она соответствующее количество столбцов, как если бы UNNEST (см. раздел Функции и операторы для массивов) вызывалась для каждого параметра по отдельности, а результаты объединялись с помощью конструкции ROWS FROM.

UNNEST( выражение_массива [, ... ] ) [WITH ORDINALITY] [[AS] псевдоним_таблицы [(псевдоним_столбца [, ... ])]]

Если псевдоним_таблицы не указан, то в качестве имени таблицы используется имя функции, а в случае c конструкцией ROWS FROM() используется имя первой функции.

Если псевдонимы столбцов не указаны, то для функции, возвращающей базовый тип данных, имя столбца также совпадает с именем функции. Для функции, возвращающей составной тип, результирующие столбцы получают имена отдельных атрибутов типа.

Несколько примеров:

CREATE TABLE foo (fooid int, foosubid int, fooname text);

CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$
    SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;

SELECT * FROM getfoo(1) AS t1;

SELECT * FROM foo
    WHERE foosubid IN (
                        SELECT foosubid
                        FROM getfoo(foo.fooid) z
                        WHERE z.fooid = foo.fooid
                      );

CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);

SELECT * FROM vw_getfoo;

В некоторых случаях полезно определить табличную функцию, которая может возвращать различные наборы столбцов при разных вариантах вызова. Для этого необходимо объявить табличную функцию как не имеющую выходных параметров (OUT) и возвращающую псевдотип record. Когда в запросе используется такая функция, ожидаемую структуру строк следует указать в самом запросе, чтобы система знала, как анализировать запрос и составить его план. Эта запись выглядит так:

вызов_функции [AS] псевдоним (определение_столбца [, ... ])
вызов_функции AS [псевдоним] (определение_столбца [, ... ])
ROWS FROM( ... вызов_функции AS (определение_столбца [, ... ]) [, ... ] )

Если синтаксис ROWS FROM() не используется, список определений_столбцов заменяет список псевдонимов столбцов, который в противном случае можно было бы добавить в предложение FROM; имена в определениях столбцов служат псевдонимами. При использовании ROWS FROM() список определений_столбцов можно добавить к каждой функции отдельно; или если имеется только одна функция и нет предложения WITH ORDINALITY, список определений_столбцов можно записать вместо списка псевдонимов после ROWS FROM().

Рассмотрим этот пример:

SELECT *
    FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc')
      AS t1(proname name, prosrc text)
    WHERE proname LIKE 'bytea%';

Функция dblink (часть модуля dblink) выполняет удаленный запрос. Она объявлена как возвращающая record, поскольку его можно использовать для любого запроса. Здесь фактический набор столбцов необходимо указать в вызывающем запросе, чтобы анализатор запроса знал, например, как преобразовать *.

В этом примере используется ROWS FROM:

SELECT *
FROM ROWS FROM
    (
        json_to_recordset('[{"a":40,"b":"foo"},{"a":"100","b":"bar"}]')
            AS (a INTEGER, b TEXT),
        generate_series(1, 3)
    ) AS x (p, q, s)
ORDER BY p;

  p  |  q  | s
-----+-----+---
  40 | foo | 1
 100 | bar | 2
     |     | 3

Эта конструкция соединяет две функции в одном флаге FROM. Функции json_to_recordset() предписывается возвращать два столбца, первый integer и второй text. Результат generate_series() используется непосредственно. Предложение ORDER BY упорядочивает значения этого столбца как целочисленные.

Подзапросы LATERAL

Перед подзапросами в предложении FROM можно указать ключевое слово LATERAL. Оно позволит им ссылаться на столбцы предшествующих элементов FROM. (Без LATERAL каждый подзапрос вычисляется независимо и не может ссылаться на другие элементы FROM).

Ключевое слово LATERAL также можно указать перед табличными функциями в предложении FROM, но для них оно не необязательно; аргументы функций в любом случае могут содержать ссылки на столбцы в предыдущих элементах FROM.

Элемент LATERAL может находиться на верхнем уровне списка FROM или в дереве JOIN. В последнем случае он может также ссылаться на любые элементы в левой части JOIN, справа от которого он располагается.

Когда элемент FROM содержит перекрестные ссылки LATERAL, вычисление выполняется следующим образом: сначала для каждой строки элемента FROM, содержащей ссылочный столбец (или столбцы), или набора строк нескольких элементов FROM с этими столбцами с помощью значений столбцов этой строки или набора строк вычисляется элемент LATERAL. Затем результирующие строки соединяются со строками, из которых они были вычислены. Этот процесс повторяется для каждой строки или набора строк исходных таблиц.

Простой пример применения LATERAL:

SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) ss;

Здесь это не очень полезно, поскольку тот же результат можно получить гораздо проще:

SELECT * FROM foo, bar WHERE bar.id = foo.bar_id;

Ключевое слово LATERAL полезно в первую очередь тогда, когда ссылочный столбец необходим для вычисления соединяемых строк. Обычно оно применяется с целью передачи значения аргумента функции, возвращающей набор данных. Например, если предположить, что vertices(polygon) возвращает набор вершин многоугольника, хранящиеся в таблице близкие вершины многоугольников можно выявить так:

SELECT p1.id, p2.id, v1, v2
FROM polygons p1, polygons p2,
     LATERAL vertices(p1.poly) v1,
     LATERAL vertices(p2.poly) v2
WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;

Этот запрос можно также записать и так:

SELECT p1.id, p2.id, v1, v2
FROM polygons p1 CROSS JOIN LATERAL vertices(p1.poly) v1,
     polygons p2 CROSS JOIN LATERAL vertices(p2.poly) v2
WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;

или с другими равнозначными формулировками. (Как уже упоминалось, в данном примере в ключевом слове LATERAL нет необходимости, но мы применили его для ясности.)

Зачастую особенно удобно бывает использовать LEFT JOIN с подзапросом LATERAL, чтобы исходные строки оказались в результате, даже если подзапрос LATERAL не выдает для них строк. Например, если get_product_names() возвращает названия товаров, выпущенных определенным производителем, но некоторые производители в нашей таблице в настоящее время ничего не выпускают, мы можем определить, какие именно, таким образом:

SELECT m.name
FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true
WHERE pname IS NULL;

Предложение WHERE

Синтаксис предложения WHERE:

WHERE условие_поиска

где условие_поиска — это любое выражение значения (см. раздел Выражения значения), возвращающее значение типа boolean.

После обработки предложения FROM каждая строка получившейся виртуальной таблицы проверяется по условию поиска. Если результат условия равен true, строка остается в выходной таблице, а иначе (т. е. если результат равен false или NULL) отбрасывается. Обычно условие поиска ссылается как минимум на один столбец таблицы, получившейся в предложении FROM; это не требуется, но в противном случае предложение WHERE будет довольно бессмысленным.

Примечание
Условие для внутреннего соединения можно записать как в предложении WHERE, так и в предложении JOIN. Например, это табличное выражение:

FROM a, b WHERE a.id = b.id AND b.val > 5

равнозначно этому:

FROM a INNER JOIN b ON (a.id = b.id) WHERE b.val > 5

и, возможно, даже этому:

FROM a NATURAL JOIN b WHERE b.val > 5

Какой вариант использовать, в основном дело вкуса. Синтаксис JOIN в предложении FROM, наверное, не самый портируемый на другие SQL-СУБД, хотя он и описан в стандарте SQL. Но для внешних соединений других вариантов нет: их нужно записывать в предложении FROM. Предложения ON и USING во внешних соединениях не равнозначны условию WHERE, так как они могут добавлять строки (для несовпадающих входных строк), а также удалять их из конечного результата.

Несколько примеров предложений WHERE:

SELECT ... FROM fdt WHERE c1 > 5

SELECT ... FROM fdt WHERE c1 IN (1, 2, 3)

SELECT ... FROM fdt WHERE c1 IN (SELECT c1 FROM t2)

SELECT ... FROM fdt WHERE c1 IN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10)

SELECT ... FROM fdt WHERE c1 BETWEEN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10) AND 100

SELECT ... FROM fdt WHERE EXISTS (SELECT c1 FROM t2 WHERE c2 > fdt.c1)

fdt — это таблица, получившаяся в предложении FROM. Строки, не соответствующие условию поиска предложения WHERE, исключаются из fdt. Обратите внимание на использование скалярных подзапросов в качестве выражений значения. Как и любые другие запросы, подзапросы могут реализовывать сложные табличные выражения. Также обратите внимание, как на fdt ссылаются подзапросы. Дополнение имени c1 в виде fdt.c1 требуется, только если c1 также является именем столбца в получившейся входной таблице этого подзапроса. Но дополнение именем столбца придает ясности, даже когда в ней нет необходимости. Этот пример показывает, как область именования столбцов внешнего запроса распространяется на его внутренние запросы.

Предложения GROUP BY и HAVING

После прохождения фильтра WHERE получившуюся входную таблицу можно подвергнуть группированию с помощью предложения GROUP BY, а затем исключить группу строк с помощью предложения HAVING.

SELECT список_выборки
    FROM ...
    [WHERE ...]
    GROUP BY ссылка_на_группирующий_столбец [, ссылка_на_группирующий_столбец]...

Предложение GROUP BY применяется для группирования тех строк в таблице, у которых во всех перечисленных столбцах совпадают значения. Порядок, в котором перечислены столбцы, значения не имеет. В результате каждый набор строк с одинаковыми значениями столбцов совмещается в одну групповую строку, представляющую все строки группы. Целью этого процесса является устранение избыточности в выходных данных и/или вычисление агрегатных функций, применимых к этим группам. Например:

=> SELECT * FROM test1;
 x | y
---+---
 a | 3
 c | 2
 b | 5
 a | 1
(4 rows)

=> SELECT x FROM test1 GROUP BY x;
 x
---
 a
 b
 c
(3 rows)

Во втором запросе мы не могли написать SELECT * FROM test1 GROUP BY x, поскольку для столбца y нет единого значения, которое можно было бы связать с каждой группой. Однако к группирующим столбцам можно обращаться в списке выборки, так как они имеют единственное значение в каждой группе.

В целом, если таблица сгруппирована, к столбцам, не перечисленным в GROUP BY, можно обращаться только в агрегатных выражениях. Пример с агрегатными выражениями:

=> SELECT x, sum(y) FROM test1 GROUP BY x;
 x | sum
---+-----
 a |   4
 b |   5
 c |   2
(3 rows)

Здесь sum — это агрегатная функция, вычисляющая единственное значение для всей группы. Подробную информацию о доступных агрегатных функциях см. в разделе Агрегатные функции.

Совет
Группирование без агрегатных выражений в сущности выдает набор различающихся значений в столбце. Этот же результат можно получить с помощью предложения DISTINCT (см. подраздел DISTINCT ниже).

Еще один пример: в нем вычисляется общая сумма продаж по каждому товару (а не общая сумма по всем товарам):

SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
    FROM products p LEFT JOIN sales s USING (product_id)
    GROUP BY product_id, p.name, p.price;

В этом примере столбцы product_id, p.name и p.price должны находиться в предложении GROUP BY, поскольку к ним обращаются в списке выборки запроса (но см. ниже). Столбцу s.units необязательно присутствовать в списке GROUP BY, так как он используется только в агрегатном выражении (sum(...)), представляющем сумму продаж товара. Для каждого товара этот запрос возвращает строку с итоговой суммой по всем продажам этого товара.

Если бы таблица products была настроена так, чтобы, скажем, по столбцу product_id был создан первичный ключ, этого было бы достаточно, чтобы в вышеописанном примере сгруппировать строки по product_id, поскольку название и цена функционально зависят от ID товара и можно однозначно определить, какие название и цену возвращать для каждой группы по ID товара.

В стандарте SQL GROUP BY может группировать только по столбцам исходной таблицы, но QHB расширяет эту функциональность, позволяя GROUP BY группировать также и по столбцам в списке выборки. Также разрешается группировать по выражениям значения, а не просто по именам столбцов.

Если таблица была сгруппирована с помощью GROUP BY, но интерес представляют только определенные группы, можно воспользоваться предложением HAVING, действующим подобно предложению WHERE, и убрать лишние группы из результата. Синтаксис при этом такой:

SELECT список_выборки FROM ... [WHERE ...] GROUP BY ...
  HAVING логическое_выражение

Выражения в предложении HAVING могут обращаться как к группирующим выражениям, так и к выражениям, не участвующим в группировке (которые обязательно должны быть агрегирующими функциями).

Пример:

=> SELECT x, sum(y) FROM test1 GROUP BY x HAVING sum(y) > 3;
 x | sum
---+-----
 a |   4
 b |   5
(2 rows)

=> SELECT x, sum(y) FROM test1 GROUP BY x HAVING x < 'c';
 x | sum
---+-----
 a |   4
 b |   5
(2 rows)

Более реалистичный пример:

SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit
    FROM products p LEFT JOIN sales s USING (product_id)
    WHERE s.date > CURRENT_DATE - INTERVAL '4 weeks'
    GROUP BY product_id, p.name, p.price, p.cost
    HAVING sum(p.price * s.units) > 5000;

В примере выше предложение WHERE выбирает строки по несгруппированному столбцу (выражение истинно только для продаж за последние четыре недели), тогда как предложение HAVING ограничивает выходные данные группами с общей суммой валовой выручки больше 5000. Обратите внимание, что агрегатные выражения не обязательно должны быть одинаковыми во всех частях запроса.

Если в запросе есть вызовы агрегатных функций, но нет предложения GROUP BY, группирование все равно произойдет: результатом будет одна строка группы (или, возможно, ни одной строки, если эта строка будет исключена предложением HAVING). Это справедливо и для запросов, содержащих только предложение HAVING, но не вызовы агрегатных функции или предложение GROUP BY.

GROUPING SETS, CUBE и ROLLUP

Более сложные, чем описанные выше, операции группировки возможны с концептом группирующих наборов. Данные, выбранные предложениями FROM и WHERE группируются отдельно каждым заданным группирующим набором, затем для каждой группы вычисляются агрегатные функции, совсем как для простых предложений GROUP BY, после чего возвращаются результаты. Например:

=> SELECT * FROM items_sold;
 brand | size | sales
-------+------+-------
 Foo   | L    |  10
 Foo   | M    |  20
 Bar   | M    |  15
 Bar   | L    |  5
(4 rows)

=> SELECT brand, size, sum(sales) FROM items_sold GROUP BY GROUPING SETS ((brand), (size), ());
 brand | size | sum
-------+------+-----
 Foo   |      |  30
 Bar   |      |  20
       | L    |  15
       | M    |  35
       |      |  50
(5 rows)

В каждом подсписке GROUPING SETS можно задать ноль и более столбцов или выражений, которые воспринимаются так же, как если бы они находились непосредственно в предложении GROUP BY. Пустой группирующий набор означает, что все строки агрегируются до одной группы (которая выводится, даже если входных строк не было), как описано выше для агрегатных функций без предложения GROUP BY.

Для тех группирующих наборов, в которых отсутствуют группирующие столбцы или выражения, в результирующих строках ссылки на эти столбцы заменяются значениями NULL. Чтобы различать, результатом какого группирования стала конкретная выходная строка, см. таблицу Операции группировки.

Для указания двух распространенных типов группирующих наборов предусмотрена краткая запись. Предложение формы

ROLLUP ( e1, e2, e3, ... )

представляет заданный список выражений и всех префиксов списка, включая пустой список; то есть оно равнозначно записи:

GROUPING SETS (
    ( e1, e2, e3, ... ),
    ...
    ( e1, e2 ),
    ( e1 ),
    ( )
)

Обычно это применяется для анализа иерархических данных, например, общей зарплаты по отделам, подразделениям и компании в целом.

Предложение формы

CUBE (e1, e2, ...)

представляет заданный список и все его возможные множества (т. е. степенное множество). Таким образом,

CUBE (a, b, c)

равнозначно

GROUPING SETS (
    ( a, b, c ),
    ( a, b    ),
    ( a,    c ),
    ( a       ),
    (    b, c ),
    (    b    ),
    (       c ),
    (         )
)

Отдельными элементами предложений CUBE и ROLLUP могут быть либо отдельные выражения, либо подсписки элементов в скобках. В последнем случае подсписки обрабатываются как атомарные единицы с целью создания отдельных группирующих наборов. Например:

CUBE ((a, b), (c, d))

равнозначно

GROUPING SETS (
    ( a, b, c, d ),
    ( a, b       ),
    (       c, d ),
    (            )
)

и

ROLLUP (a, (b, c), d)

равнозначно

GROUPING SETS (
    ( a, b, c, d ),
    ( a, b, c    ),
    ( a          ),
    (            )
)

Конструкции CUBE и ROLLUP можно использовать либо непосредственно в предложении GROUP BY, либо вложенными в предложение GROUPING SETS. Если одно предложение GROUPING SETS вкладывается внутрь другого, результат будет таким же, как если бы все элементы внутреннего предложения были записаны непосредственно во внешнем.

Если в одном предложении GROUP BY задается несколько группирующих элементов, то итоговый список группирующих наборов является векторным произведением этих отдельных элементов. Например:

GROUP BY a, CUBE (b, c), GROUPING SETS ((d), (e))

равнозначно

GROUP BY GROUPING SETS (
    (a, b, c, d), (a, b, c, e),
    (a, b, d),    (a, b, e),
    (a, c, d),    (a, c, e),
    (a, d),       (a, e)
)

Когда задается сразу несколько группирующих элементов, итоговый набор группирующих наборов может содержать дубликаты. Например:

GROUP BY ROLLUP (a, b), ROLLUP (a, c)
is equivalent to

GROUP BY GROUPING SETS (
    (a, b, c),
    (a, b),
    (a, b),
    (a, c),
    (a),
    (a),
    (a, c),
    (a),
    ()
)

Если эти дубликаты нежелательны, их можно удалить, добавив предложение DISTINCT непосредственно в GROUP BY. Таким образом,

GROUP BY DISTINCT ROLLUP (a, b), ROLLUP (a, c)

равнозначно

GROUP BY GROUPING SETS (
    (a, b, c),
    (a, b),
    (a, c),
    (a),
    ()
)

Это не то же самое, что SELECT DISTINCT, так как выходные строки все равно могут содержать дубликаты. Если любой из не участвующих в группировании столбцов содержит NULL, он будет неотличим от значения NULL, полученного при группировании этого же столбца.

Примечание
Конструкция (a, b) обычно воспринимается в выражениях как конструктор строки. Однако в предложении GROUP BY на верхних уровнях выражений это не работает, и запись (a, b) разбирается как список выражений (как описано выше). Если по какой-то причине вам нужен конструктор строки группирующем выражении, используйте запись ROW(a, b).

Обработка оконных функций

Если запрос содержит какие-либо оконные функции (см. разделы Оконные функции и Вызовы оконных функций и таблицу Оконные функции), эти функции вычисляются после каждой группировки, агрегации и фильтрации HAVING. То есть, если запрос использует какие-либо агрегатные функции, предложения GROUP BY или HAVING, тогда строки, видимые для оконных функций, будут сгруппированными строками, а не исходными строками таблицы из FROM/WHERE.

При использовании нескольких оконных функций все функции, имеющие в определениях синтаксически равнозначные предложения PARTITION BY и ORDER BY, гарантированно обрабатывают данные за один проход. Следовательно, они увидят один порядок сортировки, даже если ORDER BY не определяет однозначный порядок. Однако для функций, имеющих разные предложения PARTITION BY и ORDER BY, никаких гарантий не дается. (В таких случаях между проходами вычислений оконных функций обычно требуется этап сортировки, и эта сортировка не гарантирует сохранение порядка строк, который ее ORDER BY считает равнозначным.)

На данный момент оконные функции всегда требуют предварительно отсортированные данные, поэтому результат запроса будет упорядочен в соответствии с теми или иными предложениями PARTITION BY/ORDER BY оконных функций. Однако полагаться на это не стоит. Если хотите добиться определенного порядка сортировки результатов, явно используйте предложение ORDER BY на верхнем уровне запроса.

Списки выборки

Как показано в предыдущем разделе, табличное выражение в команде SELECT создает промежуточную виртуальную таблицу, возможно, объединяя таблицы, представления, исключая строки, группируя и т. д. В итоге эта таблица передается на обработку в список выборки. Этот список определяет, какие столбцы промежуточной таблицы фактически должны выводиться.

Элементы списка выборки

Простейшим списком выборки является элемент *, который выбирает все столбцы, выдаваемые табличным выражением. В других случаях список выборки представляет собой список выражений значения через запятую (как определено в разделе Выражения значения). Например, это может быть список имен столбцов:

SELECT a, b, c FROM ...

Имена столбцов a, b и c являются либо фактическими именами столбцов таблиц, указанных в предложении FROM, либо их псевдонимами, заданными как описано в подразделе Псевдонимы таблиц и столбцов. Пространство имен, доступное в списке выборки, то же, что и в предложении WHERE, если не используется группировка. В последнем случае оно совпадает с пространством имен в предложении HAVING.

Если в нескольких таблицах присутствует столбец с одинаковым именем, следует также указать имя таблицы, например так:

SELECT tbl1.a, tbl2.a, tbl1.b FROM ...

Кроме того, при работе с несколькими таблицами может возникнуть необходимость запросить все столбцы конкретной таблицы:

SELECT tbl1.*, tbl2.a FROM ...

Подробную информацию о записи имя_таблицы.* см. в подразделе Использование составных типов в запросах.

Если в списке выборки используется произвольное выражение значения, оно по сути добавляет в возвращаемую таблицу новый виртуальный столбец. Выражение значения вычисляется один раз для каждой результирующей строки; при этом все ссылки столбца заменяются значениями строки. Но выражения в списке выборки не обязательно должны обращаться к каким-либо столбцам табличного выражения из предложения FROM; они могут быть, к примеру, константными арифметическими выражениями.

Метки столбцов

Записям в списке выборки можно назначить имена для последующей обработки, например, для использования в предложении ORDER BY или для отображения в клиентском приложении. Например:

SELECT a AS value, b + c AS sum FROM ...

Если после AS не указано выходное имя столбца, система назначает ему имя по умолчанию. Для простых ссылок на столбцы это будет имя ссылочного столбца, а для вызовов функций — имя функции. Для сложных выражений система генерирует некое общее имя.

Ключевое слово AS обычно можно не указывать, но в некоторых случаях, когда желаемое имя столбца совпадает с ключевым словом QHB, следует написать AS или заключить имя столбца в кавычки, чтобы избежать неоднозначности. (В приложении Ключевые слова SQL перечислены ключевые слова, для которых нужно использовать AS в качестве метки столбца.) Например, FROM — ключевое слово, поэтому такая запись не будет работать:

SELECT a from, b + c AS sum FROM ...

а любая из этих сработает:

SELECT a AS from, b + c AS sum FROM ...
SELECT a "from", b + c AS sum FROM ...

Чтобы максимально обезопасить себя от возможных конфликтов с ключевыми словами, которые могут добавиться в будущем, рекомендуется всегда либо писать AS, либо заключать выходное имя столбца в кавычки.

Примечание
Именование выходных столбцов здесь отличается от того, что происходит в предложении FROM (см. подраздел Псевдонимы таблиц и столбцов). Один и тот же столбец можно переименовать дважды, но передастся то имя, которое было назначено в списке выборки.

DISTINCT

После обработки списка выборки в результирующей таблице при необходимости можно убрать дублирующиеся строки. Для этого сразу после SELECT записывается ключевое слово DISTINCT:

SELECT DISTINCT список_выборки ...

(Чтобы задать поведение по умолчанию, когда оставляются все строки, вместо DISTINCT можно указать ключевое слово ALL.)

Разумеется, две строки считаются разными, если они содержат уникальные значения как минимум в одном столбце. При таком сравнении значения NULL рассматриваются как равные.

Кроме того, определить, какие строки будут считаться различными, можно с помощью произвольного выражения:

SELECT DISTINCT ON (выражение [, выражение ...]) список_выборки ...

Здесь выражение — это произвольное выражение значения, вычисляемое для всех строк. Набор строк, для которых все эти выражения одинаковы, считаются дубликатами, и в выходных данных остается только первая строка из набора. Обратите внимание, что «первая строка» набора может быть любой, если только запрос не отсортирован по достаточному числу столбцов, чтобы гарантировать однозначный порядок строк, поступающих в фильтр DISTINCT. (Обработка DISTINCT ON производится после сортировки ORDER BY.)

Предложение DISTINCT ON не включено в стандарт SQL, и иногда его применение считается дурным тоном из-за потенциальной непредсказуемости его результатов. При разумном использовании GROUP BY и подзапросов во FROM можно обойтись без этой конструкции, но зачастую она является наиболее удобной альтернативой.

Сочетание запросов (UNION, INTERSECT, EXCEPT)

Результаты двух запросов можно обрабатывать комплексно, используя операции над множествами: объединение, пересечение и вычитание. Синтаксис при этом следующий:

запрос1 UNION [ALL] запрос2
запрос1 INTERSECT [ALL] запрос2
запрос1 EXCEPT [ALL] запрос2

где запрос1 и запрос2 — это запросы, в которых могут использоваться все функциональные возможности, рассмотренные ранее.

UNION по сути добавляет результат запроса2 к результату запроса1 (хотя нет гарантии, что строки возвращаются именно в таком порядке). Более того, при этом из результата убираются все дублирующиеся строки, тем же способом, как это делает DISTINCT, если только не указано UNION ALL.

INTERSECT возвращает все строки, содержащиеся как в результате запроса1, так и в результате запроса2. Дублирующиеся строки убираются, если только не указано INTERSECT ALL.

EXCEPT возвращает все строки, содержащиеся в результате запроса1, но отсутствующие в результате запроса2. (Иногда это называется разницей двух запросов.) Опять же, дублирующиеся строки убираются, если только не указано EXCEPT ALL.

Чтобы вычислить объединение, пересечение или разницу двух запросов, эти два запроса должны быть «совместимыми для объединения», что означает, что они возвращают одинаковое количество столбцов и у соответствующих столбцов совместимые типы данных, как описано в разделе UNION, CASE и связанные конструкции.

Операции над множествами можно сочетать, например:

запрос1 UNION запрос2 UNION запрос3

что равнозначно

(запрос1 UNION запрос2) UNION запрос3

Как показано выше, для управления порядком вычисления можно использовать скобки. Без скобок UNION и EXCEPT объединяются слева направо, но INTERSECT имеет больший приоритет, чем эти два оператора. Таким образом,

запрос1 UNION запрос2 INTERSECT запрос3

означает

запрос1 UNION (запрос2 INTERSECT запрос3)

Также можно заключить в скобки отдельный запрос. Это важно, если в запросе необходимо использовать любое из предложений, рассматриваемых в следующих разделах, например LIMIT. Без скобок возникнет синтаксическая ошибка или предложение будет восприниматься как применяющееся к результату операции над множествами, на не к одному из ее аргументов. Например,

SELECT a FROM b UNION SELECT x FROM y LIMIT 10

допустимо, но означает

(SELECT a FROM b UNION SELECT x FROM y) LIMIT 10

а не

SELECT a FROM b UNION (SELECT x FROM y LIMIT 10)

Сортировка строк (ORDER BY)

После того как запрос выдал выходную таблицу (после обработки списка выборки), при необходимости ее можно отсортировать. Если сортировка не выбрана, строки вернутся в неопределенном порядке. В этом случае фактический порядок строк будет зависеть от вида плана сканирования и соединения, а также от порядка данных на диске, но на него не следует полагаться. Определенный порядок выводимых строк гарантируется, только если этап сортировки задан явно.

Порядок сортировки задает предложение ORDER BY:

SELECT список_выборки
    FROM табличное_выражение
    ORDER BY выражение_сортировки1 [ASC | DESC] [NULLS { FIRST | LAST }]
             [, выражение_сортировки2 [ASC | DESC] [NULLS { FIRST | LAST }] ...]

Выражениями сортировки могут быть любые выражения, допустимые в списке выборки запроса. Например:

SELECT a, b FROM table1 ORDER BY a + b, c;

Когда задается более одного выражения, последующие значения используются для сортировки строк, в которых совпали предыдущие значения. После каждого выражения при необходимости можно указать ключевое слово ASC или DESC, которые устанавливают направление сортировки по возрастанию или убыванию соответственно. По умолчанию установлено ASC. При сортировке по возрастанию сначала идут меньшие значения, где понятие «меньшие» определяется в контексте оператора <. Аналогично сортировка по убыванию определяется оператором >. ¹

Для определения того, должны ли при сортировке значения NULL располагаться до или после отличных от NULL значений, можно применить параметры NULLS FIRST и NULLS LAST соответственно. По умолчанию значения NULL считаются больше всех остальных; то есть NULLS FIRST по умолчанию применяется для порядка DESC и NULLS LAST в противном случае.

Обратите внимание, что параметры сортировки рассматриваются независимо для каждого сортируемого столбца. Например, ORDER BY x, y DESC означает ORDER BY x ASC, y DESC, и это не то же самое, что ORDER BY x DESC, y DESC.

выражение_сортировки также может быть меткой столбца или номером выводимого столбца, как в данном примере:

SELECT a + b AS sum, c FROM table1 ORDER BY sum;
SELECT a, max(b) FROM table1 GROUP BY a ORDER BY 1;

где оба запроса сортируют результат по первому выводимому столбцу. Обратите внимание, что имя выводимого столбца должно оставаться само по себе, то есть его нельзя использовать в выражении — например, эта запись не корректна:

SELECT a + b AS sum, c FROM table1 ORDER BY sum + c;

Это ограничение введено для уменьшения неоднозначности. Тем не менее неоднозначность возможна, если элементом ORDER BY является простое имя, которое может соответствовать либо имени выводимого столбца, либо столбцу из табличного выражения. В таких случаях используется выводимый столбец. Это может вызвать путаницу, только если переименовать выводимый столбец с помощью AS, чтобы его имя соответствовало имени столбца в какой-то другой таблице.

ORDER BY можно применить к результату сочетания UNION, INTERSECT и EXCEPT, однако в этом случае сортировка разрешена только по именам или номерам выводимых столбцов, но не по выражениям.

¹

На самом деле для определения порядка сортировки для ASC и DESC QHB использует класс операторов B-дерева по умолчанию. Обычно типы данных устанавливаются так, чтобы этому порядку сортировки соответствовали операторы < и >, однако можно разработать собственный тип данных с иным поведением.

LIMIT и OFFSET

LIMIT и OFFSET позволяют получить только часть строк из тех, что сгенерировал остальной запрос:

SELECT список_выборки
    FROM табличное_выражение
    [ ORDER BY ... ]
    [ LIMIT { число | ALL } ] [ OFFSET число ]

Если задано предельное число (LIMIT), вернется не больше этого заданного числа строк (возможно, и меньше, если сам запрос выдает меньшее число строк). LIMIT ALL равнозначно исключению предложения LIMIT, как и LIMIT с аргументом NULL.

OFFSET указывает пропустить заданное число строк, прежде чем начать возвращать строки. OFFSET 0 равнозначно исключению предложения OFFSET, как и OFFSET с аргументом NULL.

Если в запросе содержатся и OFFSET, и LIMIT, то сначала система пропускает OFFSET строк, а затем начинает подсчитывать LIMIT строк, которые будут возвращены.

При использовании LIMIT важно применять также предложение ORDER BY, выстраивающее результирующие строки в определенном порядке. Иначе вы получите непредсказуемые подмножества строк запроса. Можно запросить строки с десятой по двадцатую, но в каком порядке они будут выданы? Порядок будет неизвестен, если не указать ORDER BY.

Оптимизатор запросов учитывает LIMIT при генерировании планов запросов, поэтому, скорее всего, в зависимости от значений LIMIT и OFFSET вы получите разные планы (выдающие строки в разном порядке). Таким образом, использование разных значений LIMIT/OFFSET для выбора разных подмножеств результата запроса приведет к несогласованности результатов, если не установить предсказуемую сортировку результатов с помощью ORDER BY. Это не ошибка, а закономерное следствие того, что SQL не гарантирует вывод результатов запроса в каком-либо определенном порядке, если этот порядок не установлен жестко предложением ORDER BY.

Строки, пропускаемые согласно предложению OFFSET, все равно должны вычисляться на сервере; поэтому при больших значениях OFFSET может быть неэффективен.

Списки VALUES

VALUES дает возможность сгенерировать «константную таблицу», которую можно использовать в запросе без необходимости действительно создавать и наполнять таблицу на диске. Синтаксис этой команды таков:

VALUES ( выражение [, ...] ) [, ...]

Для каждого списка выражений в скобках создается строка в таблице. Во всех этих списках должно быть одинаковое число элементов (т. е. число столбцов в таблице) и соответствующие записи в каждом списке должны иметь совместимые типы данных. Фактический тип данных, назначенный каждому столбцу результата, определяется по тем же правилам, что и для UNION (см. раздел UNION, CASE и связанные конструкции).

Например:

VALUES (1, 'one'), (2, 'two'), (3, 'three');

вернет таблицу из двух столбцов и трех строк. Это равнозначно следующему запросу:

SELECT 1 AS column1, 'one' AS column2
UNION ALL
SELECT 2, 'two'
UNION ALL
SELECT 3, 'three';

По умолчанию QHB назначает столбцам таблицы VALUES имена column1, column2 и т. д. Имена столбцов не определены в стандарте SQL и в других СУБД они могут быть другими, поэтому обычно лучше переопределять имена по умолчанию списком псевдонимов таблицы, например так:

=> SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t (num,letter);
 num | letter
-----+--------
   1 | one
   2 | two
   3 | three
(3 rows)

Синтаксически команда VALUES с последующим списком выражений воспринимается как равнозначная этой записи:

SELECT список_выборки FROM табличное_выражение

и допускается везде, где допустим SELECT. К примеру, ее можно использовать как часть UNION или добавить к ней определение_сортировки (ORDER BY, LIMIT и/или OFFSET). Чаще всего VALUES используется как источник данных в команде INSERT либо в качестве подзапроса.

Дополнительную информацию см. на справочной странице команды VALUES.

Запросы WITH (общие табличные выражения)

WITH дает возможность записывать вспомогательные операторы для применения в более крупных запросах. Эти операторы, также часто называемые общими табличными выражениями (Common Table Expressions, CTE), можно представить как определения временных таблиц, которые существуют только для одного запроса. Каждый вспомогательный оператор в предложении WITH может быть SELECT, INSERT, UPDATE или DELETE, а само предложение WITH присоединяется к основному оператору, которым также может быть SELECT, INSERT, UPDATE или DELETE.

SELECT в WITH

Основное назначение SELECT в WITH заключается в разбиении сложных запросов на более простые части. Например, запрос:

WITH regional_sales AS (
    SELECT region, SUM(amount) AS total_sales
    FROM orders
    GROUP BY region
), top_regions AS (
    SELECT region
    FROM regional_sales
    WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT region,
       product,
       SUM(quantity) AS product_units,
       SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;

отображает итоги по продажам каждого товара только для регионов, лидирующих по продажам. Предложение WITH определяет два вспомогательных оператора, именуемых regional_sales и top_regions, где выходные данные regional_sales используются в top_regions, а выходные данные top_regions — в основном запросе SELECT. Этот пример можно было бы записать и без WITH, но тогда бы понадобилось два уровня вложенных подзапросов SELECT. Способ, показанный выше, немного проще.

Рекурсивные запросы

Необязательный модификатор RECURSIVE превращает WITH из просто удобной синтаксической конструкции в средство достижения того, что иным способом невозможно в стандартном SQL. Используя RECURSIVE, запрос WITH может обращаться к собственным выходным данным. Очень простой пример, суммирующий целые числа от 1 до 100:

WITH RECURSIVE t(n) AS (
    VALUES (1)
  UNION ALL
    SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;

В общем виде рекурсивный запрос WITH всегда является не рекурсивной частью, потом идет UNION (или UNION ALL), потом рекурсивная часть, где только в рекурсивной части можно обратиться к результату этого же запроса. Подобный запрос выполняется следующим образом:

Вычисление рекурсивного запроса

Вычисляется не рекурсивная часть. Для UNION (но не UNION ALL) отбрасываются дублирующиеся строки. Все оставшиеся строки включаются в результат рекурсивного запроса, а также помещаются во временную рабочую таблицу.

Пока рабочая таблица не опустеет, повторяются следующие этапы:

Вычисляется рекурсивная часть, заменяя текущее содержимое рабочей таблицы рекурсивными ссылками на этот же запрос. Для UNION (но не UNION ALL) отбрасываются дублирующиеся строки и строки, дублирующие предыдущие результирующие строки. Все оставшиеся строки включаются в результат рекурсивного запроса, а также помещаются во временную промежуточную таблицу.

Содержимое рабочей таблицы заменяется содержимым промежуточной таблицы, а затем промежуточная таблица опустошается.

Примечание
Строго говоря, этот процесс является итерацией, а не рекурсией, но комитетом по стандартам SQL был выбран термин RECURSIVE.

В показанном выше примере рабочая таблица на каждом этапе содержит всего одну строку, и в ней последовательно набираются значения от 1 до 100. На сотом этапе, благодаря предложению WHERE, не выводится ничего, поэтому запрос завершается.

Рекурсивные запросы обычно используются для работы с иерархическими данными или древовидными структурами данных. В качестве полезного примера можно привести этот запрос, который находит все непосредственные и косвенные части товара, используя только таблицу, показывающую непосредственные составляющие:

WITH RECURSIVE included_parts(sub_part, part, quantity) AS (
    SELECT sub_part, part, quantity FROM parts WHERE part = 'our_product'
  UNION ALL
    SELECT p.sub_part, p.part, p.quantity
    FROM included_parts pr, parts p
    WHERE p.part = pr.sub_part
)
SELECT sub_part, SUM(quantity) as total_quantity
FROM included_parts
GROUP BY sub_part

Порядок поиска

При вычислении обхода дерева при помощи рекурсивного запроса может возникнуть необходимость отсортировать результаты в порядке «сначала в глубину» или «сначала в ширину». Для этого можно в дополнение к остальным столбцам данных вычислить упорядочивающий столбец и использовать его для сортировки полученных результатов. Обратите внимание, что это на самом деле не определяет порядок обхода строк запросом; этот порядок, как всегда, зависит от реализации SQL. Такой столбец просто предоставляет удобный способ упорядочить полученные результаты.

Чтобы отсортировать результаты в порядке «сначала в глубину», для каждой результирующей строки вычисляется массив уже просмотренных строк. Например, рассмотрим следующий запрос, выполняющий поиск в таблице tree по полю link:

WITH RECURSIVE search_tree(id, link, data) AS (
    SELECT t.id, t.link, t.data
    FROM tree t
  UNION ALL
    SELECT t.id, t.link, t.data
    FROM tree t, search_tree st
    WHERE t.id = st.link
)
SELECT * FROM search_tree;

Чтобы добавить информацию для упорядочивания «сначала в глубину», можно написать следующее:

WITH RECURSIVE search_tree(id, link, data, path) AS (
    SELECT t.id, t.link, t.data, ARRAY[t.id]
    FROM tree t
  UNION ALL
    SELECT t.id, t.link, t.data, path || t.id
    FROM tree t, search_tree st
    WHERE t.id = st.link
)
SELECT * FROM search_tree ORDER BY path;

В общем случае, когда для идентификации строки нужно использовать более одного поля, следует использовать массив строк. Например, если требуется отследить поля f1 и f2:

WITH RECURSIVE search_tree(id, link, data, path) AS (
    SELECT t.id, t.link, t.data, ARRAY[ROW(t.f1, t.f2)]
    FROM tree t
  UNION ALL
    SELECT t.id, t.link, t.data, path || ROW(t.f1, t.f2)
    FROM tree t, search_tree st
    WHERE t.id = st.link
)
SELECT * FROM search_tree ORDER BY path;

Совет
В этом общем случае, где нужно отслеживать только одно поле, элемент ROW() можно опустить. Это позволит использовать не массив составного типа, а простой массив, повысив тем самым эффективность.

Чтобы отсортировать результаты в порядке «сначала в ширину», можно добавить столбец, отслеживающий глубину поиска, например:

WITH RECURSIVE search_tree(id, link, data, depth) AS (
    SELECT t.id, t.link, t.data, 0
    FROM tree t
  UNION ALL
    SELECT t.id, t.link, t.data, depth + 1
    FROM tree t, search_tree st
    WHERE t.id = st.link
)
SELECT * FROM search_tree ORDER BY depth;

Для обеспечения стабильной сортировки добавьте столбцы данных в качестве столбцов вторичной сортировки.

Совет
Этот алгоритм вычисления рекурсивного запроса выдает свой результат в порядке поиска «сначала в ширину». Однако это особенность реализации, и, вероятно, полагаться на него неразумно. Порядок строк внутри каждого уровня, разумеется, не определен, поэтому в любом случае может понадобиться явное упорядочивание.

Для вычисления столбца, упорядочивающего «сначала в глубину» или «сначала в ширину», существует встроенный синтаксис. Например:

WITH RECURSIVE search_tree(id, link, data) AS (
    SELECT t.id, t.link, t.data
    FROM tree t
  UNION ALL
    SELECT t.id, t.link, t.data
    FROM tree t, search_tree st
    WHERE t.id = st.link
) SEARCH DEPTH FIRST BY id SET ordercol
SELECT * FROM search_tree ORDER BY ordercol;

WITH RECURSIVE search_tree(id, link, data) AS (
    SELECT t.id, t.link, t.data
    FROM tree t
  UNION ALL
    SELECT t.id, t.link, t.data
    FROM tree t, search_tree st
    WHERE t.id = st.link
) SEARCH BREADTH FIRST BY id SET ordercol
SELECT * FROM search_tree ORDER BY ordercol;

Внутри этот синтаксис разворачивается в форму, похожую на вышеописанные. В предложении SEARCH указывается, какой тип поиска требуется — «сначала в глубину» или «сначала в ширину», список столбцов, которые будут отслеживаться для сортировки, и имя столбца, который будет содержать результирующие данные, которые можно использовать для сортировки. Этот столбец будет неявно добавлен в выходные строки CTE.

Выявление циклов

Работая с рекурсивными запросами, важно позаботиться о том, чтобы рекурсивная часть запроса в итоге не возвратила никаких кортежей, иначе этот запрос превратится в бесконечный цикл. Иногда, воспользовавшись UNION вместо UNION ALL, этого можно добиться, отбрасывая строки, дублирующие предыдущие выходные строки. Однако зачастую в цикле нет полностью совпадающих выходных строк: в таких случаях может понадобиться проверить одно или несколько полей, чтобы увидеть, не была ли эта же точка достигнута раньше. Стандартный метод решения таких задач — вычислить массив уже просмотренных значений. Например, снова рассмотрим следующий запрос, просматривающий таблицу graph по полю link:

WITH RECURSIVE search_graph(id, link, data, depth) AS (
    SELECT g.id, g.link, g.data, 1
    FROM graph g
  UNION ALL
    SELECT g.id, g.link, g.data, sg.depth + 1
    FROM graph g, search_graph sg
    WHERE g.id = sg.link
)
SELECT * FROM search_graph;

Этот запрос зациклится, если отношения link содержат циклы. Поскольку нам нужно получить данные, упорядоченные «в ширину», простое изменение UNION ALL на UNION не уберет угрозу зацикливания. Вместо этого нам необходимо определить, достигали ли мы уже этой же строки, пройдя некоторый путь из ссылок. Мы добавляем два столбца, is_cycle и path, в склонный к зацикливанию запрос:

WITH RECURSIVE search_graph(id, link, data, depth, path, cycle) AS (
    SELECT g.id, g.link, g.data, 1,
      ARRAY[g.id],
      false
    FROM graph g
  UNION ALL
    SELECT g.id, g.link, g.data, sg.depth + 1,
      path || g.id,
      g.id = ANY(path)
    FROM graph g, search_graph sg
    WHERE g.id = sg.link AND NOT cycle
)
SELECT * FROM search_graph;

Помимо предотвращения циклов, значение массива часто бывает полезно само по себе как представление «пути», приведшего к определенной строке.

В общем случае, когда для выявления цикла нужно проверить более одного поля, следует использовать массив строк. Например, если нужно сравнить поля f1 и f2:

WITH RECURSIVE search_graph(id, link, data, depth, path, cycle) AS (
    SELECT g.id, g.link, g.data, 1,
      ARRAY[ROW(g.f1, g.f2)],
      false
    FROM graph g
  UNION ALL
    SELECT g.id, g.link, g.data, sg.depth + 1,
      path || ROW(g.f1, g.f2),
      ROW(g.f1, g.f2) = ANY(path)
    FROM graph g, search_graph sg
    WHERE g.id = sg.link AND NOT cycle
)
SELECT * FROM search_graph;

Совет
В этом общем случае, где для выявления цикла нужно проверять только одно поле, элемент ROW() можно опустить. Это позволит использовать не массив составного типа, а простой массив, повысив тем самым эффективность.

Для упрощения выявления циклов существует встроенный синтаксис. Приведенный выше запрос можно также записать следующим образом:

WITH RECURSIVE search_graph(id, link, data, depth) AS (
    SELECT g.id, g.link, g.data, 1
    FROM graph g
  UNION ALL
    SELECT g.id, g.link, g.data, sg.depth + 1
    FROM graph g, search_graph sg
    WHERE g.id = sg.link
) CYCLE id SET is_cycle USING path
SELECT * FROM search_graph;

и внутри он будет переписан в вышеуказанную форму. В предложении CYCLE сначала задается список столбцов для отслеживания на предмет выявления цикла, потом имя столбца, который покажет, был ли выявлен цикл, и наконец, имя еще одного столбца, в котором будет отслеживаться путь. Столбцы, указывающие цикл и путь, будут неявно добавлены в выходные строки CTE.

Совет
Столбец, указывающий путь цикла, вычисляется тем же способом, что и столбец для упорядочивания «сначала в глубину», показанным в предыдущем подразделе. Запрос может содержать одновременно предложения SEARCH и CYCLE, но при этом будут производиться излишние вычисления, поэтому эффективнее использовать только предложение CYCLE и упорядочивать результаты по столбцу пути. Однако если требуется порядок «сначала в ширину», одновременное указание SEARCH и CYCLE может быть полезным.

Для тестирования запросов в отсутствие уверенности, что они могут зациклиться, есть полезный прием — поместить LIMIT в родительский запрос. Например, этот запрос войдет в бесконечный цикл, если не добавить LIMIT:

WITH RECURSIVE t(n) AS (
    SELECT 1
  UNION ALL
    SELECT n+1 FROM t
)
SELECT n FROM t LIMIT 100;

Но в данном случае этого не произойдет, потому что реализация QHB вычисляет ровно столько строк запроса WITH, сколько фактически выбрал родительский запрос. В производственной среде использовать этот прием не рекомендуется, так как другие системы могут работать по-другому. Кроме того, это обычно не работает, если внешний запрос сортирует результаты рекурсивного запроса или соединяет их с какой-нибудь другой таблицей, потому что в подобных случаях внешний запрос обычно все равно пытается выбрать результат запроса WITH полностью.

Материализация общих табличных выражений

У запросов WITH есть полезное свойство — обычно при выполнении всего родительского запроса они вычисляются только один раз, даже если этот запрос или соседние запросы WITH обращаются к ним неоднократно. Таким образом, затратные вычисления, которые требуются в нескольких местах, можно поместить в запрос WITH и избежать лишней работы. Другое возможное применение заключается в предотвращении нежелательных многократных вычислений функций с побочными эффектами. Однако обратная сторона медали заключается в том, что оптимизатор не может распространить ограничения родительского запроса на многократно задействуемый запрос WITH, поскольку это может повлиять на все эпизоды использования выходных данных запроса WITH, тогда как должно повлиять только на один. Многократно задействуемый запрос WITH будет вычисляться буквально, не пропуская даже те строки, которые потом может сбросить родительский запрос. (Но, как упоминалось выше, вычисление может остановиться раньше, если в ссылке на запрос будет затребовано только ограниченное число строк.)

Однако если запрос WITH не рекурсивный и свободен от побочных эффектов (то есть это SELECT, не содержащий изменчивых функций), то его можно свернуть в родительский запрос, что позволит совместно оптимизировать два уровня запросов. По умолчанию это происходит, если родительский запрос обращается к запросу WITH только один раз, но не при многократных обращениях. Это решение можно переопределить, указав MATERIALIZED, чтобы принудительно отделить вычисление запроса WITH, или NOT MATERIALIZED, чтобы присовокупить его к родительскому запросу. В последнем случае возникает риск дублирования вычисления запроса WITH, но это все равно может быть выгодно, если при каждом использовании запроса WITH требуется только небольшая часть из его полного результата.

Простой пример демонстрации этих правил:

WITH w AS (
    SELECT * FROM big_table
)
SELECT * FROM w WHERE key = 123;

Этот запрос WITH будет свернут, выполняя тот же план, что и этот запрос:

SELECT * FROM big_table WHERE key = 123;

В частности, если был создан индекс по столбцу key, вероятно, он будет использован для извлечения строк с key = 123. С другой стороны, в записи

WITH w AS (
    SELECT * FROM big_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref
WHERE w2.key = 123;

запрос WITH будет материализован, создавая временную копию таблицы big_table, которая затем будет соединена сама с собой — без участия какого-либо индекса. Этот запрос будет выполняться гораздо эффективнее, если записать его в следующем виде:

WITH w AS NOT MATERIALIZED (
    SELECT * FROM big_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref
WHERE w2.key = 123;

чтобы ограничения родительского запроса могли применяться непосредственно при сканировании big_table.

Пример, в котором NOT MATERIALIZED может быть нежелателен:

WITH w AS (
    SELECT key, very_expensive_function(val) as f FROM some_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.f = w2.f;

Здесь благодаря материализации запроса WITH очень затратная функция very_expensive_function вычисляется для строки таблицы только один раз, а не два.

Примеры выше показывают только WITH, применяемое с SELECT, но таким же образом его можно добавлять в команды INSERT, UPDATE или DELETE. В каждом случае он фактически предоставляет временную таблицу (или таблицы), к которой можно обращаться в основной команде.

Операторы изменения данных в WITH

В предложении WITH можно использовать операторы, изменяющие данные (INSERT, UPDATE или DELETE). Это позволяет выполнять в одном запросе сразу несколько различных операций. Например:

WITH moved_rows AS (
    DELETE FROM products
    WHERE
        "date" >= '2010-10-01' AND
        "date" < '2010-11-01'
    RETURNING *
)
INSERT INTO products_log
SELECT * FROM moved_rows;

Этот запрос по сути перемещает строки из products в products_log. Оператор DELETE в WITH удаляет указанные строки из products, возвращая их содержимое посредством своего предложения RETURNING, а затем главный запрос читает эти данные и вставляет их в products_log.

В примере выше есть один нюанс — предложение WITH присоединяется к оператору INSERT, а не к SELECT, вложенному в INSERT. Это необходимо, поскольку операторы, изменяющие данные, допустимы только в предложениях WITH, присоединяемых к оператору верхнего уровня. Однако при этом применяются обычные правила видимости WITH, так что к результату оператора с WITH можно обратиться и из подзапроса SELECT.

У операторов, изменяющих данные, в WITH обычно имеются предложения RETURNING (см. раздел Возврат данных из измененных строк), как показано в примере выше. Именно из результата предложения RETURNING, а не целевой таблицы оператора, изменяющего данные, формируется временная таблица, к которой можно будет обращаться в остальном запросе. Если у оператора, изменяющего данные, в WITH отсутствует предложение RETURNING, то временная таблица не создается и к ней нельзя будет обратиться в остальном запросе. Однако такой оператор все равно будет выполнен. Не особенно практичный пример:

WITH t AS (
    DELETE FROM foo
)
DELETE FROM bar;

Запрос в этом примере удалит все строки из таблиц foo и bar. В число обработанных строк, о котором будет сообщено клиенту, войдут только строки, удаленные из bar.

Рекурсивные ссылки в операторах, изменяющих данные, не допускаются. В некоторых случаях это ограничение можно обойти, обратившись к выходным данным рекурсивного WITH, например:

WITH RECURSIVE included_parts(sub_part, part) AS (
    SELECT sub_part, part FROM parts WHERE part = 'our_product'
  UNION ALL
    SELECT p.sub_part, p.part
    FROM included_parts pr, parts p
    WHERE p.part = pr.sub_part
)
DELETE FROM parts
  WHERE part IN (SELECT part FROM included_parts);

Этот запрос удалит все непосредственные и косвенные составные части товара.

Операторы, изменяющие данные, в WITH выполняются ровно один раз и всегда полностью, независимо от того, прочитает ли основной запрос их результат полностью или хотя бы частично. Обратите внимание, что это отличается от правила для SELECT в WITH: как говорилось в предыдущем разделе, SELECT выполняется только до тех пор, пока основному запросу требуются его его выходные данные.

Вложенные операторы в WITH выполняются одновременно друг с другом и с основным запросом. Таким образом, при использовании в WITH операторов, изменяющих данные, порядок, в котором указанные изменения фактически происходят, непредсказуем. Все эти операторы выполняются с одним снимком данных (см. главу Управление параллельным доступом), так что они не могут «видеть», как каждый из них воздействует на целевые таблицы. Это сглаживает эффекты непредсказуемости фактического порядка изменения строк и означает, что данные из RETURNING являются единственным способом передачи изменений от различных вложенных операторов WITH основному запросу. Например, в данном случае:

WITH t AS (
    UPDATE products SET price = price * 1.05
    RETURNING *
)
SELECT * FROM products;

внешний SELECT вернет оригинальные цены, которые были до действия UPDATE, тогда как в запросе

WITH t AS (
    UPDATE products SET price = price * 1.05
    RETURNING *
)
SELECT * FROM t;

внешний SELECT вернет измененные данные.

Изменение одной и той же строки дважды в рамках одного оператора не поддерживается. Произойдет только одно из изменений, но надежно предсказать, какое именно, нелегко (а иногда и вовсе невозможно). Это же касается удаления строки, которая уже была изменена в том же операторе: выполнится только изменение. Поэтому следует избегать попыток изменить строку дважды в одном оператора. В частности, следует избегать добавления вложенных операторов WITH, которые могут воздействовать на строки, изменяемые основным оператором или соседними вложенными операторами. Результат действия такого оператора будет непредсказуем.

В настоящее время для целевой таблицы оператора, изменяющего данные в WITH, не должно быть определено условное правило или правило ALSO или INSTEAD, распространяющееся на несколько операторов.

Типы данных

Содержание

Числовые типы

Денежные типы

Символьные типы

Двоичные типы данных

Типы даты/времени

Логический тип

Перечислимые типы

Геометрические типы

Типы сетевых адресов

Типы битовых строк

Типы текстового поиска

Тип UUID

Тип XML

Типы JSON

Массивы

Составные типы

Диапазонные типы

Типы доменов

Типы идентификаторов объектов

Тип pg_lsn

Псевдотипы

В QHB имеется богатый выбор собственных типов данных, доступных пользователям. Пользователи могут добавлять в QHB новые типы с помощью команды CREATE TYPE.

В Таблице 1 перечислены все встроенные типы данных общего назначения. Большинство альтернативных имен, перечисленных в столбце «Псевдонимы», используются внутри QHB по историческим причинам. Кроме того, имеется несколько устаревших типов и типов для внутреннего пользования, но они здесь не приводятся.

Таблица 1. Типы данных

Имя | Псевдонимы | Описание
bigint | int8 | восьмибайтное целое со знаком
bigserial | serial8 | автоинкрементное восьмибайтное целое число
bit [ (n) ] |  | битовая строка фиксированной длины
bit varying [ (n) ] | varbit [ (n) ] | битовая строка переменной длины
boolean | bool | логическое значение (true/false)
box |  | прямоугольник в плоскости
bytea |  | двоичные данные («байтовый массив»)
character [ (n) ] | char [ (n) ] | символьная строка фиксированной длины
character varying [ (n) ] | varchar [ (n) ] | символьная строка переменной длины
cidr |  | сетевой адрес IPv4 или IPv6
circle |  | круг в плоскости
date |  | календарная дата (год, месяц, день)
double precision | float8 | число с плавающей запятой двойной точности (8 байт)
inet |  | адрес хоста IPv4 или IPv6
integer | int, int4 | четырехбайтное целое со знаком
interval [ поля ] [ (p) ] |  | временной интервал
json |  | текстовые данные JSON
jsonb |  | двоичные данные JSON, разобранные
line |  | бесконечная линия в плоскости
lseg |  | отрезок в плоскости
macaddr |  | MAC (Media Access Control) адрес
macaddr8 |  | MAC (Media Access Control) адрес (в формате EUI-64)
mchar [ (n) ] |  | символьная строка фиксированной длины, совместимая с Microsoft SQL Server
money |  | денежная сумма
mvarchar [ (n) ] |  | символьная строка переменной длины, совместимая с Microsoft SQL Server
numeric [ (*p, s) ] | decimal [ (p, s) ] | точное числовое выбираемой точности
path |  | геометрический путь в плоскости
pg_lsn |  | регистрационный номер транзакции в журнале (LSN) QHB
pg_snapshot |  | снимок идентификатора транзакции на уровне пользователя
point |  | геометрическая точка в плоскости
polygon |  | замкнутый геометрический путь в плоскости
real | float4 | число одинарной точности с плавающей запятой (4 байта)
smallint | int2 | двухбайтное целое со знаком
smallserial | serial2 | двухбайтное целое число с автоприращением
serial | serial4 | четырехбайтное целое число с автоприращением
text |  | символьная строка переменной длины
time [ (p) ] [ without time zone ] |  | время суток (без часового пояса)
time [ (p) ] with time zone | timetz | время суток с учетом часового пояса
timestamp [ (p) ] [ without time zone ] |  | дата и время (без часового пояса)
timestamp [ (p) ] with time zone | timestamptz | дата и время с учетом часового пояса
tsquery |  | запрос текстового поиска
tsvector |  | документ для текстового поиска
txid_snapshot |  | снимок идентификатора транзакции на уровне пользователя (устаревший; см. pg_snapshot)
uuid |  | универсально уникальный идентификатор
xml |  | XML-данные

Совместимость
В стандарте SQL описаны следующие типы (или их имена): bigint, bit, bit varying, boolean, char, character varying, character, varchar, date, double precision, integer, interval, numeric, decimal, real, smallint, time (с часовым поясом и без), timestamp (с часовым поясом и без), xml.

Каждый тип данных имеет внешнее представление, определяемое его функциями ввода и вывода. Многие из встроенных типов имеют очевидные внешние форматы. Однако некоторые типы являются уникальными для QHB, как например геометрические типы, или имеют несколько возможных форматов, как например, типы даты и времени. Некоторые из функций ввода и вывода не являются обратными друг другу, т. е. результат функции вывода может потерять точность и не совпадать с исходным входным значением.

Числовые типы

Числовые типы включают в себя двух-, четырех- и восьмибайтные целые числа, четырех- и восьмибайтные числа с плавающей запятой и десятичные числа с выбираемой точностью. В Таблице 2 перечислены имеющиеся типы.

Таблица 2. Числовые типы

Имя | Размер | Описание | Диапазон
smallint | 2 байта | целое число малого диапазона | от -32768 до +32767
integer | 4 байта | типичный выбор для целого числа | от -2147483648 до +2147483647
bigint | 8 байт | большое целое число | от -9223372036854775808 до +9223372036854775807
decimal | переменная | указанная пользователем точность, точная | до 131072 цифр перед десятичной запятой и до 16383 цифр — после
numeric | переменная | указанная пользователем точность, точная | до 131072 цифр перед десятичной запятой и до 16383 цифр — после
real | 4 байта | переменная точность, приближенная | точность в пределах 6 десятичных цифр
double precision | 8 байт | переменная точность, приближенная | точность в пределах 15 десятичных цифр
smallserial | 2 байта | небольшое целое число с автоприращением | от 1 до 32767
serial | 4 байта | целое число с автоприращением | от 1 до 2147483647
bigserial | 8 байт | большое целое число с автоприращением | от 1 до 9223372036854775807

Синтаксис констант для числовых типов описан в подразделе Константы. Числовые типы имеют полный набор соответствующих арифметических операторов и функций. Дополнительную информацию см. в главе Функции и операторы. Подробнее эти типы описаны в следующих разделах.

Целочисленные типы

Типы smallint, integer и bigint хранят целые числа, то есть числа без дробной части, имеющие различные диапазоны. Попытки сохранить значения, выходящие за пределы допустимого диапазона, приведут к ошибке.

Тип integer является наиболее распространенным, поскольку он обеспечивает наилучший баланс между диапазоном, размером хранилища и производительностью. Тип smallint обычно используется только при нехватке объема дискового пространства. Тип bigint предназначен для тех случаев, когда диапазона типа integer недостаточно.

В SQL определены только целочисленные типы integer (или int), smallint и bigint. Имена типов int2, int4 и int8 являются расширениями, которые также используются некоторыми другими СУБД SQL.

Числа произвольной точности

Тип numeric может хранить числа с очень большим количеством цифр. Он особенно рекомендуется для хранения денежных сумм и других количественных величин, где требуется точность. Расчеты со значениями numeric дают точные результаты, где это возможно, например: сложение, вычитание, умножение. Однако операции со значениями numeric выполняются гораздо медленнее по сравнению с целочисленными типами или с типами с плавающей запятой, описываемыми в следующем разделе.

Ниже мы используем следующие термины: Точность значения numeric — это общее количество значимых цифр во всем числе, то есть количество цифр по обеим сторонам десятичной запятой. Масштаб значения numeric — это количество десятичных цифр в дробной части, справа от десятичной запятой. Таким образом, число 23,5141 имеет точность 6 и масштаб 4. Целые числа можно рассматривать как имеющие нулевой масштаб.

Для столбца numeric можно настроить как максимальную точность, так и максимальный масштаб. Чтобы объявить столбец типа numeric, используйте синтаксис:

NUMERIC(точность, масштаб)

Точность должна быть положительной, а масштаб — положительным или равным нулю. Альтернативный вариант:

NUMERIC(точность)

устанавливает масштаб 0. Форма:

NUMERIC

без указания точности или масштаба создает столбец, в котором можно сохранять числовые значения любой точности и масштаба, вплоть до предела реализации по точности. Столбец такого типа не будет приводить входные значения к какому-либо конкретному масштабу, тогда как столбцы numeric с объявленным масштабом будут приводить входные значения к этому масштабу. (Стандарт SQL требует, чтобы масштаб по умолчанию был равен 0, что соответствует приведению к целочисленной точности, но мы считаем это не очень полезным. Если вас заботит переносимость, всегда указывайте точность и масштаб явно.)

Примечание
Максимально допустимая точность, если она явно указана в объявлении типа, составляет 1000; NUMERIC без указанной точности подпадает под ограничения, описанные в Таблице 2.

Если масштаб сохраняемого значения больше, чем объявленный масштаб столбца, система округляет значение до указанного числа дробных цифр. Затем, если число цифр слева от десятичной запятой превышает объявленную точность минус заявленная шкала, возникает ошибка.

Числовые значения физически хранятся без каких-либо дополнительных нулей в начале или конце. Таким образом, заявленная точность и масштаб столбца определяют максимальный, а не фиксированный выделенный размер. (В этом смысле тип numeric больше похож на varchar(n) чем на char(n).) Фактическое требование к размеру хранения составляет два байта для каждой группы из четырех десятичных разрядов плюс дополнительные три-восемь байт.

В дополнение к обычным числовым значениям у типа numeric имеется несколько специальных значений:

Infinity
-Infinity
NaN

Это адаптированные из стандарта IEEE 754 значения, которые представляют «бесконечность», «минус бесконечность» и «не число» соответственно. Записывая эти значения в виде констант в команде SQL, их следует заключать в апострофы, например так: UPDATE table SET x = '-Infinity'. Во входных значениях такие строки распознаются без учета регистра. Как вариант, значения бесконечности можно записать как inf и -inf.

Значения бесконечности ведут себя в соответствии с математическими ожиданиями. Например, Infinity плюс любое конечное значение равно Infinity, как и Infinity плюс Infinity; но Infinity минус Infinity дает NaN (not a number, не число), поскольку у него нет четко определенной интерпретации. Обратите внимание, что бесконечность можно сохранить только в столбце типа numeric, не имеющем ограничений, потому что семантически она превышает любой конечный предел точности.

Значение NaN (не число) используется для представления неопределенных результатов вычислений. В целом, любая операция с входным NaN выдает тот же NaN. Единственное исключение составляют операции, в которых другие входные значения таковы, что один и тот же результат можно получить, даже если заменить NaN любым конечным или бесконечным числовым значением; значит, это выходное значение будет использовано и для NaN. (Пример этого принципа: результатом возведения NaN в нулевую степень будет единица.)

Примечание
В большинстве реализаций концепции «не число» NaN не считается равным любому другому числовому значению (включая сам NaN). Чтобы разрешить сортировку и использование значений numeric в древовидных индексах, QHB рассматривает значения NaN как равные друг другу и превышающие все значения, отличные от NaN.

Типы decimal и numeric равнозначны. Оба эти типа включены в стандарт SQL.

При округлении значений тип numeric выдает число, большее по модулю, в то время как (на большинстве машин) типы real и double precision выдают ближайшее четное число. Например:

SELECT x,
  round(x::numeric) AS num_round,
  round(x::double precision) AS dbl_round
FROM generate_series(-3.5, 3.5, 1) as x;
  x   | num_round | dbl_round
------+-----------+-----------
 -3.5 |        -4 |        -4
 -2.5 |        -3 |        -2
 -1.5 |        -2 |        -2
 -0.5 |        -1 |        -0
  0.5 |         1 |         0
  1.5 |         2 |         2
  2.5 |         3 |         2
  3.5 |         4 |         4
(8 rows)

Типы с плавающей запятой

Типы данных real и double precision хранят приближенные числовые значения переменной точности. На всех поддерживаемых в настоящее время платформах эти типы являются реализациями стандарта IEEE 754 для двоичной арифметики с плавающей запятой (одинарной и двойной точности соответственно) в той степени, в которой это поддерживается базовым процессором, операционной системой и компилятором.

Приближенное означает, что некоторые значения, которые невозможно преобразовать точно во внутренний формат, хранятся в виде приближений, поэтому при сохранении и извлечении значения могут обнаруживаться небольшие расхождения. Управление этими ошибками и то, как они распространяются посредством вычислений, является предметом целой отрасли математики и информатики и не будет обсуждаться здесь, за исключением следующих моментов:

Если вам требуется точное хранение и расчеты (например, для денежных сумм), используйте вместо этого тип numeric.

Если вы хотите выполнять с этими типами сложные вычисления для каких-то важных целей, особенно если вы рассчитываете на определенное поведение в граничных случаях (бесконечность, недостаточность), вам следует тщательно оценить реализацию.

Проверка равенства двух значений с плавающей запятой может не всегда давать ожидаемый результат.

На всех поддерживаемых в настоящее время платформах тип real имеет диапазон от 1E-37 до 1E+37 с точностью не менее 6 десятичных цифр. Тип double precision имеет диапазон от 1E-307 до 1E+308 с точностью не менее 15 цифр. Слишком большие или слишком маленькие значения вызовут ошибку. Если точность вводимого числа слишком высока, возможно округление. Числа, слишком близкие к нулю и непредставимые отличными от нуля, вызовут ошибку обнуления.

По умолчанию значения с плавающей запятой выводятся в текстовой форме в кратчайшем точном десятичном представлении; полученное десятичное значение ближе к истинному сохраненному двоичному значению, чем к любому другому значению, представляемому с той же двоичной точностью. (Однако выводимое значение в настоящее время никогда не находится точно посередине между двумя представимыми значениями, во избежание широко распространенной ошибки, когда функции ввода не соблюдают должным образом правило округления до ближайшего четного.) Это значение будет использовать не более 17 значащих десятичных цифр для типа float8 и не более 9 цифр для типа float4.

Примечание
Этот кратчайший точный формат вывода генерируется намного быстрее, чем исторически сложившийся формат округления.

В целях снижения точности выводимых чисел, когда это требуется, в параметре extra_float_digits можно выбрать округление десятичного числа. При значении 0 восстанавливается предыдущее округление числа по умолчанию до 6 (для float4) или 15 (для float8) значащих десятичных цифр. При отрицательных значениях количество значащих цифр уменьшается; например, при -2 выводимое число будет округлено до 4 или 13 цифр соответственно.

При любом значении extra_float_digits больше 0 выбирается кратчайший точный формат.

Примечание
Приложениям, которым были нужны точные значения, раньше приходилось задавать для параметра extra_float_digits значение 3, чтобы получить их. Для максимальной совместимости между версиями они должны продолжать это делать.

В дополнение к обычным числовым значениям типы с плавающей запятой имеют несколько специальных значений:

Infinity
-Infinity
NaN

Они представляют специальные значения IEEE 754 «бесконечность», «минус бесконечность» и «не число» соответственно. При записи этих значений в качестве констант в команде SQL необходимо заключить их в апострофы, например UPDATE table SET x = '-Infinity'. При вводе эти строки распознаются без учета регистра. Как вариант, значения бесконечности можно записать как inf и -inf.

Примечание
В IEEE754 указано, что NaN нельзя сравнивать с любым другим значением с плавающей запятой (включая сам NaN). Чтобы значения с плавающей запятой можно было сортировать и использовать в древовидных индексах, QHB рассматривает значения NaN как равные друг другу и превышающие все значения, отличные от NaN.

QHB также поддерживает стандартные обозначения SQL float и float(p) для указания неточных числовых типов. Здесь p задает минимально допустимую точность в двоичных цифрах. QHB принимает значения от float(1) до float(24) как выбор типа real, а значения от float(25) до float(53) — как выбор типа double precision. Значения p вне допустимого диапазона вызывают ошибку. float без заданной точности считается double precision.

Последовательные типы

Примечание
В этом разделе описывается специфичный для QHB способ создания столбца с автоприращением. Другой способ — использовать функцию столбца идентификаторов стандарта SQL, рассматриваемую на справочной странице команды CREATE TABLE.

Типы данных smallserial, serial и bigserial представляют собой не настоящие типы, а просто удобное средство для создания столбцов с уникальными идентификаторами (аналогично свойству AUTO_INCREMENT, поддерживаемому некоторыми базами данных). В текущей реализации указание:

CREATE TABLE имя_таблицы (
    имя_столбца SERIAL
);

равнозначно указанию:

CREATE SEQUENCE имя_таблицы_имя_столбца_seq AS integer;
CREATE TABLE имя_таблицы (
    имя_столбца integer NOT NULL DEFAULT nextval('имя_таблицы_имя_столбца_seq')
);
ALTER SEQUENCE имя_таблицы_имя_столбца_seq OWNED BY имя_таблицы.имя_столбца;

Таким образом, создается целочисленный столбец, у которого значения по умолчанию назначаются из генератора последовательности. Чтобы в этот столбец нельзя было вставить значение NULL, применяется ограничение NOT NULL. (В большинстве случаев имеет смысл также добавить ограничение UNIQUE или PRIMARY KEY, чтобы предотвратить случайную вставку дублирующихся значений, но автоматически это не происходит.) Последняя команда помечает последовательность как «принадлежащую» столбцу, поэтому при удалении столбца или таблицы она также будет удалена.

Примечание
Поскольку типы smallserial, serial и bigserial реализованы с использованием последовательностей, в последовательности значений, находящейся в столбце, могут появляться «дыры» или пропуски, даже если не удалять никакие строки. Значение, выделенное из последовательности, все равно «израсходуется», даже если строку, содержащую это значение, так и не удастся успешно вставить в столбец таблицы. Это может произойти, например, при откате транзакции вставки. См. подробное описание nextval() в разделе Функции для управления последовательностями.

Чтобы вставить следующее значение последовательности в столбец serial, укажите, что этому столбцу следует присвоить его значение по умолчанию. Это можно сделать либо путем исключения столбца из списка столбцов в команде INSERT, либо с помощью ключевого слова DEFAULT.

Имена типов serial и serial4 равнозначны: оба создают столбцы integer. Имена типов bigserial и serial8 работают аналогично, за исключением того, что они создают столбец bigint. Тип bigserial следует использовать, если за время существования таблицы вы предполагаете использовать более 2³¹

идентификаторов. Имена типов smallserial и serial2 также работают аналогично, за исключением того, что они создают столбец smallint.

Последовательность, созданная для столбца serial, автоматически удаляется при удалении столбца-владельца. Последовательность можно удалить, не удаляя столбец, но это приведет к удалению выражения по умолчанию для этого столбца.

Денежные типы

Тип money хранит денежную сумму с фиксированной точностью дробной части; см. Таблицу 3. Точность дробной части определяется настройкой параметра lc_monetary на уровне базы данных. Для указанного в таблице диапазона предполагается, что число содержит два знака после запятой. Входные данные принимаются в различных форматах, включая целочисленные литералы и литералы с плавающей запятой, а также типичные денежные форматы, например '$1,000.00'. Выводятся данные обычно в денежном формате, зависящем от региональных настроек.

Таблица 3. Денежные типы

Имя | Размер | Описание | Диапазон
money | 8 байт | денежная сумма | от -92233720368547758,08 до +92233720368547758,07

Так как выходные данные этого типа зависимы от региональных настроек, попытка загрузить данные типа money в базу данных с другим значением параметра lc_monetary может закончиться неудачей. Во избежание подобных проблем перед восстановлением дампа в новую базу данных убедитесь, что в этой базе данных параметр lc_monetary имеет то же или эквивалентное значение, что и в исходной.

Значения типов numeric, int и bigint можно привести к типу money. Преобразование из типов данных real и double precision можно выполнить, если сначала привести их к типу numeric, например:

SELECT '12.34'::float8::numeric::money;

Однако это не рекомендуется. Числа с плавающей запятой не следует использовать для обработки денег из-за возможных ошибок округления.

К типу numeric значение money можно привести без потери точности. Преобразование в другие типы потенциально может привести к потере точности и также должно выполняться в два этапа:

SELECT '52093.89'::money::numeric::float8;

Деление значения money на целое число производится с усечением дробной части в сторону нуля. Чтобы получить округленный результат, разделите его на значение с плавающей запятой или преобразуйте значение money в numeric перед делением, а затем преобразуйте результат в тип money. (Последний вариант предпочтительнее во избежание риска потери точности.) Когда значение money делится на другое значение money, результатом будет double precision (т. е. чистое число, а не денежная величина); денежные единицы измерения при делении сокращаются.

Символьные типы

Таблица 4. Символьные типы

Имя | Описание
character varying(n), varchar(n) | переменная длина с ограничением
character(n), char(n) | фиксированная длина, с дополнением
mvarchar(n) | переменная длина с ограничением; для совместимости с Microsoft SQL Server
mchar(n) | фиксированная длина, с дополнением; для совместимости с Microsoft SQL Server
text | переменная неограниченная длина

В Таблице 4 приведены типы символов общего назначения, доступные в QHB.

SQL определяет два основных типа символов: character varying(n) и character(n), где n — положительное целое число. Оба этих типа могут хранить строки длиной до n символов (не байтов). Попытка сохранить более длинную строку в столбце такого типа приведет к ошибке, если только избыточные символы не являются пробелами — тогда строка будет усечена до максимальной длины. (Это несколько странное исключение продиктовано стандартом SQL.) Если сохраняемая строка короче объявленной длины, значения типа character будут дополнены пробелами; значения типа character varying просто сохранят более короткую строку.

Если явно преобразовывать значение в character varying(n) или character(n), то значение чрезмерной длины будет усечено до n символов, не вызывая ошибку. (Это также продиктовано стандартом SQL.)

Обозначения varchar(n) и char(n) являются псевдонимами для character varying(n) и character(n) соответственно. Запись character без указания длины равнозначна character(1). Если же записать без указания длины character varying, этот тип примет строки любого размера. Последнее является расширением QHB.

В версии QHB 1.2.0 добавлены типы данных mchar и mvarchar, являющиеся аналогами типов char и varchar, но имеющие ряд особенностей поведения, повторяющих поведение типов nchar и nvarchar в Microsoft SQL Server.

Отличия от стандартных типов char и varchar в QHB:

сравнение без учета регистра, в том числе, если один из аргументов mchar или mvarchar, а второй нет (для сравнения с учетом регистра вводятся специальные версии операторов сравнения: &<, &<=, &=, &>= и &>).

mchar фиксированной длины дополняется пробелами справа, но при сравнениях эти пробелы игнорируются;

данные типа mchar или mvarchar хранятся в кодировке utf-16 (для пользователя это не важно);

для сравнения и приведения к верхнему/нижнему регистру используется ICU, поэтому результат не зависит от операционной системы;

функция получения подстроки substr(str, pos [, length]), а не substring.

Отличия от поведения nchar и nvarchar в Microsoft SQL Server:

mchar и mvarchar без указания длины трактуются как строки неопределенной неограниченной длины (похоже на nvarchar(max));

mchar неопределенной длины не дополняется пробелами справа (но явно заданные пробелы все-таки хранятся);

функция вычисления длины строки length(string), а не len, как в Microsoft SQL Server.

Для типов данных mchar и mvarchar поддерживаются индексы B-деревья и хеш-индексы.

Кроме того, QHB предоставляет тип text, в котором хранятся строки любой длины. Хотя тип text не соответствует стандарту SQL, он есть и в некоторых других СУБД SQL.

Значения типа character физически дополняются пробелами до указанной ширины n символов и сохраняются и отображаются таким образом. Однако конечные пробелы считаются семантически несущественными и не учитываются при сравнении двух значений типа character. В сопоставлениях, где пробелы являются значащими, такое поведение может привести к неожиданным результатам; например, SELECT 'a '::CHAR(2) collate "C" < E'a\n'::CHAR(2) возвращает true, несмотря на то, что в локали C символ пробел считается больше символа перевода строки. При преобразовании значения character в какой-либо другой тип конечные пробелы удаляются. Обратите внимание, что конечные пробелы являются семантически значимыми в значениях character varying и text, а также при сопоставлении с шаблоном, т. е. в операторе LIKE и в регулярных выражениях.

Какие конкретно символы можно сохранять в этих типах данных, зависит от набора символов базы данных, выбираемого при ее создании. Независимо от того, какой набор установлен, символ с кодом 0 (иногда называемый NULL) сохранить нельзя. Подробную информацию см. в разделе Поддержка кодировок.

Потребность в памяти для короткой строки (до 126 байт) составляет 1 байт плюс фактическая строка, которая в случае типа character включает заполнение пробелами. Более длинные строки требуют 4 байта служебной информации вместо 1. Длинные строки сжимаются системой автоматически, поэтому физический размер на диске может быть меньше. Очень длинные значения также хранятся в фоновых таблицах, чтобы не мешать быстрому доступу к более коротким значениям столбцов. В любом случае максимально возможный размер сохраняемой строки символов составляет около 1 ГБ. (Максимально допустимое значение n в объявлении типа данных меньше этого числа. Менять это бесполезно, поскольку в многобайтовых кодировках число символов и байтов может быть совершенно разным. Если вы хотите сохранить длинные строки без определенного верхнего предела, то вместо того чтобы задавать произвольный предел длины, используйте text или character varying без указания длины.)

Совет
Между этими типами нет разницы в производительности, за исключением увеличения места для хранения при использовании типа с пробелом и нескольких дополнительных циклов ЦП для проверки длины при сохранении в столбце с ограниченной длиной. Хотя тип character(n) имеет преимущества в производительности в некоторых других СУБД, в QHB такого преимущества нет; на самом деле из-за дополнительных затрат на хранение character(n) обычно самый медленный из всех типов. В большинстве случаев вместо него следует использовать text или character varying.

Информацию о синтаксисе строковых литералов см. в подразделе Строковые константы, а информацию о доступных операторах и функциях — в главе Функции и операторы.

Пример 1. Использование символьных типов

CREATE TABLE test1 (a character(4));
INSERT INTO test1 VALUES ('ok');
SELECT a, char_length(a) FROM test1; -- (1)

  a   | char_length
------+-------------
 ok   |           2

CREATE TABLE test2 (b varchar(5));
INSERT INTO test2 VALUES ('ok');
INSERT INTO test2 VALUES ('good      ');
INSERT INTO test2 VALUES ('too long');
ERROR:  value too long for type character varying(5)
INSERT INTO test2 VALUES ('too long'::varchar(5)); -- явное усечение
SELECT b, char_length(b) FROM test2;

   b   | char_length
-------+-------------
 ok    |           2
 good  |           5
 too l |           5

(1): Функция char_length рассматривается в разделе Строковые функции и операторы.

В QHB есть еще два символьных типа фиксированной длины, показанных в Таблице 5. Тип name существует только для хранения идентификаторов во внутренних системных каталогах и не предназначен для использования обычными пользователями. Его длина в настоящее время определяется как 64 байта (63 используемых символа плюс знак конца строки), но на него следует ссылаться, используя константу NAMEDATALEN в исходном коде C. Длина задается во время компиляции (и, следовательно, настраивается для специальных целей); максимальная длина по умолчанию может измениться в будущем выпуске. Тип "char" (обратите внимание на кавычки) отличается от char(1) тем, что требует только один байт памяти. Он используется внутри системных каталогов как упрощенный тип для перечисления.

Таблица 5. Специальные символьные типы

Имя | Размер | Описание
"char" | 1 байт | однобайтовый внутренний тип
name | 64 байта | внутренний тип для имен объектов

Двоичные типы данных

Тип данных bytea позволяет хранить двоичные строки; см. Таблицу 6.

Таблица 6. Двоичные типы данных

Имя | Размер | Описание
bytea | 1 или 4 байта плюс сама двоичная строка | двоичная строка переменной длины

Двоичная строка — это последовательность октетов (или байтов). Двоичные строки имеют два отличия от символьных строк. Во-первых, двоичные строки позволяют хранить октеты с нулевым значением и другие «непечатаемые» октеты (как правило, это октеты вне десятичного диапазона от 32 до 126). Строки символов запрещают нулевые октеты, а также любые другие значения октетов и последовательности значений октетов, недопустимые в соответствии с выбранной кодировкой набора символов базы данных. Во-вторых, операции над двоичными строками обрабатывают фактические байты, тогда как обработка символьных строк зависит от настроек локали. Иными словами, двоичные строки подходят для хранения данных, которые программист считает «необработанными байтами», тогда как символьные строки подходят для хранения текста.

Тип bytea поддерживает два формата для ввода и вывода: hex («шестнадцатеричный») и традиционный для QHB формат escape («экранированный»). Входные данные всегда принимаются в обоих форматах. Формат вывода зависит от параметра конфигурации bytea_output; по умолчанию используется «шестнадцатеричный».

Стандарт SQL определяет другой тип двоичной строки, который называется BLOB или BINARY LARGE OBJECT. Формат ввода отличается от bytea, но предоставляемые функции и операторы в основном одинаковы.

Шестнадцатеричный формат bytea

«Шестнадцатеричный» формат кодирует двоичные данные в виде 2 шестнадцатеричных цифр на байт, при этом первая цифра представляет собой наиболее значимые 4 бита. Всей строке предшествует последовательность \x (чтобы отличить ее от экранированного формата). Возможно, в некоторых контекстах понадобится экранировать обратный слеш путем его дублирования (см. подраздел Строковые константы). Вводимые шестнадцатеричные цифры могут быть в верхнем или нижнем регистре, и между парами цифр допускается пробел (но не внутри пары цифр и не в начальной последовательности \x). Шестнадцатеричный формат совместим с широким спектром внешних приложений и протоколов; кроме того, он, как правило, преобразуется быстрее экранированного формата, поэтому предпочтительнее использовать его.

Пример:

SELECT '\xDEADBEEF';

Экранированный формат bytea

Экранированный формат является традиционным форматом QHB для типа bytea. В нем используется подход, представляющий двоичную строку как последовательность символов ASCII, а те байты, которые нельзя представить в виде символа ASCII, преобразуются в специальные экранированные последовательности. Если с точки зрения приложения представление байтов в виде символов имеет смысл, то такое представление может быть удобным. Но на практике это обычно вносит путаницу, поскольку стирает различия между двоичными и символьными строками, а кроме того выбранный механизм экранирования последовательностей несколько громоздок. Поэтому в большинстве новых приложений этот формат, наверное, использовать не стоит.

При вводе значений bytea в экранированном формате октеты определенных значений нужно экранировать тогда как все октетные значения можно экранировать. В целом, чтобы экранировать октет, преобразуйте его в трехзначное восьмеричное значение и поставьте перед ним обратный слеш. Сам обратный слеш (символ с десятичным кодом 92) также можно записать в виде двойного обратного слеша. В Таблице 7 перечислены символы, которые нужно экранировать, а также приведены альтернативные экранированные последовательности, где это возможно.

Таблица 7. Экранированные октеты литералов bytea

Десятичное значение октета | Описание | Экранированное входное представление | Пример | Шестнадцатеричное представление
0 | нулевой октет | '\000' | '\000'::bytea; | \x00
39 | апостроф | '''' или '\047' | ''''::bytea; | \x27
92 | обратный слеш | '\\' или '\134' | '\\'::bytea; | \x5c
От 0 до 31 и от 127 до 255 | «Непечатные» октеты | '\ xxx' (восьмеричное значение) | '\001'::bytea; | \x01

Необходимость экранирования непечатаемых октетов зависит от настроек локали. В некоторых случаях их можно оставить без экранирования.

Причина, по которой апострофы необходимо дублировать, как показано в Таблице 7, заключается в том, что это верно для любого строкового литерала в команде SQL. Универсальный синтаксический анализатор строковых литералов убирает внешние апострофы и сводит любую пару апострофов к одному символу данных. В результате функция ввода bytea видит всего лишь один апостроф, который она обрабатывает как обычный символ данных. Однако обратный слеш функция ввода bytea обрабатывает как спецсимвол, тем самым реализуя другие действия, показанные в Таблице 7.

В некоторых контекстах обратный слеш необходимо дублировать (относительно показанных выше примеров), потому что общий анализатор строковых литералов также сократит пары обратных слешей до одного символа данных (см. подраздел Строковые константы).

Октеты bytea по умолчанию выводятся в шестнадцатеричном формате. Если поменять значение bytea_output на escape, «непечатаемые» октеты преобразуются в равнозначные им трехзначные восьмеричные значения, перед которыми стоит один обратный слеш. Большинство «печатаемых» октетов выводятся в виде стандартного представления из клиентского набора символов, например:

SET bytea_output = 'escape';

SELECT 'abc \153\154\155 \052\251\124'::bytea;
     bytea
----------------
 abc klm *\251T

Октет с десятичным значением 92 (обратный слеш) при выводе дублируется. Подробности см. в Таблице 8.

Таблица 8. Выходные экранированные октеты bytea

Десятичное значение октета | Описание | Выходное экранированное представление | Пример | Результат на выходе
92 | обратный слеш | \\ | '\134'::bytea; | \\
От 0 до 31 и от 127 до 255 | «Непечатаемые» октеты | \xxx (восьмеричное значение) | '\001'::bytea; | \001
От 32 до 126 | «Печатаемые» октеты | представление набора символов клиента | '\176'::bytea; | ~

В зависимости от используемого интерфейса QHB, может понадобиться выполнить дополнительные действия, связанные с экранированием и деэкранированием строк bytea. Например, если интерфейс автоматически преобразует символы перевода строк и возврата каретки, возможно, их тоже придется экранировать.

Типы даты/времени

QHB поддерживает полный набор типов даты и времени SQL, приведенных в Таблице 9. Операции, доступные для этих типов данных, описаны в разделе Функции и операторы даты/времени. Даты подсчитываются в соответствии с григорианским календарем, даже для времени до его введения (дополнительную информацию см. в подразделе История единиц измерения времени).

Таблица 9. Типы даты/времени

Имя | Размер | Описание | Наименьшее значение | Наибольшее значение | Точность
timestamp [ (p) ] [ without time zone ] | 8 байт | дата и время (без часового пояса) | 4713 г. до н. э. | 294276 н. э. | 1 микросекунда
timestamp [ (p) ] with time zone | 8 байт | дата и время с часовым поясом | 4713 г. до н. э. | 294276 н.э. | 1 микросекунда
date | 4 байта | дата (без времени суток) | 4713 г. до н. э. | 5874897 н. э. | 1 день
time [ (p**) ] [ without time zone ] | 8 байт | время суток (без даты) | 00:00:00 | 24:00:00 | 1 микросекунда
time [ (p) ] with time zone | 12 байт | время суток (без даты) с часовым поясом | 00: 00: 00+1459 | 24: 00: 00-1459 | 1 микросекунда
interval [ поля ] [ (p) ] | 16 байт | интервал времени | -178000000 лет | 178000000 лет | 1 микросекунда

Примечание
Стандарт SQL требует, чтобы запись одного только timestamp была равнозначна timestamp without time zone, и QHB следует этой логике. timestamptz принимается как сокращение от timestamp with time zone; это расширение QHB.

Типы time, timestamp и interval принимают необязательное значение точности p, которое указывает, сколько знаков после запятой должно сохраняться в поле секунд. По умолчанию явное ограничение точности отсутствует. Допустимый диапазон p составляет от 0 до 6.

Тип interval имеет дополнительный параметр, позволяющий ограничить набор хранимых полей путем записи одной из этих фраз:

YEAR
MONTH
DAY
HOUR
MINUTE
SECOND
YEAR TO MONTH
DAY TO HOUR
DAY TO MINUTE
DAY TO SECOND
HOUR TO MINUTE
HOUR TO SECOND
MINUTE TO SECOND

Обратите внимание, что если при выборе интервала указаны и поля, и p, то указание поля должно содержать SECOND, поскольку точность применяется только к секундам.

Тип time with time zone определяется стандартом SQL, но в определении описаны свойства сомнительной полезности. В большинстве случаев сочетание date, time, timestamp without time zone и timestamp with time zone должно обеспечивать полный диапазон функциональных возможностей даты/времени, требуемых для любого приложения.

Ввод даты/времени

Ввод даты и времени допускается практически в любом приемлемом формате, включая ISO 8601, SQL-совместимый, традиционный формат POSTGRES и другие. Для некоторых форматов порядок дня, месяца и года в вводимой дате является неоднозначным, поэтому поддерживается явное определения ожидаемого порядка этих полей. Установите для параметра DateStyle значение MDY для выбора интерпретации месяц-день-год, DMY для день-месяц-год или YMD для год-месяц-день.

QHB более гибок в обработке ввода даты/времени, чем требует стандарт SQL. В разделе Поддержка даты/времени приведены точные правила анализа ввода даты/времени и распознанные текстовые поля, включая месяцы, дни недели и часовые пояса.

Помните, что любые вводимые литералы даты или времени должны быть заключены в апострофы, как текстовые строки. Дополнительную информацию см. в разделе Константы других типов. SQL требует следующий синтаксис:

тип [ (p) ] 'значение'

где p — необязательное указание точности, задающее количество знаков после запятой в поле секунд. Точность можно указать для типов time, timestamp и interval в диапазоне от 0 до 6. Если в определении константы точность не указана, то по умолчанию используется точность литерального значения (но не более 6 цифр).

Даты

В Таблице 10 приведены некоторые возможные входные данные для типа date.

Таблица 10. Ввод даты

Пример | Описание
1999-01-08 | ISO 8601; 8 января в любом режиме (рекомендуемый формат)
January 8, 1999 | определяется однозначно в любом режиме ввода datestyle
1/8/1999 | 8 января в режиме MDY; 1 августа в режиме DMY
1/18/1999 | 18 января в режиме MDY; недопустимая дата в других режимах
01/02/03 | 2 января 2003 г. в режиме MDY; 1 февраля 2003 г. в режиме DMY; 3 февраля 2001 г. в режиме YMD
1999-Jan-08 | 8 января в любом режиме
Jan-08-1999 | 8 января в любом режиме
08-Jan-1999 | 8 января в любом режиме
99-Jan-08 | 8 января в режиме YMD; ошибка в других режимах
08-Jan-99 | 8 января; ошибка в режиме YMD
Jan-08-99 | 8 января; ошибка в режиме YMD
19990108 | ISO 8601; 8 января 1999 года в любом режиме
990108 | ISO 8601; 8 января 1999 года в любом режиме
1999.008 | год и день года
J2451187 | Юлианская дата
January 8, 99 BC | 99 год до нашей эры

Время

Типы времени суток — это time [ (p) ] without time zone и time [ (p) ] with time zone. Указание time без уточнения равнозначно time without time zone.

Допустимые входные данные для этих типов состоят из времени суток, за которым следует необязательный часовой пояс. (См. Таблицу 11 и Таблицу 12.) Если часовой пояс указан во входных данных для time without time zone, он игнорируется. Также можно указать дату, но она будет игнорироваться, если только не использовать имя часового пояса, включающее правило перехода на летнее время, например America/New_York. В этом случае указание даты требуется для определения того, какое время применяется: стандартное или летнее. Соответствующее смещение часового пояса записывается в поле со значением time with time zone.

Таблица 11. Ввод времени

Пример | Описание
04:05:06.789 | ISO 8601
04:05:06 | ISO 8601
04:05 | ISO 8601
040506 | ISO 8601
04:05 AM | то же, что и 04:05; AM не влияет на значение времени
04:05 PM | то же, что и 16:05; вводимый час должен быть <= 12
04:05:06.789-8 | ISO 8601, с часовым поясом в виде смещения от UTC
04:05:06-08:00 | ISO 8601, с часовым поясом в виде смещения от UTC
04:05-08:00 | ISO 8601, с часовым поясом в виде смещения от UTC
040506-08 | ISO 8601, с часовым поясом в виде смещения от UTC
040506+0730 | ISO 8601, с часовым поясом в виде нецелочисленного смещения от UTC
040506+07:30:00 | смещение от UTC, заданное до секунд (не допускается в ISO 8601)
04:05:06 PST | часовой пояс задан аббревиатурой
2003-04-12 04:05:06 America/New_York | часовой пояс задан полным названием

Таблица 12. Ввод часового пояса

Пример | Описание
PST | Аббревиатура (для тихоокеанского стандартного времени)
America/New_York | Полное название часового пояса
PST8PDT | Указание часового пояса в стиле POSIX
-8:00:00 | Смещение часового пояса PST от UTC
-8:00 | Смещение часового пояса PST от UTC (расширенный формат ISO-8601)
-800 | Смещение часового пояса PST от UTC (стандартный формат ISO-8601)
-8 | Смещение часового пояса PST от UTC (стандартный формат ISO-8601)
zulu | Аббревиатура для UTC, принятая у военных
z | Краткая форма zulu (тоже определена в ISO-8601)

Дополнительную информацию о том, как задавать часовые пояса см. в подразделе Часовые пояса.

Отметки времени

Допустимые входные значения для типов меток времени состоят из объединенной записи даты и времени, за которой следует необязательный часовой пояс и необязательное уточнение AD или BC. (Как вариант, AD/BC можно указать и перед часовым поясом, но это нежелательно.) Таким образом:

1999-01-08 04:05:06

и:

1999-01-08 04:05:06 -8:00

являются допустимыми вариантами, соответствующими стандарту ISO 8601. Кроме того, поддерживается общий формат:

January 8 04:05:06 1999 PST

Стандарт SQL различает литералы timestamp without time zone и timestamp with time zone по наличию символа «+» или «-» и смещению часового пояса, указанному после времени. Следовательно, согласно стандарту,

TIMESTAMP '2004-10-19 10:23:54'

это timestamp without time zone, а

TIMESTAMP '2004-10-19 10:23:54+02'

это timestamp with time zone. QHB никогда не проверяет содержимое литеральной строки перед определением ее типа, поэтому она будет обрабатывать оба приведенных выше выражения как timestamp without time zone. Чтобы литерал обрабатывался timestamp with time zone, присвойте ему правильный явный тип:

TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'

В литерале, который был определен как timestamp without time zone, QHB будет просто игнорировать любую индикацию часового пояса. Таким образом, результирующее значение получается из полей даты/времени во входном значении и не корректируется для часового пояса.

Для timestamp with time zone внутреннее сохраненное значение всегда указывается в формате UTC (универсальное координированное время, традиционно известное как среднее время по Гринвичу, GMT.) Входное значение с явно указанным часовым поясом преобразуется в UTC с использованием соответствующего смещения для этого часового пояса. Если во входной строке часовой пояс не указан, подразумевается часовой пояс, заданный системным параметром TimeZone, который преобразуется в UTC с использованием смещения для зоны timezone.

Когда выводится значение timestamp without time zone, оно всегда преобразуется из UTC в текущую зону часового пояса и отображается как местное время в этой зоне. Чтобы увидеть время в другом часовом поясе, следует изменить часовой пояс или воспользоваться конструкцией AT TIME ZONE (см. подраздел AT TIME ZONE).

Преобразования между timestamp without time zone и timestamp with time zone обычно предполагают, что значение timestamp without time zone должно быть принято или задано как местное время зоны timezone. Для преобразования в другой часовой пояс можно использовать AT TIME ZONE.

Специальные значения

Для удобства QHB поддерживает несколько специальных значений ввода даты/времени, как показано в Таблице 13. Значения infinity и -infinity имеют особое представление внутри системы и будут отображаться без изменений; другие же являются простыми сокращениями, которые при чтении будут преобразованы в обычные значения даты/времени. (В частности, now и схожие строки преобразуются в актуальные на момент прочтения значения времени.) При использовании в качестве констант в командах SQL все эти значения следует заключать в апострофы.

Таблица 13. Специальные входные значения даты/времени

Строка ввода | Действительные типы | Описание
epoch | date, timestamp | 1970-01-01 00:00:00+00 (начало отсчета времени в Unix)
infinity | date, timestamp | время после всех остальных отметок времени
-infinity | date, timestamp | время до всех остальных отметок времени
now | date, time, timestamp | время начала текущей транзакции
today | date, timestamp | время начала текущих суток (00:00)
tomorrow | date, timestamp | время начала следующих суток (00:00)
yesterday | date, timestamp | время начала предыдущих суток (00:00)
allballs | time | 00:00:00.00 UTC

Для получения текущего значения времени для соответствующего типа данных можно также воспользоваться следующими SQL-совместимыми функциями: CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIME, LOCALTIMESTAMP. Последние четыре функции принимают необязательную спецификацию с точностью до секунды. (См. подраздел Текущая дата/время.) Обратите внимание, что это функции SQL, которые не распознаются в строках ввода данных.

Внимание!
Хотя использование входных строк now, today, tomorrow и yesterday при взаимодействии с командами SQL вполне допустимо, при сохранении команд для последующего выполнения, например в подготовленных операторах, представлениях или определениях функций, их поведение может быть неожиданным. Такая строка может преобразоваться в конкретное значение времени, которое будет продолжать использоваться даже после того, как потеряет свою актуальность. В таких случаях вместо этих строк лучше прибегнуть к помощи одной из функций SQL. Например, CURRENT_DATE + 1 надежнее, чем 'tomorrow'::date.

Вывод даты/времени

Формат вывода типов даты/времени можно установить в одном из четырех стилей: ISO 8601, SQL (Ingres), традиционный POSTGRES (формат даты Unix) или немецкий. По умолчанию используется формат ISO. (Стандарт SQL требует, чтобы использовался именно формат ISO 8601. Название «SQL» дано другому формату только по историческим причинам и носит случайный характер.) В Таблице 14 приведены примеры каждого стиля вывода. Согласно приведенным примерам, выходные данные типов date и time, как правило, представляют собой только часть даты или времени. Однако в стиле POSTGRES значение даты без времени выводится в формате ISO.

Таблица 14. Стили вывода даты/времени

Стиль | Описание | пример
ISO | ISO 8601, стандарт SQL | 1997-12-17 07:37:16-08
SQL | традиционный стиль | 12/17/1997 07:37:16.00 PST
POSTGRES | оригинальный стиль | Wed Dec 17 07:37:16 1997 PST
German | региональный стиль | 17.12.1997 07:37:16.00 PST

Примечание
ISO 8601 указывает, что для разделения даты и времени используется буква Т в верхнем регистре. QHB принимает этот формат при вводе, но на выходе, как показано выше, использует пробел, а не T. Это сделано для удобочитаемости и для соответствия RFC 3339, а также некоторым другим системам баз данных.

В стилях SQL и POSTGRES день отображается перед месяцем, если указан порядок полей DMY, в противном случае месяц отображается перед днем. (Информацию о том, как этот параметр также влияет на интерпретацию входных значений см. в подразделе Ввод даты/времени.) В Таблице 15 приведены соответствующие примеры.

Таблица 15. Соглашение о порядке отображения компонентов даты

Установка datestyle | Порядок ввода | Пример вывода
SQL, DMY | день/месяц/год | 17/12/1997 15:37:16.00 CET
SQL, MDY | месяц/день/год | 12/17/1997 07:37:16.00 PST
Postgres, DMY | день/месяц/год | Wed 17 Dec 07:37:16 1997 PST

В стиле ISO часовой пояс всегда отображается в виде числового смещения со знаком относительно UTC, где положительный знак используется для поясов к востоку от Гринвича. Смещение будет отображаться как чч (только часы), если оно является целым числом часов, либо как чч:мм, если оно является целым числом минут, либо как чч:мм:сс. (Третий вариант невозможен в современных стандартах часовых поясов, но может пригодиться при работе с метками времени, предшествующими принятию стандартизированных часовых поясов.) В других стилях даты часовой пояс отображается в виде буквенной аббревиатуры, если таковая принята в текущем часовом поясе. В противном случае он отображается в виде числового смещения со знаком в стандартном формате ISO 8601 (чч или ччмм).

Пользователь может выбрать стиль даты/времени с помощью команды SET datestyle, параметра DateStyle в файле конфигурации qhb.conf или переменной среды PGDATESTYLE на сервере или клиенте.

Также в качестве более гибкого способа форматирования выходных данных даты/времени доступна функция to_char (см. раздел Функции для форматирования типов данных).

Часовые пояса

Часовые пояса и соглашения об их применении зависят от политических решений, а не только от географии. В начале прошлого века часовые пояса во всем мире были в некоторой степени стандартизированы, но продолжают претерпевать произвольные изменения, особенно в отношении правил перехода на летнее время. Для получения информации об отмененных правилах, касающихся часовых поясов, QHB использует широко распространенную базу данных часовых поясов IANA (Olson). Относительно будущего времени предполагается, что в заданном часовом поясе последние известные правила так и останутся неизменными.

В большинстве случаев QHB стремится к совместимости с определениями стандарта SQL. Однако стандарт SQL включает странное сочетание типов и возможностей даты и времени. Есть две очевидные проблемы:

Хотя у типа date не может быть связанного часового пояса, для типа time его можно указать. В реальном мире часовые пояса не имеют большого значения, если они не связаны одновременно с датой и с временем, поскольку смещение может изменяться в течение года вместе с границами летнего времени.

По умолчанию часовой пояс задается в виде постоянного числового смещения от UTC. Таким образом, при выполнении арифметических операций с датой/временем, пересекающими границы летнего времени, его невозможно учесть.

Во избежание этих трудностей рекомендуется при использовании часовых поясов выбирать типы даты/времени, содержащие и дату, и время. Мы не рекомендуем использовать тип time with time zone (хотя он и поддерживается QHB для соответствия стандарту SQL). Для всех типов, содержащих только дату или только время, в QHB предполагается местный часовой пояс.

Все значения даты и времени с учетом часового пояса хранятся внутри в формате UTC. Перед предоставлением клиенту они преобразуются в местное время в зоне, указанной параметром конфигурации TimeZone.

QHB позволяет указывать часовые пояса в трех разных формах:

Полное название часового пояса, например America/New_York. Распознанные имена часовых поясов перечислены в представлении pg_timezone_names. Для этой цели QHB использует широко распространенные данные часового пояса IANA, поэтому имена часовых поясов QHB распознаются также и другими приложениями.

Аббревиатура часового пояса, например PST. В отличие от полных имен часовых поясов, которые также могут подразумевать набор правил перехода на летнее время, такая спецификация просто определяет конкретное смещение от UTC. Распознаваемые аббревиатуры перечислены в представлении pg_timezone_abbrevs. Аббревиатуры часового пояса нельзя задать в параметрах конфигурации TimeZone или log_timezone, но можно использовать во входных значениях даты/времени и в операторе AT TIME ZONE.

В дополнение к названиям и аббревиатурам часовых поясов QHB будет принимать спецификации часовых поясов в стиле POSIX, как описано в подразделе Указание часовых поясов в стиле POSIX. Обычно этот вариант менее предпочтителен, чем использование именованного часового пояса, но может понадобиться, если для нужного часового пояса нет подходящей записи в базе IANA.

Иными словами, разница между аббревиатурами и полными названиями состоит в том, что аббревиатуры представляют конкретное смещение от UTC, тогда как многие полные названия подразумевают еще и местное правило перехода на летнее время и, соответственно, два возможных смещения UTC. Например, 2014-06-04 12:00 America/New_York представляет полдень по местному времени в Нью-Йорке, что для этого конкретного дня было восточным летним временем (UTC-4). Так что 2014-06-04 12:00 EDT обозначает тот же момент времени. Но 2014-06-04 12:00 EST обозначает полдень по восточному стандартному времени (UTC-5) независимо от того, действовало ли в этот день летнее время.

Дело осложняется тем, что в некоторых юрисдикциях одна и та же аббревиатура часового пояса используется для обозначения разных смещений UTC в разное время; например, в Москве MSK несколько лет означало UTC+3, а потом — UTC+4. QHB интерпретирует такие аббревиатуры в соответствии с тем, что они означают (или означали совсем недавно) в указанную дату; но, как и в приведенном выше примере с EST, это не обязательно будет соответствовать местному гражданскому времени на эту дату.

Во всех случаях названия и сокращения часовых поясов распознаются без учета регистра.

Ни имена часовых поясов, ни аббревиатуры не зашиты в сервер; они берутся из файлов конфигурации, хранящихся в папках .../share/timezone/ и .../share/timezonesets/ установочного каталога (см. подраздел Файлы конфигурации даты/времени).

Параметр конфигурации TimeZone можно установить в файле qhb.conf или любым другим стандартным способом, описанным в главе Конфигурация сервера. Есть также несколько специальных способов установить его:

SQL-команда SET TIME ZONE устанавливает часовой пояс для текущего сеанса. Это альтернативное написание команды SET TIMEZONE TO, более совместимое со стандартом SQL.

Переменная среды PGTZ используется клиентами libpq для отправки команды SET TIME ZONE на сервер при подключении.

Ввод интервалов

Значения типа interval можно записать с помощью следующего подробного синтаксиса:

[@] количество единица [количество единица...] [направление]

где количество — это число (возможно, со знаком); единица — это одно из значений: microsecond, millisecond, second, minute, hour, day, week, month, year, decade, century, millennium (микросекунды, миллисекунды, секунды, минуты, часы, дни, недели, месяцы, годы, десятилетия, века и тысячелетия соответственно), либо сокращения или множественные формы этих слов; направление может принимать значение ago (назад) или быть пустым. Знак @ является необязательным. Величины различных единиц неявно суммируются с учетом соответствующего знака чисел. Указание ago меняет знак всех полей на противоположный. Этот синтаксис также используется при выводе интервала, если для параметра IntervalStyle установлено значение postgres_verbose.

Количество дней, часов, минут и секунд можно задать без явного указания единиц измерения. Например, '1 12:59:10’ читается так же, как '1 day 12 hours 59 min 10 sec'. Кроме того, комбинацию лет и месяцев можно указать с тире; например, '200-10' читается так же, как '200 years 10 months'. (Фактически только эти более короткие формы и разрешены стандартом SQL, и они используются при выводе, когда для IntervalStyle установлено значение sql_standard).

Значения типа interval также можно записать в виде временных интервалов, определенных в ISO 8601, используя либо «формат с кодами раздела 4.4.3.2 этого стандарта, либо «альтернативный формат» раздела 4.4.3.3. Формат с кодами выглядит так:

P количество единица [ количество единица ...] [ T [ количество единица ...]]

Строка должна начинаться с буквы P и может содержать букву T, предваряющую единицы времени суток. Имеющиеся сокращения единиц приведены в Таблице 16. Единицы можно опускать и указывать в любом порядке, но после T должны идти единицы меньше дня. В частности, значение кода M зависит от того, находится ли он до или после T

Таблица 16. Сокращения единиц временных интервалов ISO 8601

Сокращенное название | Значение
Y | Годы
M | Месяцы (в дате)
W | Недели
D | Дни
H | Часы
M | Минуты (во времени)
S | Секунды

В альтернативном формате:

P [ годы-месяцы-дни ] [ T часы:минуты:секунды ]

строка должна начинаться с P, а T разделяет компоненты даты и времени. Значения приведены в виде чисел, аналогичных датам ISO 8601.

При записи интервальной константы со указанием полей или присвоении интервальному столбцу строки с полями интерпретация немаркированных величин зависит от полей. Например, INTERVAL '1' YEAR читается как 1 год, а INTERVAL '1' — как 1 секунда. Кроме того, значения поля «справа» от наименее значимого поля, разрешенного в определении полей, просто отбрасываются. Например, в записи INTERVAL '1 day 2:03:04' HOUR TO MINUTE будет удалено поле секунд, но не дня.

Согласно стандарту SQL, все поля значения interval должны иметь одинаковый знак, поэтому ведущий отрицательный знак применяется ко всем полям; например, знак минус в интервальном литерале '-1 2:03:04' применяется как к дням, так и к часам/минутам/секундам. QHB позволяет полям иметь разные знаки и традиционно независимо обрабатывает знак каждого поля в текстовом представлении, так что в этом примере часы/минуты/секунды считаются положительными. Если для IntervalStyle установлено значение sql_standard, то ведущий знак считается применимым ко всем полям (но только при отсутствии дополнительных знаков). В противном случае используется традиционная интерпретация QHB. Во избежание неоднозначности, если какое-либо поле является отрицательным, рекомендуется прикреплять явный знак к каждому полю.

В подробном формате ввода и в некоторых полях более компактных форматов ввода значения полей могут иметь дробные части, например '1.5 week' или '01:02:03.45'. Такое входное значение при сохранении преобразуется в соответствующее число месяцев, дней и секунд. Когда в результате у месяцев или дней появляется дробное число, эта дробь добавляется в поля младшего разряда с использованием коэффициентов преобразования 1 месяц = 30 дней и 1 день = 24 часа. Например, '1.5 month' превращается в 1 месяц и 15 дней. В виде дробного числа будут отображаться только секунды.

В Таблице 17 приведены некоторые примеры допустимых входных значений типа interval.

Таблица 17. Ввод интервалов

Пример | Описание
1-2 | Стандартный формат SQL: 1 год 2 месяца
3 4:05:06 | Стандартный формат SQL: 3 дня 4 часа 5 минут 6 секунд
1 year 2 months 3 days 4 hours 5 minutes 6 seconds | Традиционный формат PostgreSQL: 1 год 2 месяца 3 дня 4 часа 5 минут 6 секунд
P1Y2M3DT4H5M6S | «Формат с кодами» ISO 8601: то же значение, что и выше
P0001-02-03T04:05:06 | «Альтернативный формат» ISO 8601: то же значение, что и выше

Внутренние значения типа interval хранятся в виде месяцев, дней и секунд. Это сделано потому, что число дней в месяце варьируется, и в сутках может быть 23 или 25 часов, если происходит корректировка перехода на летнее время. Поля месяца и дня являются целыми числами, а поле секунд может быть дробным. Поскольку интервалы обычно получаются из константных строк или при вычитании меток времени, этот метод хранения применим в большинстве случаев, но может привести к неожиданным результатам:

SELECT EXTRACT(hours from '80 minutes'::interval);
 date_part
-----------
         1

SELECT EXTRACT(days from '80 hours'::interval);
 date_part
-----------
         0

Для настройки дней и часов, выходящих за пределы нормальных диапазонов доступны функции justify_days и justify_hours.

Вывод интервалов

Формат вывода типа interval можно установить в одном из четырех стилей: sql_standard, postgres, postgres_verbose или iso_8601 с помощью команды SET intervalstyle. По умолчанию используется формат postgres. В Таблице 18 приведены примеры каждого стиля вывода.

Стиль sql_standard создает выходные данные, соответствующие определению стандарта SQL для литеральных строк интервалов, если значение интервала соответствует ограничениям стандарта (только год-месяц или только день-время, без смешивания положительных и отрицательных компонентов). В противном случае выходные данные выглядят как стандартная литеральная строка год-месяц, за которой следует литеральная строка день-время с явными знаками, добавленными для устранения неоднозначности интервалов со смешанными знаками.

Вывод в стиле postgres соответствует формату в QHB, когда для параметра DateStyle установлено значение ISO.

Вывод в стиле postgres_verbose соответствует формату в QHB, когда для параметра DateStyle установлено значение, отличное от ISO.

Вывод в стиле iso_8601 соответствует «формату с кодами, описанному в разделе 4.4.3.2 стандарта ISO 8601.

Таблица 18. Примеры стилей вывода интервалов

Стиль | Интервал год-месяц | Интервал день-время | Смешанный интервал
sql_standard | 1-2 | 3 4:05:06 | -1-2 +3 -4: 05: 06
postgres | 1 year 2 mons | 3 days 04:05:06 | -1 year -2 mons +3 days -04:05:06
postgres_verbose | @ 1 year 2 mons | @ 3 days 4 hours 5 mins 6 secs | @ 1 year 2 mons -3 days 4 hours 5 mins 6 secs ago
iso_8601 | P1Y2M | P3DT4H5M6S | Р-1Y-2M3DT-4H-5M-6S

Логический тип

QHB предоставляет стандартный SQL-тип boolean (логический); см. Таблицу 19. Тип boolean может иметь несколько состояний: «true» (истина), «false» (ложь) и третье состояние «unknown» (неизвестно), которое представляется SQL-значением NULL.

Таблица 19. Логический тип данных

Имя | Размер | Описание
boolean | 1 байт | состояние: истина или ложь

Логические константы могут представляться в запросах SQL ключевыми словами SQL: TRUE, FALSE и NULL.

Функция ввода данных типа boolean принимает следующие строковые представления для состояния «true»:

true
yes
on
1

и следующие представления для состояния «false»:

false
no
off
0

Также допускаются уникальные префиксы этих строк, например t или n. Начальные или конечные пробелы игнорируются, а регистр символов не имеет значения.

Функция вывода данных типа boolean всегда выдает t или f, как показано в Примере 2.

Пример 2. Использование типа boolean

CREATE TABLE test1 (a boolean, b text);
INSERT INTO test1 VALUES (TRUE, 'sic est');
INSERT INTO test1 VALUES (FALSE, 'non est');
SELECT * FROM test1;
 a |    b
---+---------
 t | sic est
 f | non est

SELECT * FROM test1 WHERE a;
 a |    b
---+---------
 t | sic est

Ключевые слова TRUE и FALSE являются предпочтительным (SQL-совместимым) способом записи логических констант в запросах SQL. Но также можно использовать строковые представления, следуя общему синтаксису строковой литеральной константы, описанному в подразделе Константы других типов, например 'yes'::boolean.

Обратите внимание, что синтаксический анализатор автоматически понимает, что TRUE и FALSE относятся к типу boolean, но для NULL это не так, потому что ему может соответствовать любой тип. Поэтому в некоторых контекстах вам, возможно, придется явным образом привести NULL к типу boolean, например так: NULL::boolean. И наоборот, для строково-литерального логического значения приведение можно пропустить в тех контекстах, где анализатор может сделать вывод, что этот литерал должен иметь тип boolean.

Перечислимые типы

Перечислимые (enum) типы — это типы данных, которые содержат статический упорядоченный набор значений. Они равнозначны типам enum, поддерживаемым в ряде языков программирования. Примером данных перечислимого типа могут быть дни недели или набор значений состояния для фрагмента данных.

Объявление перечислимых типов

Перечислимые типы создаются с помощью команды CREATE TYPE, например:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

После создания перечислимый тип, так же, как и любой другой, можно использовать в определениях таблиц и функций:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE person (
    name text,
    current_mood mood
);
INSERT INTO person VALUES ('Moe', 'happy');
SELECT * FROM person WHERE current_mood = 'happy';
 name | current_mood
------+--------------
 Moe  | happy
(1 row)

Порядок

Порядок значений в перечислимом типе — это порядок, в котором значения были перечислены при его создании. Перечисления поддерживаются всеми стандартными операторами сравнения и связанными агрегатными функциями. Например:

INSERT INTO person VALUES ('Larry', 'sad');
INSERT INTO person VALUES ('Curly', 'ok');
SELECT * FROM person WHERE current_mood > 'sad';
 name  | current_mood
-------+--------------
 Moe   | happy
 Curly | ok
(2 rows)

SELECT * FROM person WHERE current_mood > 'sad' ORDER BY current_mood;
 name  | current_mood
-------+--------------
 Curly | ok
 Moe   | happy
(2 rows)

SELECT name
FROM person
WHERE current_mood = (SELECT MIN(current_mood) FROM person);
 name
-------
 Larry
(1 row)

Надежность типа

Каждый перечислимый тип данных является уникальным и не может сравниваться с другими перечислимыми типами. Взгляните на этот пример:

CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic');
CREATE TABLE holidays (
    num_weeks integer,
    happiness happiness
);
INSERT INTO holidays(num_weeks,happiness) VALUES (4, 'happy');
INSERT INTO holidays(num_weeks,happiness) VALUES (6, 'very happy');
INSERT INTO holidays(num_weeks,happiness) VALUES (8, 'ecstatic');
INSERT INTO holidays(num_weeks,happiness) VALUES (2, 'sad');
ERROR:  invalid input value for enum happiness: "sad"
-- ОШИБКА: неверное значение для перечисления happiness: "sad"
SELECT person.name, holidays.num_weeks FROM person, holidays
  WHERE person.current_mood = holidays.happiness;
ERROR:  operator does not exist: mood = happiness
-- ОШИБКА: оператор не существует: mood = happiness

Если вам действительно нужно сделать что-то подобное, можно написать собственный оператор или добавить к запросу явные приведения:

SELECT person.name, holidays.num_weeks FROM person, holidays
  WHERE person.current_mood::text = holidays.happiness::text;
 name | num_weeks
------+-----------
 Moe  |         4
(1 row)

Детали реализации

Метки перечисления чувствительны к регистру, поэтому 'happy' не равнозначно 'HAPPY'. Пробелы в метках тоже значимы.

Хотя перечислимые типы в основном предназначены для статических наборов значений, для них поддерживается добавление новых значений в существующий перечислимый тип и переименование значений (см. ALTER TYPE). А вот удалить или изменить порядок сортировки существующих значений в перечислении нельзя; для этого придется удалить и воссоздать это перечисление.

Значение перечислимого типа занимает на диске четыре байта. Длина текстовой метки перечислимого значения ограничена настройкой NAMEDATALEN, скомпилированной в QHB; в стандартных сборках длина ограничена 63 байтами.

Переводы из внутренних перечислимых значений в текстовые метки хранятся в системном каталоге pg_enum. Прямые запросы к этому каталогу могут быть полезны.

Геометрические типы

Геометрические типы данных представляют собой объекты в двумерном пространстве. В Таблице 20 перечислены геометрические типы, существующие в QHB.

Таблица 20. Геометрические типы

Имя | Размер | Описание | Представление
point | 16 байт | Точка на плоскости | (x,y)
line | 32 байта | Бесконечная прямая | {A, B, C},
lseg | 32 байта | Конечный отрезок | ((x1,y1),(x2,y2))
box | 32 байта | Прямоугольник | ((x1,y1),(x2,y2))
path | 16+16n байт | Закрытый путь (подобный многоугольнику) | ((x1,y1),...)
path | 16+16n байт | Открытый путь | [(x1,y1),...]
polygon | 40+16n байт | Многоугольник (подобный замкнутому пути) | ((x1,y1),...)
circle | 24 байта | Окружность | <(x,y),r> (центр окружности и радиус)

Для выполнения различных геометрических операций, таких как масштабирование, перемещение, вращение и определение пересечений QHB предоставляет широкий спектр функций и операторов. Они рассматриваются в разделе Геометрические функции и операторы.

Точки

Точки являются основной двумерной структурной единицей для геометрических типов. Значения типа point указываются в одном из следующих форматов:

( x, y )
  x, y

где x и y — соответствующие координаты в виде чисел с плавающей запятой.

Выводятся точки в первом формате.

Прямые

Прямые представлены линейным уравнением Ax + By + C = 0, где A и B не могут быть равны нулю одновременно. Значения типа line вводятся и выводятся в следующем виде:

{ A, B, C }

Как вариант, для ввода можно использовать любую из следующих форм:

[ ( x1, y1 ), ( x2, y2 ) ]
( ( x1, y1 ), ( x2, y2 ) )
  ( x1, y1 ), ( x2, y2 )
    x1, y1  ,   x2, y2

где (x1,y1) и (x2,y2) — это две разные точки на данной прямой.

Отрезки

Отрезок представляется парой точек, которые определяют его конец. Значения типа lseg указываются в любом из следующих форматов:

[ ( x1, y1 ), ( x2, y2 ) ]
( ( x1, y1 ), ( x2, y2 ) )
  ( x1, y1 ), ( x2, y2 )
    x1, y1  ,   x2, y2

где (x1,y1) и (x2,y2) — это конечные точки отрезка.

Выводятся отрезки в первом формате.

Прямоугольники

Прямоугольник представляется парой точек, которые находятся в противоположных его углах. Значения типа box указываются в любом из следующих форматов:

( ( x1, y1 ), ( x2, y2 ) )
  ( x1, y1 ), ( x2, y2 )
    x1, y1  ,   x2, y2

где (x1,y1) и (x2,y2) — это любые два противоположных угла прямоугольника.

Выводятся прямоугольники во втором формате.

При вводе можно указать любые два противоположных угла, но затем значения будут по мере необходимости переупорядочены для хранения в таком порядке: сначала верхний правый, потом нижний левый угол.

Пути

Пути представляются списками соединенных точек. Пути могут быть открытыми, когда считается, что первая и последняя точки в списке не соединены, и закрытыми, когда считается, что они соединены.

Значения типа path указываются в любом из следующих форматов:

[ ( x1, y1 ), ..., ( xn, yn ) ]
( ( x1, y1 ), ..., ( xn, yn ) )
  ( x1, y1 ), ..., ( xn, yn )
  ( x1, y1  , ...,   xn, yn )
    x1, y1  , ...,   xn, yn

где точки являются конечными точками отрезков, составляющих путь. Квадратные скобки ([]) обозначают открытый путь, а круглые скобки (()) — закрытый. Когда внешние скобки опущены, как в форматах с третьего по пятый, предполагается закрытый путь.

Выводятся пути в первом или втором формате, в зависимости от ситуации.

Многоугольники

Многоугольники представляются списками точек (вершин). Многоугольники очень похожи на закрытые пути, но хранятся по-другому и имеют собственный набор вспомогательных функций.

Значения типа polygon указываются в любом из следующих форматов:

( ( x1, y1 ), ..., ( xn, yn ) )
  ( x1, y1 ), ..., ( xn, yn )
  ( x1, y1  , ...,   xn, yn )
    x1, y1  , ...,   xn, yn

где точки являются конечными точками отрезков, составляющих границу многоугольника.

Выводятся многоугольники в первом формате.

Круги

Круги представляются центральной точкой и радиусом. Значения типа circle указываются в любом из следующих форматов:

< ( x, y ), r >
( ( x, y ), r )
  ( x, y ), r
    x, y  , r

где (x,y) — это центральная точка, а r — радиус окружности.

Выводятся круги в первом формате.

Типы сетевых адресов

QHB предлагает типы данных для хранения адресов IPv4, IPv6 и MAC, как показано в Таблице 21. Для хранения сетевых адресов лучше вместо обычных текстовых типов использовать эти типы, потому что они предлагают проверку ошибок ввода, а также специализированные операторы и функции (см. раздел Функции и операторы для сетевых адресов).

Таблица 21. Типы сетевых адресов

Имя | Размер | Описание
cidr | 7 или 19 байт | Сети IPv4 и IPv6
inet | 7 или 19 байт | Хосты и сети IPv4 и IPv6
macaddr | 6 байт | MAC-адреса
macaddr8 | 8 байт | MAC-адреса (формат EUI-64)

При сортировке типов inet или cidr адреса IPv4 всегда будут сортироваться до адресов IPv6, в том числе адреса IPv4, включенные или сопоставленные с адресами IPv6, например ::10.2.3.4 или ::ffff:10.4.3.2.

inet

Тип inet содержит адрес хоста IPv4 или IPv6 и, при необходимости, его подсеть, все в одном поле. Подсеть представляется количеством битов адреса сети в адресе хоста (или «маской сети»). Если маска сети равна 32, а адрес — IPv4, то это значение указывает не на подсеть, а только на отдельный хост. В IPv6 длина адреса составляет 128 бит, поэтому уникальный адрес хоста задают 128 бит. Обратите внимание, что, если вам требуется, чтобы принимались только адреса сетей, следует использовать тип cidr а не inet.

Формат ввода для этого типа: адрес/y где адрес — это адрес IPv4 или IPv6, а y — количество битов в маске сети. Если часть /y отсутствует, то маска сети равна 32 для IPv4 и 128 для IPv6, поэтому значение будет представлять только один хост. При выводе часть /y опускается, если маска сети определяет адрес одного хоста.

cidr

Тип cidr содержит определение сети IPv4 или IPv6. Форматы ввода и вывода соответствуют соглашениям CIDR (Classless Internet Domain Routing, бесклассовая междоменная маршрутизация). Сети определяются в формате адрес/y, где адрес — это сеть, представленная в виде адреса IPv4 или IPv6, а y — количество битов в маске сети. Если y опущен, он рассчитывается с использованием допущений из старой классовой системы нумерации сети, с той разницей, что он будет как минимум достаточно большим, чтобы в него вошли все октеты, записанные во входных данных. Сетевой адрес, биты которого указаны справа от заданной маски, будет считаться ошибочным.

В Таблице 22 приводится несколько соответствующих примеров.

Таблица 22. Примеры вводимых значений типа cidr

Вводимое значение cidr | Выводимое значение cidr | abbrev(cidr)
192.168.100.128/25 | 192.168.100.128/25 | 192.168.100.128/25
192.168/24 | 192.168.0.0/24 | 192.168.0/24
192.168/25 | 192.168.0.0/25 | 192.168.0.0/25
192.168.1 | 192.168.1.0/24 | 192.168.1/24
192.168 | 192.168.0.0/24 | 192.168.0/24
128.1 | 128.1.0.0/16 | 128.1/16
128 | 128.0.0.0/16 | 128.0/16
128.1.2 | 128.1.2.0/24 | 128.1.2/24
10.1.2 | 10.1.2.0/24 | 10.1.2/24
10.1 | 10.1.0.0/16 | 10.1/16
10 | 10.0.0.0/8 | 10/8
10.1.2.3/32 | 10.1.2.3/32 | 10.1.2.3/32
2001:4f8:3:ba::/64 | 2001:4f8:3:ba::/64 | 2001:4f8:3:ba::/64
2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128 | 2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128 | 2001:4f8:3:ba:2e0:81ff:fe22:d1f1
::ffff:1.2.3.0/120 | ::ffff:1.2.3.0/120 | ::ffff:1.2.3/120
::ffff:1.2.3.0/128 | ::ffff:1.2.3.0/128 | ::ffff:1.2.3.0/128

Различия между inet и cidr

Существенное различие между типами данных inet и cidr заключается в том, что inet принимает значения с ненулевыми битами справа от маски сети, а cidr — нет. Например, значение 192.168.0.1/24 допустимо для inet но не для cidr.

Совет
Если вас не устраивает формат вывода inet или cidr, попробуйте функции host, text и abbrev.

macaddr

В типе macaddr хранятся MAC-адреса, примером которых являются аппаратные адреса Ethernet-платы (хотя MAC-адреса используются и для других целей). Входные значения принимается в следующих форматах:

'08:00:2b:01:02:03'
'08-00-2b-01-02-03'
'08002b:010203'
'08002b-010203'
'0800.2b01.0203'
'0800-2b01-0203'
'08002b010203'

Все эти примеры определяют один и тот же адрес. Верхний и нижний регистр допускается для шестнадцатеричных цифр от a до f. Выводится macaddr всегда в первой из показанных форм.

Стандарт IEEE 802-2001 считает вторую из показанных форм (с дефисами) канонической формой для MAC-адресов, а первую форму (с двоеточиями) определяет как запись с обратным порядком бит, поэтому 08-00-2b-01-02-03 = 01:00:4D:08:04:0C. В настоящее время это соглашение в основном игнорируется и имеет значение только для устаревших сетевых протоколов (таких как Token Ring). QHB не поддерживает изменение порядка битов, и в всех принятых форматах используется канонический порядок LSB.

Остальные пять форматов ввода не описаны ни в каких стандартах.

macaddr8

В типе macaddr8 хранятся MAC-адреса в формате EUI-64, примером которых являются аппаратные адреса Ethernet-платы (хотя MAC-адреса также используются и для других целей). Этот тип может принимать MAC-адреса длиной 6 и 8 байт и сохранять их в формате длиной 8 байт. MAC-адреса, заданные в 6-байтовом формате, будут сохраняться в формате 8 байт, а 4-й и 5-й байты будут отданы FF и FE соответственно. Обратите внимание, что IPv6 использует модифицированный формат EUI-64, где 7-й бит должен быть установлен в 1 после преобразования из EUI-48. Для этого изменения предусмотрена функция macaddr8_set7bit. Вообще говоря, тип macaddr8 принимает любые входные строки, состоящие из пар шестнадцатеричных цифр (на границах байтов), при необходимости согласованно разделяемых одним из следующих символов: ':', '-' или '.'. Количество шестнадцатеричных цифр должно быть 16 (8 байт) или 12 (6 байт). Начальные и конечные пробелы игнорируются. Ниже приведены примеры допустимых форматов ввода:

'08:00:2b:01:02:03:04:05'
'08-00-2b-01-02-03-04-05'
'08002b:0102030405'
'08002b-0102030405'
'0800.2b01.0203.0405'
'0800-2b01-0203-0405'
'08002b01:02030405'
'08002b0102030405'

Все эти примеры определяют один и тот же адрес. Верхний и нижний регистр допускается для шестнадцатеричных цифр от a до f. Вывод всегда представляется в первой из показанных форм.

Последние шесть вышеуказанных форматов ввода не описаны ни в каких стандартах.

Чтобы преобразовать традиционный 48-битный MAC-адрес в формате EUI-48 в модифицированный формат EUI-64, который будет включен в адрес IPv6 качестве адреса хоста, воспользуйтесь функцией macaddr8_set7bit, как показано ниже:

SELECT macaddr8_set7bit('08:00:2b:01:02:03');

    macaddr8_set7bit
-------------------------
 0a:00:2b:ff:fe:01:02:03
(1 row)

Типы битовых строк

Битовые строки — это строки, состоящие из 1 и 0. Их можно использовать для хранения или визуализации битовых масок. В SQL существует два типа битовых строк: bit(n) и bit varying(n), где n — это положительное целое число.

Данные типа bit должны точно соответствовать длине n; попытка сохранить более короткие или более длинные битовые строки приведет к ошибке. Данные типа bit varying имеют переменную длину вплоть до максимальной длины n; более длинные строки будут отклонены. Запись bit без указания длины равнозначна bit(1), тогда как bit varying без указания длины означает неограниченную длину.

Примечание
Если явно привести значение битовой строки к типу bit(n), оно будет усечено или дополнено нулями справа до длины ровно n битам, не приводя к ошибке. Аналогичным образом, если явно привести значение битовой строки к типу bit varying(n), она будет усечено справа, если ее длина превышает n бит.

Информацию о синтаксисе констант битовых строк см. в подразделе Константы битовых строк. Доступные битовые логические операторы и функции обработки строк описаны в разделе Функции и операторы битовых строк.

Пример 3. Использование типов битовых строк

CREATE TABLE test (a BIT(3), b BIT VARYING(5));
INSERT INTO test VALUES (B'101', B'00');
INSERT INTO test VALUES (B'10', B'101');

ERROR:  bit string length 2 does not match type bit(3)
-- ОШИБКА: длина битовой строки 2 не соответствует типу bit(3)

INSERT INTO test VALUES (B'10'::bit(3), B'101');
SELECT * FROM test;

  a  |  b
-----+-----
 101 | 00
 100 | 101

Для хранения битовых значений требуется по 1 байту для каждой группы из 8 бит, плюс 5 или 8 байт дополнительно в зависимости от длины строки (но длинные значения могут быть сжаты или вынесены за пределы строки, как описано в разделе Символьные типы применительно к символьным строкам).

Типы для текстового поиска

QHB предоставляет два типа данных, предназначенных для поддержки полнотекстового поиска. Текстовый поиск — это операция по выполнению поиска в коллекции документов на естественном языке с целью обнаружения тех из них, что лучше всего соответствуют запросу. Тип tsvector представляет документ в форме, оптимизированной для текстового поиска; тип tsquery аналогичным образом представляет текстовый запрос. Подробное объяснение этой возможности дается в главе Полнотекстовый поиск, а связанные с ней функции и операторы перечислены в разделе Функции и операторы текстового поиска.

tsvector

Значение типа tsvector — это упорядоченный список дискретных лексем, представляющих собой слова, которые были нормализованы для сведения различных вариантов одного и того же слова в один (подробную информацию см. в главе Полнотекстовый поиск). Сортировка и удаление дубликатов выполняются автоматически во время ввода данных, как показано в этом примере:

SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector;
                      tsvector
----------------------------------------------------
 'a' 'and' 'ate' 'cat' 'fat' 'mat' 'on' 'rat' 'sat'

Чтобы представить лексемы, содержащие пробелы или знаки пунктуации, заключите их в апострофы:

SELECT $$the lexeme '    ' contains spaces$$::tsvector;
                 tsvector
-------------------------------------------
 '    ' 'contains' 'lexeme' 'spaces' 'the'

(В этом и следующем примерах мы используем строковые литералы, заключенные в двойные знаки доллара, во избежание путаницы из-за необходимости дублировать апострофы внутри литералов). При этом включаемые апострофы и обратные слэши необходимо продублировать:

SELECT $$the lexeme 'Joe''s' contains a quote$$::tsvector;
                    tsvector
------------------------------------------------
 'Joe''s' 'a' 'contains' 'lexeme' 'quote' 'the'

По желанию к лексемам можно добавить их целочисленные позиции:

SELECT 'a:1 fat:2 cat:3 sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10 fat:11 rat:12'::tsvector;
                                  tsvector
-------------------------------------------------------------------------------
 'a':1,6,10 'and':8 'ate':9 'cat':3 'fat':2,11 'mat':7 'on':5 'rat':12 'sat':4

Позиция обычно указывает местоположение исходного слова в документе. Информацию о положении слова можно использовать для ранжирования близости. Значения позиции могут варьироваться от 1 до 16383; числа выше максимума автоматически устанавливаются на 16383. Дублирующие позиции для одной и той же лексемы отбрасываются.

Лексемы с позициями можно дополнительно пометить весом, который можно выразить буквами A, B, C или D. D является значением по умолчанию и, следовательно, не отображается при выводе:

SELECT 'a:1A fat:2B,4C cat:5D'::tsvector;
          tsvector
----------------------------
 'a':1A 'cat':5 'fat':2B,4C

Веса, как правило, используются для отражения структуры документа, например, помечая слова заголовка не так, как слова основного текста. Функции ранжирования текстового поиска могут назначать разные приоритеты различным маркерам веса.

Важно понимать, что тип tsvector сам по себе не выполняет нормализацию слов; предполагается, что заданные слова уже соответствующим образом нормализованы для приложения. Например,

SELECT 'The Fat Rats'::tsvector;
      tsvector
--------------------
 'Fat' 'Rats' 'The'

Для большинства англоязычных приложений для поиска текста вышеприведенные слова будут считаться ненормализованными, но для типа tsvector это не важно. Обычно для нормализации слов, подходящих для поиска, необработанный текст документа следует обработать функцией to_tsvector:

SELECT to_tsvector('english', 'The Fat Rats');
   to_tsvector
-----------------
 'fat':2 'rat':3

Опять же, более подробную информацию см. в главе Полнотекстовый поиск.

tsquery

Значение tsquery хранит искомые лексемы и может комбинировать их, используя логические операторы & (И), | (ИЛИ) и ! (НЕ), а также оператор поиска фразы <-> (ПРЕДШЕСТВУЕТ). Существует также вариант оператора ПРЕДШЕСТВУЕТ вида <N> , где N — это целочисленная константа, которая определяет расстояние между двумя искомыми лексемами. Запись <-> равнозначна <1>.

Для принудительной группировки этих операторов можно использовать скобки. Без скобок эти операторы имеют разную степень привязки, в порядке убывания: ! (НЕТ), <-> (ПРЕДШЕСТВУЕТ), & (И) и | (ИЛИ).

Вот некоторые примеры:

SELECT 'fat & rat'::tsquery;
    tsquery
---------------
 'fat' & 'rat'

SELECT 'fat & (rat | cat)'::tsquery;
          tsquery
---------------------------
 'fat' & ( 'rat' | 'cat' )

SELECT 'fat & rat & ! cat'::tsquery;
        tsquery
------------------------
 'fat' & 'rat' & !'cat'

При желании лексемы в tsquery можно дополнить одной или несколькими весовыми буквами, при этом они будут сопоставляться только с теми лексемами tsvector, которые имеют один из этих весов:

SELECT 'fat:ab & cat'::tsquery;
    tsquery
------------------
 'fat':AB & 'cat'

Кроме того, лексемы в tsquery можно дополнить *, чтобы указать сопоставление по префиксу:

SELECT 'super:*'::tsquery;
  tsquery
-----------
 'super':*

Этот запрос будет сопоставляться с любым словом в tsvector, которое начинается с «super».

Правила, касающиеся апострофов, для лексем такие же, как описано ранее для лексем в tsvector, и, как и в случае с tsvector, любую необходимую нормализацию слов следует выполнять перед преобразованием в тип tsquery. Для выполнения такой нормализации подходит функция to_tsquery:

SELECT to_tsquery('Fat:ab & Cats');
    to_tsquery
------------------
 'fat':AB & 'cat'

Обратите внимание, что to_tsquery будет обрабатывать префиксы так же, как и другие слова, поэтому это сравнение возвращает true:

SELECT to_tsvector( 'postgraduate' ) @@ to_tsquery( 'postgres:*' );
 ?column?
----------
 t

потому что в postgres будет выделена основа postgr:

SELECT to_tsvector( 'postgraduate' ), to_tsquery( 'postgres:*' );
  to_tsvector  | to_tsquery
---------------+------------
 'postgradu':1 | 'postgr':*

которая будет соответствовать усеченной форме postgraduate.

Тип UUID

Тип данных uuid сохраняет универсальные уникальные идентификаторы (Universally Unique Identifiers, UUID) в соответствии с определением в RFC 4122, ISO/IEC 9834-8: 2005 и связанных стандартах. (В некоторых системах этот тип данных называется глобальным уникальным идентификатором, или GUID.) Этот идентификатор является 128-битной величиной, генерируемой специально выбранным алгоритмом, практически гарантирующим, что этот же идентификатор не будет сгенерирован кем-либо еще в известной вселенной с использованием того же алгоритма. Поэтому для распределенных систем эти идентификаторы обеспечивают лучшую гарантию уникальности, чем генераторы последовательностей, которые уникальны только в пределах одной базы данных.

UUID записывается в виде последовательности шестнадцатеричных цифр в нижнем регистре, разделенных дефисами на несколько групп, в следующем порядке: группа из 8 цифр, затем три группы из 4 цифр, а следом группа из 12 цифр — всего 32 цифры, представляющие 128 бит. Пример UUID в этой стандартной форме:

a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11

QHB также принимает следующие альтернативные формы для ввода: использование цифр в верхнем регистре, стандартный формат, заключенный в фигурные скобки, пропуск некоторых или всех дефисов, добавление дефиса после любой группы из четырех цифр. Примеры:

A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
a0eebc999c0b4ef8bb6d6bb9bd380a11
a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}

Выводятся значения этого типа всегда в стандартной форме.

Как UUID генерируется в QHB, описывается в разделе Функции генерирования UUID.

Тип XML

Тип данных xml можно использовать для хранения данных XML. Его преимущество перед хранением данных XML в текстовом поле состоит в том, что он проверяет вводимые значения на корректность, а кроме того для выполнения над ним типобезопасных операций существуют вспомогательные функции; см. раздел Функции XML. Для использования этого типа данных установку необходимо скомпилировать с конфигурацией configure --with-libxml.

Тип xml может сохранять правильно оформленные «документы», в соответствии со стандартом XML, а также фрагменты «содержимого», характеризующиеся как менее ограниченные «узлы документа» в модели данных XQuery и XPath. Грубо говоря, это означает, что в фрагментах содержимого может быть более одного элемента верхнего уровня или символьного узла. Определить, является ли конкретное значение xml полным документом или только фрагментом содержимого, можно при помощи выражения xml-значение IS DOCUMENT.

Информацию об ограничениях и совместимости для типа данных xml можно найти в подразделе Ограничения XML и соответствие SQL/XML.

Создание значений XML

Чтобы получить значение типа xml из символьных данных, используйте функцию xmlparse:

XMLPARSE ( { DOCUMENT | CONTENT } значение)

Примеры:

XMLPARSE (DOCUMENT '<?xml version="1.0"?><book><title>Manual</title><chapter>...</chapter></book>')
XMLPARSE (CONTENT 'abc<foo>bar</foo><bar>foo</bar>')

Хотя в соответствии со стандартом SQL это единственный способ преобразовать символьные строки в значения XML, также можно использовать форматы, специфичные для QHB:

xml '<foo>bar</foo>'
'<foo>bar</foo>'::xml

Тип xml не проверяет входные значения по схеме объявления типа документа (DTD), даже когда они указывают на DTD. В настоящее время также отсутствует встроенная поддержка проверки на соответствие другим языкам схем XML, например XML Schema.

Обратная операция, получение символьного строкового значения из xml, выполняется с помощью функции xmlserialize:

XMLSERIALIZE ( { DOCUMENT | CONTENT } значение AS тип )

тип может быть иметь значение character, character varying или text (или их псевдонимы). Опять же, согласно стандарту SQL, это единственный способ преобразования между типами xml и символьными типами, но QHB позволяет просто приводить значение к нужному типу.

Когда значение символьной строки приводится к типу xml или наоборот без помощи функций XMLPARSE или XMLSERIALIZE соответственно, выбор режима DOCUMENT или CONTENT определяется параметром конфигурации сеанса «XML option», который можно установить с помощью стандартной команды:

SET XML OPTION { DOCUMENT | CONTENT };

или записью, более характерной для QHB:

SET xmloption TO { DOCUMENT | CONTENT };

По умолчанию установлен режим CONTENT, поэтому допускаются все формы XML-данных.

Обработка кодировки

При работе с разными кодировками символов на стороне клиента и сервера, а также в передаваемых через них XML-данных необходимо соблюдать осторожность. Когда передача запросов на сервер и возврат результатов клиенту происходит в текстовом режиме (это нормальный режим), QHB преобразует все символьные данные, передаваемые от клиента серверу и наоборот, в кодировку для соответствующей стороны; см. раздел Поддержка кодировок. Сюда входят и строковые представления значений XML, такие как, например, в приведенных выше примерах. Обычно это означает, что объявления кодировки, содержащиеся в XML-данных, могут стать недействительными при преобразовании символьных данных в другие кодировки в процессе перемещения между клиентом и сервером, поскольку объявление встроенной кодировки не изменяется. Для решения этой проблемы объявления кодировки, содержащиеся в символьных строках, представленных для ввода в тип xml, игнорируются, и предполагается, что содержимое находится в текущей серверной кодировке. Следовательно, для правильной обработки таких строк XML-данных клиент должен передавать их в своей текущей кодировке. Именно клиент перед отправкой документов на сервер должен либо преобразовать их в свою текущую кодировку, либо изменить эту кодировку соответствующим образом. При выводе значения типа xml не будут содержать объявления кодировки, и клиент должен предполагать, что все данные находятся в его текущей кодировке.

Когда передача параметров запроса на сервер и возврат результатов клиенту происходит в двоичном режиме, преобразование кодировки не выполняется, поэтому возникает другая ситуация. В этом случае будет наблюдаться объявление кодировки в XML-данных, а при его отсутствии предполагается, что данные закодированы в UTF-8 (что соответствует стандарту XML; обратите внимание, что QHB не поддерживает UTF-16). При выводе данные будут содержать объявление кодировки, выбранной на стороне клиента, однако если это UTF-8, объявление будет опущено.

Нет необходимости говорить, что обработка XML-данных с помощью QHB будет менее подвержена ошибкам и более эффективна, если и в XML-данных, и на стороне клиента, и на стороне сервера будет использована одна кодировка. Поскольку внутри XML-данные закодированы в UTF-8, вычисления будут наиболее эффективными, если на сервере также выбрана кодировка UTF-8.

ВНИМАНИЕ!
Некоторые функции, связанные с XML, могут вообще не работать с данными, отличными от ASCII, если кодировка сервера не соответствует UTF-8. В частности, известно, что эта проблема есть у функций xmltable() и xpath().

Обращение к значениям XML

Тип данных xml необычен тем, что не предоставляет операторов сравнения. Это связано с тем, что для XML-данных не существует четко определенного и универсально полезного алгоритма сравнения. Одним из следствий этого является то, что вы не можете получить строки, сравнивая столбец xml со значением поиска. Поэтому обычно значения XML должны дополняться отдельным ключевым полем, например ID. Альтернативное решение для сравнения значений XML — сначала преобразовать их в символьные строки, но обратите внимание, что сравнение этих строк совершенно бесполезно для сравнения XML-данных.

Поскольку для типа данных xml нет операторов сравнения, невозможно создать индекс непосредственно для столбца этого типа. Если требуется быстрый поиск в XML-данных, это ограничение можно обойти, приведя выражение к типу символьной строки и проиндексировав его, либо проиндексировав выражение XPath. Конечно, фактический запрос следует скорректировать для поиска по индексируемому выражению.

Также для ускорения поиска документов в XML-данных можно использовать функциональность полнотекстового поиска в QHB. Однако это требует поддержки предварительной обработки, а в дистрибутиве QHB ее пока нет.

Типы JSON

Типы данных JSON предназначены для хранения данных JSON (JavaScript Object Notation, запись объектов JavaScript), в соответствии со стандартом RFC 7159. Подобные данные можно хранить и в виде текста, но преимущество типов JSON в том, что они проверяют каждое сохраненное значение на соответствие правилам JSON. Также для работы с данными, хранящимися в этих типах, существуют различные JSON-специфические функции и операторы; см. раздел Функции и операторы JSON.

QHB предлагает два типа для хранения данных JSON: json и jsonb. Для реализации эффективных механизмов запросов к этим типам данных QHB также предоставляет тип данных jsonpath, описанный в подразделе Тип jsonpath.

Типы данных json и jsonb принимают на ввод почти одинаковые наборы значений. Основное практическое отличие заключается в эффективности. Тип данных json хранит точную копию введенного текста, которую функции обработки должны заново анализировать при каждом выполнении; тогда как данные jsonb хранятся в разобранном двоичном формате, что немного замедляет его ввод из-за дополнительных затрат на преобразование, но значительно ускоряет обработку, так как не требуется повторный анализ. Помимо этого, тип jsonb поддерживает индексирование, что может быть существенным преимуществом.

Поскольку тип json хранит точную копию введенного текста, он сохраняет семантически незначимые пробелы между синтаксическими единицами, а также порядок ключей в объектах JSON. Кроме того, если объект JSON внутри значения содержит повторяющиеся ключи, то сохранятся все пары ключ/значение. (Функции обработки считают действительным последнее значение.) Тип jsonb, напротив, не сохраняет ни пробел, ни порядок ключей объекта, ни их дубликаты. Если во входных данных указаны дублирующиеся ключи, сохраняется только последнее значение.

В целом, в большинстве приложений предпочтительнее хранить данные JSON в формате jsonb, если нет специализированных требований, таких как устаревшие предположения о порядке расположения ключей объектов.

В RFC 7159 говорится, что строки JSON должны быть представлены в кодировке UTF8. Поэтому типы JSON не могут жестко соответствовать спецификации, если кодировка базы данных не UTF8. Напрямую вставить символы, непредставимые в кодировке базы данных, не получится; и наоборот, символы, представимые в кодировке базы данных, но не в UTF8, будут допустимыми.

RFC 7159 позволяет строкам JSON содержать экранированные последовательности Unicode, обозначенные через \u__XXXX__. В функции ввода для типа json экранирование Unicode разрешено независимо от кодировки базы данных и проверяется только на синтаксическую корректность (то есть, после \u должны идти четыре шестнадцатеричные цифры). Однако функция ввода для jsonb более строгая: она запрещает экранирование Unicode для символов, не относящихся к ASCII (т. е. символов выше U+007F), если кодировка базы данных не UTF8. Тип jsonb также не принимает \u0000 (поскольку это значение не может быть представлено в типе text QHB) и требует, чтобы суррогатные пары Unicode для обозначения символов вне основной многоязычной плоскости (BMP) Unicode использовались правильно. Допустимые управляющие символы Unicode преобразуются для хранения в эквивалентные символы ASCII или UTF8 (сюда относится и сворачивание суррогатных пар в один символ).

Примечание
Многие из функций обработки JSON, описанные в разделе Функции и операторы JSON, преобразуют управляющие символы Unicode в обычные и поэтому будут выдавать ошибки того же типа, что и вышеописанные, даже если им ввести данные типа json, а не jsonb. То, что функция ввода в тип json не выполняет эти проверки, можно рассматривать как исторический артефакт, хотя это и позволяет просто сохранять (без обработки) управляющие символы JSON Unicode в кодировке базы данных, не поддерживающей представленные символы.

При преобразовании вводимого текста JSON в jsonb примитивные типы, описанные в RFC 7159, в сущности отображаются в собственные типы QHB, как показано в Таблице 23. Следовательно, к допустимым данным типа jsonb предъявляются некоторые незначительные дополнительные требования, относящиеся к ограничениям представления нижележащего типа данных, которые не применимы ни к типу json, ни к формату JSON в целом. В частности, jsonb будет отклонять числа, выходящие за пределы типа numeric QHB, а json — нет. Подобные ограничения, накладываемые реализацией, допустимы согласно RFC 7159. Однако на практике такие проблемы гораздо более вероятны в других реализациях, поскольку обычно примитивный тип JSON number представляется в виде числа с плавающей запятой двойной точности IEEE 754 (что RFC 7159 явно предусматривает и допускает). При использовании JSON в качестве формата обмена данных с такими системами следует учитывать опасность потери точности чисел относительно данных, изначально сохраняемых в QHB.

И наоборот, как отмечено в таблице, существуют некоторые незначительные ограничения на формат ввода примитивных типов JSON, которые не применяются к соответствующим типам QHB.

Таблица 23. Примитивные типы JSON и соответствующие им типы QHB

Примитивный тип JSON | Тип QHB | Примечания
string | text | \u0000 не допускается, так как управляющие символы Unicode недоступны в этой кодировке базы данных
number | numeric | Значения NaN и infinity не допускаются
boolean | boolean | Допускается только строчное написание true и false
null | (none) | В SQL NULL имеет другой смысл

Синтаксис вводимых и выводимых значений JSON

Синтаксис ввода/вывода типов данных JSON соответствует RFC 7159.

Ниже приведены все допустимые выражения json (или jsonb):

-- Простое скалярное/примитивное значение
-- Примитивные значения могут быть числа, строки в кавычках, true, false или null
SELECT '5'::json;

-- Массив из нуля и более элементов (элементы могут быть разных типов)
SELECT '[1, 2, "foo", null]'::json;

-- Объект, содержащий пары ключей и значений
-- Обратите внимание, что ключи объекта всегда должны быть строками в кавычках
SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json;

-- Массивы и объекты могут вкладываться произвольным образом
SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json;

Как было сказано ранее, когда значение JSON вводится, а затем выводится без какой-либо дополнительной обработки, тип json выводит тот же текст, который был введен, тогда как тип jsonb не сохраняет семантически незначимые детали, такие как пробелы. Например, обратите внимание на эти различия:

SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::json;
                      json
-------------------------------------------------
 {"bar": "baz", "balance": 7.77, "active":false}
(1 row)

SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb;
                      jsonb
--------------------------------------------------
 {"bar": "baz", "active": false, "balance": 7.77}
(1 row)

Одна семантически незначимая деталь, на которую стоит обратить внимание: в jsonb числа будут выводиться исходя из поведения нижележащего типа numeric. На практике это означает, что числа, введенные в записи с E, будут выведены без нее, например:

SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
         json          |          jsonb
-----------------------+-------------------------
 {"reading": 1.230e-5} | {"reading": 0.00001230}
(1 row)

Однако, как видно из этого примера, тип jsonb сохранит конечные нули дробного числа, даже если они семантически незначимы для таких целей, как проверки на равенство.

Список встроенных функций и операторов, доступных для построения и обработки значений JSON, см. в разделе Функции и операторы JSON.

Разработка документов JSON

Представлять данные в JSON можно значительно более гибко, чем в традиционной модели реляционных данных, что особенно важно в средах с изменчивыми требованиями. Оба подхода вполне могут сосуществовать и дополнять друг друга в одном приложении. Однако даже для приложений, где требуется максимальная гибкость, все же рекомендуется, чтобы документы JSON имели некоторую фиксированную структуру. Эта структура, как правило, не навязывается принудительно (хотя возможно декларативное внедрение некоторых бизнес-правил), но ее предсказуемость облегчает написание запросов, которые эффективно извлекают данные из набора «документов» (информационных блоков) в таблице.

Данные JSON подчиняются тем же соображениям управления параллельным доступом, что и любой другой тип данных при хранении в таблице. Хотя хранение больших документов вполне осуществимо, имейте в виду, что при любом изменении вся строка блокируется на уровне строки. Для снижения частоты блокировок транзакций, изменяющих данные, рассмотрите возможность ограничения размера документов JSON до разумных пределов. В идеале каждый документ JSON должен представлять собой атомарный информационных блок, который, согласно бизнес- правилам, нельзя разделить на более мелкие, независимо изменяемые блоки.

Проверка на вложение и существование в jsonb

Проверка вложения является важной особенностью jsonb, не имеющей аналога для типа json. Эта проверка определяет, вложен ли один документ jsonb в другой. Приведенные ниже примеры возвращают значение true, за исключением отмеченных случаев:

-- Простые скалярные/примитивные значения включают только одно идентичное значение:
SELECT '"foo"'::jsonb @> '"foo"'::jsonb;

-- Массив с правой стороны вложен в массив слева:
SELECT '[1, 2, 3]'::jsonb @> '[1, 3]'::jsonb;

-- Порядок элементов в массиве не важен, поэтому эта запись тоже верна:
SELECT '[1, 2, 3]'::jsonb @> '[3, 1]'::jsonb;

-- Повторяющиеся элементы массива тоже не имеют значения:
SELECT '[1, 2, 3]'::jsonb @> '[1, 2, 2]'::jsonb;

-- Объект с одной парой с правой стороны вложен в объект слева:
SELECT '{"product": "PostgreSQL", "version": 9.4, "jsonb": true}'::jsonb @> '{"version": 9.4}'::jsonb;

-- Массив с правой стороны не считается вложенным в массив слева, хотя в
-- последний и вложен подобный массив:
SELECT '[1, 2, [1, 3]]'::jsonb @> '[1, 3]'::jsonb;  -- выдает false

-- Но если добавить уровень вложенности, массив считается вложенным:
SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb;

-- Аналогично это вложением не считается:
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"bar": "baz"}'::jsonb;  -- выдает false

-- Ключ на верхнем уровне с пустым объектом вложен в объект с таким ключом:
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"foo": {}}'::jsonb;

Общий принцип этой проверки заключается в том, что вложенный объект должен соответствовать содержащему его объекту по структуре и содержимому данных, возможно, после исключения из содержащего объекта некоторых неподходящих элементов массива или пар ключ/значение объекта. Но помните, что при сопоставлении вложений порядок элементов массива не имеет значения, а повторяющиеся элементы массива учитываются только один раз.

В качестве специального исключения из общего принципа соответствия структур, массив может содержать примитивное значение:

-- В этот массив вложено примитивное строковое значение:
SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb;

-- Это исключение не действует в обратную сторону -- здесь вложения нет:
SELECT '"bar"'::jsonb @> '["bar"]'::jsonb;  -- выдает false

У типа jsonb также имеется оператор существования, являющийся вариацией темы вложения: он проверяет, отображается ли строка (заданная как значение text) как ключ объекта или элемент массива на верхнем уровне значения jsonb. Приведенные ниже примеры возвращают значение true, за исключением отмеченных случаев:

-- Строка существует в качестве элемента массива:
SELECT '["foo", "bar", "baz"]'::jsonb ? 'bar';

-- Строка существует в качестве ключа объекта:
SELECT '{"foo": "bar"}'::jsonb ? 'foo';

-- Значения объектов не рассматриваются:
SELECT '{"foo": "bar"}'::jsonb ? 'bar';  -- выдает false

-- Как и вложение, существование должно определяться на верхнем уровне:
SELECT '{"foo": {"bar": "baz"}}'::jsonb ? 'bar'; -- выдает false

-- Строка считается существующей, если она соответствует примитивной строке JSON:
SELECT '"foo"'::jsonb ? 'foo';

Объекты JSON больше подходят для проверок на вложение или существование со множеством ключей или элементов, нежели массивы, поскольку, в отличие от массивов, они внутренне оптимизированы для поиска и не требуют линейного поиска.

Совет
Поскольку содержимое JSON является вложенным, правильно составленный запрос может пропустить явную выборку внутренних объектов. В качестве примера предположим, что у нас есть столбец doc, содержащий объекты на верхнем уровне, и большинство этих объектов содержит поля tags с массивами внутренних объектов. Данный запрос находит записи, в которых внутренние объекты содержат как "term":"paris", так и "term":"food", при этом пропуская такие ключи вне массива tags:

SELECT doc->'site_name' FROM websites
  WHERE doc @> '{"tags":[{"term":"paris"}, {"term":"food"}]}';

Можно сделать то же самое, скажем, так:

SELECT doc->'site_name' FROM websites
  WHERE doc->'tags' @> '[{"term":"paris"}, {"term":"food"}]';

Но такой подход менее гибок, а зачастую и менее эффективен.

С другой стороны, оператор существования JSON не является вложенным: он будет искать указанный ключ или элемент массива только на верхнем уровне значения JSON.

Различные операторы вложений и существования, а также все остальные операторы и функции JSON описаны в разделе Функции и операторы JSON.

Индексация jsonb

Для эффективного поиска ключей или пар ключ/значение, встречающихся в большом количестве документов jsonb (информационных блоков), можно использовать индексы GIN. Для этого предоставляются два «класса операторов» GIN, предлагающих различные компромиссы между производительностью и гибкостью.

Класс операторов GIN по умолчанию для jsonb поддерживает запросы с операторами существования ключа на верхнем уровне: ?, ?& и ?| и оператор существования пути/значения @>. (Подробнее семантика, которую реализуют эти операторы, описана в таблице Дополнительные операторы jsonb.) Пример создания индекса с этим классом операторов:

CREATE INDEX idxgin ON api USING GIN (jdoc);

Нестандартный класс операторов GIN jsonb_path_ops поддерживает индексирование только оператора @>. Пример создания индекса с этим классом операторов:

CREATE INDEX idxginp ON api USING GIN (jdoc jsonb_path_ops);

Рассмотрим пример таблицы, в которой хранятся документы JSON, полученные от стороннего веб-сервиса, с документированным определением схемы. Типичный документ:

{
    "guid": "9c36adc1-7fb5-4d5b-83b4-90356a46061a",
    "name": "Angela Barton",
    "is_active": true,
    "company": "Magnafone",
    "address": "178 Howard Place, Gulf, Washington, 702",
    "registered": "2009-11-07T08:53:22 +08:00",
    "latitude": 19.793713,
    "longitude": 86.513373,
    "tags": [
        "enim",
        "aliquip",
        "qui"
    ]
}

Мы сохраняем эти документы в таблице с именем api, в столбце jsonb с именем jdoc. Если для этого столбца создается индекс GIN, он может использоваться в таких запросах:

-- Найти документы, в которых ключ "company" имеет значение "Magnafone"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"company": "Magnafone"}';

Однако в запросах, подобных следующему, этот индекс применять нельзя, потому что хотя оператор ? и индексируется, он не применяется непосредственно к индексируемому столбцу jdoc:

-- Найти документы, в которых ключ "tags" содержит ключ или элемент массива "qui"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc -> 'tags' ? 'qui';

Тем не менее при надлежащем использовании индексов выражений в этом запросе можно использовать индекс. Если запросы к определенным элементам в ключе "tags" выполняются часто, возможно, будет целесообразно определить такой индекс:

CREATE INDEX idxgintags ON api USING GIN ((jdoc -> 'tags'));

Теперь предложение WHERE jdoc -> 'tags' ? 'qui' будет распознаваться как приложение индексируемого оператора ? к индексируемому выражению jdoc -> 'tags'. (Более подробную информацию об индексах выражений можно найти в разделе Индексы по выражениям).

Еще одним подходом к запросам является использование проверки на существование, например:

-- Найти документы, в которых ключ "tags" содержит элемент массива "qui"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qui"]}';

В этом запросе можно задействовать простой индекс GIN по столбцу jdoc. Но обратите внимание, что такой индекс будет хранить копии каждого ключа и значения в столбце jdoc, тогда как индекс выражения из предыдущего примера хранить только данные, найденные в объекте с ключом tags. Хотя подход с простым индексом более гибкий (поскольку он поддерживает запросы по любому ключу), индексы целевых выражений скорее всего будут меньше и быстрее в поиске, чем простой индекс.

Кроме того, индекс GIN поддерживает операторы @@ и @?, которые выполняют сопоставление jsonpath.

SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';

SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] ? (@ == "qui")';

Индекс GIN извлекает из jsonpath операторы следующей формы: цепочка_аксессоров = константа. Цепочка аксессоров может включать аксессоры .ключ, [*] и [индекс]. jsonb_ops дополнительно поддерживает аксессоры .* и .**.

Хотя класс операторов jsonb_path_ops поддерживает только запросы с операторами @>, @@ и @?, его производительность заметно выше по сравнению с классом операторов по умолчанию jsonb_ops. Индекс jsonb_path_ops обычно намного меньше индекса jsonb_ops для тех же данных и более точен при поиске, особенно когда запросы содержат ключи, часто встречающиеся в данных. Поэтому поисковые операции с индексом jsonb_path_ops обычно выполняются лучше, чем с классом операторов по умолчанию.

Техническое различие между индексами GIN jsonb_ops и jsonb_path_ops состоит в том, что первый создает независимые элементы индекса для всех ключей/значений в данных, а второй — только для значений.¹ По сути, каждый элемент индекса jsonb_path_ops является хешем значения и приводящего к нему ключа (или ключей); например, для индексации {"foo": {"bar": "baz"}}, будет создан один элемент индекса, объединяющий в хеш все три элемента: foo, bar и baz. Таким образом, запрос вложений, ищущий эту структуру, приведет к максимально точному поиску по индексу; но определить, является ли foo ключом, с помощью этого индекса невозможно. С другой стороны, индекс jsonb_ops создаст три элемента индекса, представляющих foo, bar и baz по отдельности, а затем для проверки на вложения будет искать строки, содержащие все три этих элемента. Хотя индексы GIN могут выполнять поиск с AND довольно эффективно, он все равно будет менее точным и более медленным, чем равнозначный поиск с jsonb_path_ops, особенно если любой из этих трех элементов индекса содержится в очень большом количестве строк.

Недостаток класса операторов jsonb_path_ops заключается в том, что он не создает индексных записей для структур JSON, не содержащих никаких значений, таких как {"a": {}}. При запросе на поиск документов, содержащих такую структуру, потребуется полное индексное сканирование, которое выполняется довольно медленно. Поэтому jsonb_path_ops не подходит для приложений, часто выполняющих такой поиск.

Тип jsonb также поддерживает индексы btree и hash. Обычно они полезны, только если необходимо проверить равенство документов JSON в целом. Порядок сортировки btree для данных jsonb редко представляет большой интерес, но для полноты он приводится ниже:

Объект > Массив > Логическое значение > Число > Строка > Null

Объект с n парами > объект с n - 1 парами

Массив с n элементами > массив с n - 1 элементами

Объекты с равным количеством пар сравниваются в следующем порядке:

ключ-1, значение-1, ключ-2 ...

Обратите внимание, что ключи объектов сравниваются в порядке их хранения; в частности, поскольку более короткие ключи хранятся перед более длинными, в итоге результаты могут оказаться не интуитивными. Например:

{ "aa": 1, "c": 1} > {"b": 1, "d": 1}

Массивы с равным количеством элементов сравниваются аналогично:

элемент-1, элемент-2 ...

Примитивные значения JSON сравниваются по тем же правилам сравнения, что и нижележащие типы данных QHB. Строки сравниваются с учетом порядка сортировки по умолчанию в этой базе данных.

Обращение по индексу к элементам jsonb

Тип данных jsonb поддерживает извлечение и изменение элементов в стиле обращения по индексу к элементам массива. Указывать на вложенные значения можно, задавая цепочку выражений, обращающихся к элементам, следуя при этом тем же правилам, что и аргумент path в функции jsonb_set. Если значение jsonb является массивом, числовые индексы начинаются с нуля, а отрицательные целые числа отсчитывают от последнего элемента в массива к первому. Обращения к срезам массивов не поддерживаются. Результат обращающегося по индексу выражения всегда имеет тип данных jsonb.

Для изменения значений jsonb можно воспользоваться обращением по индексу в предложении SET оператора UPDATE. Пути такого обращения должны быть преодолимыми для всех задействованных значений, если они существуют. Например, путь val['a']['b']['c'] можно пройти полностью до c, если val, val['a'] и val['a']['b'] являются объектами. Если же значение val['a'] или val['a']['b'] не определено, оно будет создано в виде пустого объекта и должным образом заполнено. Однако если само значение val или любое из промежуточных значений существует и является не объектом, а, к примеру, строкой, числом или элементом jsonb null, пройти этот путь невозможно, поэтому возникает ошибка и транзакция прерывается.

Пример синтаксиса обращения по индексу:

-- Извлечь значение объекта по ключу
SELECT ('{"a": 1}'::jsonb)['a'];

-- Извлечь значение вложенного объекта по пути ключа
SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c'];

-- Извлечь элемент массива по индексу
SELECT ('[1, "2", null]'::jsonb)[1];

-- Изменить значение объекта по ключу. Обратите внимание на апострофы вокруг '1':
-- присваиваемое значение также должно быть типа jsonb
UPDATE table_name SET jsonb_field['key'] = '1';

-- Это вызовет ошибку, если jsonb_field['a']['b'] в какой-либо записи не является
-- объектом. Например, в значении {"a": 1} ключу 'a' соответствует числовое
-- значение.
UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';

-- Отфильтровать записи предложением WHERE с обращением по индексу. Поскольку результат
-- обращения имеет тип jsonb, значение, с которым мы его сравниваем, должно быть того же типа.
-- Благодаря кавычкам строка "value" тоже стала допустимой строкой jsonb.
SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';

Присваивание jsonb посредством обращения по индексу в некоторых особых случаях работает не так, как с функцией jsonb_set. Когда исходное значение jsonb равно NULL, присваивание при обращении по индексу происходит так, будто это значение было пустым значением JSON (типа объект или массив, в зависимости от ключа обращения):

-- Там, где поле jsonb_field было NULL, оно станет {"a": 1}
UPDATE table_name SET jsonb_field['a'] = '1';

-- Там, где поле jsonb_field было NULL, оно станет [1]
UPDATE table_name SET jsonb_field[0] = '1';

Если индекс указан для массива, содержащего недостаточно элементов, в него будут добавляться элементы со значением NULL до тех пор, пока индекс не станет достижимым и пока не станет возможным задать значение.

-- Там, где поле jsonb_field было [], оно станет [null, null, 2];
-- Там, где поле jsonb_field было [0], оно станет [0, null, 2]
UPDATE table_name SET jsonb_field[2] = '2';

Значение jsonb будет принимать присваивания по несуществующим путям обращения, пока последний существующий элемент, который нужно пройти, является объектом или массивом, в зависимости от соответствующего компонента пути (элемент, на который указывает последний компонент пути, не проходится и может быть любым). Будут созданы вложенные структуры массивов и объектов, и для массивов они дополнятся элементами NULL до позиции, заданной в пути обращения, в которую можно будет поместить присваиваемое значение.

-- Там, где поле jsonb_field было {}, оно станет {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';

-- Там, где поле jsonb_field было [], оно станет [null, {'a': 1}]
UPDATE table_name SET jsonb_field[1]['a'] = '1';

Преобразования

Для различных процедурных языков доступны дополнительные расширения, которые реализуют преобразования для типа jsonb.

Тип jsonpath

Тип jsonpath реализует поддержку языка путей SQL/JSON в QHB, позволяющего эффективно выполнять запросы данных JSON. Он обеспечивает двоичное представление разобранного выражения пути SQL/JSON, которое определяет, какие элементы нужно извлечь механизмом пути из данных JSON для дальнейшей обработки в функциях запроса SQL/JSON.

Семантика предикатов и операторов пути SQL/JSON в целом соответствует SQL. В то же время, чтобы обеспечить наиболее естественный способ работы с данными JSON, в синтаксисе пути SQL/JSON приняты некоторые соглашения JavaScript:

Точка (.) используется для доступа к члену объекта.

Квадратные скобки ([]) используются для доступа к массиву.

Массивы SQL/JSON нумеруются, начиная с 0, тогда как обычные массивы SQL — с 1.

Выражение пути SQL/JSON обычно записывается в запросе SQL в виде символьного строкового литерала, поэтому его необходимо заключать в апострофы, а любые апострофы, требуемые в этом значении, необходимо удвоить (см. подраздел Строковые константы). Строковые литералы также требуется использовать и внутри некоторых форм выражений пути. На такие встроенные строковые литералы распространяются соглашения JavaScript/ECMAScript: они должны заключаться в кавычки, а для представления символов, которые трудно ввести иначе, можно использовать экранированные символы с обратным слэшем. В частности, символ кавычки во встроенном строковом литерале записывается как \", а сам обратный слэш — как \\. Другие спецпоследовательности с обратным слэшем включают в себя те, которые распознаются в строках JSON: \b, \f, \n, \r, \t, \v для различных управляющих символов ASCII и \uNNNN для символа Unicode, идентифицируемого кодом из 4 шестнадцатеричных цифр. Синтаксис обратного слэша также включает два варианта, которые не допускаются JSON: \xNN для символьного кода в виде только двух шестнадцатеричных цифр и \u{N...} для символьного кода, записанного с использованием от 1 до 6 шестнадцатеричных цифр.

Выражение пути состоит из последовательности элементов пути, которые могут быть следующими:

Литералы пути примитивных типов JSON:Unicode text, numeric, true, false или null.

Переменные пути, перечисленные в Таблице 24.

Операторы аксессоров, перечисленные в Таблице 25.

Операторы и методы jsonpath, перечисленные в подразделе Операторы и методы пути SQL/JSON.

Круглые скобки, которые можно использовать для ввода выражений фильтра или определения порядка вычисления пути.

Подробную информацию об использовании выражений jsonpath с функциями запросов SQL/JSON см. в подразделе Язык путей SQL/JSON.

Таблица 24. Переменные jsonpath

Переменная | Описание
$ | Переменная, представляющая текст JSON для запроса (элемент контекста).
$varname | Именованная переменная. Ее значение можно задать в параметре vars некоторых функций обработки JSON. Подробную информацию см. в таблице Функции для обработки JSON и ее примечаниях.
@ | Переменная, представляющая результат вычисления пути в выражениях фильтров.

Таблица 25. Операторы аксессоров jsonpath

Оператор аксессора
Описание

.ключ
."$имя_переменной"
Оператор аксессора к члену, который возвращает член объекта с указанным ключом. Если имя ключа задается именованной переменной, начинающейся с $, или не соответствует правилам JavaScript относительно идентификаторов, оно должно быть заключено в кавычки как символьный строковый литерал.

.*
Оператор аксессора по подстановочному символу, который возвращает значения всех элементов, расположенных на верхнем уровне текущего объекта.

.**
Рекурсивный оператор аксессора по подстановочному символу, который обрабатывает все уровни иерархии JSON текущего объекта и возвращает все значения элементов, независимо от уровня их вложенности. Это реализованное в QHB расширение стандарта SQL/JSON.

.**{уровень}
.**{начальный_уровень to конечный_уровень}
То же, что и .**, но с фильтром по уровням вложенности иерархии JSON. Уровни вложенности указываются как целые числа. Нулевой уровень соответствует текущему объекту. Чтобы получить доступ к самому низкому уровню вложенности, можно использовать ключевое слово last. Это реализованное в QHB расширение стандарта SQL/JSON.

[нижний_индекс, ...]
Оператор аксессора к элементу массива. Нижний_индекс можно задать в двух формах: индекс или начальный_индекс to конечный_индекс. Первая форма возвращает единственный элемент массива по его индексу. Вторая форма возвращает срез массива по диапазону индексов, включая элементы, которые соответствуют предоставленным начальному_индексу и конечному_индексу.

Задаваемый индекс может быть целым числом или выражением, возвращающим единственное числовое значение, которое автоматически приводится к целому числу. Нулевой индекс соответствует первому элементу массива. Также для обозначения последнего элемента массива можно использовать ключевое слово last, что полезно при обработке массивов неизвестной длины.

[*]
Оператор аксессора к элементам массива по подстановочному символу, который возвращает все элементы массива.

¹

Поэтому термин «значение» включает элементы массива, хотя в терминологии JSON иногда элементы массива считаются отличными от значений внутри объектов.

Массивы

QHB позволяет определять столбцы таблицы как многомерные массивы переменной длины. Можно создать массивы любого встроенного или определенного пользователем базового типа, типа перечисления, составного типа, типа диапазона или домена.

Объявление типов массивов

Чтобы проиллюстрировать использование типов массивов, создадим эту таблицу:

CREATE TABLE sal_emp (
    name            text,
    pay_by_quarter  integer[],
    schedule        text[][]
);

Как показано в примере, тип данных массива объявляется путем добавления квадратных скобок ([]) к имени типа данных элементов массива. Приведенная выше команда создаст таблицу с именем sal_emp со столбцом типа text (name), одномерный массив типа integer (pay_by_quarter), представляющий квартальную зарплату сотрудника, и двумерный массив text (schedule), представляющий недельный график сотрудника.

Синтаксис CREATE TABLE позволяет указывать точный размер массивов, например:

CREATE TABLE tictactoe (
    squares   integer[3][3]
);

Однако текущая реализация игнорирует любые предоставленные ограничения размера массива, т. е. все массивы обрабатываются как имеющие неопределенную длину.

Текущая реализация также не ограничивает число измерений. Все массивы с элементами определенного типа считаются одного типа, независимо от размера или числа измерений. Поэтому объявление размера массива или числа измерений в CREATE TABLE нужно только для документирования; на логику работы с массивом это не влияет.

Для одномерных массивов можно использовать альтернативный синтаксис с ключевым словом ARRAY, который соответствует стандарту SQL. Столбец pay_by_quarter можно было бы определить так:

    pay_by_quarter  integer ARRAY[4],

Или, если не указан размер массива:

    pay_by_quarter  integer ARRAY,

Однако, как и в предыдущем случае, QHB не налагает ограничения на фактический размер массива.

Ввод значений массива

Чтобы записать значение массива в виде литеральной константы, заключите значения элементов в фигурные скобки и разделите их запятыми. (Если вы знаете C, это очень похоже на синтаксис для инициализации структур в C.) Вы можете заключить в кавычки значение любого элемента, а если он содержит запятые или фигурные скобки, то должны сделать это обязательно. (Подробнее см. ниже.) Таким образом, общий формат константы массива выглядит следующим образом:

'{ значение1 разделитель значение2 разделитель ... }'

где разделитель — это символ, указанный в качестве разделителя для данного типа в его записи в таблице pg_type. Все стандартные типы данных, представленные в дистрибутиве QHB, используют запятую (,), за исключением типа box, в котором используется точка с запятой (;). Каждое значение является либо константой типа элемента массива, либо вложенным массивом. Пример константы массива:

'{{1,2,3},{4,5,6},{7,8,9}}'

Эта константа представляет собой двумерный массив размером 3x3, состоящий из трех подмножеств целых чисел.

Чтобы присвоить элементу константы массива значение NULL, нужно написать NULL для его значения. (При этом регистр символов не имеет значения.) Если же нужно добавить в массив строку со словом «NULL», нужно заключить его в кавычки.

(Эти виды констант массива на самом деле являются лишь частным случаем констант универсального типа, рассматриваемых в подразделе Константы других типов. Константа первоначально обрабатывается как строка и передается процедуре преобразования входных данных массива. Может потребоваться явное указание целевого типа).

Теперь мы можем показать некоторые операторы INSERT:

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"meeting", "lunch"}, {"training", "presentation"}}');

INSERT INTO sal_emp
    VALUES ('Carol',
    '{20000, 25000, 25000, 25000}',
    '{{"breakfast", "consulting"}, {"meeting", "lunch"}}');

Результат двух предыдущих добавлений выглядит так:

SELECT * FROM sal_emp;
 name  |      pay_by_quarter       |                 schedule
-------+---------------------------+-------------------------------------------
 Bill  | {10000,10000,10000,10000} | {{meeting,lunch},{training,presentation}}
 Carol | {20000,25000,25000,25000} | {{breakfast,consulting},{meeting,lunch}}
(2 rows)

У многомерных массивов все размерности должны быть одинаковой величины, иначе возникнет ошибка. Например:

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"meeting", "lunch"}, {"meeting"}}');
ERROR:  multidimensional arrays must have array expressions with matching dimensions
-- ОШИБКА: выражения многомерных массивов должны задаваться с соответствующими размерностями

Также можно использовать синтаксис конструктора ARRAY:

INSERT INTO sal_emp
    VALUES ('Bill',
    ARRAY[10000, 10000, 10000, 10000],
    ARRAY[['meeting', 'lunch'], ['training', 'presentation']]);

INSERT INTO sal_emp
    VALUES ('Carol',
    ARRAY[20000, 25000, 25000, 25000],
    ARRAY[['breakfast', 'consulting'], ['meeting', 'lunch']]);

Обратите внимание, что элементы массива являются обычными константами или выражениями SQL; например, строковые литералы заключаются в апострофы, а не в кавычки, как это было бы в литерале массива. Синтаксис конструктора ARRAY более подробно рассматривается в подразделе Конструкторы массивов.

Обращение к массивам

Теперь можно выполнить несколько запросов к таблице. Сначала мы покажем, как обратиться к одному элементу массива. Этот запрос выводит имена сотрудников, зарплата которых изменилась во втором квартале:

SELECT name FROM sal_emp WHERE pay_by_quarter[1] <> pay_by_quarter[2];

 name
-------
 Carol
(1 row)

Номера индексов массива пишутся в квадратных скобках. По умолчанию в QHB действует соглашение о нумерации массивов, начиная с единицы, то есть массив из n элементов начинается с array[1] и заканчивается array[n].

Этот запрос выводит зарплату всех сотрудников за третий квартал:

SELECT pay_by_quarter[3] FROM sal_emp;

 pay_by_quarter
----------------
          10000
          25000
(2 rows)

Также возможно получить доступ к произвольным прямоугольным срезам массива, или подмассивам. Срез массива обозначается записью нижняя-граница:верхняя- граница для одной или нескольких размерностей. Например, этот запрос выводит первые пункты в расписании Билла за первые два дня недели:

SELECT schedule[1:2][1:1] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{meeting},{training}}
(1 row)

Если какая-либо размерность записывается как срез, т. е. содержит двоеточие, то все размерности рассматриваются как срезы. Если при этому у размерности указано только одно число (без двоеточия), в срез войдут элементы от 1 до указанного числа. Например, [2] обрабатывается как [1:2], как показано в этом примере:

SELECT schedule[1:2][2] FROM sal_emp WHERE name = 'Bill';

                 schedule
-------------------------------------------
 {{meeting,lunch},{training,presentation}}
(1 row)

Во избежание путаницы с обращением к элементу, лучше использовать синтаксис срезов для всех размерностей, например, писать [1:2][1:1], а не [2][1:1].

Значения нижняя-граница и/или верхняя-граница в указании среза можно опустить; отсутствующая граница заменяется нижним или верхним пределом индексов массива. Например:

SELECT schedule[:2][2:] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{lunch},{presentation}}
(1 row)

SELECT schedule[:][1:1] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{meeting},{training}}
(1 row)

Выражение индекса массива вернет значение NULL, если либо сам массив, либо любое из выражений индексов его элемента равно NULL. Кроме того, NULL возвращается, если индекс находится за пределами границ массива (это не вызывает ошибку). Например, если массив schedule в настоящее время имеет размеры [1:3][1:2], то обращение к schedule[3][3] выдаст NULL. Точно так же при обращении к массиву с неправильным числом индексов возвращается NULL, а не ошибка.

Выражение среза массива также вернет NULL, если сам массив или любое из выражений индексов его элемента равно NULL. Однако в других случаях, например, при выборе среза, который находится за пределами текущих границ массива, возвращается не NULL, а пустой массив (с размерностью 0). (Это отличается от обращений к элементам и выполняется по историческим причинам). Если запрашиваемый срез частично выходит за границы массива, то возвращается не NULL, а срез, автоматически сокращенный до области пересечения.

Текущие размеры любого значения массива можно получить с помощью функции array_dims:

SELECT array_dims(schedule) FROM sal_emp WHERE name = 'Carol';

 array_dims
------------
 [1:2][1:2]
(1 row)

array_dims выдает результат типа text, что удобно скорее для людей, чем для программ. Размеры массива также можно получить с помощью функций array_upper и array_lower, которые возвращают верхнюю и нижнюю границу указанной размерности соответственно:

SELECT array_lower(schedule, 1), array_upper(schedule, 1) FROM sal_emp WHERE name = 'Carol';

  array_lower array_upper
--------------------------
  1           2
(1 row)

Функция array_length вернет число элементов указанной размерности массива:

SELECT array_length(schedule, 1) FROM sal_emp WHERE name = 'Carol';

 array_length
--------------
            2
(1 row)

Функция cardinality возвращает общее количество элементов в массиве по всем измерениям. По сути это число строк, которое возвращает функция unnest:

SELECT cardinality(schedule) FROM sal_emp WHERE name = 'Carol';

 cardinality
-------------
           4
(1 row)

Изменение массивов

Значение массива можно полностью заменить следующим образом:

UPDATE sal_emp SET pay_by_quarter = '{25000,25000,27000,27000}'
    WHERE name = 'Carol';

или используя синтаксис выражения ARRAY:

UPDATE sal_emp SET pay_by_quarter = ARRAY[25000,25000,27000,27000]
    WHERE name = 'Carol';

Также можно изменить один элемент массива:

UPDATE sal_emp SET pay_by_quarter[4] = 15000
    WHERE name = 'Bill';

или срез:

UPDATE sal_emp SET pay_by_quarter[1:2] = '{27000,27000}'
    WHERE name = 'Carol';

При этом в указании среза можно опустить значение нижней-границы и/или верхней-границы, но только при изменении значения массива, отличного от NULL и не имеющего нулевой размерности (в противном случае не будет граничных значений индекса, которые должны подставлять вместо опущенных).

Сохраненный массив можно расширять, присваивая значения элементам, которых в нем еще нет. Все позиции между уже существующими и новыми элементами будут заполнены значениями NULL. Например, если массив myarray содержит 4 элемента, после присвоения значение элементу myarray[6] в нем будет 6 элементов, а myarray[5] будет содержать NULL. В настоящее время подобное расширение допускается только для одномерных, но не многомерных массивов.

Определение элементов по индексам позволяет создавать массивы, в которых нумерация элементов начинается не с 1. Например, можно записать выражение myarray[-2:7], тем самым создав массив с индексами от -2 до 7.

Новые значения массива также можно создать с помощью оператора конкатенации ||:

SELECT ARRAY[1,2] || ARRAY[3,4];
 ?column?
-----------
 {1,2,3,4}
(1 row)

SELECT ARRAY[5,6] || ARRAY[[1,2],[3,4]];
      ?column?
---------------------
 {{5,6},{1,2},{3,4}}
(1 row)

Оператор конкатенации позволяет вставить отдельный элемент в начало или конец одномерного массива. Он также принимает два N-мерных массива или N-мерный и N+1-мерный массивы.

Когда отдельный элемент помещается в начало или конец одномерного массива, в результаты получается массив с тем же индексом нижней границы, что и у массива- операнда. Например:

SELECT array_dims(1 || '[0:1]={2,3}'::int[]);
 array_dims
------------
 [0:2]
(1 row)

SELECT array_dims(ARRAY[1,2] || 3);
 array_dims
------------
 [1:3]
(1 row)

Когда объединяются два массива с равным числом размерностей, в результате сохраняется индекс нижней границы внешней размерности левого операнда. Получившийся массив содержит все элементы левого операнда, за которыми следуют все элементы правого операнда. Например:

SELECT array_dims(ARRAY[1,2] || ARRAY[3,4,5]);
 array_dims
------------
 [1:5]
(1 row)

SELECT array_dims(ARRAY[[1,2],[3,4]] || ARRAY[[5,6],[7,8],[9,0]]);
 array_dims
------------
 [1:5][1:2]
(1 row)

Когда N-мерный массив помещается в начало или конец N+1-мерного массива, он вставляется так же, как элемент (описано выше). Каждый N-мерный подмассив по сути является элементом внешней размерности N+1-мерного массива. Например:

SELECT array_dims(ARRAY[1,2] || ARRAY[[3,4],[5,6]]);
 array_dims
------------
 [1:3][1:2]
(1 row)

Также массив можно создать с помощью функции array_prepend, array_append или array_cat. Первые две функции поддерживают только одномерные массивы, а array_cat поддерживает и многомерные. Несколько примеров:

SELECT array_prepend(1, ARRAY[2,3]);
 array_prepend
---------------
 {1,2,3}
(1 row)

SELECT array_append(ARRAY[1,2], 3);
 array_append
--------------
 {1,2,3}
(1 row)

SELECT array_cat(ARRAY[1,2], ARRAY[3,4]);
 array_cat
-----------
 {1,2,3,4}
(1 row)

SELECT array_cat(ARRAY[[1,2],[3,4]], ARRAY[5,6]);
      array_cat
---------------------
 {{1,2},{3,4},{5,6}}
(1 row)

SELECT array_cat(ARRAY[5,6], ARRAY[[1,2],[3,4]]);
      array_cat
---------------------
 {{5,6},{1,2},{3,4}}

В простых случаях вместо прямого вызова этих функций предпочтительнее рассмотренный выше оператор конкатенации. Однако поскольку оператор конкатенации слишком перегружен для выполнения всех трех заданий, в некоторых ситуациях во избежание неоднозначности лучше воспользоваться одной из этих функций. Например, рассмотрим:

SELECT ARRAY[1, 2] || '{3, 4}';  -- нетипизированный литерал воспринимается как массив
 ?column?
-----------
 {1,2,3,4}

SELECT ARRAY[1, 2] || '7';                 -- как и этот
ERROR:  malformed array literal: "7"

SELECT ARRAY[1, 2] || NULL;                -- как и буквальный NULL
 ?column?
----------
 {1,2}
(1 row)

SELECT array_append(ARRAY[1, 2], NULL);    -- возможно, это и имелось в виду
 array_append
--------------
 {1,2,NULL}

В приведенных выше примерах анализатор видит целочисленный массив на одной стороне оператора конкатенации и константу неопределенного типа на другой. Эвристический алгоритм, который он использует для разрешения типа константы, должен полагать, что она имеет тот же тип, что и другие входные данные оператора — в данном случае целочисленный массив. Поэтому предполагается, что оператор конкатенации представляет array_cat, а не array_append. Если это неправильный выбор, его можно исправить, приведя константу к типу элемента массива; но, возможно, лучше будет явно использовать array_append.

Поиск в массивах

Для поиска значения в массиве необходимо проверить каждое значение. Это можно сделать вручную, если вы знаете размер массива. Например:

SELECT * FROM sal_emp WHERE pay_by_quarter[1] = 10000 OR
                            pay_by_quarter[2] = 10000 OR
                            pay_by_quarter[3] = 10000 OR
                            pay_by_quarter[4] = 10000;

Однако с большими массивами этот метод быстро становится утомительным, а если размер массива неизвестен, то и бесполезным. Альтернативный метод описан в разделе Сравнение строк и массивов. Приведенный выше запрос можно заменить на:

SELECT * FROM sal_emp WHERE 10000 = ANY (pay_by_quarter);

А таким образом можно найти строки, в которых все значения массива равны 10000:

SELECT * FROM sal_emp WHERE 10000 = ALL (pay_by_quarter);

В качестве альтернативы можно использовать функцию generate_subscripts. Например:

SELECT * FROM
   (SELECT pay_by_quarter,
           generate_subscripts(pay_by_quarter, 1) AS s
      FROM sal_emp) AS foo
 WHERE pay_by_quarter[s] = 10000;

Эта функция описана в таблице Функции, генерирующие индексы массивов.

Также поиск в массиве можно выполнить с помощью оператора &&, который проверяет, перекрывается ли левый операнд с правым. Например:

SELECT * FROM sal_emp WHERE pay_by_quarter && ARRAY[10000];

Этот и другие операторы для работы с массивами более подробно описаны в разделе Функции и операторы для массивов. Его можно ускорить с помощью соответствующего индекса, как описано в разделе Типы индексов.

Также в массиве можно искать конкретные значения, используя функции array_position и array_positions. Первая возвращает индекс первого вхождения значения в массив, вторая возвращает массив с индексами всех его вхождений. Например:

SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon');
 array_positions
-----------------
 2

SELECT array_positions(ARRAY[1, 4, 3, 1, 3, 4, 2, 1], 1);
 array_positions
-----------------
 {1,4,8}

Совет
Массивы не являются множествами; необходимость поиска определенных элементов массива может быть признаком неправильного проектирования базы данных. Рассмотрите возможность использования отдельной таблицы со строками для каждого элемента массива. Это облегчит поиск и, скорее всего, позволит лучше оперировать большим количеством элементов.

Синтаксис вводимых и выводимых значений массива

Внешнее текстовое представление значения массива состоит из элементов, которые интерпретируются в соответствии с правилами преобразования ввода/вывода для типа элемента массива, а также элементов оформления, которые указывают на структуру массива. Оформление состоит из фигурных скобок ({ и }) вокруг значения массива и символов-разделителей между соседними элементами. Символом- разделителем обычно служит запятая (,), но это может быть и другой знак: он определяется настройкой параметра typdelim для типа элемента массива. Для стандартных типов данных, представленных в дистрибутиве QHB, в качестве разделителя используется запятая, за исключением типа box, в котором используется точка с запятой (;). В многомерном массиве у каждой размерности (строка, плоскость, куб и т. д.) есть свой уровень фигурных скобок, а между соседними объектами в фигурных скобках на одном уровне должны стоять разделители.

Функция вывода массива помещает значения элементов в кавычки, если те являются пустыми строками, содержат фигурные скобки, символы-разделители, кавычки, обратный слэш, пробел или это слово NULL. Кавычки и обратные слэши, включенные в значения элементов, экранируются обратным слэшем. Можно с уверенностью предположить, что числовые типы данных никогда не будут выводиться в кавычках, но следует быть готовыми к тому, что в текстовых типах они могут появиться.

По умолчанию значение индекса нижней границы размерностей массива равно 1. Для представления массивов с другими нижними границами можно явно указать диапазоны индексов перед содержимым массива. Это оформление включает в себя квадратные скобки ([]) вокруг нижней и верхней границ каждой размерности с двоеточием (:) в качестве разделителя между ними. За этим выражением следует знак равенства (=). Например:

SELECT f1[1][-2][3] AS e1, f1[1][-1][5] AS e2
 FROM (SELECT '[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[] AS f1) AS ss;

 e1 | e2
----+----
  1 |  6
(1 row)

Функция вывода массива будет включать в результат явное указание размерностей только в том случае, если нижняя граница одной или нескольких размерностей отличается от 1.

Если в качестве значения элемента записывается NULL (в любом варианте), этот элемент считается равным NULL. Если же запись включает кавычки или обратный слэш, это позволяет вводить буквальное строковое значение литерала «NULL». Кроме того, при желании можно выключить параметр конфигурации array_nulls (установив значение off), и тогда строки NULL не будут восприниматься как значения NULL.

Как было показано ранее, при записи значения массива любой его отдельный элемент можно заключить в кавычки. Это нужно делать в том случае, если значение элемента без кавычек может запутать синтаксический анализатор значений массива. Например, в кавычки нужно заключать элементы, содержащие фигурные скобки, запятые (или символ-разделитель, определенный для этого типа данных), кавычки, обратные слэши или пробелы в начале или конце строки. Пустые строки и строки, соответствующие слову NULL, также должны быть заключены в кавычки. Чтобы поместить в значение элемента массива, заключенного в кавычки, кавычку или обратный слэш, поставьте перед этим символом обратный слэш. Или же можно обойтись без кавычек и использовать обратный слэш для экранирования всех символов в данных, которые в противном случае могут быть восприняты как синтаксис массива.

Перед открывающей или после закрывающей скобки можно добавить пробел. Также можно добавить пробел до или после любой отдельной строки элемента. Во всех этих случаях пробелы будут игнорироваться. Однако пробелы в элементах, заключенных в кавычки, или окруженные с обеих сторон отличными от пробелов символами, учитываются.

Совет
При записи значений массива в командах SQL зачастую удобнее работать с синтаксисом конструктора ARRAY (см. раздел Конструкторы массивов), чем с синтаксисом литерала массива. В ARRAY значения отдельных элементов записываются так же, как если бы они не были членами массива.

Составные типы

Составной тип представляет структуру строки или записи; по существу, это просто список имен полей и их типов данных. QHB позволяет использовать составные типы во многом так же, как и простые типы. Например, столбец таблицы можно объявить как составной тип.

Объявление составных типов

Вот два простых примера определения составных типов:

CREATE TYPE complex AS (
    r       double precision,
    i       double precision
);

CREATE TYPE inventory_item AS (
    name            text,
    supplier_id     integer,
    price           numeric
);

Синтаксис похож на CREATE TABLE, за исключением того, что можно указывать только имена и типы полей; какие-либо ограничения (такие как NOT NULL) в настоящее время не поддерживаются. Обратите внимание, что ключевое слово AS имеет важное значение; без него система будет думать, что подразумевается другой вид команды CREATE TYPE, и вы получите странные синтаксические ошибки.

Определив типы, мы можем использовать их для создания таблиц:

CREATE TABLE on_hand (
    item      inventory_item,
    count     integer
);

INSERT INTO on_hand VALUES (ROW('fuzzy dice', 42, 1.99), 1000);

или функций:

CREATE FUNCTION price_extension(inventory_item, integer) RETURNS numeric
AS 'SELECT $1.price * $2' LANGUAGE SQL;

SELECT price_extension(item, 10) FROM on_hand;

Каждый раз при создании таблицы вместе с ней автоматически создается составной тип с тем же именем, что и таблица. Этот тип представляет тип строки таблицы. Например, при создании таблицы inventory_item:

CREATE TABLE inventory_item (
    name            text,
    supplier_id     integer REFERENCES suppliers,
    price           numeric CHECK (price > 0)
);

в качестве побочного результата возникнет такой же составной тип inventory_item, что и в примере выше, и использовать его можно так же. Однако обратите внимание на важное ограничение текущей реализации: поскольку с составным типом не связаны никакие ограничения, те ограничения, что показаны в определении таблицы, не применяются к значениям составного типа вне таблицы. (Чтобы обойти это, создайте поверх составного типа домен и добавьте желаемые ограничения в виде ограничений CHECK для домена.)

Построение составных значений

Чтобы записать составное значение в виде литеральной константы, заключите значения полей в круглые скобки и разделите их запятыми. Любое значения поля можно заключить в кавычки, и это необходимо делать, если оно содержит запятые или скобки. (Подробнее см. ниже). Таким образом, в общем формате составная константа записывается следующим образом:

'( значение1, значение2, ... )'

Например, запись:

'("fuzzy dice",42,1.99)'

будет допустимым значением типа inventory_item, определенного выше. Чтобы присвоить полю NULL, его позицию в списке нужно оставить пустой. Например, эта константа задает NULL для третьего поля:

'("fuzzy dice",42,)'

Если нужна пустая строка, а не NULL, напишите кавычки:

'("",42,)'

Здесь в первом поле будет пустая строка, а в третьем — NULL.

(Эти константы на самом деле являются лишь частным случаем констант универсального типа, рассматриваемых в подразделе Константы других типов. Константа изначально воспринимается как строка и передается программе преобразования входных данных составного типа. Чтобы определить, к какому типу следует привести константу, может потребоваться явно указать тип.)

Также для создания составных значений можно использовать синтаксис выражения ROW. В большинстве случаев он гораздо удобнее, чем синтаксис строкового литерала, поскольку нет нужды беспокоиться о нескольких уровнях цитирования. Мы уже использовали этот метод ранее:

ROW('fuzzy dice', 42, 1.99)
ROW('', 42, NULL)

На самом деле, если в выражении указано более одного поля, ключевое слово ROW указывать необязательно, поэтому эту запись можно упростить до:

('fuzzy dice', 42, 1.99)
('', 42, NULL)

Синтаксис выражения ROW более подробно рассматривается в подразделе Конструкторы строк.

Обращение к составным типам

Чтобы обратиться к полю составного столбца, нужно написать точку и имя поля, как при выборе поля из имени таблицы. На самом деле, эти обращения почти неотличимы, поэтому зачастую необходимо использовать круглые скобки, чтобы не запутать синтаксический анализатор. Например, можно попробовать выбрать несколько подполей из тестовой таблицы on_hand следующим образом:

SELECT item.name FROM on_hand WHERE item.price > 9.99;

Это не будет работать, так как согласно правилам синтаксиса SQL имя item считается именем таблицы, а не столбца on_hand. Следует написать запрос так:

SELECT (item).name FROM on_hand WHERE (item).price > 9.99;

или, если требуется также указать имя таблицы (например, в запросе с несколькими таблицами), то так:

SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9.99;

Теперь заключенный в скобки объект правильно интерпретируется как ссылка на столбец item, из которого затем можно выбрать субполе.

Подобные синтаксические проблемы возникают всякий раз, когда вы выбираете поле из составного значения. Например, чтобы выбрать только одно поле из результата функции, возвращающей составное значение, нужно написать что-то вроде:

SELECT (my_func(...)).field FROM ...

Без дополнительных скобок это приведет к синтаксической ошибке.

Специальное имя поля * означает «все поля»; более подробно это описано в подразделе Использование составных типов в запросах.

Изменение составных типов

Вот несколько примеров правильного написания команд для добавления и изменения составных столбцов. Прежде всего, команды для добавления и изменения всего столбца:

INSERT INTO mytab (complex_col) VALUES((1.1,2.2));

UPDATE mytab SET complex_col = ROW(1.1,2.2) WHERE ...;

В первом примере ключевое слово ROW опущено, во втором оно есть; оставить или опустить его можно в обоих случаях.

Можно изменить отдельное подполе составного столбца:

UPDATE mytab SET complex_col.r = (complex_col).r + 1 WHERE ...;

Обратите внимание, что при этом не нужно (и, более того, нельзя) заключать в скобки имя столбца, следующего за предложением SET, но при обращении к тому же столбцу в выражении справа от знака равенства скобки обязательны.

Также можно указать подполя как цели команды INSERT:

INSERT INTO mytab (complex_col.r, complex_col.i) VALUES(1.1, 2.2);

Если бы мы не указали значения для всех подполей столбца, оставшиеся подполя были бы заполнены значениями NULL.

Использование составных типов в запросах

С составными типами в запросах связаны различные специальные синтаксические правила и поведение. Эти правила предоставляют удобные сокращения, но могут вызывать сложности, если не понимать стоящую за ними логику.

В QHB ссылка на имя (или псевдоним) таблицы в запросе фактически является ссылкой на составное значение текущей строки в этой таблице. Например, имея таблицу inventory_item, показанную выше, мы можем написать:

SELECT c FROM inventory_item c;

Этот запрос создает один составной столбец, поэтому результат может быть таким:

           c
------------------------
 ("fuzzy dice",42,1.99)
(1 row)

Однако обратите внимание, что простые имена сопоставляются сначала с именами столбцов и только потом с именами таблиц, поэтому данный пример работает только потому, что в таблицах запроса нет столбца с именем c.

Обычный синтаксис полного имени столбца вида имя_таблицы.имя_столбца можно понимать как применение выбора поля к составному значению текущей строки таблицы. (Из соображений эффективности на самом деле это реализовано по-другому.)

Когда мы пишем

SELECT c.* FROM inventory_item c;

то, согласно стандарту SQL, мы должны получить содержимое таблицы, развернутое в отдельные столбцы:

name       | supplier_id | price
-----------+-------------+-------
fuzzy dice |          42 |  1.99
(1 row)

как будто выполнялся запрос

SELECT c.name, c.supplier_id, c.price FROM inventory_item c;

QHB будет применять это развертывание к любому составному выражению, хотя, как показано выше, значение, к которому применяется .*, следует заключать в скобки в случаях, когда это не простое имя таблицы. Например, если myfunc() — это функция, возвращающая составной тип со столбцами a, b и c, то эти два запроса выдадут одинаковый результат:

SELECT (myfunc(x)).* FROM some_table;
SELECT (myfunc(x)).a, (myfunc(x)).b, (myfunc(x)).c FROM some_table;

Совет
QHB выполняет развертывание столбцов, фактически превращая первую форму во вторую. Таким образом, в этом примере myfunc() будет вызываться три раза в каждой строке с любым из этих синтаксисов. Если это затратная функция и вы хотите избежать лишних вызовов, можно написать такой запрос:

SELECT m.* FROM some_table, LATERAL myfunc(x) AS m;

Размещение функции в элемент LATERAL FROM позволяет вызывать ее для строки не более одного раза. Элемент m.* по-прежнему разворачивается в m.a, m.b и m.c, но теперь эти переменные являются просто ссылками на выходные значения элемента FROM. (Ключевое слово LATERAL здесь необязательно, но мы указываем его, чтобы подчеркнуть, что функция получает x из some_table.)

Синтаксис составное_значение.* приводит к такому развертыванию столбца, что он появляется на верхнем уровне выходного списка SELECT, списка RETURNING команд INSERT/UPDATE/ DELETE, предложения VALUES или конструктора строки. Во всех других контекстах (в том числе вложенные в одну из этих конструкций) добавление .* к составному значению не меняет его, поскольку это означает «все столбцы», и поэтому снова выдается то же составное значение. Например, если somefunc() принимает составное значение в качестве аргумента, эти запросы равносильны:

SELECT somefunc(c.*) FROM inventory_item c;
SELECT somefunc(c) FROM inventory_item c;

В обоих случаях текущая строка inventory_item передается функции в виде одного аргумента с составным значением. Несмотря на то, что в таких случаях .** ни на что не влияет, его использование считается хорошим тоном, поскольку это ясно показывает, что здесь имеется в виду составное значение. В частности, синтаксический анализатор воспримет c в записи c.* как ссылку на имя или псевдоним таблицы, а не имя столбца, что убирает неоднозначность; тогда как без .* неясно, означает ли c имя таблицы или имя столбца, и на самом деле если есть столбец с именем c, будет выбрана вторая интерпретация.

Эти концепции демонстрирует следующий пример, в котором все запросы означают одно и то же:

SELECT * FROM inventory_item c ORDER BY c;
SELECT * FROM inventory_item c ORDER BY c.*;
SELECT * FROM inventory_item c ORDER BY ROW(c.*);

Все эти предложения ORDER BY задают составное значение строки, в результате чего строки сортируются в соответствии с правилами, описанными в подразделе Сравнение составных типов. Однако если строка inventory_item содержит столбец с именем c, то первый запрос будет отличаться от других, поскольку подразумевает сортировку только по этому столбцу. Учитывая показанные выше имена столбцов, эти запросы также равнозначны предыдущим:

SELECT * FROM inventory_item c ORDER BY ROW(c.name, c.supplier_id, c.price);
SELECT * FROM inventory_item c ORDER BY (c.name, c.supplier_id, c.price);

(В последнем случае используется конструктор строки с опущенным ключевым словом ROW).

Другая особенность синтаксиса, связанная с составными значениями, состоит в том, что для извлечения поля составного значения можно использовать функциональную запись. Это легко объяснить тем, что записи поле(таблица) и таблица.поле взаимозаменяемы. Например, эти запросы равнозначны:

SELECT c.name FROM inventory_item c WHERE c.price > 1000;
SELECT name(c) FROM inventory_item c WHERE price(c) > 1000;

Более того, если у нас есть функция, которая принимает один аргумент составного типа, мы можем вызвать ее любой записью. Все эти запросы равнозначны:

SELECT somefunc(c) FROM inventory_item c;
SELECT somefunc(c.*) FROM inventory_item c;
SELECT c.somefunc FROM inventory_item c;

Эта равнозначность между функциональной записью и записью полей позволяет использовать с составными типами функции, реализующие «вычисляемые поля». Приложению, использующему последний из запросов выше, не нужно явно знать, что somefunc — не настоящий столбец таблицы.

Совет
Учитывая это поведение, неразумно давать функции, принимающей один аргумент составного типа, то же имя, что и одному из полей этого составного типа. В случае неоднозначности интерпретация имени поля будет выбрана при использовании синтаксиса обращения к полю, тогда как функция будет выбрана при использовании синтаксиса вызова функции.

Синтаксис вводимых и выводимых значений составного типа

Внешнее текстовое представление составного значения состоит из элементов, которые интерпретируются в соответствии с правилами преобразования ввода/вывода для отдельных типов полей, а также элементов оформления, которые указывают на составную структуру. Оформление состоит из круглых скобок (( и )) вокруг всего значения и запятых (,) между соседними элементами. Пробелы вне скобок игнорируются, но в скобках они считаются частью значения поля и могут учитываться или не учитываться в зависимости от правил преобразования входных данных для типа данных поля. Например, в записи:

'(  42)'

пробел будет игнорироваться, если у поля целочисленный тип, но не текстовый.

Как было показано ранее, при написании составного значения любой его отдельный элемент можно заключить в кавычки. Это нужно делать в том случае, если значение элемента без кавычек может запутать синтаксический анализатор составных значений. Например, в кавычки нужно заключать поля, содержащие круглые скобки, запятые, кавычки или обратные слэши. Чтобы поместить кавычку или обратный слэш в элемент составного значения, заключенного в кавычки, поставьте перед этим символом обратный слэш. (Кроме того, пара кавычек в значении поля, заключенного в кавычки, используется для представления символа одинарной кавычки аналогично правилам для апострофов в строковых литералах SQL.) Или же можно обойтись без кавычек и использовать обратный слэш для экранирования всех символов в данных, которые в противном случае могут быть восприняты как синтаксис составного значения.

Абсолютно пустое значение поля (без запятых или круглых скобок) означает NULL. Чтобы ввести именно пустую строку а не NULL, напишите "".

Программа вывода составных значений помещает кавычки вокруг значений полей, если те являются пустыми строками или содержат круглые скобки, запятые, кавычки, обратный слэш или пробел. (Для пробелов это делать необязательно, но это способствует удобочитаемости.) Кавычки и обратные слэши, заключенные в значения полей, будут удвоены.

Примечание
Помните, что все, что вы пишете в команде SQL, сначала будет интерпретироваться как строковый литерал, а затем как составное значение. Из-за этого количество необходимых обратных слэшей удваивается (при условии использования синтаксиса с экранированием строки). Например, чтобы вставить в составное значение поле text с кавычками и обратным слэшем, нужно написать:

INSERT ... VALUES ('("\"\\")');

Обработчик строковых литералов удаляет один уровень обратного слэша, так что синтаксический анализатор составных значений получает на вход ("\"\\"). В свою очередь, в программу ввода типа данных text попадает уже строка "\. (Если бы мы работали с типом данных, у которого программа ввода также обрабатывает обратный слэш особым образом, например bytea, нам могло бы потребоваться уже восемь обратных слэшей в команде, чтобы получить один в сохраненном поле составного значения.) Во избежание необходимости такого дублирования символов строки можно заключать в знаки доллара (см. раздел Строковые константы с экранированием знаками доллара).

Совет
При записи составных значений в командах SQL зачастую удобнее работать с синтаксисом конструктора ROW, чем с синтаксисом литерала составного типа. В ROW значения отдельных элементов записываются так же, как если бы они не были членами составного выражения.

Диапазонные типы

Диапазонные типы — это типы данных, представляющие диапазон значений типа некоторого элемента (также называемого подтипом диапазона). Например, диапазоны типа timestamp можно использовать для представления временного интервала, когда зарезервирована переговорная. В этом случае типом данных будет tsrange (сокращение от «timestamp range»), а подтипом — timestamp. Подтип должен быть полностью упорядочен, чтобы было четко определено, где находятся значения элемента: внутри, до или после диапазона.

Диапазонные типы полезны, поскольку представляют множество значений элементов в одном значении диапазона, а также потому, что можно четко выразить такие понятия, как перекрывающиеся диапазоны. Наиболее ярким примером является использование диапазонов времени и даты при планировании, но также могут быть полезны диапазоны цен, интервалы измерений инструментов и т. п.

Для каждого диапазонного типа имеется соответствующий мультидиапазонный тип. Мультидиапазон представляет собой упорядоченный список несмежных, непустых и отличных от NULL диапазонов. Большинство диапазонных операторов работают и с мультидиапазонами, а кроме того у мультидиапазонных типов есть несколько собственных функций.

Встроенные диапазонные и мультидиапазонные типы

QHB поставляется со следующими встроенными диапазонными типами:

int4range — диапазон подтипа integer, int4multirange — соответствующий мультидиапазон

int8range — диапазон подтипа bigint, int8multirange — соответствующий мультидиапазон

numrange — диапазон подтипа numeric, nummultirange — соответствующий мультидиапазон

tsrange — диапазон подтипа timestamp without time zone, tsmultirange — соответствующий мультидиапазон

tstzrange — диапазон подтипа timestamp with time zone, tstzmultirange — соответствующий мультидиапазон

daterange — диапазон подтипа date, datemultirange — соответствующий мультидиапазон

Кроме того, вы можете определить собственные диапазонные типы; подробную информацию см. на справочной странице команды CREATE TYPE.

Примеры

CREATE TABLE reservation (room int, during tsrange);
INSERT INTO reservation VALUES
    (1108, '[2010-01-01 14:30, 2010-01-01 15:30)');

-- Вхождение
SELECT int4range(10, 20) @> 3;

-- Перекрытие
SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0);

-- Получение верхней границы
SELECT upper(int8range(15, 25));

-- Вычисление пересечения
SELECT int4range(10, 20) * int4range(15, 25);

-- Является ли диапазон пустым?
SELECT isempty(numrange(1, 5));

Полный список операторов и функций для диапазонных типов приведен в таблицах Диапазонные операторы и Диапазонные функции.

Включение и исключение границ

Каждый непустой диапазон имеет две границы: нижнюю и верхнюю. Все точки между этими значениями включены в диапазон. Включение границы означает, что сама граничная точка также включена в диапазон, а исключение — что она не включена.

В текстовой записи диапазона включение нижней границы обозначается символом "[", а исключение — символом "(". Аналогично включение верхней границы обозначается символом "]", а исключение — символом ")". (Подробную информацию см. в подразделе Ввод/вывод диапазонов).

Функции lower_inc и upper_inc проверяют, включается ли нижняя или верхняя граница в диапазон, соответственно.

Бесконечные (неограниченные) диапазоны

Нижнюю границу диапазона можно опустить, обозначая тем самым, что в диапазон включены все значения, лежащие ниже верхней границы, например (,3]. Аналогичным образом, если опустить верхнюю границу диапазона, то в него войдут все значения, лежащие выше нижней границы. Если же опустить обе границы, то в диапазон войдут все значения подтипа. Указание отсутствующей границы как включаемой в диапазон автоматически преобразуется в ее исключение, например, [,] преобразуется в (,). Эти отсутствующие значения можно воспринимать как +/- бесконечность, но все же это особые значения типа диапазона, которые считаются выходящими за пределы значений +/- бесконечность любого подтипа.

Для подтипов, в которых есть понятие «бесконечность», infinity можно использовать в качестве явного значения границы. Например, в диапазонах меток времени запись [today,infinity) подтипа timestamp исключает его особое значение infinity, тогда как запись [today,infinity] его включает, как и записи [today,) и [today,].

Функции lower_inf и upper_inf проверяют, опущена ли нижняя или верхняя границы диапазона, соответственно.

Ввод/вывод диапазонов

Вводимое значение диапазона должно записываться по одному из следующих шаблонов:

(нижняя-граница,верхняя-граница)
(нижняя-граница,верхняя-граница]
[нижняя-граница,верхняя-граница)
[нижняя-граница,верхняя-граница]
empty

Тип скобок (круглые или квадратные) указывает, включены ли в диапазон нижняя и верхняя границы, как описано выше. Обратите внимание, что последний шаблон содержит только слово empty и обозначает пустой диапазон (диапазон, не содержащий точек).

Нижняя-граница может быть либо строкой с допустимым входным значением подтипа, либо быть пустой, указывая на отсутствие нижней границы. Подобным же образом верхняя-граница может задаваться допустимым входным значением подтипа или быть пустой (неограниченной).

Каждое значение границы можно заключить в кавычки ("). Это необходимо делать, если значение границы содержит круглые или квадратные скобки, запятые, кавычки или обратный слэш, поскольку иначе эти символы будут рассматриваться как часть синтаксиса диапазона. Чтобы поместить в значение диапазона, заключенное в кавычки, кавычку или обратный слэш, нужно вставить перед ними перед ним обратный слэш. (Кроме того, пара кавычек в значении диапазона, заключенного в кавычки, используется для представления символа одинарной кавычки аналогично правилам для апострофов в строковых литералах SQL.) Или же можно обойтись без кавычек и использовать обратный слэш для экранирования всех символов в данных, которые в противном случае могут быть восприняты как синтаксис диапазона. Кроме того, чтобы задать в качестве значения границы пустую строку, следует написать "", так как пустая строка означает отсутствие границы.

Пробел допускается до и после значения диапазона, но любой пробел внутри круглых или квадратных скобок воспринимается как часть значения нижней или верхней границы. (Хотя в некоторых подтипах такие пробелы могут игнорироваться.)

Примечание
Эти правила очень похожи на правила записи значений полей в литералах составного типа. Дополнительные замечания см. в разделе Синтаксис вводимых и выводимых значений составного типа.

Примеры:

-- в диапазон включается 3, не включается 7 и включаются все точки между ними
SELECT '[3,7)'::int4range;

-- в диапазон не включаются ни 3, ни 7, но включаются все точки между ними
SELECT '(3,7)'::int4range;

-- в диапазон включается только значение 4
SELECT '[4,4]'::int4range;

-- диапазон не включает никаких точек (нормализация заменит эту запись на 'empty')
SELECT '[4,4)'::int4range;

Входное значение для мультидиапазона заключается в фигурные скобки ({ и }) и содержит ноль или более допустимых диапазонов, разделенных запятыми. До и после скобок и запятых допускаются пробельные символы. Синтаксис сделан похожим на синтаксис массивов, хотя мультидиапазоны гораздо проще: у них всего одна размерность и их содержимое не нужно заключать в кавычки. (Однако, как показано выше, границы диапазонов в них могут заключаться в кавычки.)

Примеры:

SELECT '{}'::int4multirange;
SELECT '{[3,7)}'::int4multirange;
SELECT '{[3,7), [8,9)}'::int4multirange;

Конструирование диапазонов и мультидиапазонов

Для каждого типа диапазонов имеется функция-конструктор с тем же именем, что и данный тип. Использование этого конструктора зачастую более удобно, чем запись литеральной константы диапазона, поскольку это избавляет от необходимости заключать значения границ в дополнительные кавычки. Функция-конструктор может принимать два или три аргумента. Вариант с двумя аргументами создает диапазон в стандартной форме (нижняя граница включена, верхняя граница исключена), тогда как вариант с тремя аргументами создает диапазон, у которого включение границ определяется третьим аргументом. Этот аргумент должен содержать одну из строк: "()", "(]", "[)" или "[]". Например:

-- Полная форма: нижняя граница, верхняя граница и текстовый аргумент, определяющий
-- включение/исключение границ.
SELECT numrange(1.0, 14.0, '(]');

-- Если третий аргумент опущен, подразумевается '[)'.
SELECT numrange(1.0, 14.0);

-- Хотя здесь указывается '(]', при выводе значение будет преобразовано в
-- каноническую форму, так как int8range — это тип дискретного диапазона (см. ниже).
SELECT int8range(1, 14, '(]');

-- Если указать NULL вместо любой из границ, этой границы у диапазона не будет.
SELECT numrange(NULL, 2.2);

Для каждого диапазонного типа есть также конструктор мультидиапазона с тем же именем, что и у мультидиапазонного типа. Функция-конструктор принимает ноль или более аргументов, которые представляют собой все диапазоны соответствующего типа. Например:

SELECT nummultirange();
SELECT nummultirange(numrange(1.0, 14.0));
SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));

Типы дискретных диапазонов

Дискретный диапазон — это диапазон, для подтипа которого четко определен «шаг», как, например, для типов integer и date. Элементы этих типов можно назвать соседними, когда между ними нет никаких допустимых значений. Это отличает дискретные диапазоны от непрерывных, в которых всегда (или почти всегда) можно найти другие значения элемента между двумя данными. Например, непрерывными является диапазон с подтипом numeric или timestamp. (Хотя timestamp имеет ограниченную точность и поэтому теоретически может рассматриваться как дискретный, лучше считать его непрерывным, поскольку обычно размер его шага не представляет интереса.)

Другой способ определения типа дискретного диапазона состоит в том, что в нем существует четкое представление о «следующем» или «предыдущем» элементе для каждого значения. Зная это, можно преобразовывать границы диапазона из включаемых в исключаемые, выбрав следующий или предыдущий элемент вместо заданного изначально. Например, в диапазоне целочисленного типа [4,8] и (3,9) обозначают одно и то же множество значений; но для диапазона числового типа это не так.

Тип дискретного диапазона должен иметь функцию канонизации, которая учитывает желаемый размер шага для данного подтипа. Функция канонизации отвечает за преобразование равнозначных значений типа диапазона в единичные представления, в частности, согласованность включаемых и исключаемых границ. Если функция канонизации не указана, то диапазоны с различным форматированием всегда будут считаться разными, даже если в действительности они представляют одно и то же множество значений.

Для встроенных диапазонных типов int4range, int8range и daterange используется каноническая форма, которая включает нижнюю границу и исключает верхнюю, то есть [). Однако для пользовательских типов можно использовать другие соглашения.

Определение новых диапазонных типов

Пользователи могут определять собственные диапазонные типы. Наиболее распространенная причина для этого — использование диапазонов с подтипами, для которых нет встроенных диапазонных типов. Например, можно определить новый диапазонный тип для подтипа float8:

CREATE TYPE floatrange AS RANGE (
    subtype = float8,
    subtype_diff = float8mi
);

SELECT '[1.234, 5.678]'::floatrange;

Поскольку float8 не имеет значимого «шага», функция канонизации в этом примере не задана.

Когда вы определяете собственный диапазон, вы автоматически получаете соответствующий мультидиапазонный тип.

Определение собственного диапазонного типа также позволяет выбрать другой класс оператора B-дерева или правило сортировки для его подтипа, чтобы изменить порядок сортировки, определяющий, какие значения попадают в данный диапазон.

Если считается, что значения подтипа дискретные, а не непрерывные, в команде CREATE TYPE следует указать функцию canonical. Эта функция принимает на вход значение диапазона и должна возвращать равнозначное значение, у которого могут быть другие границы и форматирование. Для двух диапазонов, представляющих одно множество значений, например целочисленные диапазоны [1, 7] и [1, 8), функция канонизации должна выдавать одинаковый результат. Неважно, какое представление вы выберете как каноническое при условии, что два равнозначных значения с разным форматированием всегда преобразуются в одно значение с одинаковым форматированием. Кроме настройки формата включаемых/исключаемых границ функция канонизации также может округлять граничные значения, если желаемый размер шага превышает емкость хранения подтипа. Например, в диапазонном типе для подтипа timestamp можно определить размер шага, равный часу, тогда функция канонизации должна будет округлить границы, не кратные часу, или, возможно, выдать вместо этого ошибку.

Кроме того, для любого диапазонного типа, предназначенного к использованию с индексами GiST или SP-GiST, должна быть определена разница подтипов, или функция subtype_diff. (Индекс будет работать и без subtype_diff, но, скорее всего, будет гораздо менее эффективен.) Эта функция принимает на вход два значения подтипа и возвращает их разницу (то есть X минус Y) в значении типа float8. В примере выше можно использовать функцию float8mi, которая является нижележащей версией обычного оператора «минус» для типа float8, но для другого подтипа может понадобиться преобразование. Кроме того, для представления разницы в числовом виде может потребоваться творческий подход. Функция subtype_diff должна, насколько это возможно, согласовываться с порядком сортировки, подразумеваемым выбранным классом оператора и правилами сортировки; то есть ее результат должен быть положительным, если, согласно порядку сортировки, первый аргумент больше второго.

Менее упрощенный пример функции subtype_diff:

CREATE FUNCTION time_subtype_diff(x time, y time) RETURNS float8 AS
'SELECT EXTRACT(EPOCH FROM (x - y))' LANGUAGE sql STRICT IMMUTABLE;

CREATE TYPE timerange AS RANGE (
    subtype = time,
    subtype_diff = time_subtype_diff
);

SELECT '[11:10, 23:00]'::timerange;

Дополнительную информацию о создании диапазонных типов см. на справочной странице команды CREATE TYPE.

Индексирование

Для столбцов с диапазонным типом можно создать индексы GiST и SP-GiST. Например, так создается индекс GiST:

CREATE INDEX reservation_idx ON reservation USING GIST (during);

Индекс GiST или SP-GiST может ускорять запросы с участием следующих диапазонных операторов: =, &&, <@, @>, <<, >>, -|-, &< и &>. Индекс GiST для мультидиапазонов может ускорить запросы, задействующие один набор мультидиапазонных операторов. Индекс GiST для диапазонов и индекс GiST для мультидиапазонов также может ускорить запросы, задействующие следующие межтиповые операторы, преобразующие диапазоны в мультидиапазоны и обратно: &&, <@, @>, <<, >>, -|-, &< и &>. Дополнительную информацию см. в таблице Диапазонные операторы.

Кроме того, для таких столбцов можно создать индексы В-деревья или хеш-индексы. Для индексов таких типов полезна, собственно, только одна операция диапазона — равенство. Для значений диапазона определен порядок сортировки B-деревьев с соответствующими операторами < и >, но этот порядок довольно произвольный и обычно бесполезен в реальном мире. Поддержка B-деревьев и хешей диапазонными типами предназначена в первую очередь для внутренней сортировки и хеширования при выполнении запросов, а не для создания собственно индексов.

Ограничения для диапазонов

Тогда как для скалярных значений UNIQUE является естественным ограничением, для диапазонных типов оно обычно не подходит. Более подходящими чаще всего являются ограничения-исключения (см. CREATE TABLE ... CONSTRAINT ... EXCLUDE). Такие ограничения позволяют задавать для диапазонов такие условия, как, например, «непересечение». Например:

CREATE TABLE reservation (
    during tsrange,
    EXCLUDE USING GIST (during WITH &&)
);

Это ограничение не позволит одновременно сохранить в таблице перекрывающиеся значения:

INSERT INTO reservation VALUES
    ('[2010-01-01 11:30, 2010-01-01 15:00)');
INSERT 0 1

INSERT INTO reservation VALUES
    ('[2010-01-01 14:45, 2010-01-01 15:45)');
ERROR:  conflicting key value violates exclusion constraint "reservation_during_excl"
DETAIL:  Key (during)=(["2010-01-01 14:45:00","2010-01-01 15:45:00")) conflicts
with existing key (during)=(["2010-01-01 11:30:00","2010-01-01 15:00:00")).
-- ОШИБКА:  конфликтующее значение ключа нарушает ограничение-исключение "reservation_during_excl"
-- ДЕТАЛИЗАЦИЯ: Ключ (during)=(["2010-01-01 14:45:00","2010-01-01 15:45:00"))
-- конфликтует с существующим ключом (during)=(["2010-01-01 11:30:00","2010-01-01 15:00:00")).

Для максимальной гибкости можно комбинировать ограничения-исключения для диапазонов с ограничениями-исключениями для простых скалярных типов данных, которые определяются расширением btree_gist. Например, после установки btree_gist следующее ограничение не будет допускать перекрывающиеся диапазоны, только если номера переговорных тоже совпадают:

CREATE EXTENSION btree_gist;
CREATE TABLE room_reservation (
    room text,
    during tsrange,
    EXCLUDE USING GIST (room WITH =, during WITH &&)
);

INSERT INTO room_reservation VALUES
    ('123A', '[2010-01-01 14:00, 2010-01-01 15:00)');
INSERT 0 1

INSERT INTO room_reservation VALUES
    ('123A', '[2010-01-01 14:30, 2010-01-01 15:30)');
ERROR:  conflicting key value violates exclusion constraint "room_reservation_room_during_excl"
DETAIL:  Key (room, during)=(123A, ["2010-01-01 14:30:00","2010-01-01 15:30:00")) conflicts
with existing key (room, during)=(123A, ["2010-01-01 14:00:00","2010-01-01 15:00:00")).
-- ОШИБКА:  конфликтующее значение ключа нарушает ограничение-исключение
-- "room_reservation_room_during_excl"
-- ДЕТАЛИЗАЦИЯ:  Ключ (room, during)=(123A, [ 2010-01-01 14:30:00,
-- 2010-01-01 15:30:00 )) конфликтует
-- с существующим ключом (room, during)=(123A, ["2010-01-01 14:00:00","2010-01-01 15:00:00")).

INSERT INTO room_reservation VALUES
    ('123B', '[2010-01-01 14:30, 2010-01-01 15:30)');
INSERT 0 1

Типы доменов

Домен — это пользовательский тип данных, основанный на другом нижележащем типе. При необходимости для домена можно задать условия, ограничивающие его допустимые значения подмножеством значений, допустимых для нижележащего типа. Во всем остальном он ведет себя как нижележащий тип — например, с типом домена будет работать любой оператор или функция, применимые к нижележащему типу. Нижележащим типом может быть любой встроенный или пользовательский базовый тип, перечислимый тип, тип массива, составной тип, диапазонный тип или другой домен.

Например, можно создать домен поверх целых чисел, принимающий только положительные целые числа:

CREATE DOMAIN posint AS integer CHECK (VALUE > 0);
CREATE TABLE mytable (id posint);
INSERT INTO mytable VALUES(1);   -- работает
INSERT INTO mytable VALUES(-1);  -- ошибка

Когда к значению домена применяется оператор или функция нижележащего типа, домен автоматически преобразуется в нижележащий тип. Так, например, результат операции mytable.id - 1 будет считаться имеющим тип integer, а не posint. Мы могли бы написать (mytable.id - 1)::posint, чтобы привести результат обратно к posint, вызвав перепроверку ограничений домена. В этом случае, если выражение было применено к значению id, равному 1, произойдет ошибка. Допускается присвоение значения нижележащего типа полю или переменной типа домена без написания явного приведения, но ограничения домена все равно будут проверены.

Дополнительную информацию см. на справочной странице команды CREATE DOMAIN.

Типы идентификаторов объектов

Идентификаторы объектов (OID) используются внутри QHB в качестве первичных ключей для различных системных таблиц. Идентификатор объекта представляется типом oid. Также для oid определено несколько типов-псевдонимов с именами вида regсущность. Обзор этих псевдонимов представлен в Таблице 26.

В настоящее время тип oid реализован как четырехбайтное целое число без знака. Следовательно, он недостаточно велик, чтобы полностью обеспечить уникальность в больших базах данных или даже в отдельных больших таблицах.

Для самого типа oid помимо сравнения существует всего несколько операций. Однако его можно привести к целому числу, а затем обработать с помощью стандартных целочисленных операторов. (При этом следует опасаться возможной путаницы между знаковыми и беззнаковыми значениями.)

У типов-псевдонимов OID нет собственных операций, кроме специализированных функций ввода и вывода. Эти функции способны принимать и отображать не исходное числовое значение, как тип oid, а символические имена системных объектов. Типы-псевдонимы позволяют упростить поиск объектов по значениям OID. Например, чтобы проверить в pg_attribute строки, относящиеся к таблице mytable, можно написать:

SELECT * FROM pg_attribute WHERE attrelid = 'mytable'::regclass;

вместо:

SELECT * FROM pg_attribute
  WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'mytable');

Хотя второй запрос сам по себе не так уж плох, он все равно чересчур упрощен. Если в разных схемах есть несколько таблиц с именем mytable, то для выбора правильного OID потребуется гораздо более сложный подзапрос. Преобразователь вводимого значения типа regclass находит таблицу в соответствии с настройкой пути к схеме, поэтому автоматически выполняет «правильные действия». Точно так же приведение OID таблицы к типу regclass удобно для символического отображения числового OID.

Таблица 26. Типы идентификаторов объектов

Имя | Ссылки | Описание | Пример значения
oid | Любые | числовой идентификатор объекта | 564182
regclass | pg_class | имя отношения | pg_type
regcollation | pg_collation | имя правила сортировки | "POSIX"
regconfig | pg_ts_config | конфигурация текстового поиска | english
regdictionary | pg_ts_dict | словарь текстового поиска | simple
regnamespace | pg_namespace | имя пространства имен | pg_catalog
regoper | pg_operator | имя оператора | +
regoperator | pg_operator | оператор с типами аргументов | *(integer,integer) или -(NONE,integer)
regproc | pg_proc | имя функции | sum
regprocedure | pg_proc | функция с типами аргументов | sum(int4)
regrole | pg_authid | имя роли | smithee
regtype | pg_type | имя типа данных | integer

Все типы-псевдонимы OID для объектов, сгруппированных в пространстве имен, принимают имена, дополненные именем схемы, и будут выводить имена со схемой, если этот объект нельзя найти в текущем пути поиска без имени схемы. Например, myschema.mytable является приемлемым входным значением для regclass (если такая таблица существует). Это значение может выводиться как myschema.mytable или просто mytable, в зависимости от текущего пути поиска. Типы-псевдонимы regproc и regoper принимают только уникальные вводимые имена (не перегруженные), поэтому их использование ограничено; в большинстве случаев больше подходят regprocedure или regoperator. Для regoperator при определении унарных операторов вместо неиспользуемого операнда записывается слово NONE.

Функции ввода для данных типов допускают пробелы между синтаксическими единицами и приводят буквы верхнего регистра к нижнему, за исключением строки в кавычках; это сделано для того, чтобы правила синтаксиса были похожа на принятые для записи имен объектов в SQL. И наоборот, функции вывода будут при необходимости использовать кавычки, чтобы выводимая строка была допустимым идентификатором SQL. Например,OID функции с именем Foo (с F в верхнем регистре), принимающей два целочисленных аргумента, можно ввести как ' "Foo" ( int, integer ) '::regprocedure. Вывод будет выглядеть как "Foo"(integer,integer). Как имя функции, так и имена типов аргументов также могут быть дополнены схемой.

Многие встроенные функции QHB принимают OID таблицы или другого вида объекта базы данных и для удобства объявляются как принимающие regclass (или соответствующий тип-псевдоним OID). Это означает, что нет необходимости искать OID объекта вручную, а можно просто ввести его имя в виде строкового литерала. Например, функция nextval(regclass) принимает OID отношения последовательности, поэтому ее можно вызвать так:

nextval('foo')              обращается к последовательности foo
nextval('FOO')              то же самое
nextval('"Foo"')            обращается к последовательности Foo
nextval('myschema.foo')     обращается к myschema.foo
nextval('"myschema".foo')   то же самое
nextval('foo')              ищет foo в пути поиска

Примечание
Когда аргумент такой функции записывается в виде текстовой строки без каких-либо добавлений, она становится константой типа regclass (или соответствующего типа). Поскольку на самом деле это просто OID, оно будет приводить к изначально идентифицированной последовательности, несмотря на то, что впоследствии она может быть переименована, перенесена в другую схему и т. д. Это «раннее связывание» обычно желательно для ссылок на объект в значениях столбцов по умолчанию и представлениях. Но иногда может возникнуть необходимость в «позднем связывании», когда ссылка на объект распознается в процессе выполнения. Чтобы получить такое поведение, нужно принудительно изменить тип константы с regclass на text:

nextval('foo'::text)      foo разыскивается во время выполнения

Для поиска во время выполнения также можно использовать функцию to_regclass() и подобные ей. См. таблицу Функции для получения информации из системных каталогов.

Другой практический пример использования regclass — поиск OID таблицы, отображенной в представлениях information_schema, которые не предоставляют подобные OID напрямую. Например, возникла необходимость вызвать функцию pg_relation_size(), для которой требуется OID таблицы. Учитывая вышеизложенные правила, правильный способ ее вызвать выглядит так:

SELECT table_schema, table_name,
       pg_relation_size((quote_ident(table_schema) || '.' ||
                         quote_ident(table_name))::regclass)
FROM information_schema.tables
WHERE ...

Функция quote_ident() при необходимости заключит идентификаторы в кавычки. Более простой на вид способ

SELECT pg_relation_size(table_name)
FROM information_schema.tables
WHERE ...

использовать не рекомендуется, потому что он не сработает для таблиц, которые не входят в заданный путь поиска или имеют имена, которые нужно заключать в кавычки.

Дополнительным свойством большинства типов-псевдонимов OID является создание зависимостей. Если в сохраненном выражении (например в выражении столбца по умолчанию или в представлении) находится константа одного из этих типов, это создает зависимость от ссылочного объекта. Например, если у столбца выражение по умолчанию nextval(’my_seq’::regclass), QHB понимает, что это выражение зависит от последовательности my_seq; система не позволит удалить последовательность, не удалив предварительно выражение по умолчанию. Альтернативная запись nextval('my_seq'::text) не создает зависимость. (Тип regrole является единственным исключением для этого свойства. Константы этого типа в таких выражениях не допускаются.)

Другим типом идентификаторов, используемым системой, является xid, или идентификатор транзакции (сокращенно xact). Это тип данных системных столбцов xmin и xmax. Идентификаторы транзакции являются 32-битными величинами. В некоторых контекстах используется 64-битный вариант xid8. В отличие от значений xid, значения xid8 увеличиваются строго монотонно и не могут быть повторно использованы на протяжении всего существования кластера баз данных.

Третий тип идентификатора, используемый системой, — cid, или идентификатор команды. Это тип данных системных столбцов cmin и cmax. Идентификаторы команд также являются 32-битными величинами.

Последний тип идентификатора, используемый системой, — это tid, или идентификатор кортежа (идентификатор строки). Это тип данных системного столбца ctid. Идентификатор кортежа — это пара (номер блока, индекс кортежа в блоке), которая определяет физическое расположение строки в таблице.

(Более подробно системные столбцы рассматриваются в разделе Системные столбцы).

Тип pg_lsn

Тип данных pg_lsn может использоваться для хранения значения LSN (порядковый номер в журнале, Log Sequence Number), которое является указателем на позицию в WAL (журнале с упреждающей записью). Этот тип является представлением XLogRecPtr и внутренним системным типом QHB.

Внутренне LSN представляет собой 64-разрядное целое число, представляющее позицию байта в потоке WAL. Он выводится в виде двух шестнадцатеричных чисел длиной до 8 цифр каждое, разделенных косой чертой; например 16/B374D848. Тип pg_lsn поддерживает стандартные операторы сравнения, такие как = и >. Можно вычесть один номер LSN из другого с помощью оператора -; результатом будет число байтов между этими позициями в журнале упреждающей записи. Кроме того, число байтов можно добавлять и вычитать из LSN с помощью операторов +(pg_lsn,numeric) и -(pg_lsn,numeric) соответственно. Обратите внимание, что вычисленный LSN должен находиться в диапазоне типа pg_lsn, т. е. между 0/0 и FFFFFFFF/FFFFFFFF.

Псевдотипы

Система типов QHB содержит ряд записей специального назначения, которые в совокупности называются псевдотипами. Псевдотип нельзя использовать в качестве типа данных столбца, но можно объявить функцию с аргументом или результатом такого типа. Каждый из доступных псевдотипов полезен в ситуациях, когда поведение функции не соответствует простому принятию или возвращению значения определенного типа данных SQL. Все существующие псевдотипы перечислены в Таблице 27.

Таблица 27. Псевдо-типы

Имя | Описание
any | Указывает, что функция принимает любой тип входных данных.
anyelement | Указывает, что функция принимает любой тип данных (см. подраздел Полиморфные типы).
anyarray | Указывает, что функция принимает любой тип массива (см. подраздел Полиморфные типы).
anynonarray | Указывает, что функция принимает любой тип данных, кроме массива (см. подраздел Полиморфные типы).
anyenum | Указывает, что функция принимает любой перечислимый тип данных (см. подраздел Полиморфные типы и раздел Перечислимые типы).
anyrange | Указывает, что функция принимает любой диапазонный тип данных (см. подраздел Полиморфные типы и раздел Диапазонные типы).
anymultirange | Указывает, что функция принимает любой мультидиапазонный тип данных (см. подраздел Полиморфные типы и раздел Диапазонные типы).
anycompatible | Указывает, что функция принимает любой тип данных и автоматически приводит различные аргументы к общему типу данных (см. подраздел Полиморфные типы).
anycompatiblearray | Указывает, что функция принимает любой тип массива и автоматически приводит различные аргументы к общему типу данных (см. подраздел Полиморфные типы).
anycompatiblenonarray | Указывает, что функция принимает любой тип данных, отличный от массива и автоматически приводит различные аргументы к общему типу данных (см. подраздел Полиморфные типы).
anycompatiblerange | Указывает, что функция принимает любой диапазонный тип данных и автоматически приводит различные аргументы к общему типу данных (см. подраздел Полиморфные типы и раздел Перечислимые типы).
anycompatiblemultirange | Указывает, что функция принимает любой мультидиапазонный тип данных и автоматически приводит различные аргументы к общему типу данных (см. подраздел Полиморфные типы и раздел Перечислимые типы).
cstring | Указывает, что функция принимает или возвращает строку в стиле C с NULL в конце.
internal | Указывает, что функция принимает или возвращает внутренний серверный тип данных.
language_handler | Обработчик вызова процедурного языка объявляется как возвращающий тип language_handler.
fdw_handler | Обработчик обертки сторонних данных объявляется как возвращающий тип fdw_handler.
index_am_handler | Обработчик метода индексного доступа объявляется как возвращающий тип index_am_handler.
tsm_handler | Обработчик метода выборки из таблицы объявляется как возвращающий тип tsm_handler.
record | Определяет функцию, принимающую или возвращающую неопределенный тип строки.
trigger | Триггерная функция объявляется как возвращающая тип trigger.
event_trigger | Функция событийного триггера объявляется как возвращающая тип event_trigger.
pg_ddl_command | Определяет представление команд DDL, доступных для триггеров событий.
void | Указывает, что функция не возвращает значение.
unknown | Определяет еще не определенный тип, например, необработанный строковый литерал.

Функции, написанные на языке C/RUST (встроенные или динамически загружаемые), могут быть объявлены для принятия или возврата любого из этих псевдотипов. Ответственность за то, что функция будет безопасна с псевдотипом в качестве аргумента, несет разработчик функции.

Функции, написанные на процедурных языках, могут использовать псевдотипы, только если это позволяет их язык реализации. В настоящее время большинство процедурных языков запрещают использование псевдотипа в качестве аргумента, а для результата допускают только типы void и record (и trigger или event_trigger, когда функция реализует триггер или триггер события). Некоторые языки также поддерживают полиморфные функции, использующие полиморфные псевдотипы, показанные выше и подробно рассмотренные в подразделе Полиморфные типы.

Псевдотип internal используется для объявления функций, предназначенных только для внутреннего вызова СУБД, а не путем прямого вызова в запросе SQL. Если у функции есть хотя бы один аргумент типа internal, ее нельзя вызвать из SQL. Чтобы сохранить типобезопасность данного ограничения, нужно следовать этому правилу кодирования: не создавайте функцию, которая объявляется как возвращающая internal, если у нее нет ни одного аргумента internal.

Функции и операторы

Содержание

Логические операторы

Функции и операторы сравнения

Математические функции и операторы

Строковые функции и операторы

format

Функции и операторы для двоичных строк

Функции и операторы для битовых строк

Сопоставление с шаблоном

LIKE

Регулярные выражения SIMILAR TO

Регулярные выражения POSIX

Функции для форматирования типов данных

Функции и операторы даты/времени

EXTRACT, date_part

date_trunc

date_bin

At TIME ZONE

Текущая дата/время

Задержка выполнения

Функции поддержки перечислений

Геометрические функции и операторы

Функции и операторы для сетевых адресов

Функции и операторы текстового поиска

Функции генерирования UUID

Функции XML

Создание XML-контента

Предикаты XML

Обработка XML

Отображение таблиц в XML

Расширенные функции XML

Функции и операторы JSON

Обработка и создание данных JSON

Язык путей SQL/JSON

Функции для управления последовательностями

Условные выражения

CASE

COALESCE

NULLIF

GREATEST и LEAST

Функции и операторы для массивов

Диапазонные/мультидиапазонные функции и операторы

Агрегатные функции

Оконные функции

Выражения подзапросов

EXISTS

IN

NOT IN

ANY/SOME

ALL

Сравнение единичных строк

Сравнение строк и массивов

IN

NOT IN

ANY/SOME (с массивом)

ALL (с массивом)

Сравнение конструкторов строк

Сравнение составных типов

Функции, возвращающие множества

Системные информационные функции и операторы

Функции для системного администрирования

Функции для параметров конфигурации

Функции для передачи сигналов серверу

Функции для управления резервным копированием

Функции для управления восстановлением

Функции синхронизации снимков

Функции для управления репликацией

Функции для управления объектами базы данных

Функции для обслуживания индексов

Функции доступа к типовым файлам

Функции рекомендательных блокировок

Функции для управления метриками и аннотациями

Триггерные функции

Функции событийных триггеров

Сбор изменений в конце команды

Обработка объектов, удаленных командой DDL

Обработка события перезаписи таблицы

Функции статистической информации

Проверка списков MCV

QHB предоставляет огромное количество функций и операторов для встроенных типов данных. В этой главе описаны основные из них, тогда как некоторые специальные функции описываются в соответствующих разделах данного руководства. Кроме того, пользователи могут определять свои собственные функции и операторы, как описано в главе Серверное программирование. Для вывода списка всех имеющихся функций и операторов можно воспользоваться командами psql \df и \do соответственно.

Обозначения, используемые в этой главе для описания аргумента и результирующего типа данных функции или оператора, выглядят так:

repeat ( text, integer ) → text

В данной записи говорится, что функция repeat принимает один текстовый и один целочисленный аргумент и возвращает результат текстового типа. Стрелка вправо также служит указанием на результат, например:

repeat('Pg', 4) → PgPgPgPg

Если для вас важна переносимость, учтите, что большинство функций и операторов, описанных в этой главе, за исключением самых простых арифметических операторов и операторов сравнения и некоторых явно отмеченных функций, не указаны в стандарте SQL. Эта расширенная функциональность отчасти присутствует и в других СУБД SQL, и во многих случаях эта функциональность совместима и согласована между различными реализациями.

Логические операторы

Среди логических операторов имеются обычные:

boolean AND boolean → boolean
boolean OR boolean → boolean
NOT boolean → boolean

SQL использует трехзначную логическую систему: true (истина), false (ложь) и NULL («не определено»). Рассмотрите следующие таблицы истинности:

a | b | a AND b | a OR b
TRUE | TRUE | TRUE | TRUE
TRUE | FALSE | FALSE | TRUE
TRUE | NULL | NULL | TRUE
FALSE | FALSE | FALSE | FALSE
FALSE | NULL | FALSE | NULL
NULL | NULL | NULL | NULL

.

a | NOT a
TRUE | FALSE
FALSE | TRUE
NULL | NULL

Операторы AND и OR являются коммутативными, то есть левый и правый операнд можно менять местами, не влияя на результат. (Однако нет гарантии, что левый операнд будет вычисляться перед правым. Дополнительную информацию о порядке вычисления подвыражений см. в разделе Правила вычисления выражений.)

Функции и операторы сравнения

Среди операторов сравнения имеются обычные операторы, перечисленные в Таблице 1.

Таблица 1. Операторы сравнения

Оператор | Описание
тип_данных < тип_данных → boolean | Меньше
тип_данных > тип_данных → boolean | Больше
тип_данных <= тип_данных → boolean | Меньше или равно
тип_данных >= тип_данных → boolean | Больше или равно
тип_данных = тип_данных → boolean | Равно
тип_данных <> тип_данных → boolean | Не равно
тип_данных != тип_данных → boolean | Не равно

Примечание
Для условия «не равно» в стандарте SQL принята запись <>. Запись != является псевдонимом, который преобразуется в <> на самом раннем этапе синтаксического анализа. Как следствие, реализовать операторы != и <> так, чтобы они работали по-разному, невозможно.

Эти операторы сравнения имеются для всех встроенных типов данных с естественной сортировкой, включая числовые, строковые типы и типы даты/времени. Кроме того, сравниваться могут массивы, составные типы и диапазоны, если сравнимы типы данных их компонентов.

Обычно также можно сравнивать значения связанных типов данных; например, возможно сравнение integer > bigint. Некоторые подобные операции реализуются непосредственно «межтиповыми» операторами сравнения, но если такой оператор недоступен, синтаксический анализатор принудительно приведет менее распространенные тип к более распространенному и применит подходящий для последнего оператор сравнения.

Как показано выше, все операторы сравнения являются бинарными операторами, которые возвращают значения типа boolean. Таким образом, выражения типа 1 < 2 < 3 недопустимы (потому что нет оператора < для сравнения логического значения с 3). Для проверки принадлежности диапазону используйте предикат BETWEEN, описанный ниже

Существует также несколько предикатов сравнения, перечисленных в Таблице 2. Они ведут себя подобно операторам, но имеют специальный синтаксис, предписанный стандартом SQL.

Таблица 2. Предикаты сравнения

Предикат

Описание
Пример(ы)

тип_данных BETWEEN тип_данных AND тип_данных → boolean

Между (включая границы диапазона).
2 BETWEEN 1 AND 3 → t
2 BETWEEN 3 AND 1 → f

тип_данных NOT BETWEEN тип_данных AND тип_данных → boolean

Не между (обратное к BETWEEN).
2 NOT BETWEEN 1 AND 3 → f

тип_данных BETWEEN SYMMETRIC тип_данных AND тип_данных → boolean

Между, после сортировки граничных значений.
2 BETWEEN SYMMETRIC 3 AND 1 → t

тип_данных NOT BETWEEN SYMMETRIC тип_данных AND тип_данных → boolean

Не между, после сортировки граничных значений.
2 NOT BETWEEN SYMMETRIC 3 AND 1 → f

тип_данных IS DISTINCT FROM тип_данных → boolean

Не равно, при этом рассматривая NULL как обычное значение.
1 IS DISTINCT FROM NULL → t (а не NULL)
NULL IS DISTINCT FROM NULL → f (а не NULL)

тип_данных IS NOT DISTINCT FROM тип_данных → boolean

Равно, при этом рассматривая NULL как обычное значение.
1 IS NOT DISTINCT FROM NULL → f (а не NULL)
NULL IS NOT DISTINCT FROM NULL → t (а не NULL)

тип_данных IS NULL → boolean

Проверяет, равно ли значение NULL.
1.5 IS NULL → f

тип_данных IS NOT NULL → boolean

Проверяет, отличается ли значение от NULL.
'null' IS NOT NULL → t

тип_данных ISNULL → boolean

Проверяет, равно ли значение NULL (нестандартный синтаксис).

тип_данных NOTNULL → boolean

Проверяет, отличается ли значение от NULL (нестандартный синтаксис).

boolean IS TRUE → boolean

Проверяет, является ли результат логического выражения значением true.
true IS TRUE → t
NULL::boolean IS TRUE → f (а не NULL)

boolean IS NOT TRUE → boolean

Проверяет, является ли результат логического выражения значением false или неизвестным.
true IS NOT TRUE → f
NULL::boolean IS NOT TRUE → t (а не NULL)

boolean IS FALSE → boolean

Проверяет, является ли результат логического выражения значением false.
true IS FALSE → f
NULL::boolean IS FALSE → f (а не NULL)

boolean IS NOT FALSE → boolean

Проверяет, является ли результат логического выражения значением true или неизвестным.
true IS NOT FALSE → t
NULL::boolean IS NOT FALSE → t (а не NULL)

boolean IS UNKNOWN → boolean

Проверяет, является ли результат логического выражения неизвестным значением.
true IS UNKNOWN → f
NULL::boolean IS UNKNOWN → t (а не NULL)

boolean IS NOT UNKNOWN → boolean

Проверяет, является ли результат логического выражения значением true или false.
true IS NOT UNKNOWN → t
NULL::boolean IS NOT UNKNOWN → f (а не NULL)

Предикат BETWEEN упрощает проверки принадлежности диапазону:

a BETWEEN x AND y

равнозначно

a >= x AND a <= y

Обратите внимание, что BETWEEN обрабатывает граничные значения как включенные в диапазон. BETWEEN SYMMETRIC аналогичен BETWEEN за исключением того, что аргумент слева от AND не обязательно должен быть меньше или равен аргументу справа. Если это не так, эти два аргумента автоматически меняются местами, поэтому всегда подразумевается непустой диапазон.

Различные варианты BETWEEN реализуются посредством обычных операторов сравнения, и поэтому они будут работать с любыми типами данных, которые можно сравнивать.

Примечание
Использование AND в синтаксисе BETWEEN создает неоднозначность с использованием AND как логического оператора. Для ее устранения в качестве второго аргумента предложения BETWEEN допускается только ограниченный набор типов выражений. Если вам нужно записать в BETWEEN более сложное подвыражение, заключите его в скобки.

Обычные операторы сравнения выдают NULL (что означает «неопределенность»), а не true или false, когда любой из аргументов равен NULL. Например, 7 = NULL возвращает NULL, как и 7 <> NULL. Когда это поведение не устраивает, можно использовать предикаты IS [NOT] DISTINCT FROM:

a IS DISTINCT FROM b
a IS NOT DISTINCT FROM b

Для входных значений не NULL условие IS DISTINCT FROM равнозначно оператору <>. Однако если оба аргумента NULL, он возвращает false, а если только один из аргументов NULL, он возвращает true. Аналогично условие IS NOT DISTINCT FROM идентично = для аргументов не NULL, но возвращает true, если оба аргумента NULL, и false, если только один аргумент NULL. Таким образом, эти предикаты по сути ведут себя так, как если бы NULL был нормальным значением данных, а не «неопределенным».

Чтобы проверить, является ли значение NULL, используются предикаты:

выражение IS NULL
выражение IS NOT NULL

или равнозначные, но нестандартные предикаты:

выражение ISNULL
выражение NOTNULL

Не пишите выражение = NULL, потому что NULL «не равно» NULL. (Значение NULL представляет неопределенность, и неизвестно, равны ли два неопределенных значения).

Совет
Некоторые приложения могут ожидать, что выражение = NULL возвращает true, если результатом выражения является значение NULL. Настоятельно рекомендуется изменить эти приложения в соответствии со стандартом SQL. Однако если это невозможно, имеется переменная конфигурации transform_null_equals. Если она установлена, QHB преобразует предложения x = NULL в x IS NULL.

Если выражение имеет строковое значение, то IS NULL равно true, если само выражение строки NULL или когда все поля строки NULL, а IS NOT NULL равно true, если само выражение строки не NULL и все поля строки не NULL. Из-за этого поведения IS NULL и IS NOT NULL не всегда возвращают обратные результаты для таких выражений; в частности, строковое выражение, содержащее поля NULL и не NULL, вернет false для обеих проверок. В некоторых случаях может быть предпочтительнее записать строка IS DISTINCT FROM NULL или строка IS NOT DISTINCT FROM NULL, которые просто проверят, равно ли NULL все значение строки, без каких-либо дополнительных проверок полей строки.

Логические значения также можно проверить с использованием предикатов

логическое_выражение IS TRUE
логическое_выражение IS NOT TRUE
логическое_выражение IS FALSE
логическое_выражение IS NOT FALSE
логическое_выражение IS UNKNOWN
логическое_выражение IS NOT UNKNOWN

Они всегда будут возвращать true или false и никогда NULL, даже если операнд — NULL. Аргумент NULL рассматривается как логическое значение «неопределенность». Обратите внимание, что IS UNKNOWN и IS NOT UNKNOWN фактически то же самое, что и IS NULL и IS NOT NULL соответственно, за исключением того, что входное выражение должно иметь логический тип.

Также существует несколько связанных со сравнениями функций, перечисленных в Таблице 3.

Таблица 3. Функции сравнения

Функция

Описание
Пример(ы)

num_nonnulls ( VARIADIC "any" ) → integer

Возвращает количество аргументов, отличных от NULL
num_nonnulls(1, NULL, 2) → 2

num_nulls ( VARIADIC "any" ) → integer

Возвращает количество аргументов NULL
num_nulls(1, NULL, 2) → 1

Математические функции и операторы

Математические операторы предоставляются для многих типов QHB. Фактическое поведение с типами без стандартных математических соглашений (например типами даты/времени) мы опишем в последующих разделах.

В Таблице 4 перечислены математические операторы, имеющиеся для стандартных числовых типов. Если не указано иное, операторы, показанные как принимающие числовой_тип, имеются для типов smallint, integer, bigint, numeric, real и double precision. Операторы, показанные как принимающие целочисленный_тип, имеются для типов smallint, integer и bigint. За исключением отмеченных случаев, все формы операторов возвращают тот же тип данных, какой имеет их аргумент (или аргументы). Вызовы с неодинаковыми типами аргументов, например integer + numeric, разрешаются использованием типа, идущего в этих списках последним.

Таблица 4. Математические операторы

Оператор

Описание
Пример(ы)

числовой_тип + числовой_тип → числовой_тип

Сложение
2 + 3 → 5

+ числовой_тип → числовой_тип

Унарный плюс (нет операции)
+ 3.5 → 3.5

числовой_тип - числовой_тип → числовой_тип

Вычитание
2 - 3 → -1

- числовой_тип → числовой_тип

Смена знака
- (-4) → 4

числовой_тип * числовой_тип → числовой_тип

Умножение
2 * 3 → 6

числовой_тип / числовой_тип → числовой_тип

Деление (при делении целочисленных типов результат округляется в сторону нуля)
5.0 / 2 → 2.5000000000000000
5 / 2 → 2
(-5) / 2 → -2

числовой_тип % числовой_тип → числовой_тип

Остаток целочисленного деления (по модулю); имеется для типов smallint, integer, bigint и numeric
5 % 4 → 1

numeric ^ numeric → numeric
double precision ^ double precision → double precision

Возведение в степень
2 ^ 3 → 8
В отличие от обычной математической практики, ^ по умолчанию вычисляется слева направо:
2 ^ 3 ^ 3 → 512
2 ^ (3 ^ 3) → 134217728

|/ double precision → double precision

Квадратный корень
|/ 25.0 → 5

||/ double precision → double precision

Кубический корень
||/ 64.0 → 4

@ числовой_тип → числовой_тип

Абсолютное значение (модуль)
@ -5.0 → 5

целочисленный_тип & целочисленный_тип → целочисленный_тип

Битовое И
91 & 15 → 11

целочисленный_тип | целочисленный_тип → целочисленный_тип

Битовое ИЛИ
32 | 3 → 35

целочисленный_тип # целочисленный_тип → целочисленный_тип

Битовое исключающее ИЛИ
17 # 5 → 20

~ целочисленный_тип → целочисленный_тип

Битовое НЕ
~1 → -2

целочисленный_тип << integer → целочисленный_тип

Битовый сдвиг влево
1 << 4 → 16

целочисленный_тип >> integer → целочисленный_тип

Битовый сдвиг вправо
8 >> 2 → 2

В Таблице 5 перечислены все имеющиеся математические функции. Многие из этих функций представлены в нескольких формах с различными типами аргументов. За исключением отмеченных случаев, все формы функций возвращают тот же тип данных, какой имеет их аргумент (или аргументы); случаи с неодинаковыми типами разрешаются тем же способом, который был описан выше для операторов. Функции, работающие с данными double precision, в основном реализованы поверх C-библиотеки системы сервера, поэтому точность и поведение в граничных случаях могут варьироваться в зависимости от системы сервера.

Таблица 5. Математические функции

Функция

Описание
Пример(ы)

abs ( числовой_тип ) → числовой_тип

Абсолютное значение (модуль)
abs(-17.4) → 17.4

cbrt ( double precision ) → double precision

Кубический корень
cbrt(64.0) → 4

ceil ( numeric ) → numeric
ceil ( double precision ) → double precision

Ближайшее целое число, большее или равное аргументу
ceil(42.2) → 43
ceil(-42.8) → -42

ceiling ( numeric ) → numeric
ceiling ( double precision ) → double precision

Ближайшее целое число, большее или равное аргументу (то же, что и ceil)
ceiling(95.3) → 96

degrees ( double precision ) → double precision

Преобразует радианы в градусы
degrees(0.5) → 28.64788975654116

div ( y numeric, x numeric ) → numeric

Целочисленное частное y/x (округленное в сторону нуля)
div(9, 4) → 2

exp ( numeric ) → numeric
exp ( double precision ) → double precision

Экспоненциал (e возводится в заданную степень)
exp(1.0) → 2.7182818284590452

factorial ( bigint ) → numeric

Факториал
factorial(5) → 120

floor ( numeric ) → numeric
floor ( double precision ) → double precision

Ближайшее целое число, меньшее или равное аргументу
floor(42.8) → 42
floor(-42.8) → -43

gcd ( числовой_тип, числовой_тип ) → числовой_тип

Наибольший общий делитель (наибольшее положительное число, на которое оба аргумента делятся без остатка); возвращает 0, если оба аргумента равны нулю; имеется для типов integer, bigint и numeric
gcd(1071, 462) → 21

lcm ( числовой_тип, числовой_тип ) → числовой_тип

Наименьшее общее кратное (наименьшее строго положительное число, которое делится на оба аргумента без остатка; возвращает 0, если оба аргумента равны нулю; имеется для типов integer, bigint и numeric
lcm(1071, 462) → 23562

ln ( numeric ) → numeric
ln ( double precision ) → double precision

Натуральный логарифм
ln(2.0) → 0.6931471805599453

log ( numeric ) → numeric
log ( double precision ) → double precision

Десятичный логарифм
log(100) → 2

log10 ( numeric ) → numeric
log10 ( double precision ) → double precision

Десятичный логарифм (то же, что и log)
log10(1000) → 3

log ( b numeric, x numeric ) → numeric

Логарифм x по основанию b
log(2.0, 64.0) → 6.0000000000

min_scale ( numeric ) → integer

Минимальный масштаб (количество цифр в дробной части), необходимый для точного представления заданного значения
min_scale(8.4100) → 2

mod ( y числовой_тип, x
числовой_тип ) → числовой_тип

Остаток от y/x; имеется для типов integer, bigint и numeric
mod(9, 4) → 1

pi ( ) → double precision

Примерное значение π
pi() → 3.141592653589793

power ( a numeric, b numeric ) → numeric
power ( a double precision, b double precision ) → double precision

a возводится в степень b
power(9, 3) → 729

radians ( double precision ) → double precision

Преобразует градусы в радианы
radians(45.0) → 0.7853981633974483

round ( numeric ) → numeric
round ( double precision ) → double precision

Округляет до ближайшего целого числа. Для numeric половина (.5) округляется до одного по модулю. Для double precision способ округление половины зависит от платформы, но, как правило, выполняется «округление до ближайшего четного».
round(42.4) → 42

round ( v numeric, s integer ) → numeric

Округляет v до s десятичных знаков. Половина (.5) округляется до одного по модулю.
round(42.4382, 2) → 42.44

scale ( numeric ) → integer

Масштаб аргумента (количество десятичных цифр в дробной части)
scale(8.4100) → 4

sign ( numeric ) → numeric
sign ( double precision ) → double precision

Знак аргумента (-1, 0 или +1)
sign(-8.4) → -1

sqrt ( numeric ) → numeric
sqrt ( double precision ) → double precision

Квадратный корень
sqrt(2) → 1.4142135623730951

trim_scale ( numeric ) → numeric

Сокращает масштаб значения (количество десятичных цифр в дробной части), убирая конечные нули
trim_scale(8.4100) → 8.41

trunc ( numeric ) → numeric
trunc ( double precision ) → double precision

Округляет до целого (в сторону нуля)
trunc(42.8) → 42
trunc(-42.8) → -42

trunc ( v numeric, s integer ) → numeric

Округляет v до s десятичных знаков
trunc(42.4382, 2) → 42.43

width_bucket ( операнд numeric, нижняя_граница numeric, верхняя_граница numeric, число integer ) → integer
width_bucket ( операнд double precision, нижняя_граница double precision, верхняя_граница double precision, число integer ) → integer

Возвращает номер блока, в который попадает операнд в гистограмме с числом блоков равного размера, расположенных в диапазоне от нижней_границы до верхней_границы. Возвращает 0 или число+1, если операнд лежит вне этого диапазона.
width_bucket(5.35, 0.024, 10.06, 5) → 3

width_bucket ( операнд anycompatible, пределы anycompatiblearray ) → integer

Возвращает номер блока, в который попадает операнд (при этом нижние границы блоков перечислены в заданном массиве пределов). Возвращает 0, если операнд меньше первой нижней границы. операнд и элементы массива могут быть любого типа, для которого определены стандартные операторы сравнения. Массив пределов должен быть отсортирован по возрастанию, иначе будут получены неожиданные результаты.
width_bucket(now(), array['yesterday', 'today', 'tomorrow']::timestamptz[]) → 2

В Таблице 6 перечислены функции для генерации случайных чисел.

Таблица 6. Случайные функции

Функция

Описание
Пример(ы)

random ( ) → double precision

Возвращает случайное значение в диапазоне 0.0 <= x < 1.0
random() → 0.897124072839091

setseed ( double precision ) → void

Задает начальное значение для последующих вызовов random(); аргументом должно быть число от -1.0 до 1.0, включая границы
setseed(0.12345)

Функция random() использует простой линейный конгруэнтный алгоритм. Она работает быстро, но не подходит для криптографических приложений; более безопасная альтернатива имеется в модуле pgcrypto. Если вызывается setseed(), то результаты последующих вызовов random() в текущем сеансе можно повторить путем повторных вызовов setseed() с тем же аргументом.

В Таблице 7 перечислены имеющиеся тригонометрические функции. Все эти функции представлены в двух вариантах: один измеряет углы в радианах, а другой — в градусах.

Таблица 7. Тригонометрические функции

Функция

Описание
Пример(ы)

acos ( double precision ) → double precision

Арккосинус в радианах
acos(1) → 0

acosd ( double precision ) → double precision

Арккосинус в градусах
acosd(0.5) → 60

asin ( double precision ) → double precision

Арксинус в радианах
asin(1) → 1.5707963267948966

asind ( double precision ) → double precision

Арксинус в градусах
asind(0.5) → 30

atan ( double precision ) → double precision

Арктангенс в радианах
atan(1) → 0.7853981633974483

atand ( double precision ) → double precision

Арктангенс в градусах
atand(1) → 45

atan2 ( y double precision, x
double precision ) → double precision

Арктангенс отношения y/x в радианах
atan2(1, 0) → 1.5707963267948966

atan2d ( y double precision, x
double precision ) → double precision

Арктангенс отношения y/x в градусах
atan2d(1, 0) → 90

cos ( double precision ) → double precision

Косинус в радианах
cos(0) → 1

cosd ( double precision ) → double precision

Косинус в градусах
cosd(60) → 0.5

cot ( double precision ) → double precision

Котангенс в радианах
cot(0.5) → 1.830487721712452

cotd ( double precision ) → double precision

Котангенс в градусах
cotd(45) → 1

sin ( double precision ) → double precision

Синус в радианах
sin(1) → 0.8414709848078965

sind ( double precision ) → double precision

Синус в градусах
sind(30) → 0.5

tan ( double precision ) → double precision

Тангенс в радианах
tan(1) → 1.5574077246549023

tand ( double precision ) → double precision

Тангенс в градусах
tand(45) → 1

Примечание
Другой способ работы с углами, измеряемыми в градусах, состоит в использовании функций преобразования единиц измерения radians() и degrees(), описанных ранее. Однако предпочтительнее использовать тригонометрические функции с градусами, поскольку этот способ позволяет избежать ошибки округления в особых случаях, например, при вычислении sind(30).

В Таблице 8 перечислены имеющиеся гиперболические функции.

Таблица 8. Гиперболические функции

Функция

Описание
Пример(ы)

sinh ( double precision ) → double precision

Гиперболический синус
sinh(1) → 1.1752011936438014

cosh ( double precision ) → double precision

Гиперболический косинус
cosh(0) → 1

tanh ( double precision ) → double precision

Гиперболический тангенс
tanh(1) → 0.7615941559557649

asinh ( double precision ) → double precision

Ареасинус
asinh(1) → 0.881373587019543

acosh ( double precision ) → double precision

Ареакосинус
acosh(1) → 0

atanh ( double precision ) → double precision

Ареатангенс
atanh(0.5) → 0.5493061443340548

Строковые функции и операторы

В этом разделе описываются функции и операторы для проверки и манипулирования строковыми значениями. Строки в этом контексте включают значения типов character, character varying и text. Если не указано иное, эти функции и операторы объявлены принимающими и возвращающими тип text. Также они будут взаимозаменяемо принимать аргументы типа character varying. А значения типа character до вызова функции или оператора будут преобразовываться в text, в результате чего все завершающие пробелы в значении character будут обрезаться.

В SQL определены некоторые строковые функции, в которых для разделения аргументов вместо запятых используются ключевые слова. Подробнее эти слова описаны в Таблице 9. QHB также предоставляет версии этих функций, использующие обычный синтаксис вызова функций (см. Таблицу 10).

Примечание
Оператор конкатенации строк (||) примет нестроковый аргумент, если хотя бы один аргумент имеет строковый тип, как показано в Таблице 9. В других случаях, чтобы оператор принял нестроковый аргумент, можно использовать явное преобразование в text.

Таблица 9. Строковые функции и операторы SQL

Функция/оператор

Описание
Пример(ы)

text || text → text

Конкатенирует две строки.
'Quan' || 'tum' → Quantum

text || anynonarray → text
anynonarray || text → text

Преобразует нестроковый аргумент в текст, а затем конкатенирует две строки. (Нестроковый аргумент не может быть типом-массивом, поскольку это может вызвать неоднозначность с операторами массивов ||. Если требуется конкатенировать эквивалент текста в массиве, следует явно привести его к типу text.)
'Value: ' || 42 → Value: 42

text IS [NOT] [форма] NORMALIZED → boolean

Проверяет, соответствует ли строка определенной форме нормализации Unicode. Форма указывается в необязательном ключевом слове форма: NFC (по умолчанию), NFD, NFKC или NFKD. Это выражение можно использовать, только если кодировка сервера — UTF8. Обратите внимание, что проверка нормализации с помощью этого выражения зачастую бывает быстрее, чем нормализация уже, возможно, нормализованных строк.
U&'\0061\0308bc' IS NFD NORMALIZED → t

bit_length ( text ) → integer

Возвращает количество битов в строке (это число в 8 раз больше octet_length).
bit_length('jose') → 32

char_length ( text ) → integer
character_length ( text ) → integer

Возвращает количество символов в строке.
char_length('josé') → 4

lower ( text ) → text

Переводит символы строки в нижний регистр в соответствии с правилами локали базы данных.
lower('TOM') → tom

normalize ( text [, форма ] ) → text

Преобразует строку в заданную форму нормализации Unicode. Форма указывается в необязательном ключевом слове форма: NFC (по умолчанию), NFD, NFKC или NFKD. Эту функцию можно использовать, только если кодировка сервера — UTF8.
normalize(U&'\0061\0308bc', NFC) → U&'\00E4bc'

octet_length ( text ) → integer

Возвращает количество байтов в строке.
octet_length('josé') → 5 (если кодировка сервера — UTF8)

octet_length ( character ) → integer

Возвращает количество байтов в строке. Поскольку эта версия функции принимает непосредственно тип character, она не обрезает пробелы, завершающие строку.
octet_length('abc '::character(4)) → 4

overlay ( строка text PLACING новая_подстрока text FROM начало integer [FOR число integer] ) → text

Заменяет в строке подстроку, начинающуюся с символа на позиции начало и длиной в заданное число символов, на новую_подстроку. Если число опущено, по умолчанию количество заменяемых символов определяется длиной новой_подстроки.
overlay('Txxxxas' placing 'hom' from 2 for 4) → Thomas

position ( подстрока text IN строка text ) → integer

Возвращает начальную позицию первого вхождения подстроки в строке или ноль, если такого вхождения нет.
position('om' in 'Thomas') → 3

substring ( строка text [ FROM начало integer ] [ FOR число integer ] ) → text

Извлекает из строки подстроку, начиная с символа на позиции начало (если таковая указана) и останавливаясь после заданного числа символов (если оно указано). Должен присутствовать хотя бы один из аргументов: начало или число.
substring('Thomas' from 2 for 3) → hom
substring('Thomas' from 3) → omas
substring('Thomas' for 2) → Th

substring ( строка text FROM шаблон text ) → text

Извлекает первую подстроку, соответствующую регулярному выражению POSIX; см. подраздел Регулярные выражения POSIX.
substring('Thomas' from '...$') → mas

substring ( строка text SIMILAR шаблон text ESCAPE управляющий_символ text ) → text
substring ( строка text FROM шаблон text FOR управляющий_символ text ) → text

Извлекает первую подстроку, соответствующую регулярному выражению SQL; см. подраздел Регулярные выражения SIMILAR TO. Первая форма была определена в стандарте, начиная с SQL:2003; вторая форма существовала только в стандарте SQL:1999, и ее следует считать устаревшей.
substring('Thomas' similar '%#"o_a#"_' escape '#') → oma

trim ( [LEADING | TRAILING | BOTH] [символы text] FROM строка text ) → text

Удаляет самую длинную подстроку, содержащую только заданные символы (по умолчанию пробелы), с начала (LEADING), конца (TRAILING) или с обеих сторон (BOTH) (по умолчанию — BOTH) строки.
trim(both 'xyz' from 'yxTomxx') → Tom

trim ( [ LEADING | TRAILING | BOTH ] [ FROM ] строка text [, символы text ] ) → text

Это нестандартный синтаксис для trim().
trim(both 'xyz' from 'yxTomxx') → Tom

upper ( text ) → text

Переводит символы строки в верхний регистр в соответствии с правилами локали базы данных.
upper('tom') → TOM

Кроме этого в QHB имеются дополнительные функции для работы со строками, перечисленные в Таблице 10. Некоторые из них используются для внутренней реализации стандартных строковых функций SQL, перечисленных в Таблице 9.

Таблица 10. Другие строковые функции

Функция

Описание
Пример(ы)

ascii ( text ) → integer

Возвращает числовой код первого символа аргумента. Для кодировки UTF8 возвращает код символа в Unicode. Для других многобайтовых кодировок аргумент должен быть символом ASCII.
ascii('x') → 120

btrim ( строка text [, символы text ] ) → text

Удаляет самую длинную подстроку, содержащую только заданные символы (по умолчанию пробелы) с начала и конца строки.
btrim('xyxtrimyyx', 'xyz') → trim

chr ( integer ) → text

Возвращает символ с заданным кодом. Для кодировки UTF8 возвращает код символа в Unicode. Для других многобайтовых кодировок аргумент должен быть символом ASCII. Эта функция не может выдать chr(0), поскольку этот символ нельзя сохранить в текстовых типах данных.
chr(65) → A

concat ( значение1 "any" [, значение2 "any" [, ...] ] ) → text

Конкатенирует текстовые представления всех аргументов. Аргументы NULL игнорируются.
concat('abcde', 2, NULL, 22) → abcde222

concat_ws ( разделитель text, значение1 "any" [, значение2 "any" [, ...] ] ) → text

Конкатенирует все аргументы, кроме первого, с разделителями. Первый аргумент используется как строка-разделитель и должен быть отличен от NULL. Другие аргументы NULL игнорируются.
concat_ws(',', 'abcde', 2, NULL, 22) → abcde,2,22

format ( строка_формата text [, форматируемые_аргументы "any" [, ...] ] ) → text

Форматирует аргументы в соответствии со строкой формата; см. подраздел format. Эта функция схожа с функцией sprintf в языке C.
format('Hello %s, %1$s', 'World') → Hello World, World

initcap ( text ) → text

Переводит первую букву каждого слова в верхний регистр, а остальные — в нижний. Словами являются последовательности буквенно-цифровых символов, разделенных любыми отличными от них символами.
initcap('hi THOMAS') → Hi Thomas

left ( строка text, n integer ) → text

Возвращает первые n символов в строке или, если n — отрицательное число, возвращает все символы, кроме последних |n| символов.
left('abcde', 2) → ab

length ( text ) → integer

Возвращает количество символов в строке.
length('jose') → 4

lpad ( строка text, длина integer [, заполнитель text ] ) → text

Расширяет строку до заданной длины путем добавления слева символов-заполнителей (по умолчанию пробелов). Если строка уже длиннее заданной длины, она обрезается (справа).
lpad('hi', 5, 'xy') → xyxhi

ltrim ( строка text [, символы text ] ) → text

Удаляет самую длинную подстроку, содержащую только заданные символы (по умолчанию пробелы) с начала строки.
ltrim('zzzytest', 'xyz') → test

md5 ( text ) → text

Вычисляет MD5-хеш аргумента с результатом, написанным в шестнадцатеричном формате.
md5('abc') → 900150983cd24fb0​d6963f7d28e17f72

parse_ident ( полный_идентификатор text [, строгий_режим boolean DEFAULT true ] ) → text[]

Разделяет полный_идентификатор на массив идентификаторов, удаляя кавычки, в которые заключены отдельные идентификаторы. По умолчанию лишние символы после последнего идентификатора считаются ошибкой, но если установить второй параметр в false, то такие лишние символы игнорируются. (Это поведение полезно для синтаксического анализа имен таких объектов, как функции.) Обратите внимание, что эта функция не усекает чрезмерно длинные идентификаторы. Если есть необходимость в усеченных именах, можно привести результат к name[].
parse_ident('"SomeSchema".someTable') → {SomeSchema,sometable}

pg_client_encoding ( ) → name

Возвращает имя текущей кодировки клиента.
pg_client_encoding() → UTF8

quote_ident ( text ) → text

Возвращает заданную строку, надлежащим образом подготовленную для использования в качестве идентификатора в строке оператора SQL. Кавычки добавляются только при необходимости (т. е. если строка содержит символы, недопустимые в идентификаторе, или буквы в разном регистре). Кавычки, уже содержащиеся в строке, дублируются. См. также пример Заключение в кавычки значений в динамических запросах.
quote_ident('Foo bar') → "Foo bar"

quote_literal ( text ) → text

Возвращает заданную строку, надлежащим образом подготовленную для использования в качестве строкового литерала в строке оператора SQL. Апострофы и обратные слэши, уже содержащиеся в строке, дублируются. Обратите внимание, что quote_literal возвращает NULL, когда входящее значение NULL; если аргумент может оказаться NULL, то зачастую лучше вызвать quote_nullable. См также пример Заключение в кавычки значений в динамических запросах.
quote_literal(E'O\'Reilly') → 'O''Reilly'

quote_literal ( anyelement ) → text

Преобразует заданное значение в текст, а затем заключает в апострофы как литерал. Апострофы и обратные слэши, уже содержащиеся в значении, дублируются.
quote_literal(42.5) → '42.5'

quote_nullable ( text ) → text

Возвращает заданную строку, подходящим образом подготовленную для использования в качестве строкового литерала в строке оператора SQL, или, если аргумент NULL, возвращает NULL. Апострофы и обратные слэши, уже содержащиеся в строке, дублируются. См. также пример Заключение в кавычки значений в динамических запросах.
quote_nullable(NULL) → NULL

quote_nullable ( anyelement ) → text

Преобразует заданное значение в текст, а затем заключает в апострофы как литерал или, если аргумент NULL, возвращает NULL. Апострофы и обратные слэши, уже содержащиеся в значении, дублируются.
quote_nullable(42.5) → '42.5'

regexp_match ( строка text, шаблон text [, флаги text ] ) → text[]

Возвращает фиксированные подстроки, полученные из первого вхождения регулярного выражения POSIX в строке; см. подраздел Регулярные выражения POSIX.
regexp_match('foobarbequebaz', '(bar)(beque)') → {bar,beque}

regexp_matches ( строка text, шаблон text [, флаги text ] ) → setof text[]

Возвращает фиксированные подстроки, полученные из первого вхождения регулярного выражения POSIX в строке или из нескольких вхождений, если используется флаг g; см. подраздел Регулярные выражения POSIX.

regexp_matches('foobarbequebaz', 'ba.', 'g') →
    {bar}
    {baz}

regexp_replace ( строка text, шаблон text, подстановка text [, флаги text ] ) → text

Заменяет подстроки, полученные из первого вхождения регулярного выражения POSIX, или несколько вхождений подстрок, если используется флаг g; см. подраздел Регулярные выражения POSIX.
regexp_replace('Thomas', '.[mN]a.', 'M') → ThM

regexp_split_to_array ( строка text, шаблон text [, флаги text ] ) → text[]

Разделяет строку, используя в качестве разделителя регулярное выражение POSIX и выдавая массив результатов; см. подраздел Регулярные выражения POSIX.
regexp_split_to_array('hello world', '\s+') → {hello,world}

regexp_split_to_table ( строка text, шаблон text [, флаги text ] ) → setof text

Разделяет строку, используя в качестве разделителя регулярное выражение POSIX и выдавая набор результатов; см. подраздел Регулярные выражения POSIX.

regexp_split_to_table('hello world', '\s+') →
    hello
    world

repeat ( строка text, число integer ) → text

Повторяет строку заданное число раз.
repeat('Pg', 4) → PgPgPgPg

replace ( строка text, цель text, подстановка text ) → text

Заменяет все вхождения в строке подстроки цель подстрокой подстановка.
replace('abcdefabcdef', 'cd', 'XX') → abXXefabXXef

reverse ( text ) → text

Переставляет символы в строке в обратном порядке.
reverse('abcde') → edcba

right ( строка text, n integer ) → text

Возвращает последние n символов в строке или, когда n — отрицательное число, возвращает все символы, кроме первых |n| символов.
right('abcde', 2) → de

rpad ( строка text, длина integer [, заполнитель text ] ) → text

Расширяет строку до заданной длины путем добавления справа символов-заполнителей (по умолчанию пробелов). Если строка уже длиннее заданной длины, она обрезается (слева).
rpad('hi', 5, 'xy') → hixyx

rtrim ( строка text [, символы text ] ) → text

Удаляет самую длинную подстроку, содержащую только заданные символы (по умолчанию пробелы), с конца строки.
rtrim('testxxzx', 'xyz') → test

split_part ( строка text, разделитель text, n integer ) → text

Разделяет строку по разделителю и возвращает n-ое поле (считая с первого) или, когда n — отрицательное число, возвращает |n|-ое поле с конца.
split_part('abc~@~def~@~ghi', '~@~', 2) → def
split_part('abc,def,ghi,jkl', ',', -2) → ghi

strpos ( строка text, подстрока text ) → integer

Возвращает начальную позицию первого вхождения заданной подстроки в строке или ноль, если такого вхождения нет. (То же, что и функция position(подстрока in строка), но обратите внимание на обратный порядок аргументов.)
strpos('high', 'ig') → 2

substr ( строка text, начало integer [, число integer ] ) → text

Извлекает из строки подстроку, начинающуюся с символа на позиции начало и длиной в заданное число символов, если оно указано. (То же, что и функция substring(строка from начало for число).)
substr('alphabet', 3) → phabet
substr('alphabet', 3, 2) → ph

starts_with ( строка text, префикс text ) → boolean

Возвращает true, если строка начинается с префикса.
starts_with('alphabet', 'alph') → t

string_to_array ( строка text, разделитель text [, нулевая_строка text ] ) → text[]

Разделяет строку по разделителю и формирует из результирующих полей массив типа text. Если разделитель равен NULL, каждый символ в строке станет отдельным элементом массива. Если разделителем является пустая строка, то вся строка рассматривается как одно поле. Если задана нулевая_строка и она отлична от NULL, то поля, соответствующие этой строке, заменяются значениями NULL.
string_to_array('xx~yy~zz', '~', 'yy') → {xx,NULL,zz}

string_to_table ( строка text, разделитель text [, нулевая_строка text ] ) → setof text

Разделяет строку по разделителю и возвращает результирующие поля в виде набора текстовых строк. Если разделитель равен NULL, каждый символ в строке станет отдельной строкой результата. Если в разделителе передается пустая строка, то строка рассматривается как одно поле. Если задана нулевая_строка и она отлична от NULL, то поля, соответствующие этой строке, заменяются значениями NULL.

string_to_table('xx~^~yy~^~zz', '~^~', 'yy') →
    xx
    NULL
    zz

to_ascii ( строка text ) → text
to_ascii ( строка text, кодировка name ) → text
to_ascii ( строка text, кодировка integer ) → text

Преобразует строку в ASCII из другой кодировки, которая может задаваться по имени или по номеру. Если кодировка опущена, подразумевается кодировка базы данных (что на практике является единственным полезным вариантом). В основном преобразование сводится к отбрасыванию диакритических знаков. Преобразование поддерживается только для кодировок LATIN1, LATIN2, LATIN9 и WIN1250. (Другое, более гибкое, решение реализовано в модуле unaccent.)
to_ascii('Karél') → Karel

to_hex ( integer ) → text
to_hex ( bigint ) → text

Преобразует число в равнозначное ему шестнадцатеричное представление.
to_hex(2147483647) → 7fffffff

translate ( строка text, цель text, подстановка text ) → text

Заменяет каждый символ в строке, совпадающий с символом из множества цель, на соответствующий символ в множестве подстановка. Если цель длиннее подстановки, вхождения лишних символов цели удаляются.
translate('12345', '143', 'ax') → a2x5

unistr ( text ) → text

Определяет управляющие символы Unicode в аргументе. Символы Unicode можно задать в виде \XXXX (4 шестнадцатеричные цифры), \+XXXXXX (6 шестнадцатеричных цифр), \uXXXX (4 шестнадцатеричные цифры) или \UXXXXXXXX (8 шестнадцатеричных цифр). Чтобы указать обратный слэш, напишите два обратных слэша. Все остальные символы берутся буквально.
Если кодировка сервера не UTF-8, символ с кодом Unicode, заданный одной из этих управляющих последовательностей, преобразуется в фактическую кодировку сервера; если это невозможно, выдается ошибка.
Эта функция предоставляет (нестандартную) альтернативу строковым константам с управляющими последовательностями Unicode (см. подраздел Строковые константы с экранированием Unicode).
unistr('d\0061t\+000061') → data
unistr('d\u0061t\U00000061') → data

Функции concat, concat_ws и format принимают переменное число аргументов, поэтому им можно передавать значения для конкатенирования или форматирования в виде массива, помеченного ключевым словом VARIADIC (см. раздел Функции SQL с переменным числом аргументов). Элементы такого массива обрабатываются так, как если бы они были отдельными обычными аргументами функции. Если вместо массива в аргументе передается NULL, функции concat и concat_ws возвращают NULL, а format воспринимает NULL как массив с нулевым элементом.

Также обратите внимание на агрегатную функцию string_agg в разделе Агрегатные функции и функции для преобразования текста в bytea и наоборот в таблице Функции для преобразования текстовых/двоичных строк.

format

Функция format создает выходные данные, отформатированные в соответствии со строкой формата, в стиле, аналогичном функции C sprintf.

format(строка_формата text [, форматируемые_аргументы "any" [, ...] ])

Аргумент строка\ формата определяет, как результат должен быть отформатирован. Текст в строке формата копируется непосредственно в результат, кроме случаев, когда используются спецификаторы формата. Спецификаторы формата действуют как заполнители в строке, определяя, как именно последующие аргументы функции должны быть отформатированы и вставлены в результат. Каждый форматируемый_аргумент преобразуется в текст в соответствии с обычными правилами вывода для его типа данных, а затем форматируется и вставляется в результирующую строку в соответствии со спецификаторами формата.

Спецификаторы формата вводятся символом % и имеют форму

%[позиция][флаги][ширина]тип

где полями компонентов являются:

позиция (необязательный)
Строка вида n$, где n — индекс выводимого аргумента. Индекс, равный 1, означает первый аргумент после formatstr. Если позиция опущена, по умолчанию используется следующий аргумент в последовательности.

флаги (необязательный)
Дополнительные параметры, управляющие форматированием вывода спецификатора формата. В настоящее время единственным поддерживаемым флагом является знак минус (-), который приведет к выравниванию вывода спецификатора формата по левому краю. Этот флаг не имеет никакого эффекта, если не указано поле ширина.

ширина (необязательный)
Задает минимальное количество символов, которое используется для отображения выходных данных спецификатора формата. Выходные данные дополняются слева или справа (в зависимости от флага -) пробелами, необходимыми для заполнения до заданной ширины. Слишком маленькая ширина не приводит к усечению вывода, а просто игнорируется. Ширина можно указать любым из следующих способов: положительным целым числом, звездочкой (*) для использования в качестве ширины следующего аргумента функции или строкой вида *n$ для использования в качестве ширины n-го аргумента функции.

Если ширина берется из аргумента функции, этот аргумент задействуется раньше аргумента, используемого для значения спецификатора формата. Если аргумент ширины отрицательный, результат выравнивается по левому краю (как если бы был указан флаг -) в поле длиной abs(width).

тип (обязательный)
Тип преобразования формата, используемый для создания выходных данных спецификатора формата. Поддерживаются следующие типы:

s форматирует значение аргумента как простую строку. Значение NULL рассматривается как пустая строка.

I обрабатывает значение аргумента как идентификатор SQL, при необходимости заключая его в кавычки. Здесь значение NULL является ошибкой (так же, как и для quote_ident).

L заключает значение аргумента в апострофы, как литерал SQL. Значение NULL отображается в виде строки NULL, без кавычек (так же, как и у quote_nullable).

В дополнение к спецификаторам формата, описанным выше, можно использовать специальную последовательность %% для вывода буквального символа %.

Вот несколько примеров основных преобразований формата:

SELECT format('Hello% s', 'World');
Результат: Hello World

SELECT format('Testing %s, %s, %s, %%', 'one', 'two', 'three');
Результат: Testing one, two, three, %

SELECT format('INSERT INTO %I VALUES(%L)', 'Foo bar', E'O\'Reilly');
Результат: INSERT INTO "Foo bar" VALUES('O''Reilly')

SELECT format('INSERT INTO %I VALUES(%L)', 'locations', 'C:\Program Files');
Результат: INSERT INTO locations VALUES('C:\Program Files')

Вот примеры использования полей ширины и флага -:

SELECT format('|%10s|', 'foo');
Результат: |       foo|

SELECT format('|%-10s|', 'foo');
Результат: |foo       |

SELECT format('|%*s|', 10, 'foo');
Результат: |       foo|

SELECT format('|%*s|', -10, 'foo');
Результат: |foo       |

SELECT format('|%-*s|', 10, 'foo');
Результат: |foo       |

SELECT format('|%-*s|', -10, 'foo');
Результат: |foo       |

Эти примеры показывают использование полей позиции:

SELECT format('Testing %3$s, %2$s, %1$s', 'one', 'two', 'three');
Результат: Testing three, two, one

SELECT format('|%*2$s|', 'foo', 10, 'bar');
Результат: |       bar|

SELECT format('|%1$*2$s|', 'foo', 10, 'bar');
Результат: |       foo|

В отличие от стандартной функции C sprintf, функция format в QHB позволяет сочетать в одной строке формата спецификаторы формата с полями позиции и без них. Спецификатор формата без поля позиции всегда использует следующий аргумент после последнего задействованного аргумента. Кроме того, функция format не требует использования в строке формата всех аргументов функции. Например:

SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three');
Результат: Testing three, two, three

Спецификаторы формата %I и %L особенно полезны для безопасного построения динамических операторов SQL. См. пример Заключение в кавычки значений в динамических запросах.

Функции и операторы для двоичных строк

В этом разделе описываются функции и операторы для проверки и обработки двоичных строк, то есть значений типа bytea. Многие из них идентичны по назначению и синтаксису функциям для текстовых строк, описанным в предыдущем разделе.

В SQL определены некоторые строковые функции, в которых для разделения аргументов вместо запятых используются ключевые слова. Подробнее это показано в Таблице 11. QHB также предоставляет версии этих функций, которые используют обычный синтаксис вызова функций (см. Таблицу 12).

Таблица 11. Функции и операторы SQL для двоичных строк

Функция/оператор

Описание
Пример(ы)

bytea || bytea → bytea

Конкатенирует две двоичные строки.
'\x123456'::bytea || '\x789a00bcde'::bytea → \x123456789a00bcde

bit_length ( bytea ) → integer

Возвращает количество битов в двоичной строке (это число в 8 раз больше octet_length).
bit_length('\x123456'::bytea) → 24

octet_length ( bytea ) → integer

Возвращает количество байтов в двоичной строке.
octet_length('\x123456'::bytea) → 3

overlay ( двоичная_строка bytea PLACING новая_подстрока bytea FROM начало integer [ FOR число integer ] ) → bytea

Заменяет в двоичной_строке подстроку, начинающуюся с байта на позиции начало и длиной в заданное число байт, на новую_подстроку. Если число опущено, по умолчанию количество заменяемых байтов определяется длиной новой_подстроки.
overlay('\x1234567890'::bytea placing '\002\003'::bytea from 2 for 3) → \x12020390

position ( подстрока bytea IN двоичная_строка bytea ) → integer

Возвращает начальную позицию первого вхождения подстроки в двоичной_строке или ноль, если такого вхождения нет.
position('\x5678'::bytea in '\x1234567890'::bytea) → 3

substring ( двоичная_строка bytea [ FROM начало integer ] [ FOR число integer ] ) → bytea

Извлекает из двоичной_строки подстроку, начиная с байта на позиции начало (если она указана) и останавливаясь после заданного числа байт, если оно указано. Должен присутствовать хотя бы один из аргументов: начало или число.
substring('\x1234567890'::bytea from 3 for 2) → \x5678

trim ( [ LEADING | TRAILING | BOTH ] удаляемые_байты bytea FROM двоичная_строка bytea ) → bytea

Удаляет самую длинную подстроку, содержащую удаляемые_байты, с начала (LEADING), конца (TRAILING) или с обеих сторон (BOTH) (по умолчанию BOTH) двоичной_строки.
trim('\x9012'::bytea from '\x1234567890'::bytea) → \x345678

trim ( [ LEADING | TRAILING | BOTH ] [ FROM ] двоичная_строка bytea, удаляемые_байты bytea ) → bytea

Это нестандартный синтаксис функции trim().
trim(both from '\x1234567890'::bytea, '\x9012'::bytea) → \x345678

В QHB имеются также дополнительные функции для обработки двоичных строк, перечисленные в Таблице 12. Некоторые из них используются для внутренней реализации стандартных строковых функций SQL, перечисленных в Таблице 11.

Таблица 12. Другие функции для двоичных строк

Функция

Описание
Пример(ы)

bit_count ( двоичная_строка bytea ) → bigint

Возвращает количество установленных битов (единиц) в двоичной строке (эта операция также известна как «popcount»).
bit_count('\x1234567890'::bytea) → 15

btrim ( двоичная_строка bytea, удаляемые_байты bytea ) → bytea

Удаляет самую длинную строку, содержащую только заданные удаляемые_байты, с начала и конца двоичной_строки.
btrim('\x1234567890'::bytea, '\x9012'::bytea) → \x345678

get_bit ( двоичная_строка bytea, n bigint ) → integer

Извлекает n-й бит из двоичной строки.
get_bit('\x1234567890'::bytea, 30) → 1

get_byte ( двоичная_строка bytea, n integer ) → integer

Извлекает n-й байт из двоичной строки.
get_byte('\x1234567890'::bytea, 4) → 144

length ( bytea ) → integer

Возвращает количество байтов в двоичной строке.
length('\x1234567890'::bytea) → 5

length ( двоичная_строка bytea, кодировка name ) → integer

Возвращает количество символов в двоичной строке, предполагая, что она содержит текст в заданной кодировке.
length('jose'::bytea, 'UTF8') → 4

ltrim ( двоичная_строка bytea, удаляемые_байты bytea ) → bytea

Удаляет самую длинную подстроку, содержащую только заданные удаляемые_байты, с начала двоичной_строки.
ltrim('\x1234567890'::bytea, '\x9012'::bytea) → \x34567890

md5 ( bytea ) → text

Вычисляет MD5-хеш двоичной строки с результатом в шестнадцатеричном виде.
md5('Th\000omas'::bytea) → 8ab2d3c9689aaf18​b4958c334c82d8b1

rtrim ( двоичная_строка bytea, удаляемые_байты bytea ) → bytea

Удаляет самую длинную подстроку, содержащую только заданные удаляемые_байты, с конца двоичной_строки.
rtrim('\x1234567890'::bytea, '\x9012'::bytea) → \x12345678

set_bit ( двоичная_строка bytea, n bigint, новое_значение integer ) → bytea

Задает для n-го бита в двоичной строке новое_значение.
set_bit('\x1234567890'::bytea, 30, 0) → \x1234563890

set_byte ( двоичная_строка bytea, n integer, новое_значение integer ) → bytea

Задает для n-го байта в двоичной строке новое_значение.
set_byte('\x1234567890'::bytea, 4, 64) → \x1234567840

sha224 ( bytea ) → bytea

Вычисляет хеш SHA-224 для двоичной строки.
sha224('abc'::bytea) → \x23097d223405d8228642a477bda2​55b32aadbce4bda0b3f7e36c9da7

sha256 ( bytea ) → bytea

Вычисляет хеш SHA-256 для двоичной строки.
sha256('abc'::bytea) → \xba7816bf8f01cfea414140de5dae2223​b00361a396177a9cb410ff61f20015ad

sha384 ( bytea ) → bytea

Вычисляет хеш SHA-384 для двоичной строки.
sha384('abc'::bytea) → \xcb00753f45a35e8bb5a03d699ac65007​272c32ab0eded1631a8b605a43ff5bed​8086072ba1e7cc2358baeca134c825a7

sha512 ( bytea ) → bytea

Вычисляет хеш SHA-512 для двоичной строки.
sha512('abc'::bytea) → \xddaf35a193617abacc417349ae204131​12e6fa4e89a97ea20a9eeee64b55d39a​2192992a274fc1a836ba3c23a3feebbd​454d4423643ce80e2a9ac94fa54ca49f

substr ( двоичная_строка bytea, начало integer [, число integer ] ) → bytea

Извлекает из двоичной_строки подстроку, начинающуюся с байта на позиции начало и длиной в заданное число байт, если оно указано. (То же, что и функция substring(двоичная_строка from начало for число).)
substr('\x1234567890'::bytea, 3, 2) → \x5678

Функции get_byte и set_byte нумеруют первый байт двоичной строки как байт 0. Функции get_bit и set_bit нумеруют биты справа налево внутри каждого байта; например, бит 0 является самым младшим значащим битом первого байта, а бит 15 — самым старшим значащим битом второго байта.

По историческим причинам функция md5 возвращает шестнадцатеричное значение типа text тогда как функции SHA-2 возвращают тип bytea. Для преобразования этих значений одно в другое используйте функции encode и decode. Например, можно написать encode(sha256(’abc’), ’hex’) для получения шестнадцатеричного текстового представления или decode(md5('abc'), 'hex') для получения значения bytea.

Функции для преобразования строк из одного набора символов (кодировки) в другой, а также для представления произвольных двоичных данных в текстовой форме перечислены в Таблице 13. Для этих функций аргумент или результат типа text выражается в кодировке базы данных по умолчанию, тогда как аргументы или результаты типа bytea — в кодировке, заданной другим аргументом.

Таблица 13. Функции для преобразования текстовых/двоичных строк

Функция

Описание
Пример(ы)

convert ( двоичная_строка bytea, исходная_кодировка name, новая_кодировка name ) → bytea

Преобразует двоичную строку, представляющую текст в исходной_кодировке, в двоичную строку в новой_кодировке (допустимые преобразования см. в подразделе Доступные перекодировки).
convert('text_in_utf8', 'UTF8', 'LATIN1') → \x746578745f696e5f75746638

convert_from ( двоичная_строка bytea, исходная_кодировка name ) → text

Преобразует двоичную строку, представляющую текст в исходной_кодировке, в текст в кодировке базы данных (допустимые преобразования см. в подразделе Доступные перекодировки).
convert_from('text_in_utf8', 'UTF8') → text_in_utf8

convert_to ( строка text, новая_кодировка name ) → bytea

Преобразует строку типа text (в кодировке базы данных) в двоичную строку в новой_кодировке (допустимые преобразования см. в подразделе Доступные перекодировки).
convert_to('some_text', 'UTF8') → \x736f6d655f74657874

encode ( двоичная_строка bytea, формат text ) → text

Кодирует двоичные данные в текстовое представление; поддерживаемые значения формата: base64, escape, hex.
encode('123\000\001', 'base64') → MTIzAAE=

decode ( строка text, формат text ) → bytea

Декодирует двоичные данные из текстового представления; поддерживаются те же значения формата, что и для функции encode.
decode('MTIzAAE=', 'base64') → \x3132330001

Функции encode и decode поддерживают следующие текстовые форматы:

base64
Формат base64 описан в RFC 2045, раздел 6.8. Согласно этому RFC, закодированные строки разбиваются по 76 символов. Однако вместо маркеров CRLF (как положено по стандарту MIME) строки завершаются только символом перевода строки. Функция decode игнорирует символы возврата каретки, перевода строки, пробелы и табуляции. В иных случаях если на вход decode поступают некорректные данные base64, возникает ошибка — в том числе, когда некорректно завершающее заполнение.

escape
Формат escape преобразует нулевые байты и байты с установленным старшим битом в восьмеричные экранированные последовательности (\nnn), а обратные слэши дублируются. Другие байтовые значения представляются в буквальном виде. Если за обратным слэшем не следует еще один обратный слэш или три восьмеричных цифры, функция decode выдаст ошибку; другие байтовые значения она принимает без изменений

hex
Формат hex представляет каждые 4 бита данных в виде одной шестнадцатеричной цифры, от 0 до f, первой записывая старший бит каждого байта. Функция encode выводит шестнадцатеричные цифры a-f в нижнем регистре. Поскольку наименьшая единица данных — 8 бит, функция encode всегда возвращает четное количество символов. Функция decode принимает символы a-f либо в верхнем, либо в нижнем регистре. Если на вход decode поступают некорректные шестнадцатеричные данные, возникает ошибка — в том числе, когда вводится нечетное количество символов.

См. также агрегатную функцию string_agg в разделе Агрегатные функции и функции для больших объектов в разделе Серверные функции.

Функции и операторы для битовых строк

В этом разделе описываются функции и операторы для проверки и обработки битовых строк, то есть значений типов bit и bit varying. (Хотя в этих таблицах упоминается только тип bit, вместо них можно использовать значения типа bit varying) Битовые строки поддерживают обычные операторы сравнения, показанные в таблице Операторы сравнения, а также операторы, перечисленные в Таблице 14.

Таблица 14. Операторы для битовых строк

Оператор

Описание
Пример(ы)

bit || bit → bit

Конкатенация
B'10001' || B'011' → 10001011

bit & bit → bit

Побитовое И (операнды должны быть одинаковой длины)
B'10001' & B'01101' → 00001

bit | bit → bit

Побитовое ИЛИ (операнды должны быть одинаковой длины)
B'10001' | B'01101' → 11101

bit # bit → bit

Побитовое исключающее ИЛИ (операнды должны быть одинаковой длины)
B'10001' # B'01101' → 11100

~ bit → bit

Побитовое НЕ
~ B'10001' → 01110

bit << integer → bit

Битовый сдвиг влево (с сохранением длины строки)
B'10001' << 3 → 01000

bit >> integer → bit

Битовый сдвиг вправо (с сохранением длины строки)
B'10001' >> 2 → 00100

Некоторые из функций, имеющихся для двоичных строк, работают также и с битовыми строками, как показано в Таблице 15.

Таблица 15. Функции для битовых строк

Функция

Описание
Пример(ы)

bit_count ( bit ) → bigint

Возвращает количество установленных битов (единиц) в битовой строке (эта операция также известна как «popcount»).
bit_count(B'10111') → 4

bit_length ( bit ) → integer

Возвращает количество битов в битовой строке.
bit_length(B'10111') → 5

length ( bit ) → integer

Возвращает количество битов в битовой строке.
length(B'10111') → 5

octet_length ( bit ) → integer

Возвращает количество байтов в битовой строке.
octet_length(B'1011111011') → 2

overlay ( битовая_строка bit PLACING новая_подстрока bit FROM начало integer [ FOR число integer ] ) → bit

Заменяет в битовой_строке подстроку, начинающуюся с бита на позиции начало и длиной в заданное число бит, на новую_подстроку. Если число опущено, по умолчанию количество заменяемых битов определяется длиной новой_подстроки.
overlay(B'01010101010101010' placing B'11111' from 2 for 3) → 0111110101010101010

position ( подстрока bit IN битовая_строка bit ) → integer

Возвращает начальную позицию первого вхождения подстроки в битовой_строке или ноль, если такого вхождения нет.
position(B'010' in B'000001101011') → 8

substring ( битовая_строка bit [ FROM начало integer ] [ FOR число integer ] ) → bit

Извлекает из битовой_строки подстроку, начиная с бита на позиции начало (если таковая указана), и останавливаясь после заданного числа бит, если оно указано. Должен присутствовать хотя бы один из параметров: начало или число.
substring(B'110010111111' from 3 for 2) → 00

get_bit ( битовая_строка bit, n integer ) → integer

Извлекает из битовой строки n-й бит; первый (самый левый) бит имеет номер 0.
get_bit(B'101010101010101010', 6) → 1

set_bit ( битовая_строка bit, n integer, новое_значение integer ) → bit

Устанавливает для n-го бита в битовой строке новое_значение; первый (самый левый) бит имеет номер 0.
set_bit(B'101010101010101010', 6, 0) → 101010001010101010

Кроме того, в тип bit и обратно можно преобразовывать целочисленные значения. Приведение целого числа к типу bit(n) копирует в него n самых правых бит. Когда целое число приводится к битовой строке с большей длиной, чем размер самого целого, результат дополняется слева знаком. Некоторые примеры:

44::bit(10)                    0000101100
44::bit(3)                     100
cast(-44 as bit(12))           111111010100
'1110'::bit(4)::integer        14

Обратите внимание, что приведение к типу «bit» без длины означает приведение к bit(1) и выдаст только один младший значащий бит целого.

Сопоставление с шаблоном

QHB предлагает три разных подхода к сопоставлению с шаблоном: традиционный оператор SQL LIKE, более современный оператор SIMILAR TO (добавленный в SQL:1999) и регулярные выражения в стиле POSIX. Помимо основного «эта строка соответствует этому шаблону?», имеются также операторы и функции для извлечения или замены соответствующих подстрок и для разделения строки по совпадающим местам.

Совет
Если для сопоставления с шаблоном вам недостаточно вышеперечисленных средств, рассмотрите возможность написания пользовательской функции на Perl или Tcl.

ВНИМАНИЕ!
Хотя по большей части поиск по регулярным выражениям может выполняться очень быстро, есть вероятность создания регулярных выражений, обработка которых занимает некоторое количество времени и памяти. Остерегайтесь шаблонов поиска по регулярным выражениям, поступающих из неблагоприятных источников. Если вам приходится это делать, рекомендуется установить тайм-аут для операторов.

Поиск с использованием шаблонов SIMILAR TO сопряжен с теми же угрозами безопасности, поскольку SIMILAR TO во многом предоставляет те же возможности, что и регулярные выражения в стиле POSIX.

Поскольку поиск с LIKE намного проще двух других вариантов, его безопаснее использовать с предположительно неблагоприятными источниками шаблонов.

Операторы сопоставления с шаблоном всех трех видов не поддерживают недетерминированные сопоставления. При необходимости обойти это ограничение примените к выражению другое сопоставление.

LIKE

строка LIKE шаблон [ESCAPE управляющий_символ]
строка NOT LIKE шаблон [ESCAPE управляющий_символ]

Выражение LIKE возвращает true, если строка соответствует предоставленному шаблону. (Как и следовало ожидать, выражение NOT LIKE возвращает false, если LIKE возвращает true, и наоборот. Этому выражению равнозначно выражение NOT (строка LIKE шаблон).)

Если шаблон не содержит знаков процента или подчеркивания, то шаблон представляет только саму строку; в этом случае LIKE действует как оператор равенства. Подчеркивание (_) в шаблоне обозначает (подменяет) любой отдельный символ; знак процента (%) соответствует любой последовательности из нуля и более символов.

Несколько примеров:

'abc' LIKE 'abc'    true
'abc' LIKE 'a%'     true
'abc' LIKE '_b_'    true
'abc' LIKE 'c'      false

При сопоставлении с шаблоном с LIKE всегда охватывается вся строка. Поэтому если требуется сопоставить последовательность где-то внутри строки, шаблон должен начинаться и заканчиваться знаком процента.

Чтобы сопоставить буквальное подчеркивание или знак процента, не сопоставляя другие символы, соответствующему символу в шаблоне должен предшествовать управляющий символ. Управляющим символом по умолчанию является обратный слеш, но с помощью предложения ESCAPE можно выбрать любой другой символ. Чтобы сопоставить сам управляющий символ, напишите его дважды.

Примечание
Если у вас выключен параметр standard_conforming_strings, любые обратные слэши, которые вы пишете в константах литеральных строк, нужно будет дублировать. Дополнительную информацию см. в подразделе Строковые константы.

Также можно отказаться от управляющих символов, написав ESCAPE ''. По сути это выключает механизм экранирования, что делает невозможным выключение специального значения знаков подчеркивания и процента в шаблоне.

Согласно стандарту SQL, отсутствие указания ESCAPE означает, что управляющий символ не определен (а не становится по умолчанию обратным слэшем), а пустое значение в ESCAPE не допускается. Таким образом, в этом отношении поведение QHB значительно отличается от стандартного.

Вместо LIKE можно использовать ключевое слово ILIKE, чтобы при сопоставлении не учитывался регистр в соответствии с текущей локалью. Это ключевое слово отсутствует в стандарте SQL и является расширением QHB.

Оператор ~~ равнозначен LIKE, а ~~* соответствует ILIKE. Также имеются операторы !~~ и !~~*, которые представляют NOT LIKE и NOT ILIKE соответственно. Все эти операторы специфичны для QHB. Имена этих операторов можно увидеть в выводе команды EXPLAIN и в иных подобных местах, поскольку синтаксический анализатор фактически заменяет LIKE и прочие на эти операторы.

Фразы LIKE, ILIKE, NOT LIKE и NOT ILIKE в синтаксисе QHB обычно обрабатываются как операторы; например, их можно использовать в конструкциях выражение оператор ANY (подзапрос), хотя предложение ESCAPE сюда включить нельзя. В некоторых сложных случаях может понадобиться использовать вместо них имена нижележащих операторов.

Также есть оператор префикса ^@ и соответствующая функция starts_with, которые полезны в случаях, когда необходимо сопоставление только с началом строки.

Регулярные выражения SIMILAR TO

строка SIMILAR TO шаблон [ESCAPE управляющий_символ]
строка NOT SIMILAR TO шаблон [ESCAPE управляющий_символ]

Оператор SIMILAR TO возвращает true или false в зависимости от того, соответствует ли его шаблон заданной строке. Он похож на LIKE, за исключением того, что он интерпретирует шаблон, используя определение регулярного выражения в стандарте SQL. Регулярные выражения SQL представляют собой любопытную смесь между нотацией LIKE и нотацией обычного (POSIX) регулярного выражения.

Как и LIKE, оператор SIMILAR TO успешно выполняется, только если его шаблон соответствует всей строке; это не похоже на обычное поведение регулярных выражений, когда шаблон может соответствовать любой части строки. Также подобно LIKE, SIMILAR TO использует символы _ и % в качестве подстановочных символов, обозначающих любой отдельный символ и любую строку соответственно (они сопоставимы с . и .* в регулярных выражениях POSIX).

В дополнение к этим возможностям, заимствованным из LIKE, SIMILAR TO поддерживает метасимволы сопоставления с шаблоном, заимствованные из регулярных выражений POSIX:

| обозначает чередование (любой из двух вариантов).

* обозначает повторение предыдущего пункта ноль и более раз.

+ обозначает повторение предыдущего пункта один и более раз.

? обозначает повторение предыдущего пункта ноль или один раз.

{m} обозначает повторение предыдущего элемента ровно m раз.

{m,} обозначает повторение предыдущего элемента m и более раз.

{m,n} обозначает повторение предыдущего элемента не менее m и не более n раз.

Круглые скобки () могут использоваться для группировки элементов в один логический элемент.

Выражение в квадратных скобках [...] определяет класс символов, как и в регулярных выражениях POSIX.

Обратите внимание, что точка (.) не является метасимволом для SIMILAR TO.

Как и в случае с LIKE, обратный слэш отменяет специальное значение любого из этих метасимволов. Можно указать другой управляющий символ с помощью ESCAPE или отказаться от экранирования, написав ESCAPE ''.

Согласно стандарту SQL, отсутствие указания ESCAPE означает, что управляющий символ не определен (а не становится по умолчанию обратным слэшем), а пустое значение в ESCAPE не допускается. Таким образом, в этом отношении поведение QHB значительно отличается от стандартного.

Еще одно нестандартное расширение заключается в том, что следующая за управляющим символом буква или цифра открывает доступ к управляющим последовательностям, определенным для регулярных выражений POSIX; см. Таблицу 20, Таблицу 21 и Таблицу 22 ниже.

Несколько примеров:

'abc' SIMILAR TO 'abc'      true
'abc' SIMILAR TO 'a'        false
'abc' SIMILAR TO '%(b|d)%'  true
'abc' SIMILAR TO '(b|c)%'   false

Функция substring с тремя параметрами извлекает подстроку, соответствующую шаблону регулярного выражения SQL. Эту функцию можно написать в соответствии со стандартным синтаксисом SQL:

substring(строка similar шаблон escape управляющий_символ)

или используя синтаксис ныне устаревшего SQL:1999:

substring(строка from шаблон for управляющий_символ)

или как простую функцию с тремя аргументами:

substring(строка, шаблон, управляющий_символ)

Как и в случае с SIMILAR TO, указанный шаблон должен соответствовать всей строке данных, иначе функция не сработает и вернет значение NULL. Чтобы указать часть шаблона, которому соответствует интересующая подстрока данных, шаблон должен содержать два вхождения управляющего символа и следующей за ним кавычки ("). При успешном сопоставлении текст, соответствующий части шаблона, возвращается, заключенный в эти разделители.

Разделители, состоящие из управляющего символа и кавычек, фактически делят шаблон substring на три независимых регулярных выражения; например, вертикальная черта (|) в любом из трех разделов влияет только на этот раздел. Кроме того, когда есть некоторая неоднозначность относительно того, какая часть строки данных соответствует какому шаблону, первое и третье из этих регулярных выражений определяются так, чтобы соответствовать наименьшему возможному объему текста, а не наибольшему. (Говоря на жаргоне POSIX, первое и третье регулярные выражения должны быть «нежадными»).

В качестве расширения стандарта SQL QHB позволяет использовать только один разделитель из управляющего символа и кавычек, и в этом случае третье регулярное выражение считается пустым; или вовсе не использовать разделители, и в этом случае считаются пустыми первое и третье регулярные выражения.

Несколько примеров с разделителями #", ограничивающими возвращаемую строку:

substring('foobar' from '%#"o_b#"%' for '#')   oob
substring('foobar' from '#"o_b#"%' for '#')    NULL

Регулярные выражения POSIX

В Таблице 16 перечислены имеющиеся операторы для сопоставления с шаблоном с использованием регулярных выражений POSIX.

Таблица 16. Операторы сопоставления регулярных выражений

Оператор

Описание
Пример(ы)

text ~ text → boolean

Строка соответствует регулярному выражению, с учетом регистра
'thomas' ~ 't.*ma' → t

text ~* text → boolean

Строка соответствует регулярному выражению, без учета регистра
'thomas' ~* 'T.*ma' → t

text !~ text → boolean

Строка не соответствует регулярному выражению, с учетом регистра
'thomas' !~ 't.*max' → t

text !~* text → boolean

Строка не соответствует регулярному выражению, без учета регистра
'thomas' !~* 'T.*ma' → f

Регулярные выражения POSIX предоставляют более мощные средства для сопоставления с шаблоном, чем операторы LIKE и SIMILAR TO. Многие инструменты Unix, такие как egrep, sed или awk, используют язык сопоставления с шаблоном, похожий на описанный здесь.

Регулярное выражение — это последовательность символов, представляющая собой сокращенное определение некоего множества строк (регулярного множества). Считается, что строка соответствует регулярному выражению, если она является членом регулярного множества, описанного регулярным выражением. Как и для LIKE, символы шаблона точно соответствуют строковым символам, если те не являются специальными символами в языке регулярных выражений — но специальные символы в регулярных выражениях отличаются от тех, что используются в LIKE. В отличие от шаблонов LIKE, регулярному выражению может совпадать с любым местом строки, если только оно не привязано явно к началу или концу строки.

Несколько примеров:

'abcd' ~ 'bc'     true
'abcd' ~ 'a.c'    true — точка соответствует любому символу
'abcd' ~ 'a.*d'   true — * повторяет предыдущий элемент шаблона
'abcd' ~ '(b|x)'  true — | означает ИЛИ для группы в скобках
'abcd' ~ '^a'     true — ^ привязывает шаблон к началу строки
'abcd' ~ '^(b|c)' false — совпадение не найдено по причине привязки

Более подробно язык шаблонов POSIX описан ниже.

Функция substring с двумя параметрами substring(строка from шаблон) извлекает подстроку, соответствующую шаблону регулярного выражения POSIX. Возвращает NULL, если совпадения нет, либо первый базовых элемент текста, соответствующий шаблону, в противном случае. Но если шаблон содержит круглые скобки, возвращается часть текста, соответствующая первому подвыражению в скобках (та, чья левая скобка стоит первой). Если вы хотите использовать в выражении круглые скобки, не запуская это исключение, можно заключить в них все выражение. Если вам нужны круглые скобки в шаблоне перед подвыражением, которое вы хотите извлечь, смотрите ниже описание использования скобок без захвата.

Несколько примеров:

substring('foobar' from 'o.b')     oob
substring('foobar' from 'o(.)b')   o

Функция regexp_replace подставляет новый текст вместо подстрок, соответствующих шаблонам регулярных выражений POSIX. Она имеет синтаксис regexp_replace(исходная_строка, шаблон, замена [, флаги ]). Если нет совпадения с шаблоном, исходная_строка возвращается без изменений. Если совпадение есть, исходная_строка возвращается с заменой вместо соответствующей подстроки. Строка замена может содержать \n, где n — число от 1 до 9, указывающее, что должна быть вставлена исходная подстрока, соответствующая n-му подвыражению шаблона в скобках, а также может содержать \&, указывающее, что должна быть вставлена подстрока, соответствующая всему шаблону. Если нужно вставить обратный слеш в текст замены буквально, напишите \\. Параметр флаги представляет собой необязательную текстовую строку, содержащую ноль или более однобуквенных флагов, которые изменяют поведение функции. Флаг i указывает, что сопоставление должно проводиться без учета регистра, а флаг g указывает, что заменяться должны все соответствующие подстроки, а не только первая из них. Поддерживаемые флаги (кроме g) описаны в Таблице 24.

Несколько примеров:

regexp_replace('foobarbaz', 'b..', 'X')
                                   fooXbaz
regexp_replace('foobarbaz', 'b..', 'X', 'g')
                                   fooXX
regexp_replace('foobarbaz', 'b(..)', 'X\1Y', 'g')
                                   fooXarYXazY

Функция regexp_match возвращает текстовый массив захваченных подстрок, полученных в результате первого сопоставления шаблона регулярного выражения POSIX со строкой. Она имеет синтаксис regexp_match(строка, шаблон [, флаги ]). Если совпадений нет, результатом будет NULL. Если совпадение найдено и шаблон не содержит подвыражений в скобках, то результатом будет текстовый массив из одного элемента, содержащий подстроку, соответствующую всему шаблону. Если совпадение найдено и шаблон содержит подвыражения в скобках, то результатом будет текстовый массив, чей n-й элемент является подстрокой, соответствующей n-му подвыражению в скобках шаблона (не считая «не захватывающих» скобок; подробности см. ниже). Параметр флаги представляет собой необязательную текстовую строку, содержащую ноль или более однобуквенных флагов, изменяющих поведение функции. Поддерживаемые флаги описаны в Таблице 24.

Несколько примеров:

SELECT regexp_match('foobarbequebaz', 'bar.*que');
 regexp_match
--------------
 {barbeque}
(1 row)

SELECT regexp_match('foobarbequebaz', '(bar)(beque)');
 regexp_match
--------------
 {bar,beque}
(1 row)

В общем случае, когда вы просто хотите получить всю соответствующую подстроку или NULL, если совпадений нет, напишите что-то вроде

SELECT (regexp_match('foobarbequebaz', 'bar.*que'))[1];
 regexp_match
--------------
 barbeque
(1 row)

Функция regexp_matches возвращает набор текстовых массивов захваченных подстрок, полученных в результате сопоставления шаблона регулярного выражения POSIX со строкой. Она имеет тот же синтаксис, что и regexp_match. Эта функция не возвращает ни одной строки, если совпадения нет, одну строка, если есть совпадение и не задан флаг g, или N строк, если есть N совпадений и задан флаг g. Каждая возвращаемая строка является текстовым массивом, содержащим всю сопоставляемую подстроку или подстроки, соответствующие подвыражениям в скобках шаблона, как описано выше для regexp_match. Функция regexp_matches принимает все флаги, перечисленные в Таблице 24, плюс флаг g, который указывает ей возвращать все совпавшие подстроки, а не только первую.

Несколько примеров:

SELECT regexp_matches('foo', 'not there');
 regexp_matches
----------------
(0 rows)

SELECT regexp_matches('foobarbequebazilbarfbonk', '(b[^b]+)(b[^b]+)', 'g');
 regexp_matches
----------------
 {bar,beque}
 {bazil,barf}
(2 rows)

Функция regexp_split_to_table разбивает строку, используя в качестве разделителя шаблон регулярного выражения POSIX. Она имеет синтаксис regexp_split_to_table(строка, шаблон [, флаги ]). Если нет совпадения с шаблоном, функция возвращает строку. Если найдено хотя бы одно совпадение, для каждого его вхождения она возвращает текст от конца предыдущего вхождения (или начала строки) до начала этого вхождения. Дойдя до последнего вхождения, она возвращает текст от конца этого последнего вхождения до конца строки. Параметр флаги представляет собой необязательную текстовую строку, содержащую ноль или более однобуквенных флагов, изменяющих поведение функции. regexp_split_to_table поддерживает флаги, описанные в Таблице 24.

Функция regexp_split_to_array ведет себя так же, как regexp_split_to_table, за исключением того, что regexp_split_to_array возвращает свой результат в виде массива элементов типа text. Она имеет синтаксис regexp_split_to_array(строка, шаблон [, флаги ]). Параметры те же, что и для regexp_split_to_table.

Несколько примеров:

SELECT foo FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', '\s+') AS foo;
  foo
-------
 the
 quick
 brown
 fox
 jumps
 over
 the
 lazy
 dog
(9 rows)

SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', '\s+');
              regexp_split_to_array
-----------------------------------------------
 {the,quick,brown,fox,jumps,over,the,lazy,dog}
(1 row)

SELECT foo FROM regexp_split_to_table('the quick brown fox', '\s*') AS foo;
 foo
-----
 t
 h
 e
 q
 u
 i
 c
 k
 b
 r
 o
 w
 n
 f
 o
 x
(16 rows)

Как показывает последний пример, функции разбиения по регулярным выражениям игнорируют вхождения нулевой длины, которые находятся в начале или конце строки или сразу после предыдущего вхождения. Это противоречит строгому определению сопоставления по регулярным выражениям, которое реализуется с помощью функций regexp_match и regexp_matches, но обычно на практике является наиболее удобным поведением. Аналогичные определения используют и другие программные системы, например Perl.

Подробное описание регулярных выражений

Регулярные выражения в QHB реализованы с использованием программного пакета, написанного Генри Спенсером (Henry Spencer). Большая часть представленного ниже описания регулярных выражений дословно скопирована из его руководства.

Регулярные выражения (Regular expression, RE), как определено в POSIX 1003.2, имеют две формы: расширенные RE, или ERE (грубо говоря, те, что предназначены для egrep), и базовые RE или BRE (грубо говоря, те, что предназначены для ed). QHB поддерживает обе формы, а также реализует некоторые расширения, которые не входят в стандарт POSIX, но стали широко использоваться благодаря их наличию в таких языках программирования, как Perl и Tcl. RE, использующие эти не предусмотренные в POSIX расширения, в данной документации называются продвинутыми RE, или ARE. ARE являются почти точным надмножеством ERE, но у BRE имеется некоторая несовместимость в нотации (кроме того, они гораздо более ограничены). Сначала мы опишем формы ARE и ERE, отметив черты, характерные только для ARE, а затем опишем, чем отличаются BRE.

Примечание
QHB всегда изначально предполагает, что регулярное выражение следует правилам ARE. Однако можно выбрать более ограниченные правила ERE или BRE, добавив в шаблон RE встроенный параметр, как описано в подразделе Метасинтаксис регулярных выражений. Это может быть полезно для совместимости с приложениями, ожидающими неукоснительного следования правилам POSIX 1003.2.

Регулярное выражение определяется как одна или несколько ветвей, разделенных символами |. Оно соответствует всему, что соответствует одной из этих ветвей.

Ветвь — это ноль или более конкатенированных количественных атомов или ограничений. Она соответствует вхождению первого элемента, за которым следует вхождение второго элемента и т. д.; пустая ветвь соответствует пустой строке.

Количественный атом — это атом, за которым может следовать один квантификатор. Без квантификатора он соответствует одному вхождению атома. С квантификатором он может соответствовать некоторому количеству вхождений атома. Атом может быть любой из возможностей, показанных в Таблице 17. Возможные квантификаторы и их значения приведены в Таблице 18.

Ограничение соответствует пустой строке, но соответствует только при выполнении определенных условий. Ограничение можно использовать там же, где можно использовать атом, за исключением того, что за ним не может следовать квантификатор. Простые ограничения показаны в Таблице 19; некоторые дополнительные ограничения описаны ниже.

Таблица 17. Атомы регулярного выражения

Атом | Описание
(re) | (где *re - любое регулярное выражение) соответствует вхождению re, причем вхождение отмечено для возможного сообщения
(?: re) | то же, что и выше, но вхождение не отмечено для отчетности (набор «не захватывающих» скобок) (только для ARE)
. | соответствует любому отдельному символу
[ chars ] | выражение в квадратных скобках, соответствующее любому из chars (более подробную информацию см. в подразделе Выражения в квадратных скобках)
\k | (где k не буквенно-цифровой символ) соответствует этому символу, взятому как обычный символ, например, \\ соответствует символу обратного слэша
\c | где c — буквенно-цифровой символ (за которым, возможно, следуют другие символы), является управляющим символом, см. подраздел Управляющие символы в регулярных выражениях (только для ARE; в ERE и BRE это соответствует c)
{ | когда за ним следует символ, отличный от цифры, соответствует символу левой фигурной скобки {; после цифры следует начало границы (см. ниже)
x | где x — это один символ, не имеющий другого значения, соответствует этому символу

RE не может заканчиваться обратным слэшем (\).

Примечание
Если у вас выключен параметр standard_conforming_strings, любые обратные слэши, которые вы пишете в константах литеральных строк, нужно будет дублировать. Дополнительную информацию см. в подразделе Строковые константы.

Таблица 18. Квантификаторы регулярных выражений

Квантификатор | Совпадения
* | последовательность из 0 или более совпадений атома
+ | последовательность из 1 или более совпадений атома
? | последовательность из 0 или 1 совпадения атома
{m} | последовательность из ровно m совпадений атома
{m,} | последовательность из m или более совпадений атома
{m,n} | последовательность из от m до n (включительно) совпадений атома; m не может превышать n
*? | нежадная версия *
+? | нежадная версия +
?? | нежадная версия ?
{m}? | нежадная версия {m}
{m,}? | нежадная версия {m,}
{m,n}? | нежадная версия {m,n}

Формы, использующие {...}, называются границами. Числа m и n внутри границы являются десятичными целыми числами без знака с допустимыми значениями от 0 до 255 включительно.

Нежадные квантификаторы (имеющиеся только в ARE) соответствуют тем же возможностям, что и их соответствующие нормальные (жадные) аналоги, но предпочитают наименьшее, а не наибольшее количество совпадений. Более подробную информацию см. в подразделе Правила сопоставления регулярных выражений.

Примечание
Квантификатор не может следовать сразу за другим квантификатором, например, запись ** недействительна. Квантификатор не может начинать выражение или подвыражение или следовать за символом ^ или |.

Таблица 19. Ограничения регулярного выражения

Ограничение | Описание
^ | соответствует началу строки
$ | соответствует концу строки
(?=re) | позитивный просмотр вперед соответствует любой точке, где начинается подстрока, соответствующая re (только для ARE)
(?!re) | негативный просмотр вперед соответствует любой точке, где не начинается подстрока, соответствующая re (только для ARE)
(?<=re) | позитивный просмотр назад соответствует любой точке, где заканчивается подстрока, соответствующая re (только для ARE)
(?<!re) | негативный просмотр назад соответствует любой точке, где не заканчивается подстрока, соответствующая re (только для ARE)

Ограничения просмотра вперед и назад не могут содержать обратных ссылок (см. подраздел Управляющие символы в регулярных выражениях), и все скобки в них считаются не захватывающими.

Выражения в квадратных скобках

Выражение в квадратных скобках — это список символов, заключенных в []. Обычно оно соответствует любому отдельному символу из этого списка (но см. ниже). Если список начинается с ^, оно соответствует любому отдельному символу, не относящемуся к остальной части этого списка. Если два символа в списке разделены знаком -, это сокращение для полного диапазона символов между этими двумя символами (включая их) в последовательности сортировки, например, запись [0-9] в ASCII соответствует любой десятичной цифре. Недопустимо, чтобы у двух диапазонов была общая граничная точка, например a-c-e. Диапазоны очень сильно зависят от последовательности сортировки, поэтому переносимые программы не должны полагаться на них.

Чтобы включить в список литерал ], сделайте его первым символом (после ^, если он используется). Чтобы включить литерал -, сделайте его первым или последним символом или второй граничной точкой диапазона. Чтобы использовать литерал - в качестве первой граничной точки диапазона, заключите его в [. и .], чтобы сделать его упорядочивающим элементом (см. ниже). За исключением этих символов, некоторых комбинаций с использованием [ (см. следующие абзацы) и управляющих символов (только для ARE), все другие специальные символы внутри выражения в квадратных скобках теряют свое особое значение. В частности, символ \ является обычным, если следовать правилам ERE или BRE, хотя и является специальным (как обозначающий экранирование) в ARE.

Внутри выражения в квадратных скобках элемент сортировки (символ, многосимвольная последовательность, которая сортируется, как если бы это был один символ, или имя последовательности сортировки для любого из них), заключенный в [. и .], обозначает последовательность символов этого элемента сортировки. Последовательность рассматривается как отдельный элемент списка выражения в квадратных скобках. Это позволяет выражению в квадратных скобках, содержащему многосимвольный элемент сортировки, соответствовать более чем одному символу, например, если последовательность сортировки включает в себя элемент сортировки ch, то RE [[.ch.]]*c соответствует первым пяти символам строки chchcc.

Примечание
В настоящее время QHB не поддерживает многосимвольные элементы сортировки. Эта информация описывает возможное будущее поведение.

В выражении в квадратных скобках элемент сортировки, заключенный в [= и =], является классом эквивалентности, обозначающим последовательности символов всех элементов сортировки, эквивалентных этому элементу, включая его самого. (Если нет других эквивалентных элементов сортировки, он обрабатывается так, как если бы был заключен в разделители [. и .]). Например, если o и ^ являются членами класса эквивалентности, то [[=o=]], [[=^=]] и [o^] являются синонимами. Класс эквивалентности не может быть граничной точкой диапазона.

В выражении в квадратных скобках имя класса символов, заключенное в [: и :], обозначает список всех символов, принадлежащих этому классу. Класс символов не может использоваться в качестве граничной точки диапазона. Стандарт POSIX определяет следующие имена классов символов: alnum (буквы и цифры), alpha (буквы), blank (пробел и табуляция), cntrl (символы управления), digit (цифры), graph (печатные символы, кроме пробела), lower (строчные буквы), print (печатаемые символы, включая пробел), punct (пунктуация), space (любой пробел), upper (заглавные буквы) и xdigit (шестнадцатеричные цифры). Поведение этих стандартных классов символов обычно одинаково на разных платформах для символов в 7-битном наборе ASCII. То, считается ли данный символ, не относящийся к ASCII, принадлежащим одному из этих классов, зависит от правила сортировки, используемого для функции или оператора регулярного выражения (см. раздел Поддержка правил сортировки), или, по умолчанию, от настройки языка LC_CTYPE базы данных (см. раздел Поддержка языковых стандартов). Классификация символов, отличных от ASCII, может варьироваться в зависимости от платформы даже в локалях с одинаковыми именами. (Но локаль C никогда не считает, что любые не относящиеся к ASCII символы принадлежат какому-либо из этих классов). В дополнение к этим стандартным классам символов, QHB определяет класс символов word, который аналогичен alnum, но вдобавок включает символ подчеркивания (_), и класс символов ascii, который содержит только и исключительно 7-битный набор ASCII.

Существует два особых случая выражений в квадратных скобках: выражения [[:<:]] и [[:>:]] являются ограничениями, соответствующими пустым строкам в начале и конце слова соответственно. Слово определяется как последовательность словообразующих символов, которая не предшествует и не сопровождается символами слова. Словообразующий символ — это любой символ, принадлежащий классу символов word, то есть любая буква, цифра или знак подчеркивания. Это расширение совместимо с POSIX 1003.2, но не указано в нем, и его следует использовать с осторожностью в программном обеспечении, предназначенном для переноса на другие системы. Обычно лучше использовать описанные ниже символы ограничений; они тоже не вполне стандартны, но их легче набирать.

Управляющие символы в регулярных выражениях

Управляющие символы — это специальные последовательности, начинающиеся с \, за которым следует буквенно-цифровой символ. Управляющие символы могут быть нескольких видов: обозначения символов, коды классов, символы ограничений и обратные ссылки. Символ \, сопровождаемый буквенно-цифровым символом, но не образующий допустимый управляющий символ, запрещен в ARE. В ERE нет управляющих символов: вне выражения в квадратных скобках \ и последующий буквенно-цифровой символ просто обозначают этот символ как обычный, а внутри выражения в квадратных скобках и сам \ — обычный символ. (Последнее является единственной фактической несовместимостью между ERE и ARE).

Особые обозначения символов существуют, чтобы упростить указание непечатаемых и других неудобных символов в RE. Они приведены в Таблице 20.

Коды классов предоставляют сокращенные обозначения для некоторых часто используемых классов символов. Они приведены в Таблице 21.

Символы ограничений — это ограничения, которые при выполнении определенных условий соответствуют пустой строке и записываются в виде управляющих символов. Они приведены в Таблице 22.

Обратная ссылка (\n) соответствует той же строке, которой соответствовало предыдущее подвыражение в скобках, указанное числом n (см. Таблицу 22). Например, ([bc])\1 соответствует bb или cc, но не bc или cb. Подвыражение должно полностью предшествовать обратной ссылке в RE. Подвыражения нумеруются в порядке их открывающих скобок. Не захватывающие скобки не определяют подвыражения. Обратные ссылки учитывают только строковые символы, соответствующие указанному подвыражению, но не содержащиеся в нем ограничения. Например, выражению (^\d)\1 будет соответствовать 22.

Таблица 20. Особые обозначения символов в регулярных выражениях

Управляющий символ | Описание
\a | символ оповещения (звонка), как в C
\b | возврат на символ, как в C
\B | синоним обратного слэша (\) для сокращения необходимости его удвоения
\cX | (где X — любой символ) символ, чьи 5 младших битов такие же, как у X, а все остальные биты равны нулю
\e | символ с именем последовательности сортировки ESC или, если это не так, символ с восьмеричным значением 033
\f | подача формы, как в C
\n | перевод строки, как в C
\r | возврат каретки, как в C
\t | горизонтальная табуляция, как в C
\uwxyz | (где wxyz — ровно четыре шестнадцатеричные цифры) символ, шестнадцатеричное значение которого равно 0xwxyz
\Ustuvwxyz | (где stuvwxyz — ровно восемь шестнадцатеричных цифр) символ, шестнадцатеричное значение которого равно 0xstuvwxyz
\v | вертикальная табуляция, как в C
\xhhh | (где hhh — любая последовательность шестнадцатеричных цифр) символ, шестнадцатеричное значение которого равно 0xhhh (один символ независимо от того, сколько шестнадцатеричных цифр используется)
\0 | символ, значение которого равно 0 (нулевой байт)
\xy | (где xy — ровно две восьмеричные цифры, а не обратная ссылка) символ, восьмеричное значение которого равно 0xy
\xyz | (где xyz — ровно три восьмеричных цифры, а не обратная ссылка) символ, восьмеричное значение которого равно 0xyz

Шестнадцатеричные цифры — это 0-9, a-f и A-F. Восьмеричные цифры — это 0-7.

Цифровые особые обозначения символов, задающие значения вне диапазона ASCII (0-127), меняют семантику в зависимости от кодировки базы данных. При кодировке UTF-8 управляющие значения равнозначны кодовым точкам Unicode, например, \u1234 означает символ U+1234. Для других многобайтовых кодировок особые обозначения символов обычно просто задают конкатенацию байтовых значений для символа. Если управляющее значение не соответствует никакому допустимому символу в кодировке базы данных, ошибки не возникнет, но это значение никогда не будет соответствовать никаким данным.

Особые обозначения символов всегда принимаются как обычные символы. Например, \135 является символом ] в ASCII, но \135 не завершает выражение в квадратных скобках.

Таблица 21. Коды классов в регулярных выражениях

Управляющий символ | Описание
\d | соответствует любой цифре; равнозначно [[:digit:]]
\s | соответствует любому пробельному символу; равнозначно [[:space:]]
\w | соответствует любому словообразующему символу; равнозначно [[:word:]]
\D | соответствует любому нецифровому символу; равнозначно [^[:digit:]]
\S | соответствует любому непробельному символу; равнозначно [^[:space:]]
\W | соответствует любому несловообразующему символу; равнозначно [^[:word:]]

Коды классов также работают в выражениях в квадратных скобках, хотя показанные выше определения не вполне синтаксически допустимы в этом контексте. Например, выражение [a-c\d] равнозначно [a-c[:digit:]].

Таблица 22. Символы ограничений в регулярных выражениях

Управляющий символ | Описание
\A | соответствует только началу строки (чем это отличается от ^, см. в подразделе Правила сопоставления регулярных выражений)
\m | соответствует только началу слова
\M | соответствует только концу слова
\y | соответствует только началу или концу слова
\Y | соответствует только точке, которая не является началом или концом слова
\Z | соответствует только концу строки (чем это отличается от $, см. в подразделе Правила сопоставления регулярных выражений)

Слово определяется как в спецификации [[:<:]] и [[:>:]] выше. В выражениях в квадратных скобках символы ограничений недопустимы .

Таблица 23. Обратные ссылки в регулярных выражениях

Управляющий символ | Описание
\m | (где m — ненулевая цифра) обратная ссылка на m-е подвыражение
\mnn | (где m — ненулевая цифра, а nn — еще несколько цифр, и десятичное значение mnn не превышает число закрывающих захватывающих скобок, замеченных до сих пор) обратная ссылка на mnn-е подвыражение

Примечание
Между обозначениями восьмеричных символов и обратными ссылками имеется существенная неоднозначность, которая разрешается следующим эвристическим алгоритмом (как упоминалось выше). Ведущий ноль всегда указывает на восьмеричное обозначение. Одна ненулевая цифра, за которой не следует другая цифра, всегда воспринимается как обратная ссылка. Последовательность из нескольких цифр, не начинающаяся с нуля, воспринимается как обратная ссылка, если она идет после подходящего подвыражения (т. е. число находится в допустимом диапазоне для обратной ссылки), а в противном случае воспринимается как восьмеричное обозначение.

Метасинтаксис регулярных выражений

В дополнение к основному синтаксису, описанному выше, существуют некоторые специальные формы и различные синтаксические средства.

RE может начинаться с одного из двух специальных префиксов-направителей. Если RE начинается с ***:, остальная его часть воспринимается как ARE. (Обычно в QHB это не имеет никакого значения, поскольку предполагается, что все RE являются ARE; но это имеет смысл, если параметром flags для функции regex был задан режим ERE или BRE). Если RE начинается с ***=, остальная его часть воспринимается как литеральная строка, причем все символы считаются обычными символами.

ARE может начинаться со встроенных параметров: последовательность (?xyz) (где xyz — один или несколько буквенных символов) задает параметры, влияющие на остальную часть RE. Эти параметры переопределяют любые ранее определенные параметры — в частности, они могут переопределять чувствительность к регистру, подразумеваемую оператором регулярного выражения, или параметр flags для функции регулярного выражения. Имеющиеся буквы параметров приведены в Таблице 24. Обратите внимание, что эти же буквы параметров используются в параметрах flags функций регулярных выражений.

Таблица 24. Буквы встроенных параметров ARE

Параметр | Описание
b | остальная часть RE — BRE
c | сопоставление с учетом регистра (переопределяет тип оператора)
e | остальная часть RE — ERE
i | сопоставление без учета регистра (см. подраздел Правила сопоставления регулярных выражений) (переопределяет тип оператора)
m | исторический синоним для n
n | сопоставление с учетом перевода строк (см. подраздел Правила сопоставления регулярных выражений)
p | сопоставление с частичным учетом перевода строк (см. подраздел Правила сопоставления регулярных выражений)
q | остальная часть RE является литеральной строкой («в кавычках»), все символы обычные
s | сопоставление, не зависящее от перевода строки (по умолчанию)
t | жесткий синтаксис (по умолчанию; см. ниже)
w | обратное сопоставление с частичным учетом перевода строк («странное») (см. подраздел Правила сопоставления регулярных выражений)
x | расширенный синтаксис (см. ниже)

Встроенные параметры вступают в силу после символа ), завершающего последовательность. Они могут появляться только в начале ARE (после направителя ***: если таковой имеется).

В дополнение к обычному (строгому) синтаксису RE, в котором значимы все символы, существует расширенный синтаксис, доступный при указании встроенного параметра x. В расширенном синтаксисе символы пробела в RE игнорируются, как и все символы между знаком # и следующим переводом строки (или концом RE). Это позволяет создавать абзацы и комментировать сложные RE. Из этого основного правила есть три исключения:

пробельный символ или #, которому предшествует \, сохраняется

пробельный символ или # в выражении в квадратных скобках сохраняется

пробельный символ и комментарии не могут появляться в многосимвольных обозначениях, таких как (?:

В данном случае пробельными символами являются собственно пробелы, символы табуляции, перевода строки и любые символы, принадлежащие классу символов space.

Наконец, в ARE, вне выражений в квадратных скобках, последовательность (?#ttt) (где ttt - любой текст, не содержащий )) — это комментарий, который полностью игнорируется. Опять же, эта последовательность не допускается между символами многосимвольных обозначений, например (?:. Такие комментарии являются скорее историческим артефактом, чем полезным средством, и их использование не рекомендуется; вместо этого используйте расширенный синтаксис.

Ни одно из этих расширений метасинтаксиса не доступно, если начальным направителем ***= задано, что вводимые пользователем данные будут обрабатываться как литеральная строка, а не как RE.

Правила сопоставления регулярных выражений

В случае, когда RE может соответствовать более чем одной подстроке заданной строки, это RE соответствует той подстроке, которая начинается в ней первой. Если RE может соответствовать более чем одной подстроке, начинающейся в этой точке, то будет взято либо самое длинное, либо самое короткое вхождение из возможных, в зависимости от того, является ли RE жадным или нежадным.

Является ли RE жадным или нет, определяется следующими правилами:

Большинство атомов и все ограничения не имеют атрибута жадности (поскольку они в любом случае не могут соответствовать переменным объемам текста).

Добавление скобок вокруг RE не влияет на его жадность.

Кванторный атом с квантификатором фиксированного повторения ({m} или {m}?) имеет такую же жадность (возможно, ее отсутствие), как сам атом.

Кванторный атом с другими нормальными квантификаторами (включая {m,n} с m равным n) является жадным (предпочитает самое длинное вхождение).

Кванторный атом с нежадным квантификатором (включая {m,n}? с m равным n) не является жадным (предпочитает самое короткое вхождение).

Ветвь — то есть RE, у которого нет оператора | на верхнем уровне — имеет ту же жадность, что и первый кванторный атом в ней, имеющий атрибут жадности.

RE, состоящее из двух или более ветвей, соединенных оператором |, всегда жадное.

Приведенные выше правила связывают атрибуты жадности не только с отдельными кванторными атомами, но и с ветвями и целыми RE, содержащими кванторные атомы. Это означает, что сопоставление выполняется таким образом, что ветвь или целое RE совпадает с самой длинной или самой короткой из возможных подстрок в целом. Как только длина всего вхождения определена, его часть, соответствующая любому конкретному подвыражению, определяется на основе атрибута жадности этого подвыражения, причем подвыражения, начинающиеся в RE раньше, имеют приоритет над теми, что начинаются дальше.

Пример того, что это значит:

SELECT SUBSTRING('XY1234Z', 'Y*([0-9]{1,3})');
Result: 123
SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
Result: 1

В первом случае RE в целом жадное, потому что атом Y* жадный. Он может совпадать, начиная с Y, и соответствовать самой длинной из возможных строке, начинающейся там, то есть Y123. Выводимым результатом является ее часть в скобках, или 123. Во втором случае RE в целом нежадное, потому что атом Y*? нежадный. Он может совпадать, начиная с Y, и соответствовать самой короткой строке, начинающейся там, то есть Y1. Подвыражение [0-9]{1,3} является жадным, но не может изменить выбор относительно общей длины вхождения, поэтому ему приходится совпадать только с подстрокой 1.

Вкратце, когда RE содержит как жадные, так и нежадные подвыражения, все вхождение будет либо максимально длинным, либо максимально коротким, в соответствии с атрибутом, назначенным всему RE. Атрибуты, назначенные подвыражениям, влияют только на то, сколько из этого вхождения им разрешено «съесть» относительно друг друга.

Чтобы задать атрибут жадности или нежадности для подвыражения или целого RE, можно использовать квантификаторы {1,1} и {1,1}? соответственно. Это полезно, когда требуется, чтобы весь RE имел атрибут жадности, отличный от того, что выводится из его элементов. В качестве примера предположим, что мы пытаемся разделить строку, содержащую несколько цифр, на цифры и части до и после них. Мы можем попытаться сделать это так:

SELECT regexp_match('abc01234xyz', '(.*)(\d+)(.*)');
Result: {abc0123,4,xyz}

Это не сработало: первый атом .* жадный, поэтому он «съедает» столько, сколько может, оставляя \d+ совпадать с последним возможным местом, последней цифрой. Мы можем попытаться исправить это, сделав его нежадным:

SELECT regexp_match('abc01234xyz', '(.*?)(\d+)(.*)');
Result: {abc,0,""}

Это тоже не сработало, потому что теперь RE в целом нежадный, и поэтому он заканчивает все сравнение как можно скорее. Мы можем получить то, что хотим, заставив RE в целом быть жадным:

SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
Result: {abc,01234,xyz}

Контроль общей жадности RE отдельно от жадности его компонентов обеспечивает большую гибкость при работе с шаблонами переменной длины.

При принятии решения о том, какое вхождение длиннее или короче, длины вхождений измеряются в символах, а не в элементах сортировки. Пустая строка считается длиннее, чем отсутствие соответствия. Например: выражение bb* соответствует трем средним символам в строке abbbc; выражение (week|wee) (night|knights) — всем десяти символам в строке weeknights; когда выражение (.*).* сопоставляется со строкой abc, подвыражение в скобках соответствует всем трем символам; а когда выражение (a*)* сопоставляется со строкой bc, то и все RE, и подвыражение в скобках соответствуют пустой строке.

Если задано сопоставление без учета регистра, эффект будет таким же, как если бы из алфавита исчезли все различия строчных и прописных букв. Когда буквенный символ, существующий в нескольких регистрах, вне выражения в квадратных скобках находится как обычный символ, он по сути преобразуется в выражение в квадратных скобках, содержащее буквы в обоих регистрах, например, x становится [xX]. Когда же он находится внутри выражения в квадратных скобках, в это выражение добавляются все его варианты, например, [x] становится [xX], а [^x] становится [^xX].

Если задано сопоставление с учетом перевода строк, атом . и выражения в скобках, использующие ^, никогда не будут соответствовать символу перевода строки (так что вхождения никогда не будут заходить за границу строки, если RE явно не включит эти символы), а ^ и $ будут соответствовать не только началу и концу строки соответственно, но и пустой строке после и до символа перевода строки соответственно. Однако управляющие символы ARE \A и \Z продолжают соответствовать только началу или концу строки. Кроме того, коды классов символов \D и \W будут соответствовать переводу строки независимо от этого режима. (В старых версиях QHB они не соответствовали переводу строки в режиме с учетом перевода строк. Для включения старого поведения напишите [^[:digit:]] или [^[:word:]].)

Если указано сопоставление с частичным учетом перевода строк, то перевод строки влияет на атом . и выражения в скобках, но не на ^ и $.

Если указано обратное сопоставление с частичным учетом перевода строк, то перевод строки влияет на ^ и $, но не на атом . и выражения в скобках. Это не очень полезно, но предусмотрено для симметрии.

Пределы и совместимость

В этой реализации нет особых ограничений на длину RE. Однако программы, у которых предполагается высокая переносимость, не должны использовать RE длиной более 256 байт, поскольку реализация, совместимая с POSIX, может отказаться принимать такие RE.

Единственная особенность ARE, которая действительно несовместима с POSIX ERE, заключается в том, что \ не теряет своего особого значения в выражениях в квадратных скобках. Все остальные функциональные возможности ARE используют синтаксис, который является недопустимым или имеет эффекты, не определенные или не предусмотренные в POSIX ERE; синтаксис направителей *** также выходит за рамки синтаксиса POSIX как для BRE, так и для ERE.

Многие расширения ARE заимствованы из Perl, но некоторые были изменены в целях оптимизации, а некоторые расширения Perl отсутствуют. Заметные несовместимости включают в себя атомы \b, \B, отсутствие специальной обработки для завершающего перевода строки, добавление дополненных выражений в квадратных скобках в число случаев, на которые влияет сопоставление с учетом перевода строк, особые условия для круглых скобок и обратных ссылок в ограничениях просмотра вперед/назад, а также семантику «сопоставление самого длинного/самого короткого вхождения» (а не «первого вхождения»).

Базовые регулярные выражения

BRE отличаются от ERE в нескольких отношениях. В BRE |, + и ? являются обычными символами, и их функциональность не имеет аналогов. Разделителями для границ являются \{ и \}, причем сами по себе { и } — обычные символы. Скобками для вложенных подвыражений являются \( и \), а сами по себе ( и ) — обычные символы. ^ — обычный символ, если не стоит в начале RE или в начале подвыражения в скобках, $ — обычный символ, если не стоит в конце RE или в конце подвыражения в скобках, а * — обычный символ, если находится в начале RE или в начале подвыражения в скобках (возможно, после начального ^). Наконец, в BRE возможны обратные ссылки с одной цифрой, а \< и \> являются синонимами для [[:<:]] и [[:>:]] соответственно; в BRE нет других управляющих символов.

Отличия от XQuery (LIKE_REGEX)

Начиная с SQL:2008, стандарт SQL включает в себя оператор LIKE_REGEX, выполняющий сопоставление с шаблоном в соответствии со стандартом регулярных выражений XQuery. QHB еще не реализует этот оператор, но очень похожее поведение можно получить с помощью функции regexp_match(), поскольку регулярные выражения XQuery довольно близки к описанному выше синтаксису ARE.

Имеются заметные различия между существующей функцией регулярных выражений на основе POSIX и регулярными выражениями XQuery, в том числе:

Вычитание классов символов XQuery не поддерживается. Примером этой особенности является использование для сопоставление только с английскими согласными следующего выражения: [a-z-[aeiou]].

Коды классов символов XQuery \c, \C, \i и \I не поддерживаются.

Элементы классов символов XQuery, использующие \p{UnicodeProperty} или обратное \P{UnicodeProperty}, не поддерживаются.

В POSIX классы символов, такие как \w (см. Таблицу 21), интерпретируются в соответствии с преобладающей локалью (которой можно управлять, добавляя предложение COLLATE к оператору или функции). В XQuery эти классы определяются с помощью ссылок на свойства символов Unicode, поэтому равнозначное поведение возможно только с локалью, подчиняющейся правилам Unicode.

Стандарт SQL (не сам XQuery) пытается учесть больше вариантов «перевода строки», чем POSIX. Описанные выше варианты сопоставления с учетом перевода строк считают символом перевода строки только ASCII NL (\n), тогда как, следуя стандарту SQL, пришлось бы считать символами перевода строки также CR (\r), CRLF (\r\n) (перевод строки в стиле Windows) и некоторые символы, характерные только для Unicode, например LINE SEPARATOR (U+2028). Примечательно, что, согласно SQL, атомы . и \s должны считать последовательность \r\n одним символом, а не двумя.

Из особых обозначений символов, описанных в Таблице 20, XQuery поддерживает только \n, \r и \t.

XQuery не поддерживает синтаксис [:имя:] для классов символов в выражениях в квадратных скобках.

В XQuery нет условий для просмотра вперед или назад, а также ограничивающих символов, описанных в Таблице 22.

Метасинтаксические формы, описанные в подразделе Метасинтаксис регулярных выражений, в XQuery не существуют.

Буквы флагов регулярного выражения, определенные в XQuery, имеют нечто общее с буквами параметров для POSIX (Таблица 24), но отличаются них. Только параметры i и q одинаковы, остальные ведут себя совершенно по-другому:

Флаги XQuery s (разрешить соответствие точки переводу строки) и m (разрешить соответствие ^ и $ переводам строк) позволяют получить то же поведение, что и флаги POSIX n, p и w, но они не соответствуют поведению флагов POSIX s и m. В частности, обратите внимание, что по умолчанию точка соответствует переводу строки в POSIX, но не в XQuery.

Флаг XQuery x (игнорировать пробельные символы в шаблоне) заметно отличается от флага расширенного режима POSIX. В POSIX флаг x также допускает символ #, с которого начинается комментарий в шаблоне, и пробельный символ после обратного слэша в POSIX не игнорируется.

Функции для форматирования типов данных

Функции для форматирования QHB предоставляют мощный набор инструментов для преобразования различных типов данных (дата/время, целые числа, числа с плавающей запятой, числовые) в форматированные строки и форматированных строк в конкретные типы данных. Все они перечислены в Таблице 25. Эти функции следуют общему соглашению о вызовах: первый аргумент — это значение, которое нужно отформатировать, а второй аргумент — это шаблон, который определяет формат вывода или ввода.

Таблица 25. Функции для форматирования

Функция

Описание
Пример(ы)

to_char ( timestamp, text ) → text
to_char ( timestamp with time zone, text ) → text

Преобразует метку времени в строку согласно заданному формату.
to_char(timestamp '2002-04-20 17:31:12.66', 'HH12:MI:SS') → 05:31:12

to_char ( interval, text ) → text

Преобразует интервал в строку согласно заданному формату.
to_char(interval '15h 2m 12s', 'HH24:MI:SS') → 15:02:12

to_char ( числовой_тип, text ) → text

Преобразует число в строку согласно заданному формату; имеется для типов integer, bigint, numeric, real, double precision.
to_char(125, '999') → 125
to_char(125.8::real, '999D9') → 125.8
to_char(-125.8, '999D99S') → 125.80-

to_date ( text, text ) → date

Преобразует строку в дату согласно заданному формату.
to_date('05 Dec 2000', 'DD Mon YYYY') → 2000-12-05

to_number ( text, text ) → numeric

Преобразует строку в число согласно заданному формату.
to_number('12,454.8-', '99G999D9S') → -12454.8

to_timestamp ( text, text ) → timestamp with time zone

Преобразует строку в метку времени согласно заданному формату. (См. также to_timestamp(double precision) в таблице Функции даты/времени.)
to_timestamp('05 Dec 2000', 'DD Mon YYYY') → 2000-12-05 00:00:00-05

Совет
Функции to_timestamp и to_date существуют для обработки входных форматов, которые нельзя преобразовать простым приведением. Для большинства стандартных форматов даты/времени работает простое приведение исходной строки к требуемому типу данных, и это намного проще. Аналогично функция to_number не нужна для стандартных представлений чисел.

В строке шаблона вывода to_char есть определенные коды, которые распознаются и заменяются данными соответствующего формата на основе заданного значения. Любой текст, который не является кодом шаблона, просто дословно копируется. Точно так же в строке шаблона ввода (для других функций) эти коды идентифицируют значения, которые будут предоставлены строкой входных данных. Если в строке шаблона есть символы, не относящиеся к кодам, соответствующие символы во входной строке данных просто пропускаются (независимо от того, совпадают ли они с символам в строке шаблона).

В Таблице 26 показаны имеющиеся коды шаблонов для форматирования значений даты и времени.

Таблица 26. Коды шаблонов для форматирования даты/времени

Код | Описание
HH | час дня (01-12)
HH12 | час дня (01-12)
HH24 | час дня (00-23)
MI | минута (00-59)
SS | секунда (00-59)
MS | миллисекунда (000-999)
US | микросекунда (000000-999999)
FF1 | десятая доля секунды (0-9)
FF2 | сотая доля секунды (00-99)
FF3 | миллисекунда (000-999)
FF4 | десятая доля миллисекунды (0000-9999)
FF5 | сотая доля миллисекунды (00000-99999)
FF6 | микросекунда (000000-999999)
SSSS, SSSSS | секунды после полуночи (0-86399)
AM, am, PM или pm | индикатор времени до/после полудня (без точек)
A.M., a.m., P.M. или p.m. | индикатор времени до/после полудня (с точками)
Y,YYY | год (4 или более цифр) с запятой
YYYY | год (4 или более цифр)
YYY | последние 3 цифры года
YY | последние 2 цифры года
Y | последняя цифра года
IYYY | недельный год по ISO 8601 (4 или более цифр)
IYY | последние 3 цифры недельного года по ISO 8601
IY | последние 2 цифры недельного года по ISO 8601
I | последняя цифра недельного года по ISO 8601
BC, bc, AD или ad | индикатор эры (без точек)
B.C., b.c., A.D. или a.d. | индикатор эры (с точками)
MONTH | полное название месяца в верхнем регистре (дополненное пробелами до 9 символов)
Month | полное название месяца с заглавной буквы (дополненное пробелами до 9 символов)
month | полное название месяца в нижнем регистре (дополненное пробелами до 9 символов)
MON | сокращенное название месяца в верхнем регистре (3 буквы в английском языке; в других языках длина может отличаться)
Mon | сокращенное название месяца с заглавной буквы (3 буквы в английском языке; в других языках длина может отличаться)
mon | сокращенное название месяца в нижнем регистре (3 буквы в английском языке; в других языках длина может отличаться)
MM | номер месяца (01-12)
DAY | полное название дня недели в верхнем регистре (дополненное пробелами до 9 символов)
Day | полное название дня недели с заглавной буквы (дополненное пробелами до 9 символов)
day | полное название дня недели в нижнем регистре (дополненное пробелами до 9 символов)
DY | сокращенное название дня недели в верхнем регистре (3 буквы в английском языке; в других языках длина может отличаться)
Dy | сокращенное название дня недели с заглавной буквы (3 буквы в английском языке; в других языках длина может отличаться)
dy | сокращенное название дня недели в нижнем регистре (3 буквы в английском языке; в других языках длина может отличаться)
DDD | номер дня в году (001-366)
IDDD | номер дня в недельном году по ISO 8601 (001-371; 1-й день года — понедельник первой недели по ISO)
DD | день месяца (01-31)
D | номер дня недели, с воскресенья (1) по субботу (7)
ID | номер дня недели по ISO 8601, с понедельника (1) по воскресенье (7)
W | номер недели в месяце (1-5) (первая неделя начинается в первый день месяца)
WW | номер недели в году (1-53) (первая неделя начинается в первый день года)
IW | номер недели в недельном году по ISO 8601 (01-53; первый четверг года относится к неделе 1)
CC | век (2 цифры) (двадцать первый век начинается с 2001-01-01)
J | юлианская дата (целое число дней с 24 ноября 4714 г. до н.э. в полночь по местному времени; см. раздел Юлианские даты)
Q | квартал
RM | месяц в верхнем регистре римскими цифрами (I-XII; I=январь)
rm | месяц в нижнем регистре римскими цифрами (i-xii; i=январь)
TZ | сокращенное наименование часового пояса в верхнем регистре (поддерживается только в to_char)
tz | сокращенное наименование часового пояса в нижнем регистре (поддерживается только в to_char)
TZH | часы часового пояса
TZM | минуты часового пояса
OF | смещение часового пояса от UTC (поддерживается только в to_char)

Модификаторы, изменяющие поведение, можно применять к любому коду шаблона. Например, FMMonth — это код Month с модификатором FM. В Таблице 27 приведены коды модификаторов для форматирования даты/времени.

Таблица 27. Модификаторы кодов шаблонов для форматирования даты/времени

Модификатор | Описание | Пример
Префикс FM | режим заполнения (подавление начальных нулей и заполняющих пробелов) | FMMonth
Суффикс TH | суффикс порядкового номера в верхнем регистре | DDTH, например 12TH
Суффикс th | суффикс порядкового номера в нижнем регистре | DDth, например 12th
Префикс FX | глобальный параметр фиксированного формата (см. примечания по использованию) | FX Month DD Day
Префикс TM | режим перевода (использовать локализованные названия дней и месяцев на основе lc_time) | TMMonth
Суффикс SP | режим числа прописью (не реализован) | DDSP

Замечания по использованию форматирования даты/времени:

FM подавляет начальные нули и конечные пробелы, которые в противном случае были бы добавлены, чтобы результат кода имел фиксированную ширину. В QHB FM изменяет только следующую спецификацию, тогда как в Oracle этот префикс влияет на все последующие спецификации, а повторные применения данного модификатора включают и выключают режим заполнения.

TM подавляет конечные пробелы независимо от указания FM.

Функции to_timestamp и to_date игнорируют регистр букв во входных данных; поэтому, например, для кодов MON, Mon и mon подойдут одни и те же строки. При использовании модификатора TM смена регистра производится согласно правилам сортировки, заданным в функции для входных данных (см. раздел Поддержка правил сортировки).

Функции to_timestamp и to_date пропускают несколько пробелов в начале вводимой строки и вокруг значений даты и времени, если только не используется префикс FX. Например, to_timestamp(’ 2000 JUN’, ’YYYY MON’) и to_timestamp(’2000 - JUN’, ’YYYY-MON’) работают, но to_timestamp('2000 JUN', 'FXYYYY MON') возвращает ошибку, поскольку to_timestamp ожидает только один пробел. FX должен быть указан первым элементом шаблона.

Разделитель (пробел или символ, отличный от буквы/цифры) в строке шаблона функций to_timestamp и to_date соответствует любому отдельному разделителю в строке ввода или пропускается, если только не используется префикс FX. Например, to_timestamp('2000JUN', 'YYYY///MON') и to_timestamp('2000/JUN', 'YYYY MON') работают, но to_timestamp('2000//JUN', 'YYYY/MON') возвращает ошибку, поскольку количество разделителей во входной строке превышает количество разделителей в шаблоне.

Если указан префикс FX, то разделитель в строке шаблона соответствует ровно одному символу во входной строке. Но обратите внимание, что символ во входной строке не обязательно должен совпадать с разделителем из строки шаблона. Например, to_timestamp('2000/JUN', 'FXYYYY MON') работает, но to_timestamp('2000/JUN', 'FXYYYY MON') возвращает ошибку, поскольку второй пробел в строке шаблона занимает место буквы J из строки ввода.

Коду шаблона TZH может соответствовать номер со знаком. Без префикса FX знаки минус могут быть неоднозначными и интерпретироваться как разделитель. Эта неоднозначность разрешается следующим образом: если число разделителей перед TZH в строке шаблона меньше, чем число разделителей перед знаком минус во входной строке, знак минус интерпретируется как часть TZH. В противном случае знак минус считается разделителем между значениями. Например, в to_timestamp('2000 -10', 'YYYY TZH') полю TZH соответствует -10, но в to_timestamp('2000 -10', 'YYYY TZH') полю TZH соответствует 10.

В шаблонах to_char разрешен обычный текст, который будет выводиться как есть. Чтобы строка заведомо интерпретировалась как буквальный текст, даже если она содержит коды шаблона, ее можно заключить в кавычки. Например, в строке '"Hello Year "YYYY' YYYY будет заменен цифрами года, а одиночная Y в слове Year — нет. В функциях to_date, to_number и to_timestamp при обработке буквального текста и строк в кавычках в результате будет пропущено то количество символов, которое содержится в этой строке; например, в строке "XX" будет пропущено два входных символа (независимо от того, являются ли они XX).

Совет
Чтобы пропустить во входной строке произвольный текст, можно использовать в шаблоне буквы. Например, шаблоны to_timestamp('2000y6m1d', 'yyyytMMtDDt') и to_timestamp('2000y6m1d', 'yyyy"y"MM"m"DD"d"') пропускают буквы y, m и d.

Если вы хотите получить в выводе кавычку, следует поставить перед ней обратный слэш, например '\"YYYY Month\"'. В остальном символ обратного слэша вне кавычек не имеет специального значения. Внутри строки в кавычках следующий за обратным слэшем символ воспринимается буквально, каким бы он ни был (но это не имеет особого смысла, если следующий символ не является кавычкой или обратным слэшем).

Если в функциях to_timestamp и to_date формат года задан менее чем четырьмя цифрами, например YYY и в предоставленном значении года тоже меньше четырех цифр, год будет скорректирован так, чтобы быть ближайшим к 2020 году, например, 95 станет 1995 годом.

В функциях to_timestamp and to_date отрицательные значения годов обрабатываются как обозначающие годы до н. э. Если одновременно написать отрицательный год и явно указать код BC (индикатор годов до н. э.), год будет относиться к н. э. Введение нулевого года воспринимается как 1 год до н. э.

В функциях to_timestamp и to_date преобразование YYYY имеет ограничение, когда обрабатывается год, состоящий из более чем 4 цифр. Следует написать после YYYY какой-либо нецифровой символ или подходящий код, иначе год всегда будет интерпретироваться как 4 цифры. Например, to_date('200001131', 'YYYYMMDD') (с годом 20000) будет интерпретироваться как год с 4 цифрами; вместо этого после года нужно использовать нецифровой разделитель, например to_date('20000-1131', 'YYYY-MMDD') или to_date('20000Nov31', 'YYYYMonDD').

В функциях to_timestamp и to_date поле CC (столетие) принимается, но игнорируется, если есть поле YYY, YYYY или Y,YYY. Если CC используется с YY или Y, то результат вычисляется как этот год в указанном столетии. Если указано столетие, а год не указан, то подразумевается первый год этого столетия.

В функциях to_timestamp и to_date названия или номера дней недели (DAY, D и связанные типы полей) принимаются, но игнорируются в контексте вычисления результата. То же самое верно для полей квартала (Q).

В функциях to_timestamp и to_date недельную дату по ISO 8601 (в отличие от григорианской даты) можно указать одним из двух способов:

Год, номер недели и день недели: например, to_date('2006-42-4', 'IYYY-IW-ID') возвращает дату 2006-10-19. Если опустить день недели, он считается равным 1 (понедельник).

Год и день года: например, to_date(’2006-291’, ’IYYY-IDDD’) также возвращает 2006-10-19.

Попытка ввести дату, используя сочетание полей недельного календаря по ISO 8601 и полей григорианской даты, не имеет смысла и приведет к ошибке. В контексте недельного года по ISO 8601 понятие «месяц» или «день месяца» не имеет смысла. В контексте григорианского года не имеет смысла неделя по ISO.

ВНИМАНИЕ!
В то время как to_date будет отклонять смесь полей григорианской даты и недельного календаря по ISO, to_char примет их, поскольку спецификации выходного формата наподобие YYYY-MM-DD (IYYY-IDDD) могут быть полезны. Но избегайте введения записей подобных IYYY-MM-DD; в начале года это может привести к неожиданным результатам. (Дополнительную информацию см. в подразделе EXTRACT, date_part.)

В функции to_timestamp миллисекунды (MS) или микросекунды (US) используются как дробные части количества секунд после запятой. Например, to_timestamp('12.3', 'SS.MS') представляет собой не 3 миллисекунды, а 300, потому что преобразование обрабатывает его как 12 + 0,3 секунды. Таким образом, для формата SS.MS входные значения 12.3, 12.30 и 12.300 задают одинаковое количество миллисекунд. Чтобы получить три миллисекунды, нужно записать 12.003, что при преобразовании воспринимается как 12 + 0,003 = 12,003 секунды.

Вот более сложный пример: to_timestamp('15:12:02.020.001230', 'HH24:MI:SS.MS.US') составляет 15 часов, 12 минут и 2 секунды + 20 миллисекунд + 1230 микросекунд = 2,021230 секунд.

Нумерация дня недели в функции to_char(..., ’ID’) соответствует функции extract(isodow from ...), но в функции to_char(..., ’D’) эта нумерация не соответствует таковой в функции extract(dow from ...).

Функция to_char(interval) форматирует HH и HH12 в рамках 12 часов, например, ноль и 36 часов выводятся как 12, в то время как HH24 выводит значение часа полностью, так что в значении interval результат может быть выше 23.

В Таблице 28 показаны имеющиеся коды шаблонов для форматирования числовых значений.

Таблица 28. Коды шаблонов для форматирования чисел

Код | Описание
9 | позиция цифры (может быть опущена, если цифра незначащая)
0 | позиция цифры (не будет опущена, даже если цифра незначащая)
. (точка) | десятичная точка
, (запятая) | разделитель групп (тысяч)
PR | отрицательное значение в угловых скобках
S | знак привязанный к числу (использует локаль)
L | символ валюты (использует локаль)
D | десятичная точка (использует локаль)
G | разделитель группы (использует локаль)
MI | знак минус в заданной позиции (если число < 0)
PL | знак плюс в заданной позиции (если число > 0)
SG | знак плюс/минус в заданной позиции
RN | римская цифра (вводимое значение от 1 до 3999)
TH или th | суффикс порядкового номера
V | сдвиг на заданное количество цифр (см. примечания)
EEEE | экспонента для экспоненциальной формы записи

Замечания по применению числового форматирования:

0 указывает позицию цифры, которая будет всегда выводиться, даже если она содержит начальный/конечный ноль. 9 также указывает позицию цифры, но если это начальный ноль, то он будет заменен пробелом, а если это конечный ноль и включен режим заполнения, то он будет удален. (Для функции to_number() эти два кода шаблона равнозначны.)

Кодовые символы S, L, D и G представляют знак, символ валюты, десятичную точку и разделитель тысяч, определенные текущей локалью (см. lc_monetary и lc_numeric). Кодовые символы точка и запятая представляют те же самые символы со значениями десятичной точки и разделителя тысяч, но не зависят от локали.

Если в функции to_char() не предусмотрено явного положения для знака, то для него будет зарезервирован один столбец, и этот знак будет привязан к числу (будет находиться слева от него). Если слева от некоторых 9 находится S, знак тоже будет привязан к числу.

Знак, отформатированный с помощью кодов SG, PL или MI, не привязан к числу; например, to_char(-12, 'MI9999') выдает '- 12', но to_char(-12, 'S9999') выдает ' -12'. (Реализация Oracle не позволяет ставить MI перед 9, а наоборот, требует, чтобы 9 шел перед MI.)

TH не преобразует значения меньше нуля и дробные числа.

PL, SG и TH являются расширениями QHB.

Если в функции to_number используются коды шаблонов, не относящиеся к данным, например L или TH, пропускается соответствующее количество входных символов, вне зависимости от того, совпадают ли они с кодом, если только это не символы данных (то есть цифры, знак, десятичная точка или запятая). Например, при указании TH будет пропущено два символа, не обозначающие данные.

Код V в функции to_char умножает входные значения на 10^n, где n — количество цифр после V. В функции to_number код V аналогичным образом делит значения. Функции to_char и to_number не поддерживают использование V в сочетании с десятичной точкой (например, 99.9V99 не допускается).

Код EEEE (экспоненциальная запись) нельзя сочетать с другими кодами форматирования или модификаторами, за исключением кодов цифр и десятичных точек, и он должен находиться в конце строки формата (например, 9.99EEEE — допустимый код).

Некоторые модификаторы, изменяющие поведение, можно применять к любому коду шаблона. Например, FM99.99 — это код 99.99 с модификатором FM. В Таблице 29 приведены коды модификаторов для форматирования чисел.

Таблица 29. Модификаторы кодов шаблонов для форматирования чисел

Модификатор | Описание | Пример
Префикс FM | режим заполнения (подавление концевых нулей и заполняющих пробелов) | FM99.99
Суффикс TH | суффикс порядкового номера в верхнем регистре | 999TH
Суффикс th | суффикс порядкового номера в нижнем регистре | 999th

В Таблице 30 приведены некоторые примеры использования функции to_char.

Таблица 30. Примеры to_char

Выражение | Результат
to_char(current_timestamp, 'Day, DD HH12:MI:SS') | 'Tuesday , 06 05:39:18'
to_char(current_timestamp, 'FMDay, FMDD HH12:MI:SS') | 'Tuesday, 6 05:39:18'
to_char(-0.1, '99.99') | ' -.10'
to_char(-0.1, 'FM9.99') | '-.1'
to_char(-0.1, 'FM90.99') | '-0.1'
to_char(0.1, '0.9') | ' 0.1'
to_char(12, '9990999.9') | ' 0012.0'
to_char(12, 'FM9990999.9') | '0012.'
to_char(485, '999') | ' 485'
to_char(-485, '999') | '-485'
to_char(485, '9 9 9') | ' 4 8 5'
to_char(1485, '9,999') | ' 1,485'
to_char(1485, '9G999') | ' 1 485'
to_char(148.5, '999.999') | ' 148.500'
to_char(148.5, 'FM999.999') | '148.5'
to_char(148.5, 'FM999.990') | '148.500'
to_char(148.5, '999D999') | ' 148,500'
to_char(3148.5, '9G999D999') | ' 3 148,500'
to_char(-485, '999S') | '485-'
to_char(-485, '999MI') | '485-'
to_char(485, '999MI') | '485 '
to_char(485, 'FM999MI') | '485'
to_char(485, 'PL999') | '+485'
to_char(485, 'SG999') | '+485'
to_char(-485, 'SG999') | '-485'
to_char(-485, '9SG99') | '4-85'
to_char(-485, '999PR') | '<485>'
to_char(485, 'L999') | 'DM 485'
to_char(485, 'RN') | ' CDLXXXV'
to_char(485, 'FMRN') | 'CDLXXXV'
to_char(5.2, 'FMRN') | 'V'
to_char(482, '999th') | ' 482nd'
to_char(485, '"Good number:"999') | 'Good number: 485'
to_char(485.8, '"Pre:"999" Post:" .999') | 'Pre: 485 Post: .800'
to_char(12, '99V999') | ' 12000'
to_char(12.4, '99V999') | ' 12400'
to_char(12.45, '99V9') | ' 125'
to_char(0.0004859, '9.99EEEE') | ' 4.86e-04'

Функции и операторы даты/времени

В Таблице 32 приведены имеющиеся функции для обработки значений даты/времени; подробные описания представлены в следующих подразделах. Таблица 31 иллюстрирует поведение основных арифметических операторов (+, * и т. д.). Функции для форматирования описаны в разделе Функции для форматирования типов данных. С основной информацией о типах данных даты/времени можно ознакомиться в разделе Типы даты/времени.

Кроме того, для типов даты/времени имеются обычные операторы сравнения, перечисленные в таблице Операторы сравнения. Даты и метки времени (с часовым поясом или без него) можно сравнивать как угодно, тогда как время (с часовым поясом или без него) и интервалы можно сравнивать только со значениями того же типа. При сравнении метки времени без часового пояса и метки времени с часовым поясом предполагается, что первое значение задано в часовом поясе, установленном параметром конфигурации TimeZone, и оно меняется на UTC для сравнения со вторым значением (которое внутри уже представлено в UTC). Аналогично при сравнении даты с меткой времени предполагается, что значение даты представляет полночь в часовом поясе TimeZone.

Все описанные ниже функции и операторы, принимающие входные данные time или timestamp, фактически представлены в двух вариантах: один принимает time with time zone или timestamp with time zone, а второй — time without time zone или timestamp without time zone. Для краткости эти варианты показаны вместе. Кроме того, операторы + и * представлены в коммутационных (переместительных) парах (например, и date + integer, и integer + date); мы приводим только один вариант из каждой такой пары.

Таблица 31. Операторы даты/времени

Оператор

Описание
Пример(ы)

date + integer → date

Добавляет к дате указанное число дней
date '2001-09-28' + 7 → 2001-10-05

date + interval → timestamp

Добавляет к дате интервал
date '2001-09-28' + interval '1 hour' → 2001-09-28 01:00:00

date + time → timestamp

Добавляет к дате время
date '2001-09-28' + time '03:00' → 2001-09-28 03:00:00

interval + interval → interval

Складывает интервалы
interval '1 day' + interval '1 hour' → 1 day 01:00:00

timestamp + interval → timestamp

Добавляет к временной метке интервал
timestamp '2001-09-28 01:00' + interval '23 hours' → 2001-09-29 00:00:00

time + interval → time

Добавляет к времени интервал
time '01:00' + interval '3 hours' → 04:00:00

- interval → interval

Инвертирует интервал
- interval '23 hours' → -23:00:00

date - date → integer

Вычитает из одной даты другую, выдавая число прошедших дней
date '2001-10-01' - date '2001-09-28' → 3

date - integer → date

Вычитает из даты указанное число дней
date '2001-10-01' - 7 → 2001-09-24

date - interval → timestamp

Вычитает из даты интервал
date '2001-09-28' - interval '1 hour' → 2001-09-27 23:00:00

time - time → interval

Вычитает из одного времени другое
time '05:00' - time '03:00' → 02:00:00

time - interval → time

Вычитает из времени интервал
time '05:00' - interval '2 hours' → 03:00:00

timestamp - interval → timestamp

Вычитает из временной метки интервал
timestamp '2001-09-28 23:00' - interval '23 hours' → 2001-09-28 00:00:00

interval - interval → interval

Вычитает из одного интервала другой
interval '1 day' - interval '1 hour' → 1 day -01:00:00

timestamp - timestamp → interval

Вычитает из одной временной метки другую (преобразуя 24-часовые интервалы в дни подобно justify_hours())
timestamp '2001-09-29 03:00' - timestamp '2001-07-27 12:00' → 63 days 15:00:00

interval * double precision → interval

Умножает интервал на скалярное значение
interval '1 second' * 900 → 00:15:00
interval '1 day' * 21 → 21 days
interval '1 hour' * 3.5 → 03:30:00

interval / double precision → interval

Делит интервал на скалярное значение
interval '1 hour' / 1.5 → 00:40:00

Таблица 32. Функции даты/времени

Функция

Описание
Пример(ы)

age ( timestamp, timestamp ) → interval

Вычитает аргументы, выдавая «символический» результат, который использует годы и месяцы, а не просто дни
age(timestamp '2001-04-10', timestamp '1957-06-13') → 43 years 9 mons 27 days

age ( timestamp ) → interval

Вычитает аргумент из current_date (в полночь)
age(timestamp '1957-06-13') → 62 years 6 mons 10 days

clock_timestamp ( ) → timestamp with time zone

Текущая дата и время (меняется во время выполнения операторов); см. подраздел Текущая дата/время
clock_timestamp() → 2019-12-23 14:39:53.662522-05

current_date → date

Текущая дата; см. подраздел Текущая дата/время
current_date → 2019-12-23

current_time → time with time zone

Текущее время суток; см. подраздел Текущая дата/время
current_time → 14:39:53.662522-05

current_time ( integer ) → time with time zone

Текущее время суток, с ограниченной точностью; см. подраздел Текущая дата/время
current_time(2) → 14:39:53.66-05

current_timestamp → timestamp with time zone

Текущая дата и время (начало текущей транзакции); см. подраздел Текущая дата/время
current_timestamp → 2019-12-23 14:39:53.662522-05

current_timestamp ( integer ) → timestamp with time zone

Текущая дата и время (начало текущей транзакции), с ограниченной точностью; см. подраздел Текущая дата/время
current_timestamp(0) → 2019-12-23 14:39:53-05

date_bin ( interval, timestamp, timestamp ) → timestamp

Укладывает входное значение в заданный интервал, установленный с указанным начальным моментом; см. подраздел date_bin
date_bin('15 minutes', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00') → 2001-02-16 20:35:00

date_part ( text, timestamp ) → double precision

Выдает подполе временной метки (равнозначно extract); см. подраздел EXTRACT, date_part
date_part('hour', timestamp '2001-02-16 20:38:40') → 20

date_part ( text, interval ) → double precision

Выдает подполе интервала (равнозначно extract); см. подраздел EXTRACT, date_part
date_part('month', interval '2 years 3 months') → 3

date_trunc ( text, timestamp ) → timestamp

Усекает временную метку до указанной точности; см. подраздел date_trunc
date_trunc('hour', timestamp '2001-02-16 20:38:40') → 2001-02-16 20:00:00

date_trunc ( text, timestamp with time zone, text ) → timestamp with time zone

Усекает временную метку до указанной точности в заданном часовом поясе; см. подраздел date_trunc
date_trunc('day', timestamptz '2001-02-16 20:38:40+00', 'Australia/Sydney') → 2001-02-16 13:00:00+00

date_trunc ( text, interval ) → interval

Усекает интервал до указанной точности; см. подраздел date_trunc
date_trunc('hour', interval '2 days 3 hours 40 minutes') → 2 days 03:00:00

extract ( поле from timestamp ) → numeric

Выдает подполе временной метки; см. подраздел EXTRACT, date_part
extract(hour from timestamp '2001-02-16 20:38:40') → 20

extract ( поле from interval ) → numeric

Выдает подполе интервала; см. подраздел EXTRACT, date_part
extract(month from interval '2 years 3 months') → 3

isfinite ( date ) → boolean

Проверяет дату на конечность (не +/- бесконечность)
isfinite(date '2001-02-16') → true

isfinite ( timestamp ) → boolean

Проверяет временную метку на конечность (не +/- бесконечность)
isfinite(timestamp 'infinity') → false

isfinite ( interval ) → boolean

Проверяет интервал на конечность (в настоящее время всегда true)
isfinite(interval '4 hours') → true

justify_days ( interval ) → interval

Настраивает интервал так, чтобы 30-дневные периоды времени представлялись как месяцы
justify_days(interval '35 days') → 1 mon 5 days

justify_hours ( interval ) → interval

Настраивает интервал так, чтобы 24-часовые периоды времени представлялись как дни
justify_hours(interval '27 hours') → 1 day 03:00:00

justify_interval ( interval ) → interval

Настраивает интервал, используя justify_days и justify_hours, с дополнительной коррекцией знака
justify_interval(interval '1 mon -1 hour') → 29 days 23:00:00

localtime → time

Текущее время суток; см. подраздел Текущая дата/время
localtime → 14:39:53.662522

localtime ( integer ) → time

Текущее время суток, с ограниченной точностью; см. подраздел Текущая дата/время
localtime(0) → 14:39:53

localtimestamp → timestamp

Текущая дата и время (начало текущей транзакции); см. подраздел Текущая дата/время
localtimestamp → 2019-12-23 14:39:53.662522

localtimestamp ( integer ) → timestamp

Текущая дата и время (начало текущей транзакции), с ограниченной точностью; см. подраздел Текущая дата/время
localtimestamp(2) → 2019-12-23 14:39:53.66

make_date ( год int, месяц int, день int ) → date

Создает дату из полей года, месяца и дня (отрицательное значение года означает год до н. э.)
make_date(2013, 7, 15) → 2013-07-15

make_interval ( [ годы int [, месяцы int [, недели int [, дни int [, часы int [, минуты int [, секунды double precision ]]]]]]] ) → interval

Создает интервал из полей годов, месяцев, неделей, дней, часов, минут и секунд, каждое из которых по умолчанию равно нулю
make_interval(days => 10) → 10 days

make_time ( час int, минута int, секунда double precision ) → time

Создает время из полей часа, минуты и секунды
make_time(8, 15, 23.5) → 08:15:23.5

make_timestamp ( год int, месяц int, день int, час int, минута /b>int, секунда double precision ) → timestamp

Создает метку времени из полей года, месяца, дня, часа, минуты и секунды (отрицательное значение года означает год до н. э.)
make_timestamp(2013, 7, 15, 8, 15, 23.5) → 2013-07-15 08:15:23.5

make_timestamptz ( год int, месяц int, день int, час int, минута int, секунда double precision [, часовой_пояс text ] ) → timestamp with time zone

Создает метку времени с часовым поясом из полей года, месяца, недели, дня, часа, минуты и секунды (отрицательное значение года означает год до н. э.). Если часовой_пояс не указан, используется текущий часовой пояс; в примерах предполагается, что часовой пояс сеанса — Europe/London (Европа/Лондон)
make_timestamptz(2013, 7, 15, 8, 15, 23.5) → 2013-07-15 08:15:23.5+01
make_timestamptz(2013, 7, 15, 8, 15, 23.5, 'America/New\_York') → 2013-07-15 13:15:23.5+01

now ( ) → timestamp with time zone

Текущая дата и время (начало текущей транзакции); см. подраздел Текущая дата/время
now() → 2019-12-23 14:39:53.662522-05

statement_timestamp ( ) → timestamp with time zone

Текущая дата и время (начало текущего оператора); см. подраздел Текущая дата/время
statement_timestamp() → 2019-12-23 14:39:53.662522-05

timeofday ( ) → text

Текущая дата и время (как clock_timestamp, но в виде текстовой строки); см. подраздел Текущая дата/время
timeofday() → Mon Dec 23 14:39:53.662522 2019 EST

transaction_timestamp ( ) → timestamp with time zone

Текущая дата и время (начало текущей транзакции); см. подраздел Текущая дата/время
transaction_timestamp() → 2019-12-23 14:39:53.662522-05

to_timestamp ( double precision ) → timestamp with time zone

Преобразовывает эпоху Unix (секунды с 1970-01-01 00: 00: 00 + 00) в метку времени с часовым поясом
to_timestamp(1284352323) → 2010-09-13 04:32:03+00

В дополнение к этим функциям поддерживается оператор SQL OVERLAPS:

(начало1, конец1) OVERLAPS (начало2, конец2)
(начало1, длительность1) OVERLAPS (начало2, длительность2)

Это выражение возвращает true, когда два периода времени (определенные своими граничными точками) пересекаются, и false, когда они не пересекаются. Граничные точки могут быть указаны как пары дат, времени или меток времени или как дата, время или отметка времени с последующим интервалом. Когда предоставляется пара значений, первым можно записать как начало так и конец периода; OVERLAPS автоматически принимает в качестве начала более раннее значение из пары. Каждый период времени считается представляющим полуоткрытый интервал начало <= время < конец, если только начало и конец не равны — тогда он представляет этот единственный момент времени. Это означает, например, что два периода времени с общей граничной точкой не пересекаются.

SELECT (DATE '2001-02-16', DATE '2001-12-21') OVERLAPS
       (DATE '2001-10-30', DATE '2002-10-30');
Результат: true
SELECT (DATE '2001-02-16', INTERVAL '100 days') OVERLAPS
       (DATE '2001-10-30', DATE '2002-10-30');
Результат: false
SELECT (DATE '2001-10-29', DATE '2001-10-30') OVERLAPS
       (DATE '2001-10-30', DATE '2001-10-31');
Результат: false
SELECT (DATE '2001-10-30', DATE '2001-10-30') OVERLAPS
       (DATE '2001-10-30', DATE '2001-10-31');
Результат: true

При добавлении к значению timestamp with time zone значения interval (или при вычитании из него значения interval) компонент дней увеличивает (или уменьшает) дату timestamp with time zone на указанное количество суток, а время суток не меняется. При пересечении границы летнего времени (когда часовой пояс сеанса распознает летнее время) это означает, что interval '1 day' не обязательно равен interval '24 hours'. Например, в часовом поясе America/Denver (Америка/Денвер):

SELECT timestamp with time zone '2005-04-02 12:00:00-07' + interval '1 day';
Результат: 2005-04-03 12:00:00-06
SELECT timestamp with time zone '2005-04-02 12:00:00-07' + interval '24 hours';
Результат: 2005-04-03 13:00:00-06

Это происходит потому, что вследствие произошедшего 2005-04-03 02:00:00 в часовом поясе America/Denver перехода на летнее время был пропущен час.

Обратите внимание на возможную неоднозначность в поле months возвращаемом функцией age, потому что в разных месяцах разное количество дней. При расчете неполных месяцев QHB использует месяц от более ранней из двух дат. Например, age('2004-06-01', '2004-04-30') использует апрель, и получается 1 mon 1 day, тогда как использование мая даст 1 mon 2 days, потому что в мае 31 день, а в апреле только 30.

Вычитание дат и меток времени тоже может быть сложным. Один концептуально простой способ выполнить вычитание — преобразовать каждое значение в количество секунд, используя EXTRACT(EPOCH FROM ...), а затем вычислить разницу результаты; результатом будет количество секунд между этими двумя значениями. Это позволит учесть количество дней в каждом месяце, смену часовых поясов и переходы на летнее время. Вычитание значений даты или метки времени с помощью оператора «-» возвращает количество дней (24 часа) и часов/минут/секунд между значениями, учитывая те же настройки. Функция age возвращает годы, месяцы, дни и часы/минуты/секунды, выполняя вычитание от поля к полю и затем корректируя отрицательные значения полей. Следующие запросы иллюстрируют различия в этих подходах. Результаты примера были получены для timezone = 'US/Eastern'; между двумя использованными датами существует переход на летнее время:

SELECT EXTRACT(EPOCH FROM timestamptz '2013-07-01 12:00:00') -
       EXTRACT(EPOCH FROM timestamptz '2013-03-01 12:00:00');
Результат: 10537200
SELECT (EXTRACT(EPOCH FROM timestamptz '2013-07-01 12:00:00') -
        EXTRACT(EPOCH FROM timestamptz '2013-03-01 12:00:00'))
        / 60 / 60 / 24;
Результат: 121.958333333333
SELECT timestamptz '2013-07-01 12:00:00' - timestamptz '2013-03-01 12:00:00';
Результат: 121 days 23:00:00
SELECT age(timestamptz '2013-07-01 12:00:00', timestamptz '2013-03-01 12:00:00');
Результат: 4 mons

EXTRACT, date_part

EXTRACT(поле FROM источник)

Функция extract извлекает из значений даты/времени подполя, такие как год или час. Аргумент источник должен быть выражением типа timestamp, time или interval. (Выражения типа date приводятся к timestamp и поэтому также могут использоваться). Аргумент поле — это идентификатор или строка, выбирающая, какое поле извлечь из исходного значения. Функция extract возвращает значения типа numeric. Ниже приведены допустимые имена поля:

century

Век

SELECT EXTRACT(CENTURY FROM TIMESTAMP ’2000-12-16 12:21:13’);
Результат: 20
SELECT EXTRACT(CENTURY FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 21

Первый век начинается в 0001-01-01 00:00:00 н. э., хотя люди в то время этого не знали. Это определение относится ко всем странам с григорианским календарем. Нет века с номером 0, после -1-го века наступает 1-й век.

day

Для значений timestamp это поле дня (месяца) (1-31); для значений interval это количество дней

SELECT EXTRACT(DAY FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 16
SELECT EXTRACT(DAY FROM INTERVAL ’40 days 1 minute’);
Результат: 40

decade

Поле года, разделенное на 10

SELECT EXTRACT(DECADE FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 200

dow

День недели с воскресенья (0) по субботу (6)

SELECT EXTRACT(DOW FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 5

Обратите внимание, что нумерация дней недели в extract отличается от таковой в функции to_char(..., 'D').

doy

День года (1-365/366)

SELECT EXTRACT(DOY FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 47

epoch

Для значений timestamp with time zone это количество секунд с 1970-01-01 00:00:00 UTC (отрицательное для меток времени ранее этой даты); для значений date и timestamp это номинальное количество секунд с 1970-01-01 00:00:00 без учета часового пояса или переходов на летнее время; для значений interval это общее количество секунд в интервале

SELECT EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE ’2001-02-16
20:38:40.12-08’);
Результат: 982384720.12
SELECT EXTRACT(EPOCH FROM INTERVAL ’5 days 3 hours’);
Результат: 442800

Значение эпохи можно преобразовать обратно в timestamp with time zone с помощью функции to_timestamp:

SELECT to_timestamp(982384720.12);
Результат: 2001-02-17 04:38:40.12+00

Имейте в виду, что применение to_timestamp к времени эпохи, извлеченному из значения date или timestamp, может выдать искаженный результат: по сути этот результат будет получен, исходя из предположения, что оригинальное значение было задано в часовом поясе UTC, а это может быть не так.

hour

Поле часа (0-23)

SELECT EXTRACT(HOUR FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 20

isodow

День недели с понедельника (1) по воскресенье (7)

SELECT EXTRACT(ISODOW FROM TIMESTAMP ’2001-02-18 20:38:40’);
Результат: 7

Идентично dow, за исключением воскресенья. Соответствует нумерации дней недели по ISO 8601.

isoyear

Недельный год по ISO 8601, к которому относится дата (неприменимо к интервалам)

SELECT EXTRACT(ISOYEAR FROM DATE ’2006-01-01’);
Результат: 2005
SELECT EXTRACT(ISOYEAR FROM DATE ’2006-01-02’);
Результат: 2006

Каждый недельный год по ISO 8601 начинается с понедельника недели, содержащей 4-е января, поэтому в начале января или конце декабря год по ISO может отличаться от григорианского года. Дополнительную информацию см. в описании поля week.

julian

Юлианская дата, соответствующая дате или метке времени (неприменимо к интервалам). Метки времени, отличающиеся от полуночи по местному времени, приведут к дробным результатам. Дополнительную информацию см. в разделе Юлианские даты.

SELECT EXTRACT(JULIAN FROM DATE '2006-01-01');
Результат: 2453737
SELECT EXTRACT(JULIAN FROM TIMESTAMP '2006-01-01 12:00');
Результат: 2453737.50000000000000000000

microseconds

Поле секунд, включая дробные части, умноженное на 1 000 000; обратите внимание, что оно включает в себя и целые секунды

SELECT EXTRACT(MICROSECONDS FROM TIME ’17:12:28.5’);
Результат: 28500000

millennium

Тысячелетие

SELECT EXTRACT(MILLENNIUM FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 3

Годы в 20 века относятся ко второму тысячелетию. Третье тысячелетие началось 1 января 2001 года.

milliseconds

Поле секунд, включая дробные части, умноженное на 1000. Обратите внимание, что оно включает в себя и целые секунды.

SELECT EXTRACT(MILLISECONDS FROM TIME ’17:12:28.5’);
Результат: 28500

minute

Поле минут (0-59)

SELECT EXTRACT(MINUTE FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 38

month

Для значений timestamp это номер месяца в году (1-12); для значений interval это остаток от деления числа месяцев (по модулю) на 12 (0-11)

SELECT EXTRACT(MONTH FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 2
SELECT EXTRACT(MONTH FROM INTERVAL ’2 years 3 months’);
Результат: 3
SELECT EXTRACT(MONTH FROM INTERVAL ’2 years 13 months’);
Результат: 1

quarter

Квартал года (1-4), к которому относится дата

SELECT EXTRACT(QUARTER FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 1

second

Поле секунд, включая дробные части

SELECT EXTRACT(SECOND FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 40
SELECT EXTRACT(SECOND FROM TIME ’17:12:28.5’);
Результат: 28.5

timezone

Смещение часового пояса от UTC, измеряется в секундах. Положительные значения соответствуют часовым поясам к востоку от UTC, отрицательные — к западу от UTC. (Строго говоря, QHB не использует UTC, потому что корректировочные секунды не обрабатываются).

timezone_hour

Часовая составляющая смещения часового пояса

timezone_minute

Минутная составляющая смещения часового пояса

week

Номер недели в году по недельному календарю ISO 8601. По определению, недели ISO начинаются с понедельника, а первая неделя года содержит 4 января этого года. Другими словами, первый четверг года находится в 1 неделе этого года.

В системе нумерации недель ISO первые числа января могут быть частью 52-й или 53-й недели предыдущего года, а последние числа декабря — частью первой недели следующего года. Например, 2005-01-01 является частью 53-й недели 2004 года, 2006-01-01 — частью 52-й недели 2005 года, а 2012-12-31 — частью первой недели 2013 года. Для получения согласованных результатов рекомендуется вместе с полем week использовать поле isoyear.

SELECT EXTRACT(WEEK FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 7

year

Поле года. Имейте в виду, что 0-го год н. э. (AD) не существует, поэтому вычитать годы до н. э. (BC) из годов после н. э. следует с осторожностью.

SELECT EXTRACT(YEAR FROM TIMESTAMP ’2001-02-16 20:38:40’);
Результат: 2001

Примечание
Когда входное значение равно +/- бесконечность, функция extract возвращает +/- бесконечность для монотонно увеличивающихся полей (epoch, julian, year, isoyear, decade, century и millennium). Для других полей возвращается NULL.

Функция extract в первую очередь предназначена для вычислительной обработки. Форматирование значений даты/времени для отображения описано в разделе Функции для форматирования типов данных.

Функция date_part смоделирована на традиционном Ingres, равнозначном стандартной функции SQL extract:

date_part('поле', источник)

Обратите внимание, что здесь параметр поле должен быть строковым значением, а не именем. Допустимые имена полей для date_part такие же, как и для extract. По историческим причинам функция date_part возвращает значения типа double precision, что может в некоторых случаях привести к потере точности. Поэтому вместо нее рекомендуется использовать extract.

SELECT date_part('day', TIMESTAMP '2001-02-16 20:38:40');
Результат: 16

SELECT date_part('hour', INTERVAL '4 hours 3 minutes');
Результат: 4

date_trunc

Функция date_trunc концептуально схожа с функцией trunc для чисел.

date_trunc(поле, источник [, часовой_пояс ])

Здесь источник — это выражение значения типа timestamp, timestamp with time zone или interval. (Значения типа date и time автоматически приводятся к типам timestamp и interval соответственно). Аргумент поле определяет, с какой точностью усечь входное значение. Возвращаемое значение также имеет тип timestamp, timestamp with time zone или interval, а кроме того, все его поля, менее значимые, чем заданное, установлены в значение ноль (или один для дня и месяца).

Допустимые значения для аргумента поле:

microseconds
milliseconds
second
minute
hour
day
week
month
quarter
year
decade
century
millennium

Когда входное значение имеет тип timestamp with time zone, усечение выполняется относительно определенного часового пояса; например, усечение до поля day (день) выдает значение полуночи в этой зоне. По умолчанию усечение выполняется относительно текущего значения параметра TimeZone, но можно задать другой часовой пояс, указав необязательный аргумент часовой_пояс. Название часового пояса можно задать любым из способов, описанных в разделе Часовые пояса.

Часовой пояс нельзя указывать при обработке входных данных типа timestamp without time zone или interval.

Примеры (предполагается, что местный часовой пояс — America/New_York (Америка/Нью-Йорк)):

SELECT date_trunc('hour', TIMESTAMP '2001-02-16 20:38:40');
Результат: 2001-02-16 20:00:00

SELECT date_trunc('year', TIMESTAMP '2001-02-16 20:38:40');
Результат: 2001-01-01 00:00:00

SELECT date_trunc('day', TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40+00');
Результат: 2001-02-16 00:00:00-05

SELECT date_trunc('day', TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40+00', 'Australia/Sydney');
Результат: 2001-02-16 08:00:00-05

SELECT date_trunc('hour', INTERVAL '3 days 02:47:33');
Результат: 3 days 02:00:00

date_bin

Функция date_bin «укладывает» входную метку времени в заданный интервал (шаг), установленный с указанным начальным моментом.

date_bin(шаг, источник, начальный_момент)

Здесь источник — это выражение значения типа timestamp или timestamp with time zone. (Значения типа date автоматически приводятся к типу timestamp.) Под шагом подразумевается выражение значения типа interval. Возвращаемое значение, также имеющее тип timestamp или timestamp with time zone, отмечает начало позиции, в которую помещается источник.

Примеры:

SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-01-01');
Результат: 2020-02-11 15:30:00

SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-01-01 00:02:30');
Результат: 2020-02-11 15:32:30

В случае целых единиц измерения (1 минута, 1 час и т. д.), она дает тот же результат, что и аналогичный вызов функции date_trunc, но разница в том, что date_bin может усечь дату до произвольного интервала.

Шаговый интервал должен быть больше нуля и не может содержать единицы измерения, начиная с месяца, и выше.

AT TIME ZONE

Оператор AT TIME ZONE преобразует метку времени без часового пояса в метку времени с часовым поясом и обратно, а значения типа time with time zone пересчитывает для разных часовых поясов. В Таблице 33 показаны его варианты.

Таблица 33. Варианты AT TIME ZONE

Оператор

Описание
Пример(ы)

timestamp without time zone AT TIME ZONE часовой_пояс → timestamp with time zone

Преобразует заданную метку времени без часового пояса в метку времени с часовым поясом, в предположении, что заданное значение является определенным часовым поясом.
timestamp '2001-02-16 20:38:40' at time zone 'America/Denver' → 2001-02-17 03:38:40+00

timestamp with time zone AT TIME ZONE часовой_пояс → timestamp without time zone

Преобразует заданную метку времени с часовым поясом в метку времени без часового пояса, соответствующую времени в указанном часовом поясе.
timestamp with time zone '2001-02-16 20:38:40-05' at time zone 'America/Denver' → 2001-02-16 18:38:40

time with time zone AT TIME ZONE часовой_пояс → time with time zone

Пересчитывает заданное время с часовым поясом для нового часового пояса. Поскольку дата не предоставляется, в расчете используется действующее в настоящий момент смещение целевого часового пояса от UTC.
time with time zone '05:34:17-05' at time zone 'UTC' → 10:34:17+00

В этих выражениях желаемый часовой_пояс можно задать либо в виде текстовой строки (например 'America/Los_Angeles'), либо в виде интервала (например INTERVAL '-08:00'). В случае с текстом наименование часового пояса можно указать любым из способов, описанных в разделе Часовые пояса. Вариант с интервалом полезен только для часовых поясов с фиксированным смещением от UTC, поэтому на практике он не очень распространен

Примеры (предполагается, что текущее значение параметра TimeZone — America/ Los_Angeles (Америка/Лос-Анджелес)):

SELECT TIMESTAMP '2001-02-16 20:38:40' AT TIME ZONE 'America/Denver';
Результат: 2001-02-16 19:38:40-08

SELECT TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40-05' AT TIME ZONE 'America/Denver';
Результат: 2001-02-16 18:38:40

SELECT TIMESTAMP '2001-02-16 20:38:40-05' AT TIME ZONE 'Asia/Tokyo' AT TIME ZONE 'America/Chicago';
Результат: 2001-02-16 05:38:40

В первом примере часовой пояс добавляется к значению, в котором он отсутствует, и полученное значение отображается с использованием текущего значения параметра TimeZone. Во втором примере значение метки времени с часовым поясом перемещается в указанный часовой пояс, и возвращается значение без часового пояса. Это позволяет хранить и отображать значения, отличные от текущего значения параметра TimeZone. В третьем примере время Токио преобразуется во время Чикаго.

Функция timezone (часовой_пояс, метка_времени) равнозначна совместимой с SQL конструкции метка_времени AT TIME ZONE часовой_пояс.

Текущая дата/время

QHB предоставляет ряд функций, которые возвращают значения, зависящие от текущей даты и времени. Все эти функции, соответствующие стандарту SQL, возвращают значения, опирающиеся на время начала текущей транзакции:

CURRENT_DATE
CURRENT_TIME
CURRENT_TIMESTAMP
CURRENT_TIME(точность)
CURRENT_TIMESTAMP(точность)
LOCALTIME
LOCALTIMESTAMP
LOCALTIME(точность)
LOCALTIMESTAMP(точность)

CURRENT_TIME и CURRENT_TIMESTAMP выдают значения с часовым поясом; LOCALTIME и LOCALTIMESTAMP выдают значения без часового пояса.

CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIME и LOCALTIMESTAMP могут дополнительно принимать параметр точности, благодаря которому результат округляется до заданного в нем знака после запятой в поле секунд. Без параметра точности результат дается со всей возможной точностью.

Несколько примеров:

SELECT CURRENT_TIME;
Результат: 14:39:53.662522-05

SELECT CURRENT_DATE;
Результат: 2001-12-23

SELECT CURRENT_TIMESTAMP;
Результат: 2001-12-23 14:39:53.662522-05

SELECT CURRENT_TIMESTAMP(2);
Результат: 2001-12-23 14:39:53.66-05

SELECT LOCALTIMESTAMP;
Результат: 2001-12-23 14:39:53.662522

Поскольку эти функции возвращают время начала текущей транзакции, их значения во время транзакции не изменяются. Это считается особенностью реализации: ее цель состоит в том, чтобы позволить одной транзакции иметь согласованное представление о «текущем» времени, благодаря чему у разных изменений в рамках одной транзакции будут одинаковые метки времени.

Примечание
В других СУБД эти значения могут меняться чаще.

также предоставляет функции, которые возвращают время начала текущего оператора, а также фактическое текущее время в момент вызова функции. Полный список функций времени, не соответствующих стандарту SQL:

transaction_timestamp()
statement_timestamp()
clock_timestamp()
timeofday()
now()

Функция transaction_timestamp() равнозначна функции CURRENT_TIMESTAMP, но названа так, чтобы явно отражать, что именно она возвращает. Функция statement_timestamp() возвращает время начала текущего оператора (а точнее, время получения последнего командного сообщения от клиента). Функции statement_timestamp() и transaction_timestamp() возвращают одинаковое значение в ходе первой команды транзакции, но при выполнении последующих команд их результаты могут отличаться. Функция clock_timestamp() возвращает фактическое текущее время, и поэтому ее значение меняется даже в рамках одной команды SQL. Функция timeofday() существует в QHB по историческим причинам. Как и clock_timestamp(), она возвращает фактическое текущее время, но в виде форматированной строки типа text, а не значения timestamp with time zone. Функция now() представляет собой традиционный для QHB эквивалент функции transaction_timestamp().

Все типы данных даты/времени также принимают специальное буквальное значение now для указания текущей даты и времени (опять же, интерпретируемых как время начала транзакции). Таким образом, все три следующих запроса возвращают один и тот же результат:

SELECT CURRENT_TIMESTAMP;
SELECT now();
SELECT TIMESTAMP ’now’; -- но см. совет ниже

Совет
Не используйте третью форму при указании значения, которое будет вычисляться позднее, например, в предложении DEFAULT для столбца таблицы. Система преобразует now в значение timestamp сразу после анализа константы, поэтому когда потребуется такое значение по умолчанию, будет использовано время создания таблицы! Первые две формы не будут вычисляться, пока не понадобится значение по умолчанию, так как они являются вызовами функций. Таким образом, они выдадут желаемое поведение к моменту добавления строки в таблицу. (См. также подраздел Специальные значения)

Задержка выполнения

В случае необходимости в задержке выполнения серверного процесса, этого можно добиться с помощью следующих функций:

pg_sleep ( double precision )
pg_sleep_for ( interval )
pg_sleep_until ( timestamp with time zone )

Функция pg_sleep переводит процесс текущего сеанса в спящий режим вплоть до истечения указанного числа секунд (можно указать дробное число). Вспомогательная функция pg_sleep_for позволяет задать длительность спящего режима в виде значения типа interval. Вспомогательная функция pg_sleep_until позволяет задать определенное время выходя из спящего режима. Например:

SELECT pg_sleep(1.5);
SELECT pg_sleep_for('5 minutes');
SELECT pg_sleep_until('tomorrow 03:00');

Примечание
Эффективное разрешение интервала задержки зависит от платформы; обычно это 0,01 секунды. Длительность спящего режима не будет меньше указанного времени. Задержка может быть и дольше, в зависимости от таких факторов, как, например, нагрузка на сервер. В частности, функция pg_sleep_until не гарантирует выход из спящего режима именно в указанное время, но это точно не случится раньше.

ВНИМАНИЕ!
При вызове pg_sleep или ее вариантов убедитесь, что ваш сеанс не содержит больше блокировок, чем это необходимо. В противном случае другим сеансам, возможно, придется ждать завершения вашего спящего режима, замедляя работу всей системы.

Функции поддержки перечислений

Для перечислимых типов (описанных в разделе Перечислимые типы), существует несколько функций, позволяющих программировать чище, без жесткого кодирования конкретных значений перечислимого типа. Эти функции приведены в Таблице 34. В примерах предполагается, что перечислимый тип создан как:

CREATE TYPE rainbow AS ENUM (’red’, ’orange’, ’yellow’, ’green’, ’blue’, ’purple’);

Таблица 34. Функции поддержки перечислений

Функция

Описание
Пример(ы)

enum_first ( anyenum ) → anyenum

Возвращает первое значение входных данных перечислимого типа.
enum_first(null::rainbow) → red

enum_last ( anyenum ) → anyenum

Возвращает последнее значение входных данных перечислимого типа.
enum_last(null::rainbow) → purple

enum_range ( anyenum ) → anyarray

Возвращает все значения входных данных перечислимого типа в упорядоченном массиве.
enum_range(null::rainbow) → {red,orange,yellow,​green,blue,purple}

enum_range ( anyenum, anyenum ) → anyarray

Возвращает диапазон между двумя заданными значениями перечисления в виде упорядоченного массива. Значения должны быть принадлежать одному перечислимому типу. Если первый параметр имеет значение NULL, результат будет начинаться с первого значения перечислимого типа. Если второй параметр имеет значение NULL, результат будет заканчиваться последним значением перечислимого типа.
enum_range('orange'::rainbow, 'green'::rainbow) → {orange,yellow,green}
enum_range(NULL, 'green'::rainbow) → {red,orange,​yellow,green}
enum_range('orange'::rainbow, NULL) → {orange,yellow,green,​blue,purple}

Обратите внимание, что за исключением формы enum_range с двумя аргументами, эти функции игнорируют конкретное переданное им значение; их интересует только его объявленный тип данных. Может быть передан либо NULL, либо конкретное значение этого типа, при этом результат будет одинаковым. Как правило, эти функции применяются к столбцу таблицы или аргументу функции, а не к жестко закодированному имени типа, как показано в примерах.

Геометрические функции и операторы

Для геометрических типов point (точка), box (прямоугольник), lseg (отрезок), line (прямая), path (путь), polygon (многоугольник) и circle (окружность) существует большой набор встроенных вспомогательных функций и операторов, приведенных в Таблице 35, Таблице 36 и Таблице 37.

Таблица 35. Геометрические операторы

Оператор

Описание
Пример(ы)

геометрический_тип + point → геометрический_тип

Добавляет координаты второго аргумента типа point к координатам каждой точки первого аргумента, тем самым осуществляя перенос объекта. Имеется для типов point, box, path, circle.
box '(1,1),(0,0)' + point '(2,0)' → (3,1),(2,0)

path + path → path

Конкатенирует два открытых пути (возвращает NULL, если один из путей замкнут).
path '[(0,0),(1,1)]' + path '[(2,2),(3,3),(4,4)]' → [(0,0),(1,1),(2,2),(3,3),(4,4)]

геометрический_тип - point → геометрический_тип

Вычитает координаты второго аргумента типа point из координат каждой точки первого аргумента, тем самым осуществляя перенос объекта. Имеется для типов point, box, path, circle.
box '(1,1),(0,0)' - point '(2,0)' → (-1,1),(-2,0)

геометрический_тип * point → геометрический_тип

Умножает каждую точку первого аргумента на второй аргумент типа point (воспринимая точки как комплексные числа, представленные действительной и мнимой частями, и проводя стандартное комплексное умножение). Если интерпретировать второй аргумент типа point как вектор, это будет равнозначно увеличению размера этого объекта и расстояния от начала координат при помощи длины этого вектора с поворотом объекта против часовой стрелки относительно начала координат на угол, равный углу между вектором и осью x. Имеется для типов point, box,[a] path, circle.
path '((0,0),(1,0),(1,1))' * point '(3.0,0)' → ((0,0),(3,0),(3,3))
path '((0,0),(1,0),(1,1))' * point(cosd(45), sind(45)) → ((0,0),​(0.7071067811865475,0.7071067811865475),​(0,1.414213562373095))

геометрический_тип / point → геометрический_тип

Делит каждую точку первого аргумента на второй аргумент типа point (воспринимая точки как комплексные числа, представленные действительной и мнимой частями, и проводя стандартное комплексное деление). Если интерпретировать второй аргумент типа point как вектор, это будет равнозначно уменьшению размера этого объекта и расстояния от начала координат при помощи длины этого вектора с поворотом объекта по часовой стрелки относительно начала координат на угол, равный углу между вектором и осью x. Имеется для типов point, box,[a] path, circle.
path '((0,0),(1,0),(1,1))' / point '(2.0,0)' → ((0,0),(0.5,0),(0.5,0.5))
path '((0,0),(1,0),(1,1))' / point(cosd(45), sind(45)) → ((0,0),​(0.7071067811865476,-0.7071067811865476),​(1.4142135623730951,0))

@-@ геометрический_тип → double precision

Вычисляет общую длину. Имеется для типов lseg, path.
@-@ path '[(0,0),(1,0),(1,1)]' → 2

@@ геометрический_тип → point

Вычисляет центральную точку. Имеется для типов box, lseg, polygon, circle.
@@ box '(2,2),(0,0)' → (1,1)

box # box → box

Вычисляет пересечение двух прямоугольников или возвращает NULL, если такого пересечения нет.
box '(2,2),(-1,-1)' # box '(1,1),(-2,-2)' → (1,1),(-1,-1)

геометрический_тип ## геометрический_тип → point

Вычисляет ближайшую к первому объекту точку на втором объекте. Имеется для этих пар типов: (point, box), (point, lseg), (point, line), (lseg, box), (lseg, lseg), (line, lseg).
point '(0,0)' ## lseg '[(2,0),(0,2)]' → (1,1)

геометрический_тип <-> геометрический_тип → double precision

Вычисляет расстояние между объектами. Имеется для всех геометрических типов, кроме polygon, для всех сочетаний типа point с другим геометрическим типом, а также для этих пар типов: (box, lseg), (lseg, line), (polygon, circle) (и для этих пар в обратном порядке).
circle '<(0,0),1>' <-> circle '<(5,0),1>' → 3

геометрический_тип @> геометрический_тип → boolean

Первый объект содержит второй? Имеется для этих пар типов: (box, point), (box, box), (path, point), (polygon, point), (polygon, polygon), (circle, point), (circle, circle).
circle '<(0,0),2>' @> point '(1,1)' → t

геометрический_тип <@ геометрический_тип → boolean

Первый объект содержится в (находится на) втором? Имеется для этих пар типов: (point, box), (point, lseg), (point, line), (point, path), (point, polygon), (point, circle), (box, box), (lseg, box), (lseg, line), (polygon, polygon), (circle, circle).
point '(1,1)' <@ circle '<(0,0),2>' → t

геометрический_тип && геометрический_тип → boolean

Эти объекты пересекаются? (Для результата true достаточно одной общей точки.) Имеется для типов box, polygon, circle.
box '(1,1),(0,0)' && box '(2,2),(0,0)' → t

геометрический_тип << геометрический_тип → boolean

Первый объект строго слева от второго? Имеется для типов point, box, polygon, circle.
circle '<(0,0),1>' << circle '<(5,0),1>' → t

геометрический_тип >> геометрический_тип → boolean

Первый объект строго справа от второго? Имеется для типов point, box, polygon, circle.
circle '<(5,0),1>' >> circle '<(0,0),1>' → t

геометрический_тип &< геометрический_тип → boolean

Первый объект не простирается правее второго? Имеется для типов box, polygon, circle.
box '(1,1),(0,0)' &< box '(2,2),(0,0)' → t

геометрический_тип &> геометрический_тип → boolean

Первый объект не простирается левее второго? Имеется для типов box, polygon, circle.
box '(3,3),(0,0)' &> box '(2,2),(0,0)' → t

геометрический_тип <<| геометрический_тип → boolean

Первый объект строго ниже второго? Имеется для типов point, box, polygon, circle.
box '(3,3),(0,0)' <<| box '(5,5),(3,4)' → t

геометрический_тип |>> геометрический_тип → boolean

Первый объект строго выше второго? Имеется для типов point, box, polygon, circle.
box '(5,5),(3,4)' |>> box '(3,3),(0,0)' → t

геометрический_тип &<| геометрический_тип → boolean

Первый объект не простирается выше второго? Имеется для типов box, polygon, circle.
box '(1,1),(0,0)' &<| box '(2,2),(0,0)' → t

геометрический_тип |&> геометрический_тип → boolean

Первый объект не простирается ниже второго? Имеется для типов box, polygon, circle.
box '(3,3),(0,0)' |&> box '(2,2),(0,0)' → t

box <^ box → boolean

Первый объект ниже второго (объекты могут соприкасаться краями)?
box '((1,1),(0,0))' <^ box '((2,2),(1,1))' → t

box >^ box → boolean

Первый объект выше второго (объекты могут соприкасаться краями)?
box '((2,2),(1,1))' >^ box '((1,1),(0,0))' → t

геометрический_тип ?# геометрический_тип → boolean

Эти объекты пересекаются? Имеется для этих пар типов: (box, box), (lseg, box), (lseg, lseg), (lseg, line), (line, box), (line, line), (path, path).
lseg '[(-1,0),(1,0)]' ?# box '(2,2),(-2,-2)' → t

?- line → boolean
?- lseg → boolean

Линия горизонтальна?
?- lseg '[(-1,0),(1,0)]' → t

point ?- point → boolean

Точки выровнены по горизонтали (то есть имеют одинаковую координату по y)?
point '(1,0)' ?- point '(0,0)' → t

?| line → boolean
?| lseg → boolean

Линия вертикальна?
?| lseg '[(-1,0),(1,0)]' → f

point ?| point → boolean

Точки выровнены по вертикали (то есть имеют одинаковую координату по x)?
point '(0,1)' ?| point '(0,0)' → t

line ?-| line → boolean
lseg ?-| lseg → boolean

Линии перпендикулярны?
lseg '[(0,0),(0,1)]' ?-| lseg '[(0,0),(1,0)]' → t

line ?|| line → boolean
lseg ?|| lseg → boolean

Линии параллельны?
lseg '[(-1,0),(1,0)]' ?|| lseg '[(-1,2),(1,2)]' → t

геометрический_тип ~= геометрический_тип → boolean

Эти объекты совпадают? Имеется для типов point, box, polygon, circle.
polygon '((0,0),(1,1))' ~= polygon '((1,1),(0,0))' → t

[a] При «повороте» прямоугольника с помощью этих операторов перемещаются только его угловые точки: стороны этого прямоугольника по-прежнему считаются параллельными осям. Как следствие, в отличие от истинного поворота, при этой операции размер прямоугольника меняется.

ВНИМАНИЕ!
Обратите внимание, что оператор «тождественности», ~=, представляет собой обычную проверку равенства для типов point, box, polygon и circle. Для некоторых геометрических типов также имеется оператор =, но = проверяет только равенство площадей. Другие скалярные операторы сравнения (<= и т. п.) для тех типов, для которых они имеются, также сравнивают площади.

Таблица 36. Геометрические функции

Функция

Описание
Пример(ы)

area ( геометрический_тип ) → double precision

Вычисляет площадь. Имеется для типов box, path, circle. Входное значение типа path должно быть замкнутым, иначе возвращается NULL. Кроме того, если этот путь является самопересекающимся, результат может не иметь смысла.
area(box '(2,2),(0,0)') → 4

center ( геометрический_тип ) → point

Вычисляет центральную точку. Имеется для типов box, circle.
center(box '(1,2),(0,0)') → (0.5,1)

diagonal ( box ) → lseg

Извлекает диагональ прямоугольника в виде отрезка (аналогично lseg(box)).
diagonal(box '(1,2),(0,0)') → [(1,2),(0,0)]

diameter ( circle ) → double precision

Вычисляет диаметр окружности.
diameter(circle '<(0,0),2>') → 4

height ( box ) → double precision

Вычисляет вертикальный размер прямоугольника.
height(box '(1,2),(0,0)') → 2

isclosed ( path ) → boolean

Путь замкнутый?
isclosed(path '((0,0),(1,1),(2,0))') → t

isopen ( path ) → boolean

Путь открытый?
isopen(path '[(0,0),(1,1),(2,0)]') → t

length ( геометрический_тип ) → double precision

Вычисляет общую длину. Имеется для типов lseg, path.
length(path '((-1,0),(1,0))') → 4

npoints ( геометрический_тип ) → integer

Возвращает количество точек. Имеется для типов path, polygon.
npoints(path '[(0,0),(1,1),(2,0)]') → 3

pclose ( path ) → path

Преобразует путь в замкнутую форму.
pclose(path '[(0,0),(1,1),(2,0)]') → ((0,0),(1,1),(2,0))

popen ( path ) → path

Преобразует путь в открытую форму.
popen(path '((0,0),(1,1),(2,0))') → [(0,0),(1,1),(2,0)]

radius ( circle ) → double precision

Вычисляет радиус окружности.
radius(circle '<(0,0),2>') → 2

slope ( point, point ) → double precision

Вычисляет наклон линии, проведенной через две точки.
slope(point '(0,0)', point '(2,1)') → 0.5

width ( box ) → double precision

Вычисляет горизонтальный размер прямоугольника.
width(box '(1,2),(0,0)') → 1

Таблица 37. Функции преобразования геометрических типов

Функция

Описание
Пример(ы)

box ( circle ) → box

Вычисляет прямоугольник, вписанный в окружность.
box(circle '<(0,0),2>') → (1.414213562373095,1.414213562373095),​(-1.414213562373095,-1.414213562373095)

box ( point ) → box

Преобразует точку в пустой прямоугольник.
box(point '(1,0)') → (1,0),(1,0)

box ( point, point ) → box

Преобразует любые две угловые точки в прямоугольник.
box(point '(0,1)', point '(1,0)') → (1,1),(0,0)

box ( polygon ) → box

Вычисляет окружающий прямоугольник для многоугольника.
box(polygon '((0,0),(1,1),(2,0))') → (2,1),(0,0)

bound_box ( box, box ) → box

Вычисляет окружающий прямоугольник для двух прямоугольников.
bound_box(box '(1,1),(0,0)', box '(4,4),(3,3)') → (4,4),(0,0)

circle ( box ) → circle

Вычисляет наименьшую окружность, вмещающую прямоугольник.
circle(box '(1,1),(0,0)') → <(0.5,0.5),0.7071067811865476>

circle ( point, double precision ) → circle

Строит окружность по центру и радиусу.
circle(point '(0,0)', 2.0) → <(0,0),2>

circle ( polygon ) → circle

Преобразует многоугольник в окружность. Центром окружности является среднее арифметическое координат вершин многоугольника, а радиусом — среднее расстояние от вершин многоугольника до этого центра.
circle(polygon '((0,0),(1,3),(2,0))') → <(1,1),1.6094757082487299>

line ( point, point ) → line

Преобразует две точки в проходящую через них линию.
line(point '(-1,0)', point '(1,0)') → {0,-1,0}

lseg ( box ) → lseg

Извлекает диагональ прямоугольника в виде отрезка.
lseg(box '(1,0),(-1,0)') → [(1,0),(-1,0)]

lseg ( point, point ) → lseg

Строит отрезок по двум конечным точкам.
lseg(point '(-1,0)', point '(1,0)') → [(-1,0),(1,0)]

path ( polygon ) → path

Преобразует многоугольник в замкнутый путь с тем же списком точек.
path(polygon '((0,0),(1,1),(2,0))') → ((0,0),(1,1),(2,0))

point ( double precision, double precision ) → point

Строит точку по заданным координатам.
point(23.4, -44.5) → (23.4,-44.5)

point ( box ) → point

Вычисляет центр прямоугольника.
point(box '(1,0),(-1,0)') → (0,0)

point ( circle ) → point

Вычисляет центр окружности.
point(circle '<(0,0),2>') → (0,0)

point ( lseg ) → point

Вычисляет центр отрезка.
point(lseg '[(-1,0),(1,0)]') → (0,0)

point ( polygon ) → point

Вычисляет центр многоугольника (среднее арифметическое координат его вершин).
point(polygon '((0,0),(1,1),(2,0))') → (1,0.3333333333333333)

polygon ( box ) → polygon

Преобразует прямоугольник в многоугольник с 4 вершинами.
polygon(box '(1,1),(0,0)') → ((0,0),(0,1),(1,1),(1,0))

polygon ( circle ) → polygon

Преобразует окружность в многоугольник с 12 вершинами.
polygon(circle '<(0,0),2>') → ((-2,0),​(-1.7320508075688774,0.9999999999999999),​(-1.0000000000000002,1.7320508075688772),​(-1.2246063538223773e-16,2),​(0.9999999999999996,1.7320508075688774),​(1.732050807568877,1.0000000000000007),​(2,2.4492127076447545e-16),​(1.7320508075688776,-0.9999999999999994),​(1.0000000000000009,-1.7320508075688767),​(3.673819061467132e-16,-2),​(-0.9999999999999987,-1.732050807568878),​(-1.7320508075688767,-1.0000000000000009))

polygon ( integer, circle ) → polygon

Преобразует окружность в многоугольник с n вершинами.
polygon(4, circle '<(3,0),1>') → ((2,0),​(3,1),​(4,1.2246063538223773e-16),​(3,-1))

polygon ( path ) → polygon

Преобразует замкнутый путь в многоугольник с тем же списком вершин.
polygon(path '((0,0),(1,1),(2,0))') → ((0,0),(1,1),(2,0))

К двум числам, составляющим значение типа point можно обратиться как к элементам массива с индексами 0 и 1. Например, если t.p — столбец типа point, то SELECT p[0] FROM t возвращает координату X, а UPDATE t SET p[1] = ... изменяет координату Y. Таким же образом значение типа box или lseg можно рассматривать как массив из двух значений типа point.

Функции и операторы для сетевых адресов

Типы cidr и inet, предназначенные для сетевых IP-адресов, поддерживают обычные операторы сравнения, приведенные в таблице Операторы сравнения, а также специальные операторы и функции, приведенные в Таблице 38 и Таблице 39.

Любое значение типа cidr можно привести к типу inet неявно, поэтому операторы и функции, показанные ниже для типа inet, также работают со значениями типа cidr. (Там, где для inet cidr показаны разные функции, причина этого в том, что в данном случае их поведение с этими двумя типами различается.) Кроме того, разрешено приведение значения типа inet к типу cidr. При этому все биты справа от сетевой маски молча обнуляются, чтобы создать допустимое значение типа cidr.

Таблица 38. Операторы для IP-адресов

Оператор

Описание
Пример(ы)

inet << inet → boolean

Первая подсеть строго содержится во второй? Этот и следующие четыре оператора проверяют вхождение одной подсети в другую. Они рассматривают только сетевые части двух заданных адресов (игнорируя все биты справа от сетевой маски) и определяют, идентична ли одна сеть другой или является ее подсетью.
inet '192.168.1.5' << inet '192.168.1/24' → t
inet '192.168.0.5' << inet '192.168.1/24' → f
inet '192.168.1/24' << inet '192.168.1/24' → f

inet <<= inet → boolean

Первая подсеть содержится во второй или равна ей?
inet '192.168.1/24' <<= inet '192.168.1/24' → t

inet >> inet → boolean

Первая подсеть строго содержит вторую?
inet '192.168.1/24' >> inet '192.168.1.5' → t

inet >>= inet → boolean

Первая подсеть содержит вторую или равна ей?
inet '192.168.1/24' >>= inet '192.168.1/24' → t

inet && inet → boolean

Одна из двух подсетей содержит другую или равна ей?
inet '192.168.1/24' && inet '192.168.1.80/28' → t
inet '192.168.1/24' && inet '192.168.2.0/28' → f

~ inet → inet

Вычисляет результат побитового НЕ.
~ inet '192.168.1.6' → 63.87.254.249

inet & inet → inet

Вычисляет результат побитового И.
inet '192.168.1.6' & inet '0.0.0.255' → 0.0.0.6

inet | inet → inet

Вычисляет результат побитового ИЛИ.
inet '192.168.1.6' | inet '0.0.0.255' → 192.168.1.255

inet + bigint → inet

Добавляет смещение к адресу.
inet '192.168.1.6' + 25 → 192.168.1.31

bigint + inet → inet

Добавляет смещение к адресу.
200 + inet '::ffff:fff0:1' → ::ffff:255.240.0.201

inet - bigint → inet

Вычитает смещение из адреса.
inet '192.168.1.43' - 36 → 192.168.1.7

inet - inet → bigint

Вычисляет разность двух адресов.
inet '192.168.1.43' - inet '192.168.1.19' → 24
inet '::1' - inet '::ffff:1' → -4294901760

Таблица 39. Функции для IP-адресов

Функция

Описание
Пример(ы)

abbrev ( inet ) → text

Создает сокращенный формат отображения адреса в виде текста. (Результат получается тот же, что и у функции вывода inet; «сокращенным» он является только в сравнении с результатом явного приведения к типу text, которое по историческим причинам никогда не будет убирать часть с сетевой маской.)
abbrev(inet '10.1.0.0/32') → 10.1.0.0

abbrev ( cidr ) → text

Создает сокращенный формат отображения адреса в виде текста. (Это сокращение заключается в отбрасывании полностью нулевых октетов справа от сетевой маски; другие примеры см. в таблице Примеры вводимых значений типа cidr.)
abbrev(cidr '10.1.0.0/16') → 10.1/16

broadcast ( inet ) → inet

Вычисляет широковещательный адрес для сети в заданном адресе.
broadcast(inet '192.168.1.5/24') → 192.168.1.255/24

family ( inet ) → integer

Возвращает семейство адреса: 4 для адресов IPv4, 6 для адресов IPv6.
family(inet '::1') → 6

host ( inet ) → text

Возвращает IP-адрес в виде текста, игнорируя сетевую маску.
host(inet '192.168.1.0/24') → 192.168.1.0

hostmask ( inet ) → inet

Вычисляет маску хоста для сети в заданном адресе.
hostmask(inet '192.168.23.20/30') → 0.0.0.3

inet_merge ( inet, inet ) → cidr

Вычисляет наименьшую сеть, содержащую обе заданные сети.
inet_merge(inet '192.168.1.5/24', inet '192.168.2.5/24') → 192.168.0.0/22

inet_same_family ( inet, inet ) → boolean

Проверяет, принадлежат ли адреса одному семейству IP.
inet_same_family(inet '192.168.1.5/24', inet '::1') → f

masklen ( inet ) → integer

Возвращает длину сетевой маски в битах.
masklen(inet '192.168.1.5/24') → 24

netmask ( inet ) → inet

Вычисляет сетевую маску для сети в заданном адресе.
netmask(inet '192.168.1.5/24') → 255.255.255.0

network ( inet ) → cidr

Возвращает сетевую часть адреса, обнуляя все биты справа от сетевой маски. (Это равнозначно приведению значения к типу cidr.)
network(inet '192.168.1.5/24') → 192.168.1.0/24

set_masklen ( inet, integer ) → inet

Задает длину сетевой маски для значения типа inet. Адресная часть при этом не меняется.
set_masklen(inet '192.168.1.5/24', 16) → 192.168.1.5/16

set_masklen ( cidr, integer ) → cidr

Задает длину сетевой маски для значения cidr. Биты адреса справа от новой сетевой маски при этом устанавливаются в ноль.
set_masklen(cidr '192.168.1.0/24', 16) → 192.168.0.0/16

text ( inet ) → text

Возвращает несокращенный IP-адрес и длину сетевой маски в виде текста. (Такой же результат получается при явном приведении к типу text.)
text(inet '192.168.1.5') → 192.168.1.5/32

Совет
Функции abbrev, host и text предназначены в основном для отображения IP-адресов в альтернативных форматах.

Типы для MAC-адресов, macaddr и macaddr8, поддерживают обычные операторы сравнения, приведенные в таблице Операторы сравнения, а также специальные функции, приведенные в Таблице 40. Кроме того, они поддерживают побитовые логические операторы ~, & и | (НЕ, И и ИЛИ), показанные выше для IP-адресов.

Таблица 40. Функции для MAC-адресов

Функция

Описание
Пример(ы)

trunc ( macaddr ) → macaddr

Устанавливает в ноль 3 последних байта адреса. Оставшийся префикс можно сопоставить с конкретным производителем (используя информацию, отсутствующую в QHB).
trunc(macaddr '12:34:56:78:90:ab') → 12:34:56:00:00:00

trunc ( macaddr8 ) → macaddr8

Устанавливает в ноль 5 последних байта адреса. Оставшийся префикс можно сопоставить с конкретным производителем (используя информацию, отсутствующую в QHB).
trunc(macaddr8 '12:34:56:78:90:ab:cd:ef') → 12:34:56:00:00:00:00:00

macaddr8_set7bit ( macaddr8 ) → macaddr8

Устанавливает единицу в 7-м бите адреса, создавая так называемый модифицированный идентификатор EUI-64 для включения в адрес IPv6.
macaddr8_set7bit(macaddr8 '00:34:56:ab:cd:ef') → 02:34:56:ff:fe:ab:cd:ef

Функции и операторы текстового поиска

В Таблице 41, Таблице 42 и Таблице 43 собраны функции и операторы, предоставляемые для полнотекстового поиска. Подробное описание механизма текстового поиска в QHB см. в главе Полнотекстовый поиск.

Таблица 41. Операторы текстового поиска

Оператор

Описание
Пример(ы)

tsvector @@ tsquery → boolean
tsquery @@ tsvector → boolean

Аргумент типа tsvector соответствует аргументу типа tsquery? (Эти аргументы можно передавать в любом порядке.)
to_tsvector('fat cats ate rats') @@ to_tsquery('cat & rat') → t

text @@ tsquery → boolean

Текстовая строка, преобразованная после неявного вызова функции to_tsvector(), соответствует tsquery?
'fat cats ate rats' @@ to_tsquery('cat & rat') → t

tsvector @@@ tsquery → boolean
tsquery @@@ tsvector → boolean

Это устаревший синоним для @@.
to_tsvector('fat cats ate rats') @@@ to_tsquery('cat & rat') → t

tsvector || tsvector → tsvector

Конкатенирует два значения tsvector. Если в обоих аргументах содержатся позиции лексем, позиции второго аргумента корректируются соответствующим образом.
'a:1 b:2'::tsvector || 'c:1 d:2 b:3'::tsvector → 'a':1 'b':2,5 'c':3 'd':4

tsquery && tsquery → tsquery

Выполняет логическую операцию И с двумя значениями tsquery, создавая запрос, который соответствует документам, соответствующим обоим входным запросам.
'fat | rat'::tsquery && 'cat'::tsquery → ( 'fat' | 'rat' ) & 'cat'

tsquery || tsquery → tsquery

Выполняет логическую операцию ИЛИ с двумя значениями tsquery, создавая запрос, который соответствует документам, соответствующим любому из входных запросов.
'fat | rat'::tsquery || 'cat'::tsquery → 'fat' | 'rat' | 'cat'

!! tsquery → tsquery

Выполняет логическую операцию НЕ со значением tsquery, создавая запрос, который соответствует документам, не соответствующим входному запросу.
!! 'cat'::tsquery → !'cat'

tsquery <-> tsquery → tsquery

Конструирует фразовый запрос, который будет соответствовать документам, если два входных запроса соответствуют идущим подряд лексемам.
to_tsquery('fat') <-> to_tsquery('rat') → 'fat' <-> 'rat'

tsquery @> tsquery → boolean

Первый аргумент типа tsquery содержит второй? (Здесь учитывается только факт нахождения всех лексем одного запроса в другом, а объединение операторов игнорируется.)
'cat'::tsquery @> 'cat & rat'::tsquery → f

tsquery <@ tsquery → boolean

Первый аргумент типа tsquery содержится во втором? (Здесь учитывается только факт нахождения всех лексем одного запроса в другом, а объединение операторов игнорируется.)
'cat'::tsquery <@ 'cat & rat'::tsquery → t
'cat'::tsquery <@ '!cat & rat'::tsquery → t

Помимо этих специальных операторов, для типов tsvector и tsquery имеются обычные операторы сравнения, приведенные в таблице Операторы сравнения. Они не очень полезны для текстового поиска, но позволяют, к примеру, создавать уникальные индексы по столбцам этих типов.

Таблица 42. Функции текстового поиска

Функция

Описание
Пример(ы)

array_to_tsvector ( text[] ) → tsvector

Преобразует массив лексем в формат tsvector. Заданные строки используются как есть без дополнительной обработки.
array_to_tsvector('{fat,cat,rat}'::text[]) → 'cat' 'fat' 'rat'

get_current_ts_config ( ) → regconfig

Возвращает OID текущей конфигурации текстового поиска по умолчанию (заданной параметром default_text_search_config).
get_current_ts_config() → english

length ( tsvector ) → integer

Возвращает количество лексем в заданном значении tsvector.
length('fat:2,4 cat:3 rat:5A'::tsvector) → 3

numnode ( tsquery ) → integer

Возвращает количество лексем и операторов в заданном значении tsquery.
numnode('(fat & rat) | cat'::tsquery) → 5

plainto_tsquery ( [ конфигурация regconfig, ] запрос text ) → tsquery

Преобразует текст в формат tsquery, нормализуя слова согласно указанной или стандартной конфигурации. Все пунктуационные знаки в этой строке при этом игнорируются (она не определяет операторы запроса). Результирующий запрос соответствует документам, содержащим все слова текста, отличные от стоп-слов.
plainto_tsquery('english', 'The Fat Rats') → 'fat' & 'rat'

phraseto_tsquery ( [ конфигурация regconfig, ] запрос text ) → tsquery

Преобразует текст в формат tsquery, нормализуя слова согласно указанной или стандартной конфигурации. Все пунктуационные знаки в этой строке при этом игнорируются (она не определяет операторы запроса). Результирующий запрос соответствует фразам, содержащим все слова текста, отличные от стоп-слов.
phraseto_tsquery('english', 'The Fat Rats') → 'fat' <-> 'rat'
phraseto_tsquery('english', 'The Cat and Rats') → 'cat' <2> 'rat'

websearch_to_tsquery ( [ конфигурация regconfig, ] запрос text ) → tsquery

Преобразует текст в формат tsquery, нормализуя слова согласно указанной или стандартной конфигурации. Последовательности слов в кавычках преобразуются в проверки фраз. Слово «or» воспринимается как формирующее оператор ИЛИ, а символ минуса формирует оператор НЕ; другие пунктуационные знаки игнорируются. Это примерно соответствует поведению некоторых распространенных инструментов поиска интернете.
websearch_to_tsquery('english', '"fat rat" or cat dog') → 'fat' <-> 'rat' | 'cat' & 'dog'

querytree ( tsquery ) → text

Создает представление индексируемой части tsquery. Пустой результат или просто T означают, что запрос неиндексируемый.
querytree('foo & ! bar'::tsquery) → 'foo'

setweight ( вектор tsvector, вес "char" ) → tsvector

Присваивает вес каждому элементу вектора.
setweight('fat:2,4 cat:3 rat:5B'::tsvector, 'A') → 'cat':3A 'fat':2A,4A 'rat':5A

setweight ( вектор tsvector, вес "char", лексемы text[] ) → tsvector

Присваивает вес элементам вектора, перечисленным в списке лексем.
setweight('fat:2,4 cat:3 rat:5,6B'::tsvector, 'A', '{cat,rat}') → 'cat':3A 'fat':2,4 'rat':5A,6A

strip ( tsvector ) → tsvector

Убирает позиции и веса из значения tsvector.
strip('fat:2,4 cat:3 rat:5A'::tsvector) → 'cat' 'fat' 'rat'

to_tsquery ( [ конфигурация regconfig, ] запрос text ) → tsquery

Преобразует текст в формат tsquery, нормализуя слова согласно указанной или стандартной конфигурации. Слова должны соединяться допустимыми операторами типа tsquery.
to_tsquery('english', 'The & Fat & Rats') → 'fat' & 'rat'

to_tsvector ( [ конфигурация regconfig, ] документ text ) → tsvector

Преобразует текст в формат tsquery, нормализуя слова согласно указанной или стандартной конфигурации. В результат будет включена информация о позициях слов.
to_tsvector('english', 'The Fat Rats') → 'fat':2 'rat':3

to_tsvector ( [ конфигурация regconfig, ] документ json ) → tsvector
to_tsvector ( [ конфигурация regconfig, ] документ jsonb ) → tsvector

Преобразует все строковые значения в документе JSON в формат tsvector, нормализуя слова согласно указанной или стандартной конфигурации. Затем результаты конкатенируются в порядке следования в документе, формируя выходное значение. Информация о позициях слов генерируется таким образом, словно между каждой парой строковых значений находится одно стоп-слово. (Учтите, что в случае jsonb «порядок следования в документе» полей объекта JSON зависит от реализации; разница показана в примерах.)
to_tsvector('english', '{"aa": "The Fat Rats", "b": "dog"}'::json) → 'dog':5 'fat':2 'rat':3
to_tsvector('english', '{"aa": "The Fat Rats", "b": "dog"}'::jsonb) → 'dog':1 'fat':4 'rat':5

json_to_tsvector ( [ конфигурация regconfig, ] документ json, фильтр jsonb ) → tsvector
jsonb_to_tsvector ( [ конфигурация regconfig, ] документ jsonb, фильтр jsonb ) → tsvector

Выбирает в документе JSON все элементы, запрошенные фильтром и преобразует каждый в формат tsvector, нормализуя слова согласно указанной или стандартной конфигурации. Затем результаты конкатенируются в порядке следования в документе, формируя выходное значение. Информация о позициях слов генерируется таким образом, словно между каждой парой строковых значений находится одно стоп-слово. (Учтите, что в случае jsonb «порядок следования в документе» полей объекта JSON зависит от реализации.) Аргумент фильтр должен быть массивом jsonb, содержащим ноль и более следующих ключевых слов: "string" (включить все строковые значения), "numeric" (включить все числовые значения), "boolean" (включить все логические значения), "key" (включить все ключи) или "all" (включить всё вышеперечисленное). В особых случаях фильтр также может быть простым значением JSON, представляющим собой одно из этих ключевых слов.
json_to_tsvector('english', '{"a": "The Fat Rats", "b": 123}'::json, '["string", "numeric"]') → '123':5 'fat':2 'rat':3
json_to_tsvector('english', '{"cat": "The Fat Rats", "dog": 123}'::json, '"all"') → '123':9 'cat':1 'dog':7 'fat':4 'rat':5

ts_delete ( вектор tsvector, лексема text ) → tsvector

Удаляет все вхождения лексемы, из вектора.
ts_delete('fat:2,4 cat:3 rat:5A'::tsvector, 'fat') → 'cat':3 'rat':5A

ts_delete ( вектор tsvector, лексемы text[] ) → tsvector

Удаляет все вхождения перечисленных лексем из вектора.
ts_delete('fat:2,4 cat:3 rat:5A'::tsvector, ARRAY['fat','rat']) → 'cat':3

ts_filter ( вектор tsvector, веса "char"[] ) → tsvector

Выбирает из вектора только элементы с заданными весами.
ts_filter('fat:2,4 cat:3b,7c rat:5A'::tsvector, '{a,b}') → 'cat':3B 'rat':5A

ts_headline ( [ конфигурация regconfig, ] документ text, запрос tsquery [, параметры text ] ) → text

Отображает в сокращенной форме соответствующие запросу вхождения в документе, который должен быть просто текстом, а не значением типа tsvector. Перед сопоставлением с запросом слова в этом документе нормализуются согласно конфигурации по умолчанию или заданной. Использование этой функции рассматривается в подразделе Выделение результатов; также там описываются возможные значения параметров.
cat ate the rat.

ts_headline ( [ конфигурация regconfig, ] документ json, запрос tsquery [, параметры text ] ) → text
ts_headline ( [ конфигурация regconfig, ] документ jsonb, запрос tsquery [, параметры text ] ) → text

Отображает в сокращенной форме соответствующие запросу вхождения, найденные в строковых значениях внутри документа JSON. Подробную информацию см. в подразделе Выделение результатов.
ts_headline('{"cat":"raining cats and dogs"}'::jsonb, 'cat') → {"cat": "raining cats and dogs"}

ts_rank ( [ веса real[], ] вектор tsvector, запрос tsquery [, нормализация integer ] ) → real

Вычисляет количественный показатель, демонстрирующий, в какой степени вектор соответствует запросу. Подробную информацию см. в подразделе Ранжирование результатов поиска.
ts_rank(to_tsvector('raining cats and dogs'), 'cat') → 0.06079271

ts_rank_cd ( [ веса real[], ] вектор tsvector, запрос tsquery [, нормализация integer ] ) → real

С помощью алгоритма расчета плотности покрытия вычисляет количественный показатель, демонстрирующий, в какой степени вектор соответствует запросу. Подробную информацию см. в подразделе Ранжирование результатов поиска.
ts_rank_cd(to_tsvector('raining cats and dogs'), 'cat') → 0.1

ts_rewrite ( запрос tsquery, цель tsquery, подстановка tsquery ) → tsquery

Заменяет в запросе вхождения цели на подстановку. Подробную информацию см. в подразделе Перезапись запросов.
ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'foo|bar'::tsquery) → 'b' & ( 'foo' | 'bar' )

ts_rewrite ( запрос tsquery, select text ) → tsquery

Заменяет базовые элементы запроса, получая выборку целевых вхождений и подстановок с помощью команды SELECT. Подробную информацию см. в подразделе Перезапись запросов.
SELECT ts_rewrite('a & b'::tsquery, 'SELECT t,s FROM aliases') → 'b' & ( 'foo' | 'bar' )

tsquery_phrase ( запрос1 tsquery, запрос2 tsquery ) → tsquery

Конструирует фразовый запрос, который ищет вхождения, соответствующие запросу1 и запросу2, в идущих подряд лексемах (аналогично оператору <->).
tsquery_phrase(to_tsquery('fat'), to_tsquery('cat')) → 'fat' <-> 'cat'

tsquery_phrase ( запрос1 tsquery, запрос2 tsquery, расстояние integer ) → tsquery

Конструирует фразовый запрос, который ищет вхождения, соответствующие запросу1 и запросу2, находящиеся точно на заданном расстоянии (в лексемах) друг от друга.
tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'), 10) → 'fat' <10> 'cat'

tsvector_to_array ( tsvector ) → text[]

Преобразует значение типа tsvector в массив лексем.
tsvector_to_array('fat:2,4 cat:3 rat:5A'::tsvector) → {cat,fat,rat}

unnest ( tsvector ) → setof record ( лексема text, позиции smallint[], веса text )

Разворачивает значение tsvector в набор строк, по одной на каждую лексему.

select * from unnest('cat:3 fat:2,4 rat:5A'::tsvector) →
     lexeme | positions | weights
    --------+-----------+---------
     cat    | {3}       | {D}
     fat    | {2,4}     | {D,D}
     rat    | {5}       | {A}

Примечание
Все функции текстового поиска, принимающие необязательный аргумент regconfig, будут использовать конфигурацию, заданную параметром default_text_search_config, когда этот аргумент опущен.

Функции в Таблице 43 перечислены отдельно, потому что обычно они не используются в повседневных операциях текстового поиска. Они полезны в основном для разработки и отладки новых конфигураций текстового поиска.

Таблица 43. Функции отладки текстового поиска

Функция

Описание
Пример(ы)

ts_debug ( [ конфигурация regconfig, ] документ text ) → setof record ( псевдонимы text, описание text, синт_единица text, словари regdictionary[], словарь regdictionary, лексемы text[] )

Извлекает из документа синтаксические единицы, нормализует их согласно заданной или стандартной конфигурации текстового поиска и возвращает информацию о том, как была обработана каждая из них. Подробную информацию см. в подразделе Тестирование конфигурации.
ts_debug('english', 'The Brightest supernovaes') → (asciiword,"Word, all ASCII",The,{english_stem},english_stem,{}) ...

ts_lexize ( словарь regdictionary, синт_единица text ) → text[]

Возвращает массив заменяющих лексем, если входная синтаксическая единица есть в словаре, пустой массив, если синтаксическая единица есть в словаре, но является стоп-словом, или NULL, если это неизвестное слово. Подробную информацию см. в подразделе Тестирование словаря.
ts_lexize('english_stem', 'stars') → {star}

ts_parse ( имя_анализатора text, документ text ) → setof record ( код_единицы integer, синт_единица text )

Извлекает синтаксические единицы из документа, используя именованный синтаксический анализатор. Подробную информацию см. в подразделе Тестирование анализатора.
ts_parse('default', 'foo - bar') → (1,foo) ...

ts_parse ( oid_анализатора oid, документ text ) → setof record ( код_единицы integer, синт_единица text )

Извлекает синтаксические единицы из документа, используя синтаксический анализатор с указанным OID. Подробную информацию см. в подразделе Тестирование анализатора.
ts_parse(3722, 'foo - bar') → (1,foo) ...

ts_token_type ( имя_анализатора text ) → setof record ( код_единицы integer, псевдоним text, описание text )

Возвращает таблицу, описывающую все типы синтаксических единиц, которые может распознать именованный синтаксический анализатор. Подробную информацию см. в подразделе Тестирование анализатора.
ts_token_type('default') → (1,asciiword,"Word, all ASCII") ...

ts_token_type ( oid_анализатора oid ) → setof record ( код_единицы integer, псевдоним text, описание text )

Возвращает таблицу, описывающую все типы синтаксических единиц, которые может распознать синтаксический анализатор с указанным OID. Подробную информацию см. в подразделе Тестирование анализатора.
ts_token_type(3722) → (1,asciiword,"Word, all ASCII") ...

ts_stat ( запрос_sql text [, веса text ] ) → setof record ( слово text, число_док integer, число_вхожд integer )

Выполняет SQL-запрос, который должен возвращать единственный столбец tsvector, и выдает статистику по каждой отдельной лексеме, содержащейся в данных. Подробную информацию см. в подразделе Сбор статистики по документу.
ts_stat('SELECT vector FROM apod') → (foo,10,15) ...

Функции генерирования UUID

В QHB имеется одна функция для генерирования UUID:

gen_random_uuid () → uuid

Эта функция возвращает случайный UUID версии 4. Это наиболее часто используемый тип UUID, который подходит для большинства приложений.

Модуль uuid-ossp предоставляет дополнительные функции, реализующие другие стандартные алгоритмы генерирования UUID.

Кроме того, QHB предоставляет для UUID приведенные в таблице Операторы сравнения простые операторы сравнения.

Функции XML

Функции и подобные им выражения, описанные в этом разделе, работают со значениями типа xml. Информацию о типе xml см. в разделе Тип XML. Подобные функциям выражения xmlparse и xmlserialize, преобразовывающие значения в тип xml и из него, описаны там, а не в этом разделе.

Использование большинства этих функций требует, чтобы QHB была собрана с флагом соответствующей библиотеки (configure --with-libxml).

Создание XML-контента

Для создания XML-контента из данных SQL существует набор функций и подобных им выражений. Соответственно, они особенно подходят для форматирования результатов запросов в XML-документы для обработки в клиентских приложениях.

xmlcomment

xmlcomment ( text ) → xml

Функция xmlcomment создает значение XML, содержащее комментарий XML с заданным текстом. Текст не может содержать «--» или заканчиваться на «-», иначе полученная конструкция не будет допустимым комментарием XML. Если аргумент равен NULL, результат тоже будет NULL.

Пример:

SELECT xmlcomment('hello');

  xmlcomment
--------------
 <!--hello-->

xmlconcat

xmlconcat ( xml [, ...] ) → xml

Функция xmlconcat конкатенирует список отдельных значений XML, выдавая одно значение, содержащее фрагмент содержимого XML. Значения NULL отбрасываются; результат будет NULL, только если все аргументы равны NULL.

Пример:

SELECT xmlconcat('<abc/>', '<bar>foo</bar>');

      xmlconcat
----------------------
 <abc/><bar>foo</bar>

Объявления XML, если они есть, объединяются следующим образом. Если все значения аргументов имеют объявление одной версии XML, эта версия используется в результате, иначе версия вообще не используется. Если у всех значений аргумента имеется автономное объявление (standalone) со значением «yes», то это же значение используется в результате. Если у всех значений аргумента имеется объявление standalone и по крайней мере одно из них равно «no», тогда в результате используется это значение. Иначе у результата не будет объявления standalone. Если определено, что в результате должно быть объявление standalone, но нет объявления версии, будет выведена версия 1.0, поскольку для XML требуется, чтобы объявление XML содержало объявление версии. Объявления кодировок игнорируются и удаляются во всех случаях.

Пример:

SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone="no"?><bar/>');

             xmlconcat
-----------------------------------
 <?xml version="1.1"?><foo/><bar/>

xmlelement

xmlelement ( NAME имя [, XMLATTRIBUTES ( значение_атрибута [ AS имя_атрибута ] [, ...] ) ] [, содержимое [, ...]] ) → xml

Выражение xmlelement создает элемент XML с заданным именем, атрибутами и содержимым. Показанные в синтаксисе элементы имя и имя_атрибута представляют собой простые идентификаторы, а не значения. Элементы значение_атрибута и содержимое являются выражениями, которые могут выдавать любой тип данных QHB. Аргументы внутри XMLATTRIBUTES генерируют атрибуты элемента XML; значения содержимого конкатенируют с ними, формируя содержимое элемента.

Примеры:

SELECT xmlelement(name foo);

 xmlelement
------------
 <foo/>

SELECT xmlelement(name foo, xmlattributes('xyz' as bar));

    xmlelement
------------------
 <foo bar="xyz"/>

SELECT xmlelement(name foo, xmlattributes(current_date as bar), 'cont', 'ent');

             xmlelement
-------------------------------------
 <foo bar="2007-01-26">content</foo>

Имена элементов и атрибутов, недопустимые в XML, экранируются путем замены нарушающих символов последовательностью _xHHHH_, где HHHH — это кодовая точка символа Unicode в шестнадцатеричной записи. Например:

SELECT xmlelement(name "foo$bar", xmlattributes('xyz' as "a&b"));

            xmlelement
----------------------------------
 <foo_x0024_bar a_x0026_b="xyz"/>

Если значение атрибута является ссылкой на столбец, то указывать явно имя атрибута необязательно — в качестве этого имени по умолчанию будет использовано имя столбца. В остальных случаях атрибуту нужно явно задать имя. Поэтому этот пример применим:

CREATE TABLE test (a xml, b xml);
SELECT xmlelement(name test, xmlattributes(a, b)) FROM test;

А эти — нет:

SELECT xmlelement(name test, xmlattributes('constant'), a, b) FROM test;
SELECT xmlelement(name test, xmlattributes(func(a, b))) FROM test;

Содержимое элемента, если оно задано, будет отформатировано в соответствии с его типом данных. Если само содержимое имеет тип xml, есть возможность конструировать сложные XML-документы. Например:

SELECT xmlelement(name foo, xmlattributes('xyz' as bar),
                            xmlelement(name abc),
                            xmlcomment('test'),
                            xmlelement(name xyz));

                  xmlelement
----------------------------------------------
 <foo bar="xyz"><abc/><!--test--><xyz/></foo>

Содержимое других типов будет отформатировано в допустимые символьные данные XML. В частности, это означает, что символы <, > и & будут преобразованы в сущностные объекты. Двоичные данные (тип данных bytea) будут представлены в кодировке base64 или в шестнадцатеричной кодировке, в зависимости от настройки параметра конфигурации xmlbinary. Как ожидается, конкретное поведение для отдельных типов данных будет развиваться, чтобы согласовать сопоставления QHB с сопоставлениями, указанными в стандарте SQL:2006 и новее, как описано в подразделе Сопоставления типов данных и значений между SQL и XML.

xmlforest

xmlforest ( содержимое [ AS имя ] [, ...] ) → xml

Выражение xmlforest создает XML-лес (последовательность) элементов, используя заданные имена и содержимое. Как и в выражении xmlelement, каждое имя должно быть простым идентификатором, тогда как выражения содержимое могут иметь любой тип данных.

Примеры:

SELECT xmlforest('abc' AS foo, 123 AS bar);

          xmlforest
------------------------------
 <foo>abc</foo><bar>123</bar>

SELECT xmlforest(table_name, column_name)
FROM information_schema.columns
WHERE table_schema = 'pg_catalog';

                                         xmlforest
-------------------------------------------------------------------------------------------
 <table_name>pg_authid</table_name><column_name>rolname</column_name>
 <table_name>pg_authid</table_name><column_name>rolsuper</column_name>
 ...

Как видно из второго примера, если значение содержимого является ссылкой на столбец, имя элемента можно опустить, поскольку в этом случае по умолчанию им станет имя столбца. В противном случае имя следует указывать.

Имена элементов, недопустимые в XML, экранируются, как показано выше для выражения xmlelement. Точно так же данные содержимого экранируются, делая его допустимым для XML (за исключением тех данные, которые уже имеют тип xml).

Обратите внимание, что XML-леса не являются допустимыми документами XML, если они состоят из более чем одного элемента, поэтому может быть полезно вложить выражения xmlforest в xmlelement.

xmlpi

xmlpi ( NAME имя [, содержимое ] ) → xml

Выражение xmlpi создает инструкцию по обработке XML. Как и в выражении xmlelement, имя должно быть простым идентификатором, тогда как выражение содержимое может иметь любой тип данных. Это содержимое, если оно задано, не должно содержать последовательность символов ?>.

Пример:

SELECT xmlpi(name php, 'echo "hello world";');

            xmlpi
-----------------------------
 <?php echo "hello world";?>

xmlroot

xmlroot ( xml, VERSION {text|NO VALUE} [, STANDALONE {YES|NO|NO VALUE} ] ) → xml

Выражение xmlroot изменяет свойства корневого узла значения XML. Если задается версия, она заменяет значение в объявлении версии корневого узла; если задается значение параметра standalone, оно заменяет значение в объявлении standalone корневого узла.

SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
               version '1.0', standalone yes);

                xmlroot
----------------------------------------
 <?xml version="1.0" standalone="yes"?>
 <content>abc</content>

xmlagg

xmlagg ( xml ) → xml

Функция xmlagg, в отличие от других описанных здесь функций, является агрегатной. Он конкатенирует входные значения с вызовом агрегатной функции, во многом подобно xmlconcat, за исключением того, что конкатенация затрагивает строки, а не выражения в одной строке. Дополнительную информацию об агрегатных функциях см. в разделе Агрегатные функции.

Пример:

CREATE TABLE test (y int, x xml);
INSERT INTO test VALUES (1, '<foo>abc</foo>');
INSERT INTO test VALUES (2, '<bar/>');
SELECT xmlagg(x) FROM test;
        xmlagg
----------------------
 <foo>abc</foo><bar/>

Чтобы определить порядок конкатенации, в агрегатный вызов можно добавить предложение ORDER BY, как описано в подразделе Агрегатные выражения. Например:

SELECT xmlagg(x ORDER BY y DESC) FROM test;
        xmlagg
----------------------
 <bar/><foo>abc</foo>

Следующий нестандартный подход рекомендовался в предыдущих версиях и по-прежнему может быть полезен в определенных случаях:

SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
        xmlagg
----------------------
 <bar/><foo>abc</foo>

Предикаты XML

Выражения, описанные в этом разделе, проверяют свойства значений типа xml.

IS DOCUMENT

xml IS DOCUMENT → boolean

Выражение IS DOCUMENT возвращает true, если значение аргумента XML является правильным документом XML, false, если это не так (то есть если это фрагмент содержимого), или NULL, если аргумент равен NULL. Разница между документами и фрагментами содержимого описана в разделе Тип XML.

IS NOT DOCUMENT

xml IS NOT DOCUMENT → boolean

Выражение IS NOT DOCUMENT возвращает false, если значение аргумента XML является правильным документом XML, true, если это не так (то есть если это фрагмент содержимого), или NULL, если аргумент равен NULL.

XMLEXISTS

XMLEXISTS ( text PASSING [BY {REF|VALUE}] xml [BY {REF|VALUE}] ) → boolean

Функция xmlexists вычисляет выражение XPath 1.0 (первый аргумент), используя в качестве элемента контекста переданное значение XML. Функция возвращает false, если в результате этого вычисления выдается пустой набор узлов, true, если выдается любое другое значение, или NULL, если любой аргумент равен NULL. Отличное от NULL значение, передаваемое в качестве элемента контекста, должно быть документом XML, а не фрагментом содержимого или любым значением, отличным от XML.

Пример:

SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY VALUE '<towns><town>Toronto</town><town>Ottawa</town></towns>');

 xmlexists
------------
 t
(1 row)

Предложения BY REF и BY VALUE в QHB принимаются, но игнорируются (это рассматривается в подразделе Непреднамеренные ограничения реализации).

В стандарте SQL функция xmlexists вычисляет выражения на языке XML Query, но QHB допускает только выражение XPath 1.0 (это рассматривается в подразделе Запросы ограничены XPath 1.0).

xml_is_well_formed

xml_is_well_formed ( text ) → boolean
xml_is_well_formed_document ( text ) → boolean
xml_is_well_formed_content ( text ) → boolean

Эти функции проверяют, представляет ли строка типа text синтаксически корректным XML, возвращая логический результат. Функция xml_is_well_formed_document проверяет аргумент как синтаксически корректный документ, а функция xml_is_well_formed_content — как синтаксически корректное содержимое. Функция xml_is_well_formed выполняет первое, если в параметре конфигурации xmloption задано значение DOCUMENT, или второе, если задано CONTENT. Это означает, что xml_is_well_formed полезна для определения успешности простого приведения к типу xml, тогда как две другие функции полезны для определения того, будут ли успешными соответствующие варианты XMLPARSE.

Примеры:

SET xmloption TO DOCUMENT;
SELECT xml_is_well_formed('<>');
 xml_is_well_formed
--------------------
 f
(1 row)

SELECT xml_is_well_formed('<abc/>');
 xml_is_well_formed
--------------------
 t
(1 row)

SET xmloption TO CONTENT;
SELECT xml_is_well_formed('abc');
 xml_is_well_formed
--------------------
 t
(1 row)

SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
 xml_is_well_formed_document
-----------------------------
 t
(1 row)

SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
 xml_is_well_formed_document
-----------------------------
 f
(1 row)

Последний пример показывает, что при проверке учитывается корректное сопоставление пространств имен.

Обработка XML

Для обработки значений типа данных xml QHB предлагает функции xpath и xpath_exists, которые вычисляют выражения XPath 1.0, и табличную функцию XMLTABLE.

xpath

xpath ( xpath text, xml xml [, массив_пространств_имен text[] ] ) → xml[]

Функция xpath вычисляет выражение XPath 1.0 xpath (заданное в виде текста) для значения XML xml. Она возвращает массив значений XML, соответствующих набору узлов, созданному выражением XPath. Если выражение XPath возвращает скалярное значение, а не набор узлов, то возвращается массив из одного элемента.

Второй аргумент должен быть синтаксически корректным документом XML. В частности, он должен иметь всего один элемент корневого узла.

Необязательный третий аргумент функции — это массив сопоставлений пространств имен. Этот массив должен быть двумерным массивом типа text с длиной второй оси, равной 2 (т.е. это должен быть массив массивов, каждый из которых состоит ровно из 2 элементов). Первый элемент каждой записи массива — это имя пространства имен (псевдоним), второй — его URI. Необязательно, чтобы предоставленные в этом массиве псевдонимы совпадали с используемыми в самом документе XML (другими словами, как в документе XML, так и в контексте функции xpath псевдонимы локальны).

Пример:

SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
             ARRAY[ARRAY['my', 'http://example.com']]);

 xpath
--------
 {test}
(1 row)

Чтобы работать с пространствами имен по умолчанию (анонимными), напишите что-то вроде этого:

SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
             ARRAY[ARRAY['mydefns', 'http://example.com']]);

 xpath
--------
 {test}
(1 row)

xpath_exists

xpath_exists ( xpath text, xml xml [, массив_пространств_имен text[] ] ) → boolean

Функция xpath_exists является специализированной формой функции xpath. Вместо того чтобы возвращать отдельные значения XML, которые удовлетворяют выражению XPath 1.0, эта функция возвращает логическое значение, показывающее, был ли удовлетворен запрос или нет (в частности, было ли получено какое-либо значение, отличное от пустого набора узлов). Эта функция равнозначна предикату XMLEXISTS, за исключением того, что она также поддерживает аргумент сопоставления пространств имен.

Пример:

SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                     ARRAY[ARRAY['my', 'http://example.com']]);

 xpath_exists
--------------
 t
(1 row)

xmltable

XMLTABLE (
    [XMLNAMESPACES ( uri_пространства_имен AS имя_пространства_имен [, ...] ),]
    выражение_строки PASSING [BY {REF|VALUE}] выражение_документа [BY {REF|VALUE}]
    COLUMNS имя { тип [PATH выражение_столбца] [DEFAULT выражение_по_умолчанию] [NOT NULL | NULL]
                  | FOR ORDINALITY }
            [, ...]
) → setof record

Выражение xmltable создает таблицу на основе значения XML, фильтра XPath для извлечения строк и набора определений столбцов. Хотя синтаксически оно напоминает функцию, оно может присутствовать в запросе только в качестве таблицы в предложении FROM.

Необязательное предложение XMLNAMESPACES задает список разделенных запятыми пространств имен, где каждое uri_пространства_имен является выражением типа text, а каждое имя_пространства_имен — простой идентификатор. Оно определяет используемые в документе пространства имен XML и их псевдонимы. Специфицирование пространства имен по умолчанию в настоящее время не поддерживается.

Обязательный аргумент выражение_строки — это выражение XPath 1.0 (заданное в виде значения типа text), которое вычисляется с использованием в качестве элемента контекста переданного значения XML, для получения набора узлов XML. Эти узлы — то, что xmltable превращает в выходные строки. Если выражение_документа равно NULL или выражение_строки создает пустой набор узлов или любое значение, отличное от набора узлов, строки выдаваться не будут.

Аргумент выражение_документа предоставляет элемент контекста для аргумента выражение_строки. Этим элементом должен быть синтаксически корректный документ XML; фрагменты/леса не принимаются. Предложения BY REF и BY VALUE принимаются, но игнорируются (это рассматривается в подразделе Непреднамеренные ограничения реализации).

В стандарте SQL функция xmltable вычисляет выражения на языке XML Query, но QHB допускает только выражение XPath 1.0 (это рассматривается в подразделе Запросы ограничены XPath 1.0).

Обязательное предложение COLUMNS задает столбцы, которые будут созданы в выходной таблице. Его формат показан выше в шаблоне синтаксиса. Для каждого столбца необходимо задать имя и тип данных (если только не указано FOR ORDINALITY, что подразумевает тип integer). Предложения, задающие путь, значение по умолчанию и допустимость значений NULL, являются необязательными.

Столбец, помеченный FOR ORDINALITY, будет заполнен номерами строк, начиная с 1, в порядке узлов, полученных из результирующего набора узлов выражения_строки. Указанием FOR ORDINALITY можно пометить максимум один столбец.

Примечание
В XPath 1.0 не указывается порядок узлов в наборе, поэтому код, полагающийся на определенный порядок результатов, будет зависеть от реализации. Подробную информацию об этом см. в подразделе Ограничение XPath до версии 1.0.

Аргумент выражение_столбца для столбца — это выражение XPath 1.0, которое вычисляется для каждой строки, с использованием в качестве элемента контекста текущего узла из результата выражения_строки, чтобы найти значение этого столбца. Если выражение_столбца не задано, то в качестве неявного пути используется имя столбца.

Если выражение XPath столбца возвращает значение, отличное от XML (может быть только строкой, логическим значением или числовым значением двойной точности в XPath 1.0) и столбец имеет тип QHB, отличный от xml, значение этому столбцу будет присвоено так, как если бы типу QHB было присвоено строковое представление значения. (Если это логическое значение, его строковое представление принимается равным 1 или 0, если тип выходного столбца относится к числовой категории, и true или false в противном случае.)

Если выражение XPath столбца возвращает непустой набор узлов XML и этот столбец в QHB имеет тип xml, столбцу будет точно присвоен результат выражения, если он имеет форму документа или содержимого.¹

Присвоенный выходному столбцу xml результат, отличный от XML, выдает содержимое, представляющее собой отдельный текстовый узел со строковым значением результата. Результат XML, назначенный столбцу любого другого типа, может содержать не более одного узла, или возникает ошибка. Если существует ровно один узел, значение этому столбцу будет присвоено так, как если бы типу QHB было присвоено строковое значение узла (как определено для функции string в XPath 1.0).

Строковое значение элемента XML представляет собой конкатенацию всех текстовых узлов, содержащихся в этом элементе и его потомках, в порядке следования этих узлов в документе. Строковым значением элемента без текстовых узлов-потомков является пустая строка (не NULL). Любые атрибуты xsi:nil игнорируются. Обратите внимание, что состоящий только из пробелов узел text() между двумя нетекстовыми элементами при этом сохраняется и начальные пробелы в узле text() не убираются. Правила, определяющие строковое значение других типов узлов XML и значений, отличный от XML, можно посмотреть в описании функции string в XPath 1.0.

Представленные здесь правила преобразования не совсем соответствуют стандарту SQL, как описано в подразделе Сопоставления типов данных и значений между SQL и XML.

Если выражение пути возвращает пустой набор узлов (обычно когда ему нет соответствия) для данной строки, столбцу будет присвоено значение NULL, если только не задано выражение_по_умолчанию — в таком случае используется значение, полученное в результате вычисления этого выражения.

Это выражение_по_умолчанию вычисляется не сразу же при вызове xmltable, а каждый раз, когда для столбца требуется значение по умолчанию. Если выражение квалифицируется как стабильное или неизменное, повторное вычисление может быть пропущено. Это означает, что в выражении_по_умолчанию можно с пользой использовать изменчивые функции, такие как nextval.

Столбцы могут быть помечены признаком NOT NULL. Если выражение_столбца для столбца NOT NULL ничему не соответствует и при этом отсутствует указание DEFAULT или результатом вычисления выражения_столбца тоже является NULL, выдается сообщение об ошибке.

Примеры:

CREATE TABLE xmldata AS SELECT
xml $$
<ROWS>
  <ROW id="1">
    <COUNTRY_ID>AU</COUNTRY_ID>
    <COUNTRY_NAME>Australia</COUNTRY_NAME>
  </ROW>
  <ROW id="5">
    <COUNTRY_ID>JP</COUNTRY_ID>
    <COUNTRY_NAME>Japan</COUNTRY_NAME>
    <PREMIER_NAME>Shinzo Abe</PREMIER_NAME>
    <SIZE unit="sq_mi">145935</SIZE>
  </ROW>
  <ROW id="6">
    <COUNTRY_ID>SG</COUNTRY_ID>
    <COUNTRY_NAME>Singapore</COUNTRY_NAME>
    <SIZE unit="sq_km">697</SIZE>
  </ROW>
</ROWS>
$$ AS data;

SELECT xmltable.*
  FROM xmldata,
       XMLTABLE('//ROWS/ROW'
                PASSING data
                COLUMNS id int PATH '@id',
                        ordinality FOR ORDINALITY,
                        "COUNTRY_NAME" text,
                        country_id text PATH 'COUNTRY_ID',
                        size_sq_km float PATH 'SIZE[@unit = "sq_km"]',
                        size_other text PATH
                             'concat(SIZE[@unit!="sq_km"], " ", SIZE[@unit!="sq_km"]/@unit)',
                        premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified') ;

 id | ordinality | COUNTRY_NAME | country_id | size_sq_km |  size_other  | premier_name
----+------------+--------------+------------+------------+--------------+---------------
  1 |          1 | Australia    | AU         |            |              | not specified
  5 |          2 | Japan        | JP         |            | 145935 sq_mi | Shinzo Abe
  6 |          3 | Singapore    | SG         |        697 |              | not specified

В следующем примере показана конкатенация нескольких узлов text (), применение имени столбца в качестве фильтра XPath, а также обработка пробелов, комментариев XML и инструкций по обработке:

CREATE TABLE xmlelements AS SELECT
xml $$
  <root>
   <element>  Hello<!-- xyxxz -->2a2<?aaaaa?> <!--x-->  bbb<x>xxx</x>CC  </element>
  </root>
$$ AS data;

SELECT xmltable.*
  FROM xmlelements, XMLTABLE('/root' PASSING data COLUMNS element text);
         element
-------------------------
   Hello2a2   bbbxxxCC

Следующий пример иллюстрирует, как с помощью предложения XMLNAMESPACES можно задать список пространств имен, используемых в документе XML и в выражениях XPath:

WITH xmldata(data) AS (VALUES ('
<example xmlns="http://example.com/myns" xmlns:B="http://example.com/b">
 <item foo="1" B:bar="2"/>
 <item foo="3" B:bar="4"/>
 <item foo="4" B:bar="5"/>
</example>'::xml)
)
SELECT xmltable.*
  FROM XMLTABLE(XMLNAMESPACES('http://example.com/myns' AS x,
                              'http://example.com/b' AS "B"),
             '/x:example/x:item'
                PASSING (SELECT data FROM xmldata)
                COLUMNS foo int PATH '@foo',
                  bar int PATH '@B:bar');
 foo | bar
-----+-----
   1 |   2
   3 |   4
   4 |   5
(3 rows)

Отображение таблиц в XML

Следующие функции отображают содержимое реляционных таблиц в значения XML. Их можно рассматривать как средства экспорта в XML:

table_to_xml ( table regclass, nulls boolean,
               tableforest boolean, targetns text ) → xml
query_to_xml ( query text, nulls boolean,
               tableforest boolean, targetns text ) → xml
cursor_to_xml ( cursor refcursor, count integer, nulls boolean,
                tableforest boolean, targetns text ) → xml

Функция table_to_xml отображает содержимое именованной таблицы, передаваемой в параметр table. Тип regclass принимает строки, идентифицирующие таблицы, используя обычную запись, включая необязательное указание схемы и кавычки (подробную информацию см. в разделе Типы идентификаторов объектов). Функция query_to_xml выполняет запрос, текст которого передается в параметре query, и отображает набор результатов. Функция cursor_to_xml извлекает указанное количество строк из курсора, заданного параметром cursor. Этот вариант рекомендуется, если необходимо отобразить большие таблицы, поскольку каждой функцией создается в памяти результирующее значение.

Если параметр tableforest равен false, результирующий документ XML выглядит следующим образом:

<имя_таблицы>
  <row>
    <имя_столбца1>данные</имя_столбца1>
    <имя_столбца2>данные</имя_столбца2>
  </row>

  <row>
    ...
  </row>

  ...
</имя_таблицы>

Если же tableforest равен true, результатом является фрагмент содержимого XML, который выглядит следующим образом:

<имя_таблицы>
  <имя_столбца1>данные</имя_столбца1>
  <имя_столбца2>данные</имя_столбца2>
</имя_таблицы>

<имя_таблицы>
  ...
</имя_таблицы>

...

Если имя таблицы неизвестно, то есть при отображении запроса или курсора, то в первом формате используется строка table, а во втором — row.

Выбор между этими форматами остается за пользователем. Первый формат — это правильный XML-документ, который будет важен во многих приложениях. Второй формат скорее будет более полезен в функции cursor_to_xml, если результирующие значения будут собраны в один документ позже. Для изменения результатов по вкусу можно воспользоваться описанными выше функциями для создания содержимого XML, в частности xmlelement.

Значения данных отображаются так же, как описано выше для функции xmlelement.

Параметр nulls определяет, нужно ли включать в выходные данные значения NULL. Если он равен true, значения NULL в столбцах представляются как:

<имя_столбца xsi:nil="true"/>

где xsi — префикс пространства имен для XML Schema Instance. Соответствующее объявление пространства имен будет добавлено к значению результата. Если он равен false, столбцы, содержащие значения NULL, просто не включаются в вывод.

Параметр targetns задает целевое пространство имен XML для результата. Если конкретное пространство имен не требуется, в этом параметре должна быть передана пустая строка.

Следующие функции возвращают документы XML Schema, описывающие отображения, выполняемые соответствующими функциями, описанными выше:

table_to_xmlschema ( table regclass, nulls boolean,
                     tableforest boolean, targetns text ) → xml
query_to_xmlschema ( query text, nulls boolean,
                     tableforest boolean, targetns text ) → xml
cursor_to_xmlschema ( cursor refcursor, nulls boolean,
                      tableforest boolean, targetns text ) → xml

Для получения соответствующих отображений данных XML и документов XML Schema важно, чтобы в этих функциях передавались одинаковые параметры.

Следующие функции выдают связанные вместе отображения данных XML и соответствующую XML Schema в одном документе (или лесу). Они могут быть полезны, когда требуются автономные и самоописываемые результаты:

table_to_xml_and_xmlschema ( table regclass, nulls boolean,
                             tableforest boolean, targetns text ) → xml
query_to_xml_and_xmlschema ( query text, nulls boolean,
                             tableforest boolean, targetns text ) → xml

Кроме того, имеются следующие функции для вывода аналогичных отображений целых схем или всей текущей базы данных:

schema_to_xml ( schema name, nulls boolean,
                tableforest boolean, targetns text ) → xml
schema_to_xmlschema ( schema name, nulls boolean,
                      tableforest boolean, targetns text ) → xml
schema_to_xml_and_xmlschema ( schema name, nulls boolean,
                              tableforest boolean, targetns text ) → xml

database_to_xml ( nulls boolean,
                  tableforest boolean, targetns text ) → xml
database_to_xmlschema ( nulls boolean,
                        tableforest boolean, targetns text ) → xml
database_to_xml_and_xmlschema ( nulls boolean,
                                tableforest boolean, targetns text ) → xml

Эти функции игнорируют таблицы, которые не может читать текущий пользователь. Функции уровня базы данных дополнительно игнорируют схемы, для которых у текущего пользователя нет права USAGE (использование).

Обратите внимание, что они потенциально производят большой объем данных, которые должны быть встроены в память. При необходимости отображений содержимого больших схем или баз данных может оказаться целесообразным вместо этого рассмотреть отображение таблиц по отдельности, возможно даже через курсор.

Результат отображения содержимого схемы выглядит следующим образом:

<имя_схемы>

отображение-таблицы1

отображение-таблицы2

...

</имя_схемы>

где формат отображения таблицы зависит от параметра tableforest, описанного выше.

Результат отображения содержимого базы данных выглядит следующим образом:

<имя_бд>

<имя_схемы1>
  ...
</имя_схемы1>

<имя_схемы2>
  ...
</имя_схемы2>

...

</имя_бд>

где отображение схемы такое же, как указано выше.

В качестве примера использования выходных данных, создаваемых этими функциями, на Примере 1 показана таблица стилей XSLT, преобразующая выходные данные table_to_xml_and_xmlschema в документ HTML, содержащий табличное представление данных таблицы. Аналогичным образом результаты этих функций могут быть преобразованы в другие форматы на основе XML.

Пример 1. Таблица стилей XSLT для преобразования вывода SQL/XML в HTML

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns="http://www.w3.org/1999/xhtml"
>

  <xsl:output method="xml"
      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
      doctype-public="-//W3C/DTD XHTML 1.0 Strict//EN"
      indent="yes"/>

  <xsl:template match="/*">
    <xsl:variable name="schema" select="//xsd:schema"/>
    <xsl:variable name="tabletypename"
                  select="$schema/xsd:element[@name=name(current())]/@type"/>
    <xsl:variable name="rowtypename"
                  select="$schema/xsd:complexType[@name=$tabletypename]/xsd:sequence/xsd:element[@name='row']/@type"/>

    <html>
      <head>
        <title><xsl:value-of select="name(current())"/></title>
      </head>
      <body>
        <table>
          <tr>
            <xsl:for-each select="$schema/xsd:complexType[@name=$rowtypename]/xsd:sequence/xsd:element/@name">
              <th><xsl:value-of select="."/></th>
            </xsl:for-each>
          </tr>

          <xsl:for-each select="row">
            <tr>
              <xsl:for-each select="*">
                <td><xsl:value-of select="."/></td>
              </xsl:for-each>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>

</xsl:stylesheet>

Расширенные функции XML

Расширенные XML функции в QHB — это функции, представляющие дополнительные операции над данными, представленными в виде XML документов.

xml_append_child

xml_append_child ( XMLType_instance xml, XPath_string text, value_expr xml [, namespace_string text])

Функция xml_append_child добавляет введенное пользователем значение к целевому XML в качестве дочернего элемента узла, указанного выражением XPath.

В аргументе XMLType_instance передается экземпляр XMLType. В аргументе XPath_string передается выражение Xpath, указывающее один или несколько узлов, к которым должны быть присоединены один или несколько дочерних узлов. Можно указать абсолютную строку XPath_string с начальным слэшем или относительную строку XPath_string, опуская начальный слэш. Если опустить начальный слэш, контекст относительного пути по умолчанию соответствует корневому узлу. В аргументе value_expr передается один или несколько узлов XML. Он должен разрешаться в строку. Необязательный параметр namespace_string предоставляет информацию о пространстве имен для XPath_string. Этот параметр должен иметь тип text.

Пример:

select xml_append_child(
  '<body>
    <wow id="1">
      <lol/>
    </wow>
    <wow id="2">
      <lol/>
    </wow>
   </body>'::xml,
  '/body/wow/lol'::text,
  '<New>Append</New>'::xml
  )
;

Результат:

<body>
  <wow id="1">
    <lol>
      <New>Append</New>
    </lol>
  </wow>
  <wow id="2">
    <lol>
      <New>Append</New>
    </lol>
  </wow>
</body>

xml_insert_before

xml_insert_before ( XMLType_instance xml, XPath_string text, value_expr xml [, namespace_string text] )

Функция xml_insert_before вставляет введенное пользователем значение в целевой XML перед узлом, указанным выражением XPath. Эта функция похожа на xml_insert_after, но вставляет значение до, а не после целевого узла.

В аргументе XMLType_instance передается экземпляр XMLType. В аргументе XPath_string передается выражение Xpath, указывающее один или несколько узлов, в которые необходимо вставить один или несколько дочерних узлов. Можно указать абсолютную строку XPath_string с начальным слэшем или относительную строку XPath_string, опуская начальный слэш. Если опустить начальный слэш, контекст относительного пути по умолчанию соответствует корневому узлу. В аргументе value_expr передается фрагмент XML, определяющий один или несколько вставляемых узлов и их положение в родительском узле. Он должен разрешаться в строку. Необязательный параметр namespace_string предоставляет информацию о пространстве имен для XPath_string. Этот параметр должен иметь тип text.

Пример:

select xml_insert_before(
  '<body><wow id="1"><lol/></wow><wow id="2"><lol/></wow></body>'::xml,
  '/body/wow[2]'::text,
  '<wow id="BEFORE New one">WOOOW</wow>'::xml
  )
;

Результат:

<body>
  <wow id="1">
    <lol/>
  </wow>
  <wow id="BEFORE New one">WOOOW</wow>
  <wow id="2">
    <lol/>
  </wow>
</body>

xml_insert_after

xml_insert_after ( XMLType_instance xml, XPath_string text, value_expr xml [, namespace_string text] )

Функция xml_insert_after вставляет один или несколько узлов любого типа сразу после целевого узла, который не является узлом атрибута. Эта функция похожа на xml_insert_before, но вставляет после, а не до целевого узла.

В аргументе XMLType_instance передается целевой узел вставки. В аргументе XPath_string передается выражение XPath 1.0, которое находит в целевом узле ноль или более узлов любого типа, кроме узлов атрибутов. XML-данные вставляются сразу после каждого из этих узлов; то есть каждый указанный узел становится предшествующим одноуровневым узлом узла, указанного в value_expr. В аргументе value_expr передаются XML-данные, которые необходимо вставить. Можно указать один или несколько узлов любого типа. Порядок узлов сохраняется после вставки. В необязательном параметре namespace_string задается пространство имен для целевого узла.

Пример:

select xml_insert_after(
  '<body>
    <wow id="1">
     <lol/>
    </wow>
    <wow id="2">
     <lol/>
    </wow>
  </body>'::xml,
  '/body/wow[2]'::text,
  '<wow id="BEFORE new one">WOOOW</wow>'::xml
  )
;

Результат:

<body>
  <wow id="1">
    <lol/>
  </wow>
  <wow id="2">
    <lol/>
  </wow>
  <wow id="BEFORE new one">WOOOW</wow>
</body>

xml_insert_child_after

xml_insert_child_after ( XMLType_instance xml, XPath_string text, XPathChild_string text, value_expr xml [, namespace_string text] )

Функция xml_insert_child_after* вставляет один или несколько узлов любого типа сразу после целевого узла, который не является узлом атрибута. Документ XML, который является целью вставки, может быть основан на схеме или не основан на схеме.

В аргументе XMLType_instance передается целевой узел вставки. В аргументе XPath_string передается выражение XPath 1.0, которое находит в целевом узле ноль или более узлов любого типа, кроме узлов атрибутов. XML-данные вставляются в содержимое целевого узла, после узла, задаваемого выражением XPathChild_string. В аргументе XPathChild_string передается выражение XPath 1.0, которое находит в содержимом целевого узла ноль или более узлов, после которых будет вставлено значение value_expr. В аргументе value_expr передаются XML-данные, которые необходимо вставить. Можно указать один или несколько узлов любого типа. Порядок узлов сохраняется после вставки. В необязательном параметре namespace_string задается пространство имен для целевого и дочернего узла.

Пример:

select xml_insert_child_after(
  '<books xmlns=\"http://example.com/\">\
            <shelf>\
                <book>Book A</book>\
                <book>Book B</book>\
            </shelf>\
            <shelf>\
                <book>Book C</book>\
                <book>Book D</book>\
            </shelf>\
        </books>'::xml,
  '/books/shelf'::text,
  'book[2]'::text,
  '<book>Third book in a shelf</book>'::xml,
  'xmlns="http://example.com/"'::text
  )
;

Результат:

<books xmlns="http://example.com/">
  <shelf>
    <book>Book A</book>
    <book>Book B</book>
    <book>Third book in a shelf</book>
  </shelf>
  <shelf>
    <book>Book C</book>
    <book>Book D</book>
    <book>Third book in a shelf</book>
  </shelf>
</books>

xml_update

xml_update(XMLType_instance,Update_pair[],[, namespace_string ])

Функция xml_update принимает в качестве аргументов экземпляр XMLType и массив пар XPath-значение value_expr и возвращает экземпляр XMLType с обновленными значениями для каждой пары. Если XPath_string является элементом XML, то соответствующее value_expr должно быть экземпляром XMLType. Если XPath_string является атрибутом или текстовым узлом, то value_expr может быть любым скалярным типом данных. Можно указать абсолютную строку XPath_string с начальным слэшем или относительную строку XPath_string, опуская начальный слэш. Если опустить начальный слэш, контекст относительного пути по умолчанию соответствует корневому узлу.

Типы данных цели каждой строки XPath_string и соответствующего ей value_expr должны совпадать. Необязательный параметр namespace_string должен разрешаться в значение символьного типа, указывающее сопоставление по умолчанию или сопоставление пространства имен для префиксов, которые база данных QHB использует при оценке выражений XPath.

Если обновить элемент XML до нуля, QHB удалит атрибуты и дочерние элементы элемента, и элемент станет пустым. Если обновить до нуля текстовый узел элемента, QHB удалит текстовое значение элемента, а сам элемент останется, но будет пустым.

Эта функция материализует XML-документ в памяти и обновляет значение. Для использования в операторе UPDATE для модификации содержимого в столбце таблицы необходимо использовать результат функции в выражении UPDATE ... SET.

Пример:

select xml_update(
  '
    <body>
     <wow id="1">WOW1</wow>
     <wow id="2">WOW2</wow>
     <foo id="1">FOO1</foo>
     <foo id="2">FOO2</foo>
    </body>'::xml,
  '{"/body/wow[1]","<NewNode>I am new</NewNode>","/body/foo[2]","<NewNode>Another new node</NewNode>"}'
  )

Результат:

<body>
  <NewNode>I am new</NewNode>
  <wow id="2">WOW2</wow>
  <foo id="1">FOO1</foo>
  <NewNode>Another new node</NewNode>
</body>

xml_delete

xml_delete ( XMLType_instance, XPath_string [, namespace_string ])

Функция xml_delete удаляет узел или узлы, соответствующие выражению XPath, в целевом XML.

В аргументе XMLType_instance передается экземпляр XMLType. В аргументе XPath_string передается выражение Xpath, указывающее на один или несколько узлов, которые необходимо удалить. Можете указать абсолютную строку XPath_string с начальным слэшем или относительную строку XPath_string, опуская начальный слэш. Если опустить начальный слэш, контекст относительного пути по умолчанию соответствует корневому узлу. Любые дочерние узлы узлов, указанных в XPath_string, также удаляются. Необязательный параметр namespace_string предоставляет информацию о пространстве имен для XPath_string. Этот параметр должен иметь тип text.

Пример:

select xml_delete(
  '<body>
    <wow id="1">WOW1</wow>
    <wow id="2">WOW2</wow>
   </body>'::xml,
  '/body/wow[2]'::text
  )
;

Результат:

<body>
  <wow id="1">WOW1</wow>
</body>

xml_extract_value

xml_ ( XMLType_instance, XPath_string [, namespace_string ])

Функция xml_extract_value возвращает скалярное значение, соответствующие выражению XPath в целевом XML; если выражению XPath соответствует несколько узлов, функция завершится с ошибкой.

В аргументе XMLType_instance передается экземпляр XMLType. В аргументе XPath_string передается выражение Xpath, указывающее на один или несколько узлов для поиска. Можно указать абсолютную строку XPath_string с начальным слэшем или относительную строку XPath_string, опуская начальный слэш. Если опустить начальный слэш, контекст относительного пути по умолчанию соответствует корневому узлу. Необязательный параметр namespace_string предоставляет информацию о пространстве имен для XPath_string. Этот параметр должен иметь тип text.

Пример:

select xml_extract_value(
  '<books>\
            <shelf>\
                <book>Book A</book>\
                <book>Book B</book>\
            </shelf>\
            <shelf>\
                <book>Book C</book>\
                <book>Book D</book>\
                <book>Book D</book>\
            </shelf>\
        </books>"'::xml,
  '/books/shelf/book[3]'::text
  )
;

Результат:

Book D

xml_extract

xml_ ( XMLType_instance, XPath_string [, namespace_string ])

Функция xml_extract возвращает один или несколько узлов, соответствующих выражению XPath в целевом XML.

В аргументе XMLType_instance передается экземпляр XMLType. В аргументе XPath_string передается выражение Xpath, указывающее на один или несколько узлов, которые необходимо добавить в возвращаемый документ. Можно указать абсолютную строку XPath_string с начальным слэшем или относительную строку XPath_string, опуская начальный слэш. Если опустить начальный слэш, контекст относительного пути по умолчанию соответствует корневому узлу. Необязательный параметр namespace_string предоставляет информацию о пространстве имен для XPath_string. Этот параметр должен иметь тип text.

Пример:

select xml_extract(
  '<books>\
            <shelf>\
                <book>Book A</book>\
                <book>Book B</book>\
            </shelf>\
            <shelf>\
                <book>Book C</book>\
                <book>Book D</book>\
            </shelf>\
        </books>"'::xml,
  '/books/shelf/book'::text
  )
;

Результат:

<book>Book A</book>
<book>Book B</book>
<book>Book C</book>
<book>Book D</book>

xml_exists_node

xml_ ( XMLType_instance, XPath_string [, namespace_string ])

Функция xml_exists_node проверяет, существует ли узел или узлы, соответствующие выражению XPath в целевом XML; она возвращает 1, если хотя бы один узел найден, и 0 в противном случае.

В аргументе XMLType_instance передается экземпляр XMLType. В аргументе XPath_string передается выражение Xpath, указывающее на один или несколько узлов, которые необходимо удалить. Можно указать абсолютную строку XPath_string с начальным слэшем или относительную строку XPath_string, опуская начальный слэш. Если опустить начальный слэш, контекст относительного пути по умолчанию соответствует корневому узлу. Необязательный параметр namespace_string предоставляет информацию о пространстве имен для XPath_string. Этот параметр должен иметь тип text.

Пример:

select xml_exists_node(
  '<books>\
            <shelf>\
                <book1>Book A</book1>\
                <book2>Book B</book2>\
            </shelf>\
            <shelf>\
                <book1>Book C</book1>\
                <book2>Book D</book2>\
                <book3>Book D</book3>\
            </shelf>\
        </books>"'::xml,
  '/books/shelf/book3'::text
  )
;

Результат:

1

¹

Примером формы содержимого является результат, содержащий более одного узла элемента на верхнем уровне или какой-либо непробельный текст вне единственного элемента. Результат XPath может иметь и другую форму, например, если это выражение возвращает узел атрибута, выбранный из содержащего этот атрибут элемента. Подобный результат будет приведен в форму содержимого, где каждый такой недопустимый узел заменяется его строковым значением, как определено для функции string в XPath 1.0.

Функции и операторы JSON

В этом разделе описываются:

функции и операторы для обработки и создания данных JSON

язык путей SQL/JSON

Чтобы узнать больше о стандарте SQL/JSON, обратитесь к стандарту ISO/IEC TR 19075-6. Подробную информацию о типах JSON, поддерживаемых в QHB, см. в разделе Типы JSON.

Обработка и создание данных JSON

В Таблице 44 приведены имеющиеся операторы для использования с типами данных JSON (см. раздел Типы JSON). Кроме того, для типа jsonb (но не для json) имеются обычные операторы сравнения, приведенные в таблице Операторы сравнения. Эти операторы сравнения следуют правилам упорядочивания для операций B-дерева, изложенным в подразделе Индексация jsonb.

Таблица 44. Операторы для типов json и jsonb

Оператор

Описание
Пример(ы)

json -> integer → json
jsonb -> integer → jsonb

Извлекает n-й элемент массива JSON (элементы массива индексируются с нуля, но отрицательные числа считаются с конца).
'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json -> 2 → {"c":"baz"}
'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json -> -3 → {"a":"foo"}

json -> text → json
jsonb -> text → jsonb

Извлекает поле объекта JSON по заданному ключу.
'{"a": {"b":"foo"}}'::json -> 'a' → {"b":"foo"}

json ->> integer → text
jsonb ->> integer → text

Извлекает n-й элемент массива JSON в виде значения типа text.
'[1,2,3]'::json ->> 2 → 3

json ->> text → text
jsonb ->> text → text

Извлекает поле объекта JSON по заданному ключу в виде значения типа text.
'{"a":1,"b":2}'::json ->> 'b' → 2

json #> text[] → json
jsonb #> text[] → jsonb

Извлекает дочерний объект JSON по заданному пути, где элементами пути могут быть ключи полей или индексы массивов.
'{"a": {"b": ["foo","bar"]}}'::json #> '{a,b,1}' → "bar"

json #>> text[] → text
jsonb #>> text[] → text

Извлекает дочерний объект JSON по заданному пути в виде значения типа text.
'{"a": {"b": ["foo","bar"]}}'::json #>> '{a,b,1}' → bar

Примечание
Если структура входных данных JSON не соответствует запросу, например, если заданного ключа или элемента массива не существует, операторы извлечения поля/элемента/пути не выдают ошибку, а возвращают NULL.

Некоторые из следующих операторов существуют только для jsonb, как показано в Таблице 45. В подразделе Индексация jsonb описывается, как эти операторы можно использовать для эффективного поиска индексированных данных jsonb.

Таблица 45. Дополнительные операторы jsonb

Оператор

Описание
Пример(ы)

jsonb @> jsonb → boolean

В первое значение JSON вложено второе? (Подробную информацию о вложении см. в подразделе Проверка на вложение и существование в jsonb.)
'{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb → t

jsonb <@ jsonb → boolean

Первое значение JSON вложено во второе?
'{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb → t

jsonb ? text → boolean

Текстовая строка существует в значении JSON в качестве ключа верхнего уровня или элемента массива?
'{"a":1, "b":2}'::jsonb ? 'b' → t
'["a", "b", "c"]'::jsonb ? 'b' → t

jsonb ?| text[] → boolean

Какие-либо строки в текстовом массиве существуют в качестве ключей верхнего уровня или элементов массива?
'{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'd'] → t

jsonb ?& text[] → boolean

Все строки в текстовом массиве существуют в качестве ключей верхнего уровня или элементов массива?
'["a", "b", "c"]'::jsonb ?& array['a', 'b'] → t

jsonb || jsonb → jsonb

Конкатенирует два значения jsonb. Конкатенация двух массивов генерирует массив, содержащий все элементы каждого аргумента. Конкатенация двух объектов генерирует объект, содержащий объединение их ключей, при этом при наличии дубликатов ключей выбирается значение из второго объекта. Все другие случаи обрабатываются путем преобразования аргументов, отличных от массивов, в массивы с одним элементом, а затем конкатенируя их как два массива. Не работает рекурсивно: объединяются только массивы или структуры объекта на верхнем уровне.
'["a", "b"]'::jsonb || '["a", "d"]'::jsonb → ["a", "b", "a", "d"]
'{"a": "b"}'::jsonb || '{"c": "d"}'::jsonb → {"a": "b", "c": "d"}
'[1, 2]'::jsonb || '3'::jsonb → [1, 2, 3]
'{"a": "b"}'::jsonb || '42'::jsonb → [{"a": "b"}, 42]
Чтобы вставить один массив в другой за одну запись, оберните его в дополнительный слой массива, например:
'[1, 2]'::jsonb || jsonb_build_array('[3, 4]'::jsonb) → [1, 2, [3, 4]]

jsonb - text → jsonb

Удаляет ключ (и его значение) из объекта JSON или соответствующие строковые значения из массива JSON.
'{"a": "b", "c": "d"}'::jsonb - 'a' → {"c": "d"}
'["a", "b", "c", "b"]'::jsonb - 'b' → ["a", "c"]

jsonb - text[] → jsonb

Удаляет все соответствующие ключи или элементы массива из левого операнда.
'{"a": "b", "c": "d"}'::jsonb - '{a,c}'::text[] → {}

jsonb - integer → jsonb

Удаляет элемент массива с заданным индексом (отрицательные числа считаются с конца). Выдает ошибку, если значение JSON — не массив.
'["a", "b"]'::jsonb - 1 → ["a"]

jsonb #- text[] → jsonb

Удаляет поле или элемент массива по заданному пути, где элементы пути могут быть ключами полей или индексами массивов.
'["a", {"b":1}]'::jsonb #- '{1,b}' → ["a", {}]

jsonb @? jsonpath → boolean

Возвращает ли путь JSON какой-либо элемент для заданного значения JSON?
'{"a":[1,2,3,4,5]}'::jsonb @? '$.a[*] ? (@ > 2)' → t

jsonb @@ jsonpath → boolean

Возвращает результат проверки предиката пути JSON для заданного значения JSON. Учитывается только первый элемент результата. Если результат не является логическим, возвращается NULL.
'{"a":[1,2,3,4,5]}'::jsonb @@ '$.a[*] > 2' → t

Примечание
Операторы jsonpath @? и @@ подавляют следующие ошибки: отсутствие поля объекта или элемента массива, неожиданный тип элемента JSON и ошибки в числах и дате/времени. Это может быть полезно при поиске в коллекциях документов JSON с различной структурой.

В Таблице 46 приведены имеющиеся функции для создания значений json и jsonb.

Таблица 46. Функции для создания JSON

Функция

Описание
Пример(ы)

to_json ( anyelement ) → json
to_jsonb ( anyelement ) → jsonb

Преобразует любое значение SQL в json или jsonb. Массивы и составные объекты преобразуются рекурсивно в массивы и объекты (многомерные массивы становятся в JSON массивами массивов). В иных случаях, если задано приведение типа данных SQL к json, то для выполнения этого преобразования используется эта функция приведения;[b] для всех остальных выдается скалярное значение JSON. Значения всех скалярных типов, кроме числового, логического и NULL, будут представлены в виде текста, в который при необходимости будет добавлено экранирование символов для получения допустимого строкового значения JSON.
to_json('Fred said "Hi."'::text) → "Fred said \"Hi.\""
to_jsonb(row(42, 'Fred said "Hi."'::text)) → {"f1": 42, "f2": "Fred said \"Hi.\""}

array_to_json ( anyarray [, boolean ] ) → json

Преобразует массив SQL в массив JSON. Поведение аналогично функции to_json, за исключением того, что если необязательный логический параметр равен true, между элементами массива верхнего уровня добавятся переводы строк.
array_to_json('{{1,5},{99,100}}'::int[]) → [[1,5],[99,100]]

row_to_json ( record [, boolean ] ) → json

Преобразует составное значение SQL в объект JSON. Поведение аналогично функции to_json, за исключением того, что если необязательный логический параметр равен true, между элементами верхнего уровня добавятся переводы строк.
row_to_json(row(1,'foo')) → {"f1":1,"f2":"foo"}

json_build_array ( VARIADIC "any" ) → json
jsonb_build_array ( VARIADIC "any" ) → jsonb

Формирует массив JSON (возможно, неоднородный по типам) из переменного списка аргументов. Каждый аргумент преобразуется методом to_json или to_jsonb.
json_build_array(1, 2, 'foo', 4, 5) → [1, 2, "foo", 4, 5]

json_build_object ( VARIADIC "any" ) → json
jsonb_build_object ( VARIADIC "any" ) → jsonb

Формирует объект JSON из переменного списка аргументов. По соглашению, в списке аргументов по очереди перечисляются ключи и значения. Аргументы-ключи приводятся к текстовому типу; аргументы-значения преобразуются методом to_json или to_jsonb.
json_build_object('foo', 1, 2, row(3,'bar')) → {"foo" : 1, "2" : {"f1":3,"f2":"bar"}}

json_object ( text[] ) → json
jsonb_object ( text[] ) → jsonb

Формирует объект JSON из текстового массива. Этот массив должен иметь либо ровно одну размерность с четным числом членов (в этом случае они воспринимаются как чередующиеся пары ключ/значение), либо две размерности, и при этом каждый внутренний массив содержит ровно два элемента, которые воспринимаются как пара ключ/значение. Все значения преобразуются в строки JSON.
json_object('{a, 1, b, "def", c, 3.5}') → {"a" : "1", "b" : "def", "c" : "3.5"}
json_object('{{a, 1}, {b, "def"}, {c, 3.5}}') → {"a" : "1", "b" : "def", "c" : "3.5"}

json_object ( ключи text[], значения text[] ) → json
jsonb_object ( ключи text[], значения text[] ) → jsonb

Эта форма json_object принимает ключи и значения по парам из отдельных текстовых массивов. В остальном она идентична форме с одним аргументом.
json_object('{a,b}', '{1,2}') → {"a": "1", "b": "2"}

[b] Например, в расширении hstore определено приведение типа hstore к json, поэтому значения hstore, преобразованные посредством функций создания JSON, будут представлены в виде объектов JSON, а не примитивных строковых значений.

В Таблице 47 показаны имеющиеся функции для обработки значений json и jsonb.

Таблица 47. Функции для обработки JSON

Функция

Описание
Пример(ы)

json_array_elements ( json ) → setof json
jsonb_array_elements ( jsonb ) → setof jsonb

Разворачивает массив JSON верхнего уровня в набор значений JSON.

select * from json_array_elements('[1,true, [2,false]]') →
       value
    -----------
     1
     true
     [2,false]

json_array_elements_text ( json ) → setof text
jsonb_array_elements_text ( jsonb ) → setof text

Разворачивает массив JSON верхнего уровня в набор значений text.

select * from json_array_elements_text('["foo", "bar"]') →
       value
    -----------
    foo
    bar

json_array_length ( json ) → integer
jsonb_array_length ( jsonb ) → integer

Возвращает количество элементов в массиве JSON верхнего уровня.
json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]') → 5

json_each ( json ) → setof record ( ключ text, значение json )
jsonb_each ( jsonb ) → setof record ( ключ text, значение jsonb )

Разворачивает объект JSON верхнего уровня в набор пар ключ/значение.

select * from json_each('{"a":"foo", "b":"bar"}') →
     key | value
    -----+-------
     a   | "foo"
     b   | "bar"

json_each_text ( json ) → setof record ( ключ text, значение text )
jsonb_each_text ( jsonb ) → setof record ( ключ text, значение text )

Разворачивает объект JSON верхнего уровня в набор пар ключ/значение. Возвращаемые значения будут иметь тип text.

select * from json_each_text('{"a":"foo", "b":"bar"}') →
     key | value
    -----+-------
     a   | foo
     b   | bar

json_extract_path ( целевой_json json, VARIADIC элементы_пути text[] ) → json
jsonb_extract_path ( целевой_json jsonb, VARIADIC элементы_пути text[] ) → jsonb

Извлекает подобъект JSON по заданному пути. (Функционально это равнозначно действию оператора #>, но в некоторых случаях может быть удобнее записать путь в виде списка с переменным числом аргументов.)
json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}', 'f4', 'f6') → "foo"

json_extract_path_text ( целевой_json json, VARIADIC элементы_пути text[] ) → text
jsonb_extract_path_text ( целевой_json jsonb, VARIADIC элементы_пути text[] ) → text

Извлекает подобъект JSON по заданному пути в виде значения text. (Функционально это равнозначно действию оператора #>>.)
json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}', 'f4', 'f6') → foo

json_object_keys ( json ) → setof text
jsonb_object_keys ( jsonb ) → setof text

Возвращает набор ключей в объекте JSON верхнего уровня.

select * from json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}') →
     json_object_keys
    ------------------
     f1
     f2

json_populate_record ( основа anyelement, целевой_json json ) → anyelement
jsonb_populate_record ( основа anyelement, целевой_json jsonb ) → anyelement

Разворачивает объект JSON верхнего уровня в строку, имеющую составной тип аргумента основа. Объект JSON сканируется на наличие полей, чьи имена соответствуют именам столбцов типа выходной строки, и их значения вставляются в эти столбцы вывода. (Поля, не соответствующие именам никаких выходных столбцов, игнорируются.) Как правило, значение основы просто равно NULL, что означает, что выходные столбцы не соответствующие полям объекта, будут заполнены значениями NULL. Однако если основа отлична от NULL, то для заполнения таких столбцов будут использованы содержащиеся в нем значения. Для преобразования значения JSON в SQL-тип выходного столбца последовательно применяются следующие правила:

Значение NULL в JSON в любом случае преобразуется в значение NULL в SQL.

Если выходной столбец имеет тип json или jsonb, значение JSON воспроизводится без изменений.

Если выходной столбец имеет составной тип (тип строки) и значение JSON является объектом JSON, поля этого объекта преобразуются в столбцы типа выходной строки посредством рекурсивного применения этих правил.

Аналогично если выходной столбец имеет тип массива и значение JSON является массивом JSON, элементы этого массива JSON преобразуются в столбцы типа выходной строки посредством рекурсивного применения этих правил.

Иначе, если значение JSON является строкой, содержимое этой строки передается входной функции преобразования для типа данных целевого столбца.

В остальных случаях входной функции преобразования для типа данных целевого столбца передается обычное текстовое представление значения JSON.

Хотя в следующем примере используется неизменное значение JSON, обычно выполняется обращение (с помощью LATERAL) к столбцу json или jsonb из другой таблицы, находящейся в предложении FROM того же запроса. Включение функции json_populate_record в предложение FROM — удачный прием, поскольку все извлекаемые столбцы доступны для использования без необходимости в повторных вызовах функции.

create type subrowtype as (d int, e text); create type myrowtype as (a int, b text[],
c subrowtype);
select * from json_populate_record(null::myrowtype, '{"a": 1, "b": ["2", "a b"], "c":
{"d": 4, "e": "a b c"}, "x": "foo"}') →
    a |   b       |      c
   ---+-----------+-------------
    1 | {2,"a b"} | (4,"a b c")

json_populate_recordset ( основа anyelement, целевой_json json ) → setof anyelement
jsonb_populate_recordset ( основа anyelement, целевой_json jsonb ) → setof anyelement

Разворачивает массив JSON верхнего уровня с объектами в набор строк, имеющих составной тип аргумента основа. Каждый элемент массива JSON обрабатывается так же, как описано выше для json[b]_populate_record.

create type twoints as (a int, b int);
select * from json_populate_recordset(null::twoints, '[{"a":1,"b":2}, {"a":3, "b":4}]') →
    a | b
   ---+---
    1 | 2
    3 | 4

json_to_record ( json ) → record
jsonb_to_record ( jsonb ) → record

Разворачивает объект JSON верхнего уровня в строку, имеющую составной тип, определенный в предложении AS. (Как и со всеми функциями, возвращающими значение record, вызывающий запрос должен явно определить структуру записи в предложении AS.) Выходная запись заполняется значениями полей объекта JSON так же, как описано выше для json[b]_populate_record. Поскольку этой функции не передается значение записи, несоответствующие столбцы всегда заполняются значениями NULL.

create type myrowtype as (a int, b text);
select * from json_to_record('{"a":1,"b":[1,2,3],"c":[1,2,3],"e":"bar","r": {"a": 123,
"b": "a b c"}}') as x(a int, b text, c int[], d text, r myrowtype) →
    a |    b    |    c    | d |       r
   ---+---------+---------+---+---------------
    1 | [1,2,3] | {1,2,3} |   | (123,"a b c")

json_to_recordset ( json ) → setof record
jsonb_to_recordset ( jsonb ) → setof record

Разворачивает массив JSON верхнего уровня с объектами в набор строк, имеющих составной тип, определенный в предложении AS. (Как и со всеми функциями, возвращающими значение record, вызывающий запрос должен явно определить структуру записи в предложении AS.) Каждый элемент массива JSON обрабатывается так же, как описано выше для json[b]_populate_record.

select * from json_to_recordset('[{"a":1,"b":"foo"}, {"a":"2","c":"bar"}]') as x(a int,
b text) →
    a |  b
   ---+-----
    1 | foo
    2 |

jsonb_set ( цель jsonb, путь text[], новое_значение jsonb [, создать_если_отсутствует boolean ] ) → jsonb

Возвращает цель, где элемент по указанному пути заменяется новым_значением, либо новое_значение добавляется, если аргумент создать_если_отсутствует равен true (это значение по умолчанию) и элемент, на который указывает путь, не существует. Все предыдущие звенья пути должны существовать, иначе цель будет возвращена без изменений. Как и с операторами, ориентированными на пути, отрицательные целые числа, находящиеся в пути, считают элементы с конца массивов JSON. Если последнее звено пути является индексом, выходящим за границы массива, и аргумент создать_если_отсутствует равен true, новое значение добавляется в начало массива, когда индекс отрицательный, или в его конец, когда индекс положительный.
jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0,f1}', '[2,3,4]', false) → [{"f1": [2, 3, 4], "f2": null}, 2, null, 3]
jsonb_set('[{"f1":1,"f2":null},2]', '{0,f3}', '[2,3,4]') → [{"f1": 1, "f2": null, "f3": [2, 3, 4]}, 2]

jsonb_set_lax ( цель jsonb, путь text[], новое_значение jsonb [, создать_если_отсутствует boolean [, обращение_с_null text ]] ) → jsonb

Если новое_значение отлично от NULL, эта функция ведет себя идентично jsonb_set. В противном случае она действует согласно аргументу обращение_с_null, который может принимать значения 'raise_exception' (сгенерировать исключение), 'use_json_null' (использовать NULL в JSON), 'delete_key' (удалить ключ) или 'return_target' (вернуть цель без изменений). Значение по умолчанию — 'use_json_null'.
jsonb_set_lax('[{"f1":1,"f2":null},2,null,3]', '{0,f1}', null) → [{"f1":null,"f2":null},2,null,3]
jsonb_set_lax('[{"f1":99,"f2":null},2]', '{0,f3}', null, true, 'return_target') → [{"f1": 99, "f2": null}, 2]

jsonb_insert ( цель jsonb, путь text[], новое_значение jsonb [, вставить_после boolean ] ) → jsonb

Возвращает цель с вставленным в нее новым_значением. Если элемент, на который указывает путь, является элементом массива, новое_значение будет вставлено перед этим элементом, если параметр вставить_после равен false (это значение по умолчанию), или после него, если вставить_после равен true. Если элемент, на который указывает путь, является полем объекта, новое_значение будет вставлено, только если объект еще не содержит этот ключ. Все предыдущие звенья пути должны существовать, иначе цель будет возвращена без изменений. Как и с операторами, ориентированными на пути, отрицательные целые числа, находящиеся в пути, считают элементы с конца массивов JSON. Если последнее звено пути является индексом, выходящим за границы массива, новое значение добавляется в начало массива, когда индекс отрицательный, или в его конец, когда тот положительный.
jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"') → {"a": [0, "new_value", 1, 2]}
jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"', true) → {"a": [0, 1, "new_value", 2]}

json_strip_nulls ( json ) → json
jsonb_strip_nulls ( jsonb ) → jsonb

Рекурсивно удаляет из заданного значения JSON все поля объектов, имеющие значения NULL. Значения NULL, не относящиеся к полям объектов, не затрагиваются.
json_strip_nulls('[{"f1":1, "f2":null}, 2, null, 3]') → [{"f1":1},2,null,3]

jsonb_path_exists ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → boolean

Проверяет, возвращает ли путь JSON какой-либо элемент для заданного значения JSON. Если задается аргумент переменные, он должен быть объектом JSON, и его поля предоставляют именованные значения, подставляемые в выражение jsonpath. Если задается аргумент немой_режим и он равен true, эта функция подавляет те же ошибки, что и операторы @? и @@.
jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2, "max":4}') → t

jsonb_path_match ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → boolean

Возвращает результат проверки предиката пути JSON для заданного значения JSON. Учитывается только первый элемент результата. Если результат не является логическим, возвращается NULL. Необязательные аргументы переменные и немой_режим действуют так же, как и для jsonb_path_exists.
jsonb_path_match('{"a":[1,2,3,4,5]}', 'exists($.a[*] ? (@ >= $min && @ <= $max))', '{"min":2, "max":4}') → t

jsonb_path_query ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → setof jsonb

Возвращает все элементы JSON, полученные по данному пути JSON для заданного значения JSON. Необязательные аргументы переменные и немой_режим действуют так же, как и для jsonb_path_exists.

select * from jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)',
'{"min":2, "max":4}') →
    jsonb_path_query
   ------------------
    2
    3
    4

jsonb_path_query_array ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → jsonb

Возвращает все элементы JSON, полученные по данному пути JSON для заданного значения JSON, в виде массива JSON. Необязательные аргументы переменные и немой_режим действуют так же, как и для jsonb_path_exists.
jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2, "max":4}') → [2, 3, 4]

jsonb_path_query_first ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → jsonb

Возвращает первый элемент JSON, полученный по данному пути JSON для заданного значения JSON. Если результатов нет, возвращает NULL. Необязательные аргументы переменные и немой_режим действуют так же, как и для jsonb_path_exists.
jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2, "max":4}') → 2

jsonb_path_exists_tz ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → boolean
jsonb_path_match_tz ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → boolean
jsonb_path_query_tz ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → setof jsonb
jsonb_path_query_array_tz ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → jsonb
jsonb_path_query_first_tz ( цель jsonb, путь jsonpath [, переменные jsonb [, немой_режим boolean ]] ) → jsonb

Данные функции действуют подобно их двойникам без суффикса _tz, за исключением того, что эти функции поддерживают сравнение значений даты/времени, которое требует преобразований с учетом часовых поясов. В следующем примере требуется интерпретация значения даты без указания времени 2015-08-02 в виде метки времени с часовым поясом, поэтому результат зависит от текущего значения параметра TimeZone. Из-за этой зависимости данные функции помечаются как стабильные, что означает, что их нельзя использовать в индексах. Их двойники являются постоянными, и поэтому могут применяться в индексах, но при запросе на подобные сравнения они будут выдавать ошибку.
jsonb_path_exists_tz('["2015-08-01 12:00:00 -05"]', '$[*] ? (@.datetime() < "2015-08-02".datetime())') → t

jsonb_pretty ( jsonb ) → text

Преобразует заданное значение JSON в структурированный текст с отступами.

jsonb_pretty('[{"f1":1,"f2":null}, 2]') →
    [
        {
            "f1": 1,
            "f2": null
        },
        2
    ]

json_typeof ( json ) → text
jsonb_typeof ( jsonb ) → text

Возвращает тип значения JSON на верхнем уровне в виде текстовой строки. Возможные типы: object, array, string, number, boolean и null. (Не следует путать результат null со значением NULL в SQL; см. примеры.)
json_typeof('-123.4') → number
json_typeof('null'::json) → null
json_typeof(NULL::json) IS NULL → t

См. также раздел Агрегатные функции, где описывается агрегатная функция json_agg, агрегирующая значения записей в виде JSON, и агрегатная функция json_object_agg, агрегирующая пары значений в объект JSON, а также их аналоги для типа jsonb: jsonb_agg и jsonb_object_agg.

Язык путей SQL/JSON

Выражения пути SQL/JSON задают элементы, которые нужно извлечь из данных JSON аналогично выражениям XPath, используемым для доступа SQL к XML. В QHB выражения пути реализованы как тип данных jsonpath и могут использовать любые элементы, описанные в разделе Тип jsonpath.

Функции и операторы запросов JSON передают предоставленное выражение пути механизму пути для вычисления. Если выражение соответствует запрашиваемым данным JSON, возвращается соответствующий элемент или набор элементов JSON. Выражения пути написаны на языке путей SQL/JSON и могут включать в себя арифметические выражения и функции.

Выражение пути состоит из последовательности элементов, допустимых для типа данных jsonpath. Обычно выражение пути вычисляется слева направо, но при желании изменить порядок операций можно воспользоваться скобками. Если вычисление прошло успешно, создается последовательность элементов JSON и результат вычисления возвращается в функцию запроса JSON, которая завершает заданную обработку.

Для обращения к запрошенному значению JSON (элементу контекста) в выражении пути используется переменная $. За ней могут следовать один или несколько операторов аксессоров, которые опускаются в структуре JSON уровень за уровнем, чтобы извлечь подэлементы содержимого элемента контекста. Каждый последующий оператор имеет дело с результатом предыдущего этапа вычисления.

Например, предположим, у вас есть некоторые данные JSON с GPS-трекера, которые вы хотели бы проанализировать, например:

{
  "track": {
    "segments": [
      {
        "location": [ 47.763, 13.4034 ],
        "start time": "2018-10-14 10:05:14",
        "HR": 73
      },
      {
        "location": [ 47.706, 13.2635 ],
        "start time": "2018-10-14 10:39:21",
        "HR": 135
      }
    ]
  }
}

Для получения доступных сегментов треков вам нужно использовать оператор аксессора .ключ, чтобы спуститься сквозь окружающие объекты JSON:

$.track.segments

Для извлечения содержимого массива обычно применяется оператор [*]. Например, следующий путь вернет координаты местоположения для всех доступных сегментов треков:

$.track.segments[*].location

Чтобы вернуть координаты только первого сегмента, можно указать соответствующий нижний индекс в операторе аксессора []. Обратите внимание, что индексы массивов JSON считаются с 0:

$.track.segments[0].location

Результат каждого этапа вычисления пути может обрабатываться одним или несколькими операторами и методами jsonpath, перечисленными в подразделе Операторы и методы пути SQL/JSON. Каждому имени метода должна предшествовать точка. Например, так можно получить размер массива:

$.track.segments.size()

Дополнительные примеры использования операторов и методов jsonpath в выражениях пути см. ниже в подразделе Операторы и методы пути SQL/JSON.

При определении пути также можно использовать одно или несколько выражений фильтра, которые работают аналогично предложению WHERE в SQL. Выражение фильтра начинается со знака вопроса и содержит условие в скобках:

? (условие)

Выражения фильтра должны быть указаны сразу после того этапа вычисления пути, к которому они должны применяться. Результат этого этапа фильтруется, чтобы содержать только те элементы, которые удовлетворяют предоставленному условию. В SQL/JSON определена трехзначная логику, поэтому условие может быть true, false или unknown. Значение unknown играет ту же роль, что и NULL в SQL, и его можно проверить с помощью предиката unknown. На дальнейших этапах вычисления пути задействуются только те элементы, для которых выражение фильтра вернуло true.

Функции и операторы, которые можно использовать в выражениях фильтра, перечислены в Таблице 49. Значение, подлежащее фильтрации (т. е. результат предыдущего этапа пути), обозначается в выражении фильтра переменной @. Для получения элементов этого компонента можно написать после @ операторы аксессоров.

Например, предположим, вы хотите получить все значения частоты сердцебиений, превышающие 130. Это можно сделать с помощью следующего выражения:

$.track.segments[*].HR ? (@ > 130)

Чтобы получить время начала сегментов с такими значениями, следует перед возвратом времени начала отфильтровать ненужные сегменты, поэтому выражение фильтра применяется к предыдущему этапу, а путь, используемый в условии, отличается:

$.track.segments[*] ? (@.HR > 130)."start time"

При необходимости можно последовательно использовать несколько выражений фильтра. Например, следующее выражение выбирает время начала всех сегментов, содержащих местоположения с соответствующими координатами и высокими значениями частоты сердцебиений:

$.track.segments[*] ? (@.location[1] < 13.4) ? (@.HR > 130)."start time"

Также допускается использование выражений фильтра на разных уровнях вложенности. В следующем примере сначала все сегменты фильтруются по местоположению, а затем для подходящих сегментов, если таковые имеются, возвращаются высокие значения частоты сердцебиений:

$.track.segments[*] ? (@.location[1] < 13.4).HR ? (@ > 130)

Кроме того, можно вкладывать выражения фильтра друг в друга:

$.track ? (exists(@.segments[*] ? (@.HR > 130))).segments.size()

Это выражение возвращает размер трека, если оно содержит сегменты с высокими значениями частоты сердцебиений, или пустую последовательность в остальных случаях.

Реализация языка путей SQL/JSON в QHB имеет следующие отклонения от стандарта SQL/JSON:

Выражение пути может быть логическим предикатом, хотя стандарт SQL/JSON допускает предикаты только в фильтрах. Это необходимо для реализации оператора @@. Например, в QHB допустимо следующее выражение jsonpath:

$.track.segments[*].HR < 70

Существуют небольшие различия в интерпретации шаблонов регулярных выражений, используемых в фильтрах like_regex; эти различия описаны в подразделе Регулярные выражения SQL/JSON.

Строгий и нестрогий режимы

При запросе данных JSON выражение пути может не соответствовать фактической структуре данных JSON. Попытка получить доступ к несуществующему члену объекта или элементу массива приводит к структурной ошибке. Для выражений пути SQL/JSON существует два режима обработки структурных ошибок:

lax (по умолчанию) — нестрогий режим, при котором механизм пути неявно адаптирует запрошенные данные к указанному пути. Все оставшиеся структурные ошибки подавляются и преобразуются в пустые последовательности SQL/JSON.

strict — строгий режим, в котором при возникновении структурной ошибки она выдается как есть.

Нестрогий режим облегчает сопоставление структуры документа JSON и выражения пути, если данные JSON не соответствуют ожидаемой схеме. Если операнд не соответствует требованиям конкретной операции, перед выполнением этой операции он может автоматически оборачиваться в массив SQL/JSON или разворачиваться путем преобразования его элементов в последовательность SQL/JSON. Кроме того, операторы сравнения автоматически разворачивают свои операнды в нестрогом режиме, поэтому массивы SQL/JSON можно сравнивать, не прикладывая дополнительных усилий. Массив размером 1 считается равным своему единственному элементу. Автоматическое разворачивание не выполняется только в тех случаях, когда:

Выражение пути содержит методы type() или size(), которые возвращают тип и количество элементов в массиве соответственно.

Запрашиваемые данные JSON содержат вложенные массивы. В этом случае разворачивается только самый внешний массив, а все внутренние массивы остаются неизменными. Таким образом, на каждом этапе вычисления пути неявное развертывание может опускаться только на один уровень.

Например, при запросе перечисленных выше данных GPS в нестрогом режиме можно абстрагироваться от того факта, что в них хранится массив сегментов:

lax $.track.segments.location

В строгом режиме указанный путь должен точно соответствовать структуре запрашиваемого документа JSON, чтобы вернуть элемент SQL/JSON, поэтому использование этого выражения пути приведет к ошибке. Чтобы получить тот же результат, что и в нестрогом режиме, следует явно развернуть массив segments:

strict $.track.segments\[\*\].location

В нестрогом режиме аксессор .** может вызвать неожиданные результаты. Например, следующий запрос выберет каждое значение HR дважды:

lax $.**.HR

Это происходит из-за того, что аксессор .**выбирает и массив segments, и каждый из его элементов, тогда как аксессор .HR в нестрогом режиме автоматически разворачивает массивы. Чтобы избежать неожиданных результатов, рекомендуется использовать аксессор .** только в строгом режиме. Следующий запрос выбирает каждое значение HR только один раз:

strict $.**.HR

Операторы и методы пути SQL/JSON

В Таблице 48 показаны операторы и методы, имеющиеся в jsonpath. Обратите внимание, что унарные операторы и методы можно применять к нескольким значениям, полученным на предыдущем этапе пути, тогда как бинарные операторы (сложение и т. п.) можно применять только к отдельным значениям.

Таблица 48. Операторы и методы jsonpath

Оператор/Метод

Описание
Пример(ы)

число + число → число

Сложение
jsonb_path_query('[2]', '$[0] + 3') → 5

+ число → число

Унарный плюс (нет операции); в отличие от сложения, он может выполнить проход по нескольким значениям
jsonb_path_query_array('{"x": [2,3,4]}', '+ $.x') → [2, 3, 4]

число - число → число

Вычитание
jsonb_path_query('[2]', '7 - $[0]') → 5

- число → число

Смена знака; в отличие от вычитания, он может выполнить проход по нескольким значениям
jsonb_path_query_array('{"x": [2,3,4]}', '- $.x') → [-2, -3, -4]

число * число → число

Умножение
jsonb_path_query('[4]', '2 * $[0]') → 8

число / число → число

Деление
jsonb_path_query('[8.5]', '$[0] / 2') → 4.2500000000000000

число % число → число

Остаток целочисленного деления (по модулю)
jsonb_path_query('[32]', '$[0] % 10') → 2

значение . type() → строка

Тип элемента JSON (см. json_typeof)
jsonb_path_query_array('[1, "2", {}]', '$[*].type()') → ["number", "string", "object"]

значение . size() → число

Размер элемента JSON (число элементов массива или 1, если это не массив)
jsonb_path_query('{"m": [11, 15]}', '$.m.size()') → 2

значение . double() → число

Приблизительное число с плавающей запятой, преобразованное из числа JSON или строки
jsonb_path_query('{"len": "1.9"}', '$.len.double() * 2') → 3.8

число . ceiling() → число

Ближайшее целое число, большее или равное заданному числу
jsonb_path_query('{"h": 1.3}', '$.h.ceiling()') → 2

число . floor() → число

Ближайшее целое число, меньшее или равное заданному числу
jsonb_path_query('{"h": 1.7}', '$.h.floor()') → 1

число . abs() → число

Абсолютное значение (модуль) заданного числа
jsonb_path_query('{"z": -0.3}', '$.z.abs()') → 0.3

строка . datetime() → тип_даты_времени (см. примечание)

Значение даты/времени, преобразованное из строки
jsonb_path_query('["2015-8-1", "2015-08-12"]', '$[*] ? (@.datetime() < "2015-08-2".datetime())') → "2015-8-1"

строка . datetime(шаблон) → тип_даты_времени (см. примечание)

Значение даты/времени, преобразованное из строки с помощью заданного шаблона to_timestamp
jsonb_path_query_array('["12:30", "18:40"]', '$[*].datetime("HH24:MI")') → ["12:30:00", "18:40:00"]

объект . keyvalue() → массив

Пары ключ-значение объекта, представленные в виде массива объектов, содержащего три поля: "key" (ключ), "value" (значение) и "id"; "id" представляет собой уникальный идентификатор объекта, которому принадлежит данная пара ключ-значение
jsonb_path_query_array('{"x": "20", "y": 32}', '$.keyvalue()') → [{"id": 0, "key": "x", "value": "20"}, {"id": 0, "key": "y", "value": 32}]

Примечание
Результирующим типом методов datetime() и datetime(шаблон) может быть date, timetz, time, timestamptz или timestamp. Оба метода определяют свой результирующий тип динамически.

Метод datetime() последовательно пытается сопоставить свою входную строку с форматами ISO типов date, timetz, time, timestamptz и timestamp. На первом же подходящем формате он останавливается и выдает соответствующий тип данных.

Метод datetime(шаблон) определяет результирующий тип в соответствии с полями в предоставленной строке шаблона.

Методы datetime() и datetime(шаблон) применяют те же правила синтаксического анализа, что и функция SQL to_timestamp (см. раздел Функции для форматирования типов данных), но с тремя исключениями. Во-первых, эти методы не позволяют использовать в шаблоне коды без соответствия. Во-вторых, в строке шаблона допускаются только следующие разделители: знак минуса, точка, косая черта (слэш), запятая, апостроф, точка с запятой, двоеточие и пробел. В-третьих, разделители в строке шаблона должны в точности соответствовать входной строке.

Если нужно сравнить разные типы даты/времени, применяется неявное приведение. Значение date можно привести к типу timestamp или timestamptz, timestamp — к типу timestamptz, а time — к типу timetz. Однако все эти преобразования, кроме первого, зависят от текущего значения TimeZone и поэтому могут выполняться только в функциях jsonpath, учитывающих часовой пояс.

В Таблице 49 приведены имеющиеся элементы выражения фильтра.

Таблица 49. Элементы выражения фильтра jsonpath

Предикат/Значение

Описание
Пример(ы)

значение == значение → boolean

Сравнение равенства (этот и другие операторы сравнения работают со всеми скалярными значениями JSON)
jsonb_path_query_array('[1, "a", 1, 3]', '$[*] ? (@ == 1)') → [1, 1]
jsonb_path_query_array('[1, "a", 1, 3]', '$[*] ? (@ == "a")') → ["a"]

значение != значение → boolean
значение <> значение → boolean

Сравнение неравенства
jsonb_path_query_array('[1, 2, 1, 3]', '$[*] ? (@ != 1)') → [2, 3]
jsonb_path_query_array('["a", "b", "c"]', '$[*] ? (@ <> "b")') → ["a", "c"]

значение < значение → boolean

Сравнение «меньше»
jsonb_path_query_array('[1, 2, 3]', '$[*] ? (@ < 2)') → [1]

значение <= значение → boolean

Сравнение «меньше или равно»
jsonb_path_query_array('["a", "b", "c"]', '$[*] ? (@ <= "b")') → ["a", "b"]

значение > значение → boolean

Сравнение «больше»
jsonb_path_query_array('[1, 2, 3]', '$[*] ? (@ > 2)') → [3]

значение >= значение → boolean

Сравнение «больше или равно»
jsonb_path_query_array('[1, 2, 3]', '$[*] ? (@ >= 2)') → [2, 3]

true → boolean

JSON-константа true
jsonb_path_query('[{"name": "John", "parent": false}, {"name": "Chris", "parent": true}]', '$[*] ? (@.parent == true)') → {"name": "Chris", "parent": true}

false → boolean

JSON-константа false
jsonb_path_query('[{"name": "John", "parent": false}, {"name": "Chris", "parent": true}]', '$[*] ? (@.parent == false)') → {"name": "John", "parent": false}

null → значение

JSON-константа null (обратите внимание, что, в отличие от SQL, сравнение с null работает нормально)
jsonb_path_query('[{"name": "Mary", "job": null}, {"name": "Michael", "job": "driver"}]', '$[*] ? (@.job == null) .name') → "Mary"

логическое_значение && логическое_значение → boolean

Логическое И
jsonb_path_query('[1, 3, 7]', '$[*] ? (@ > 1 && @ < 5)') → 3

логическое_значение || логическое_значение → boolean

Логическое ИЛИ
jsonb_path_query('[1, 3, 7]', '$[*] ? (@ < 1 || @ > 5)') → 7

! логическое_значение → boolean

Логическое НЕ
jsonb_path_query('[1, 3, 7]', '$[*] ? (!(@ < 5))') → 7

логическое_значение is unknown → boolean

Проверяет, является ли unknown логическим условием.
jsonb_path_query('[-1, 2, 7, "foo"]', '$[*] ? ((@ > 0) is unknown)') → "foo"

строка like_regex строка [ flag строка ] → boolean

Проверяет, соответствует ли первый операнд регулярному выражению, заданному во втором операнде, с необязательными модификациями, описываемыми строкой символов flag (см. подраздел Регулярные выражения SQL/JSON).
jsonb_path_query_array('["abc", "abd", "aBdC", "abdacb", "babc"]', '$[*] ? (@ like_regex "^ab.*c")') → ["abc", "abdacb"]
jsonb_path_query_array('["abc", "abd", "aBdC", "abdacb", "babc"]', '$[*] ? (@ like_regex "^ab.*c" flag "i")') → ["abc", "aBdC", "abdacb"]

строка starts with строка → boolean

Проверяет, является ли второй операнд начальной подстрокой первого.
jsonb_path_query('["John Smith", "Mary Stone", "Bob Johnson"]', '$[*] ? (@ starts with "John")') → "John Smith"

exists ( выражение_пути ) → boolean

Проверяет, соответствует ли выражение пути хотя бы одному элементу SQL/JSON. Возвращает unknown, если это выражение пути могло вызвать ошибку; это используется во втором примере, чтобы избежать ошибки «ключ не найден» в строгом режиме.
jsonb_path_query('{"x": [1, 2], "y": [2, 4]}', 'strict $.* ? (exists (@ ? (@[*] > 2)))') → [2, 4]
jsonb_path_query_array('{"value": 41}', 'strict $ ? (exists (@.name)) .name') → []

Регулярные выражения SQL/JSON

Выражения путей SQL/JSON позволяют сопоставлять текст с регулярным выражением при помощи фильтра like_regex. Например, следующий запрос пути SQL/JSON будет соответствовать всем строкам в массиве, которые начинаются с английской гласной, без учета регистра:

$[*] ? (@ like_regex "^[aeiou]" flag "i")

Необязательная строка flag может включать один или несколько следующих символов: i — сопоставления без учета регистра, m — допускается сопоставление ^ и $ с переводами строк, s — допускается сопоставление . с переводом строки, и q — заключить в кавычки весь шаблон (сводя поиск к простому сопоставлению подстроки).

Стандарт SQL/JSON заимствует свое определение для регулярных выражений из оператора LIKE_REGEX, в котором, в свою очередь, используется стандарт XQuery. В настоящее время в QHB оператор LIKE_REGEX не поддерживается. Поэтому фильтр like_regex реализован с использованием механизма регулярных выражений POSIX, описанного в подразделе Регулярные выражения POSIX. Это приводит к различным незначительным несоответствиям стандартному поведению SQL/JSON, которые каталогизированы в подразделе Отличия от XQuery (LIKE_REGEX). Тем не менее, обратите внимание, что описанные там несовместимости флаговых букв не применяются к SQL/JSON, поскольку в SQL/JSON флаговые буквы XQuery переводятся во флаги, подходящие механизму POSIX.

Не забывайте, что аргумент шаблона like_regex является строковым литералом пути JSON, написанным в соответствии с правилами, приведенными в подразделе Тип jsonpath. В частности, это означает, что все обратные слеши в регулярном выражении необходимо дублировать. Например, чтобы сопоставить строковые значения корневого документа, содержащие только цифры, нужно написать следующее:

$.* ? (@ like_regex "^\\d+$")

Функции для управления последовательностями

В этом разделе описываются функции для работы с объектами последовательности, также называемыми генераторами последовательностей или просто последовательностями. Объекты последовательности — это специальные однострочные таблицы, созданные с помощью команды CREATE SEQUENCE. Эти объекты обычно используются для генерирования уникальных идентификаторов для строк таблицы. Функции для последовательностей, перечисленные в Таблице 50, предоставляют простые и безопасные для одновременного использования методы получения значений последовательности из таких объектов.

Таблица 50. Функции для последовательностей

Функция

Описание

nextval ( regclass ) → bigint

Продвигает объект последовательности к его следующему значению и возвращает это значение. Это происходит атомарно: даже если nextval выполняется параллельно в нескольких сеансах, в каждом вызове будет беспрепятственно получено отдельное значение последовательности. Если объект последовательности был создан с параметрами по умолчанию, при последующих вызовах nextval будут возвращаться следующие по порядку значения, начиная с 1. Изменить это поведение можно с помощью соответствующих параметров команды CREATE SEQUENCE.
Этой функции требуется право USAGE или UPDATE для последовательности.

setval ( regclass, bigint [, boolean ] ) → bigint

Устанавливает для объекта последовательности текущее значение и при необходимости флаг is_called. Форма с двумя параметрами устанавливает у последовательности заданное значение в поле last_value, а также устанавливает в поле is_called значение true, подразумевая, что при следующем вызове nextval последовательность продвинется к очередному значению, после чего вернет его. При этом currval также выдаст заданное значение. В форме с тремя параметрами в поле is_called можно установить true или false. У значения true будет тот же эффект, что и в форме с двумя параметрами. Если же присвоено значение false, то при первом вызове nextval после этого вернется именно заданное значение, а продвижение последовательности произойдет при следующем вызове nextval. Более того, в этом случае значение, выдаваемое currval, не изменится. Например:

SELECT setval('myseq', 42);           При следующем вызове nextval вернется 43
SELECT setval('myseq', 42, true);     То же самое
SELECT setval('myseq', 42, false);    При следующем вызове nextval вернется 42

Результатом, возвращаемым setval, является просто значение ее второго аргумента.
Этой функции требуется право UPDATE для последовательности.

currval ( regclass ) → bigint

Возвращает значение, полученное при последнем вызове nextval для этой последовательности в текущем сеансе. (Если nextval никогда не вызывалась для этой последовательности в этом сеансе, будет выдана ошибка.) Поскольку возвращаемое значение ограничено рамками сеанса, данная функция выдает предсказуемый ответ, независимо от того, выполнялась ли потом nextval в других сеансах или нет.
Этой функции требуется право USAGE или UPDATE для последовательности.

lastval () → bigint

Возвращает значение, выданное при последнем вызове nextval в текущем сеансе. Эта функция идентична currval, за исключением того, что она не принимает в качестве аргумента имя последовательности, а обращается к той последовательности, к которой в последний раз в текущем сеансе применялась nextval. Если nextval в текущем сеансе еще не вызывалась, то при вызове lastval возникнет ошибка.
Этой функции требуется право USAGE или UPDATE для последней использованной последовательности.

ВНИМАНИЕ!
Во избежание блокировки параллельных транзакций, получающих числа из одной последовательности, значение, полученное функцией nextval, не освобождается обратно для повторного использования, если вызывающая транзакция впоследствии прерывается. Это означает, что прерывание транзакции или сбой базы данных могут привести к появлению пропусков в последовательности задействованных значений. Это может случиться и без прерывания транзакции. Например, команда INSERT с предложением ON CONFLICT вычислит добавляемый кортеж, в том числе произведя все необходимые вызовы nextval, а потом обнаружит какой-нибудь конфликт, из-за которого, возможно, вместо добавления будет следовать правилу ON CONFLICT. Таким образом объекты последовательности QHB нельзя использовать для получения «сплошных» последовательностей.

Аналогично изменения состояния последовательности, произведенные функцией setval, немедленно становятся видимыми для других транзакций и не отменяются при откате транзакции, вызвавшей эту функцию.

Если сбой кластера баз данных происходит до фиксации транзакции, содержащей вызов nextval или setval, изменение состояния последовательности может не попасть в постоянное хранилище, поэтому неизвестно, в каком состоянии будет находиться последовательность после перезапуска кластера: оригинальном или измененном. Это безвредно при использовании последовательности внутри базы данных, поскольку другие эффекты незафиксированных транзакции тоже не будут видны. Однако если вы хотите постоянно использовать значение последовательности вне базы данных, предварительно убедитесь, что вызов nextval был зафиксирован.

Последовательность, которой должна управлять функция для последовательностей, задается аргументом regclass, представляющим собой просто OID последовательности в системном каталоге pg_class. Однако нет необходимости искать этот OID вручную, так как преобразователь вводимого значения типа regclass сделает всю работу за вас. Подробную информацию см. в разделе Типы идентификаторов объектов.

Условные выражения

В этом разделе описываются SQL-совместимые условные выражения, имеющиеся в QHB.

Совет
Если вам требуется нечто, выходящее за пределы возможностей этих условных выражений, как вариант, попробуйте написать серверные функции на более выразительном языке программирования.

Примечание
Хотя COALESCE, GREATEST и LEAST синтаксически похожи на функции, они не являются обычными функциями и поэтому не могут использоваться с аргументами, в которых явно описан массив VARIADIC.

CASE

Выражение CASE в SQL является общим условным выражением, схожим с операторами if/else в других языках программирования:

CASE WHEN условие THEN результат
   [WHEN ...]
   [ELSE результат]
END

Предложения CASE можно использовать везде, где допускаются выражения. Каждое условие здесь — это выражение, возвращающее результат типа boolean. Если результат условия равен true, значением выражения CASE является результат, следующий за условием, а остальная часть выражения CASE не обрабатывается. Если результат условия не равен true, любые последующие предложения WHEN проверяются таким же образом. Если ни одно из условий WHEN не возвращает true, значением выражения CASE является результат предложения ELSE. Если предложение ELSE опущено и ни одно из условий не равно true, результат будет равен NULL.

Пример:

SELECT * FROM test;

 a
---
 1
 2
 3

SELECT a,
       CASE WHEN a=1 THEN 'one'
            WHEN a=2 THEN 'two'
            ELSE 'other'
       END
    FROM test;

 a | case
---+-------
 1 | one
 2 | two
 3 | other

Типы данных всех выражений результатов должны быть преобразованы в один выходной тип. Более подробную информацию см. в разделе UNION, CASE и связанные конструкции.

Существует «простая» форма выражения CASE, которая представляет собой вариант общей формы, описанной выше:

CASE выражение
    WHEN значение THEN результат
  [WHEN ...]
  [ELSE результат]
END

Первое выражение вычисляется, а затем сравнивается с каждым из выражений значение в предложениях WHEN, пока не будет найдено равное ему. Если совпадений не найдено, возвращается результат предложения ELSE (или значение NULL). Эта форма похожа на оператор switch в языке C.

Приведенный выше пример можно записать с простым синтаксисом CASE:

SELECT a,
       CASE a WHEN 1 THEN 'one'
              WHEN 2 THEN 'two'
              ELSE 'other'
       END
    FROM test;

 a | case
---+-------
 1 | one
 2 | two
 3 | other

Выражение CASE не вычисляет подвыражения, которые не нужны для определения результата. Например, таким способом возможно избежать ошибки деления на ноль:

SELECT ... WHERE CASE WHEN x <> 0 THEN y/x > 1.5 ELSE false END;

Примечание
Как описано в разделе Правила вычисления выражений, существуют различные ситуации, в которых подвыражения выражения вычисляются в разное время, поэтому соблюдение принципа «CASE вычисляет только необходимые подвыражения» не гарантировано. Например, константное подвыражение 1/0 обычно приводит к ошибке деления на ноль во время планирования, даже если оно находится в ветви CASE, которая вовсе не задействуется во время выполнения.

COALESCE

COALESCE(значение [, ...])

Функция COALESCE возвращает первый же из своих аргументов, отличный от NULL. NULL возвращается, только если все аргументы равны NULL. Эта функция часто используется для замены значения по умолчанию значениями NULL при извлечении данных для отображения, например:

SELECT COALESCE(description, short_description, '(none)') ...

Этот запрос возвращает description, если оно не равно NULL, либо short_description, если оно не равно NULL, либо (none), если они оба равны NULL.

Все аргументы должны быть приводимыми к одному общему типу данных, который и будет типом результата (подробную информацию см. в разделе UNION, CASE и связанные конструкции).

Как и выражение CASE, COALESCE вычисляет только аргументы, которые необходимы для определения результата; то есть аргументы справа от первого отличного от NULL аргумента не вычисляются. Эта соответствующая стандарту SQL функция предоставляет возможности, схожие с таковыми NVL и IFNULL, использующихся в некоторых других СУБД.

NULLIF

NULLIF(значение1, значение2)

Функция NULLIF возвращает значение NULL, если значение1 равно значению2; в противном случае она возвращает значение1. Это можно использовать для выполнения операции, обратной той, что показана в приведенном выше примере COALESCE:

SELECT NULLIF(value, ’(none)’) ...

В этом примере если value равно (none), возвращается NULL, в противном случае возвращается значение value.

Два аргумента этой функции должны быть сравнимых типов. Если говорить конкретнее, они сравниваются точно так же, как в записи значение1 = значение2, поэтому для этих типов должен иметься подходящий оператор =.

Результат имеет тот же тип, что и первый аргумент — но есть одна тонкость. Фактически возвращается первый аргумент подразумеваемого оператора =, и в некоторых случаях он преобразуется, чтобы соответствовать типу второго аргумента. К примеру, NULLIF(1, 2.2) выдает numeric, поскольку оператора integer = numeric не существует, есть лишь оператор numeric = numeric.

GREATEST и LEAST

GREATEST(значение [, ...])

LEAST(значение [, ...])

Функции GREATEST и LEAST выбирают наибольшее или наименьшее значение из списка с любым числом выражений. Все выражения должны быть преобразуемыми в общий тип данных, который и будет типом результата (подробную информацию см. в разделе UNION, CASE и связанные конструкции). Значения NULL в списке игнорируются. Результатом будет NULL, только если все выражения вычисляются как равные NULL.

Обратите внимание, что GREATEST и LEAST не входят в стандарт SQL, но относятся к распространенным расширениям. В некоторых другие базах данных они возвращают NULL, если любой из аргументов равен NULL, а не только когда все аргументы равны NULL.

Функции и операторы для массивов

В Таблице 51 приведены специальные операторы, имеющиеся для типов массивов. Кроме них для массивов имеются обычные операторы сравнения, перечисленные в таблице Операторы сравнения. Эти операторы сравнения сопоставляют содержимое массивов поэлементно, используя функцию сравнения для B-дерева, определенную для типа данных этого элемента по умолчанию, и сортируют по принципу первого различия. В многомерных массивах элементы просматриваются в построчном порядке (последний индекс меняется в первую очередь). Если содержимое двух массивов одинаково, но размерности отличаются, порядок сортировки определяется первым отличием в информации о размерности.

Таблица 51. Операторы для массивов

Оператор

Описание
Пример(ы)

anyarray @> anyarray → boolean

Первый массив содержит второй, то есть каждый элемент, находящийся во втором массиве, равен какому-либо элементу первого? (Дубликаты обрабатываются на общих основаниях, поэтому массивы ARRAY[1] и ARRAY[1,1] считаются содержащими друг друга.)
ARRAY[1,4,3] @> ARRAY[3,1,3] → t

anyarray <@ anyarray → boolean

Первый массив содержится во втором?
ARRAY[2,2,7] <@ ARRAY[1,7,4,2,6] → t

anyarray && anyarray → boolean

Массивы пересекаются, то есть имеют какие-либо общие элементы?
ARRAY[1,4,3] && ARRAY[2,1] → t

anycompatiblearray || anycompatiblearray → anycompatiblearray

Конкатенирует два массива. Со значениями NULL или пустыми массивами эта операция не проводится; в иных случаях массивы должны иметь одинаковое число размерностей (как показано в первом примере) или отличаться на одну размерность (как показано во втором примере). Если эти массивы не одинаковых типов элементов, они будут приведены к общему типу (см. раздел UNION, CASE и связанные конструкции).
ARRAY[1,2,3] || ARRAY[4,5,6,7] → {1,2,3,4,5,6,7}
ARRAY[1,2,3] || ARRAY[[4,5,6],[7,8,9.9]] → {{1,2,3},{4,5,6},{7,8,9.9}}

anycompatible || anycompatiblearray → anycompatiblearray

Конкатенирует элемент с началом массива (который должен быть пустым или одномерным).
3 || ARRAY[4,5,6] → {3,4,5,6}

anycompatiblearray || anycompatible → anycompatiblearray

Конкатенирует элемент с концом массива (который должен быть пустым или одномерным).
ARRAY[4,5,6] || 7 → {4,5,6,7}

Более подробную информацию о поведении операторов для массивов см. в разделе Массивы. Более подробную информацию о том, какие операторы поддерживают операции с индексами, см. в разделе Типы индексов.

В Таблице 52 приведены имеющиеся функции для использования с типами массивами. Дополнительную информацию и примеры использования этих функций см. в разделе Массивы.

Таблица 52. Функции для массивов

Функция

Описание
Пример(ы)

array_append ( anycompatiblearray, anycompatible ) → anycompatiblearray

Добавляет элемент в конец массива (так же, как оператор anycompatiblearray || anycompatible).
array_append(ARRAY[1,2], 3) → {1,2,3}

array_cat ( anycompatiblearray, anycompatiblearray ) → anycompatiblearray

Конкатенирует два массива (так же, как оператор anycompatiblearray || anycompatiblearray).
array_cat(ARRAY[1,2,3], ARRAY[4,5]) → {1,2,3,4,5}

array_dims ( anyarray ) → text

Возвращает текстовое представление размерностей массива.
array_dims(ARRAY[[1,2,3], [4,5,6]]) → [1:2][1:3]

array_fill ( anyelement, integer[] [, integer[] ] ) → anyarray

Возвращает массив, заполненный копиями заданного значения и имеющий размерности той длины, которая указана во втором аргументе. Необязательный третий аргумент предоставляет значения нижней границы для каждой размерности (по умолчанию все они равны 1).
array_fill(11, ARRAY[2,3]) → {{11,11,11},{11,11,11}}
array_fill(7, ARRAY[3], ARRAY[2]) → [2:4]={7,7,7}

array_length ( anyarray, integer ) → integer

Возвращает длину запрошенной размерности массива.
array_length(array[1,2,3], 1) → 3

array_lower ( anyarray, integer ) → integer

Возвращает нижнюю границу запрошенной размерности массива.
array_lower('[0:2]={1,2,3}'::integer[], 1) → 0

array_ndims ( anyarray ) → integer

Возвращает количество размерностей массива.
array_ndims(ARRAY[[1,2,3], [4,5,6]]) → 2

array_position ( anycompatiblearray, anycompatible [, integer ] ) → integer

Возвращает индекс первого вхождения второго аргумента в массиве или NULL, если таковой отсутствует. Если задан третий аргумент, поиск начинается с этого индекса. Массив должен быть одномерным. Сравнения производятся с применением семантики IS NOT DISTINCT FROM, что позволяет искать значения NULL.
array_position(ARRAY['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'], 'mon') → 2

array_positions ( anycompatiblearray, anycompatible ) → integer[]

Возвращает массив индексов всех вхождений второго аргумента в массиве, заданном в первом аргументе. Массив должен быть одномерным. Сравнения производятся с применением семантики IS NOT DISTINCT FROM, что позволяет искать значения NULL. NULL возвращается, только массив равен NULL; если заданное значение отсутствует в массиве, возвращается пустой массив.
array_positions(ARRAY['A','A','B','A'], 'A') → {1,2,4}

array_prepend ( anycompatible, anycompatiblearray ) → anycompatiblearray

Добавляет элемент в начало массива (так же, как оператор anycompatible || anycompatiblearray).
array_prepend(1, ARRAY[2,3]) → {1,2,3}

array_remove ( anycompatiblearray, anycompatible ) → anycompatiblearray

Удаляет из массива все элементы, равные заданному значению. Массив должен быть одномерным. Сравнения производятся с применением семантики IS NOT DISTINCT FROM, что позволяет удалять значения NULL.
array_remove(ARRAY[1,2,3,2], 2) → {1,3}

array_replace ( anycompatiblearray, anycompatible, anycompatible ) → anycompatiblearray

Заменяет каждый элемент массива, равный второму аргументу, значением третьего аргумента.
array_replace(ARRAY[1,2,5,4], 5, 3) → {1,2,3,4}

array_to_string ( массив anyarray, разделитель text [, нулевая_строка text ] ) → text

Преобразует каждый элемент массива в текстовое представление и конкатенирует те из них, что разделены строкой-разделителем. Если задана нулевая_строка и она отлична от NULL, то эта строка представляет содержащиеся в массиве записи NULL; в противном случае такие записи опускаются.
array_to_string(ARRAY[1, 2, 3, NULL, 5], ',', '*') → 1,2,3,*,5

array_upper ( anyarray, integer ) → integer

Возвращает верхнюю границу запрошенной размерности массива.
array_upper(ARRAY[1,8,3,7], 1) → 4

cardinality ( anyarray ) → integer

Возвращает общее количество элементов в массиве или 0, если это пустой массив.
cardinality(ARRAY[[1,2],[3,4]]) → 4

trim_array ( массив anyarray, n integer ) → anyarray

Усекает массив, удаляя последние n элементов. Если массив многомерный, усекается только первая размерность.
trim_array(ARRAY[1,2,3,4,5,6], 2) → {1,2,3,4}

unnest ( anyarray ) → setof anyelement

Разворачивает массив в набор строк. Элементы массива прочитываются в порядке хранения.

unnest(ARRAY[1,2]) →
     1
     2

unnest(ARRAY[['foo','bar'],['baz','quux']]) →
     foo
     bar
     baz
     quux

unnest ( anyarray, anyarray [, ... ] ) → setof anyelement, anyelement [, ... ]

Разворачивает несколько массивов (возможно, разных типов данных) в набор строк. Если у массивов неодинаковая длина, то более короткие заполняются значениями NULL. Эта форма допускается только в предложении FROM; см. подраздел Табличные функции.

select * from unnest(ARRAY[1,2], ARRAY['foo','bar','baz']) as x(a,b) →
     a |  b
    ---+-----
     1 | foo
     2 | bar
       | baz

Описание работающей с массивами функции array_agg см. в разделе Агрегатные функции.

Диапазонные/мультидиапазонные функции и операторы

Описание диапазонных типов данных см. в разделе Диапазонные типы.

В Таблице 53 приведены специальные операторы, имеющиеся для диапазонных типов. В Таблице 54 приведены специальные операторы, имеющиеся для мультидиапазонных типов. Помимо них для диапазонных и мультидиапазонных типов имеются обычные операторы сравнения, перечисленные в таблице Операторы сравнения. Операторы сравнения сначала сравнивают нижние границы диапазонов, и только если они равны, сравнивают верхние границы. Мультидиапазонные операторы сравнивают каждый диапазон, пока не найдут неравный. Такие операции обычно не приводят к полезному в целом упорядочиванию, но зато позволяют строить по диапазонам уникальные индексы.

Таблица 53. Диапазонные операторы

Оператор

Описание
Пример(ы)

anyrange @> anyrange → boolean

Первый диапазон содержит второй?
int4range(2,4) @> int4range(2,3) → t

anyrange @> anyelement → boolean

Диапазон содержит этот элемент?
'[2011-01-01,2011-03-01)'::tsrange @> '2011-01-10'::timestamp → t

anyrange <@ anyrange → boolean

Первый диапазон содержится во втором?
int4range(2,4) <@ int4range(1,7) → t

anyelement <@ anyrange → boolean

Элемент содержится в этом диапазоне?
42 <@ int4range(1,7) → f

anyrange && anyrange → boolean

Диапазоны пересекаются, то есть имеют какие-либо общие элементы?
3 || ARRAY[4,5,6] → {3,4,5,6}

anyrange << anyrange → boolean

Первый диапазон находится строго слева от второго?
int8range(1,10) << int8range(100,110) → t

anyrange >> anyrange → boolean

Первый диапазон находится строго справа от второго?
int8range(50,60) >> int8range(20,30) → t

anyrange &< anyrange → boolean

Первый диапазон не простирается правее второго?
int8range(1,20) &< int8range(18,20) → t

anyrange &> anyrange → boolean

Первый диапазон не простирается левее второго?
int8range(7,20) &> int8range(5,10) → t

anyrange -|- anyrange → boolean

Диапазоны граничат друг с другом?
numrange(1.1,2.2) -|- numrange(2.2,3.3) → t

anyrange + anyrange → anyrange

Вычисляет объединение диапазонов. Диапазоны должны пересекаться или граничить друг с другом, чтобы их объединение было одним диапазоном (но см. range_merge()).
numrange(5,15) + numrange(10,20) → [5,20)

anyrange * anyrange → anyrange

Вычисляет пересечение диапазонов.
int8range(5,15) * int8range(10,20) → [10,15)

anyrange - anyrange → anyrange

Вычисляет разность диапазонов. Второй диапазон не должен содержаться в первом таким образом, чтобы разностью был не один диапазон.
int8range(5,15) - int8range(10,20) → [5,10)

Таблица 54. Мультидиапазонные операторы

Оператор

Описание
Пример(ы)

anymultirange @> anymultirange → boolean

Первый мультидиапазон содержит второй?
'{[2,4)}'::int4multirange @> '{[2,3)}'::int4multirange → t

anymultirange @> anyrange → boolean

Мультидиапазон содержит этот диапазон?
'{[2,4)}'::int4multirange @> int4range(2,3) → t

anymultirange @> anyelement → boolean

Мультидиапазон содержит этот элемент?
'{[2011-01-01,2011-03-01)}'::tsmultirange @> '2011-01-10'::timestamp → t

anyrange @> anymultirange → boolean

Диапазон содержит этот мультидиапазон?
'[2,4)'::int4range @> '{[2,3)}'::int4multirange → t

anymultirange <@ anymultirange → boolean

Первый мультидиапазон содержится во втором?
'{[2,4)}'::int4multirange <@ '{[1,7)}'::int4multirange → t

anymultirange <@ anyrange → boolean

Мультидиапазон содержится в этом диапазоне?
'{[2,4)}'::int4multirange <@ int4range(1,7) → t

anyrange <@ anymultirange → boolean

Диапазон содержится в этом мультидиапазоне?
int4range(2,4) <@ '{[1,7)}'::int4multirange → t

anyelement <@ anymultirange → boolean

Элемент содержится в этом мультидиапазоне?
42 <@ '{[1,7)}'::int4multirange → t

anymultirange && anymultirange → boolean

Мультидиапазоны пересекаются, то есть имеют какие-либо общие элементы?
'{[3,7)}'::int8multirange && '{[4,12)}'::int8multirange → t

anymultirange && anyrange → boolean

Мультидиапазон пересекает этот диапазон?
'{[3,7)}'::int8multirange && int8range(4,12) → t

anyrange && anymultirange → boolean

Диапазон пересекает этот мультидиапазон?
int8range(3,7) && '{[4,12)}'::int8multirange → t

anymultirange << anymultirange → boolean

Первый мультидиапазон находится строго слева от второго?
'{[1,10)}'::int8multirange << '{[100,110)}'::int8multirange → t

anymultirange << anyrange → boolean

Мультидиапазон находится строго слева от этого диапазона?
'{[1,10)}'::int8multirange << int8range(100,110) → t

anyrange << anymultirange → boolean

Диапазон находится строго слева от этого мультидиапазона?
int8range(1,10) << '{[100,110)}'::int8multirange → t

anymultirange >> anymultirange → boolean

Первый мультидиапазон находится строго справа от второго?
'{[50,60)}'::int8multirange >> '{[20,30)}'::int8multirange → t

anymultirange >> anyrange → boolean

Мультидиапазон находится строго справа от этого диапазона?
'{[50,60)}'::int8multirange >> int8range(20,30) → t

anyrange >> anymultirange → boolean

Диапазон находится строго справа от этого мультидиапазона?
int8range(50,60) >> '{[20,30)}'::int8multirange → t

anymultirange &< anymultirange → boolean

Первый мультидиапазон не простирается правее второго?
'{[1,20)}'::int8multirange &< '{[18,20)}'::int8multirange → t

anymultirange &< anyrange → boolean

Мультидиапазон не простирается правее этого диапазона?
'{[1,20)}'::int8multirange &< int8range(18,20) → t

anyrange &< anymultirange → boolean

Диапазон не простирается правее этого мультидиапазона?
int8range(1,20) &< '{[18,20)}'::int8multirange → t

anymultirange &> anymultirange → boolean

Первый мультидиапазон не простирается левее второго?
'{[7,20)}'::int8multirange &> '{[5,10)}'::int8multirange → t

anymultirange &> anyrange → boolean

Мультидиапазон не простирается левее этого диапазона?
'{[7,20)}'::int8multirange &> int8range(5,10) → t

anyrange &> anymultirange → boolean

Диапазон не простирается левее этого мультидиапазона?
int8range(7,20) &> '{[5,10)}'::int8multirange → t

anymultirange -|- anymultirange → boolean

Мультидиапазоны граничат друг с другом?
'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange → t

anymultirange -|- anyrange → boolean

Мультидиапазон граничит с этим диапазоном?
'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3) → t/code>

anyrange -|- anymultirange → boolean

Диапазон граничит с этим мультидиапазоном?
numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange → t

anymultirange + anymultirange → anymultirange

Вычисляет объединение мультидиапазонов. Мультидиапазонам необязательно пересекаться или граничить друг с другом.
'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange → {[5,10), [15,20)}

anymultirange * anymultirange → anymultirange

Вычисляет пересечение мультидиапазонов.
'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange → {[10,15)}

anymultirange - anymultirange → anymultirange

Вычисляет разность мультидиапазонов.
'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange → {[5,10), [15,20)}

Операторы слева/справа/граничит всегда возвращают false, когда один из диапазонов или мультидиапазонов пуст; то есть считается, что пустой диапазон находится не перед и не после какого-либо другого диапазона.

В остальных случаях пустые диапазоны и мультидиапазоны воспринимаются как нулевой элемент: все, что объединяется с пустым значением, остается неизменным. Все, из чего вычитается пустое значение, остается неизменным. Пустой мультидиапазон имеет точно такие же особенности, что и пустой диапазон. Каждый диапазон содержит пустой диапазон. Каждый мультидиапазон содержит сколько угодно пустых диапазонов.

Операторы объединения и разности выдадут ошибку, если результирующему диапазону нужно содержать два непересекающихся поддиапазона, поскольку у таких диапазонов не может быть представления. Существуют отдельные операторы для объединения и разности, которые принимают мультидиапазонные параметры и возвращают мультидиапазон, и они не выдают ошибку, даже если их аргументы не пересекаются. Поэтому если вам требуется объединение или разность диапазонов, которые могут быть непересекающимися, ошибки можно избежать, если сначала привести диапазоны к мультидиапазонам.

В Таблице 55 приведены имеющиеся функции для диапазонных типов. В Таблице 56 приведены функции для мультидиапазонных типов.

Таблица 55. Диапазонные функции

Функция

Описание
Пример(ы)

lower ( anyrange ) → anyelement

Извлекает нижнюю границу диапазона (NULL, если диапазон пуст или его нижняя граница бесконечна).
lower(numrange(1.1,2.2)) → 1.1

upper ( anyrange ) → anyelement

Извлекает верхнюю границу диапазона (NULL, если диапазон пуст или его верхняя граница бесконечна).
upper(numrange(1.1,2.2)) → 2.2

isempty ( anyrange ) → boolean

Диапазон пустой?
isempty(numrange(1.1,2.2)) → f

lower_inc ( anyrange ) → boolean

Нижняя граница диапазона включена в него?
lower_inc(numrange(1.1,2.2)) → t

upper_inc ( anyrange ) → boolean

Верхняя граница диапазона включена в него?
upper_inc(numrange(1.1,2.2)) → f

lower_inf ( anyrange ) → boolean

Нижняя граница диапазона бесконечна?
lower_inf('(,)'::daterange) → t

upper_inf ( anyrange ) → boolean

Верхняя граница диапазона бесконечна?
upper_inf('(,)'::daterange) → t

range_merge ( anyrange, anyrange ) → anyrange

Вычисляет наименьший диапазон, включающий оба заданных диапазона.
range_merge('[1,2)'::int4range, '[3,4)'::int4range) → [1,4)

Таблица 56. Мультидиапазонные функции

Функция

Описание
Пример(ы)

lower ( anymultirange ) → anyelement

Извлекает нижнюю границу мультидиапазона (NULL, если мультидиапазон пуст или его нижняя граница бесконечна).
lower('{[1.1,2.2)}'::nummultirange) → 1.1

upper ( anymultirange ) → anyelement

Извлекает верхнюю границу мультидиапазона (NULL, если мультидиапазон пуст или его верхняя граница бесконечна).
upper('{[1.1,2.2)}'::nummultirange) → 2.2

isempty ( anymultirange ) → boolean

Мультидиапазон пустой?
isempty('{[1.1,2.2)}'::nummultirange) → f

lower_inc ( anymultirange ) → boolean

Нижняя граница мультидиапазона включена в него?
lower_inc('{[1.1,2.2)}'::nummultirange) → t

upper_inc ( anymultirange ) → boolean

Верхняя граница мультидиапазона включена в него?
upper_inc('{[1.1,2.2)}'::nummultirange) → f

lower_inf ( anymultirange ) → boolean

Нижняя граница мультидиапазона бесконечна?
lower_inf('{(,)}'::datemultirange) → t

upper_inf ( anymultirange ) → boolean

Верхняя граница мультидиапазона бесконечна?
upper_inf('{(,)}'::datemultirange) → t

range_merge ( anymultirange ) → anyrange

Вычисляет наименьший диапазон, включающий весь заданный мультидиапазон.
range_merge('{[1,2), [3,4)}'::int4multirange) → [1,4)

multirange ( anyrange ) → anymultirange

Возвращает мультидиапазон, содержащий только заданный диапазон.
multirange('[1,2)'::int4range) → {[1,2)}

unnest ( anymultirange ) → setof anyrange

Разворачивает мультидиапазон в набор диапазонов. Диапазоны прочитываются в порядке хранения (по возрастанию)

unnest('{[1,2), [3,4)}'::int4multirange) →
    [1,2)
    [3,4)

Функции lower_inc, upper_inc, lower_inf и upper_inf возвращают false, если диапазон или мультидиапазон пуст.

Агрегатные функции

Агрегатные функции вычисляют единственный результат из набора входных значений. Встроенные агрегатные функции общего назначения перечислены в Таблице 57, а статистические агрегаты — в Таблице 58. Встроенные внутригрупповые упорядочивающие агрегатные функции перечислены в Таблице 59, а встроенные внутригрупповые гипотезирующие — в Таблице 60. Операции группировки, тесно связанные с агрегатными функциями, перечислены в Таблице 61. Особые рекомендации по синтаксису для агрегатных функций приведены в подразделе Агрегатные выражения. Дополнительную вводную информацию см. в разделе Агрегатные функции.

Агрегатные функции, поддерживающие частичный режим, подходят для участия в различных оптимизациях, например в параллельном агрегировании.

Таблица 57. Агрегатные функции общего назначения

Функция

Частичный режим

Описание

array_agg ( anynonarray ) → anyarray
Нет

Собирает всех входные значения, включая NULL, в массив.

array_agg ( anyarray ) → anyarray
Нет

Конкатенирует все входные массивы в массив с размерностью больше на один. (Входные массивы должны иметь одинаковую размерность и не могут быть пустыми или равны NULL.)

avg ( smallint ) → numeric
avg ( integer ) → numeric
avg ( bigint ) → numeric
avg ( numeric ) → numeric
avg ( real ) → double precision
avg ( double precision ) → double precision
avg ( interval ) → interval
Да

Вычисляет среднее арифметическое все входных значений, отличных от NULL.

bit_and ( smallint ) → smallint
bit_and ( integer ) → integer
bit_and ( bigint ) → bigint
bit_and ( bit ) → bit
Да

Вычисляет побитовое И для всех входных значений, отличных от NULL.

bit_or ( smallint ) → smallint
bit_or ( integer ) → integer
bit_or ( bigint ) → bigint
bit_or ( bit ) → bit
Да

Вычисляет побитовое ИЛИ для всех входных значений, отличных от NULL.

bit_xor ( smallint ) → smallint
bit_xor ( integer ) → integer
bit_xor ( bigint ) → bigint
bit_xor ( bit ) → bit
Да

Вычисляет побитовое исключающее ИЛИ для всех входных значений, отличных от NULL. Может быть полезна в качестве контрольной суммы для неупорядоченного набора значений.

bool_and ( boolean ) → boolean
Да

Возвращает true, если все отличные от NULL входные значения равны true, в противном случае — false.

bool_or ( boolean ) → boolean
Да

Возвращает true, если какое-либо отличное от NULL входное значение равно true, в противном случае — false.

count ( * ) → bigint
Да

Вычисляет количество входных строк.

count ( "any" ) → bigint
Да

Вычисляет количество входных строк, в которых входное значение отлично от NULL.

every ( boolean ) → boolean
Да

Это соответствующий стандарту SQL аналог bool_and.

json_agg ( anyelement ) → json
jsonb_agg ( anyelement ) → jsonb
Нет

Собирает все входные значения, включая NULL, в массив JSON. Значения преобразуются в JSON методом to_json или to_jsonb.

json_object_agg ( ключ "any", значение "any" ) → json
jsonb_object_agg ( ключ "any", значение "any" ) → jsonb
Нет

Собирает все пары ключ/значение в объект JSON. Аргументы-ключи приводятся к текстовому типу; аргументы-значения преобразуются методом to_json или to_jsonb. Значения могут быть NULL, но ключи — нет.

max ( см. описание ) → тот же тип, что и на входе
Да

Вычисляет максимальное из входных значений, отличных от NULL. Имеется для всех числовых, строковых, перечислимых типов и типов даты/времени, а также для типов inet, interval, money, oid, pg_lsn, tid и массивов любого из этих типов.

min ( см. описание ) → тот же тип, что и на входе
Да

Вычисляет минимальное из входных значений, отличных от NULL. Имеется для всех числовых, строковых, перечислимых типов и типов даты/времени, а также для типов inet, interval, money, oid, pg_lsn, tid и массивов любого из этих типов.

range_agg ( значение anyrange ) → anymultirange
Нет

Вычисляет объединение входных значений, отличных от NULL.

range_intersect_agg ( значение anyrange ) → anyrange
range_intersect_agg ( значение anymultirange ) → anymultirange
Нет

Вычисляет пересечение входных значений, отличных от NULL.

string_agg ( значение text, разделитель text ) → text
string_agg ( значение bytea, разделитель bytea ) → bytea
Нет

Конкатенирует отличные от NULL входные значения в строку. Перед каждым значением, кроме первого, добавляется соответствующий разделитель (если он отличен от NULL).

sum ( smallint ) → bigint
sum ( integer ) → bigint
sum ( bigint ) → numeric
sum ( numeric ) → numeric
sum ( real ) → real
sum ( double precision ) → double precision
sum ( interval ) → interval
sum ( money ) → money
Да

Вычисляет сумму входных значений, отличных от NULL.

xmlagg ( xml ) → xml
Нет

Конкатенирует отличные от NULL входные значения XML (см. подраздел xmlagg).

Следует отметить, что, за исключением count, эти функции возвращают значение NULL, когда не выбрана ни одна строка. В частности, в отсутствие строк sum возвращает это значение вместо нуля (как можно было бы ожидать), а array_agg — вместо пустого массива. Для замены нуля или пустого массива на NULL можно при необходимости воспользоваться функцией coalesce.

Агрегатные функции array_agg, json_agg, jsonb_agg, json_object_agg, jsonb_object_agg, string_agg и xmlagg, а также аналогичные им пользовательские агрегатные функции выдают значимо разные результирующие значения в зависимости от порядка входных значений. Этот порядок не задается по умолчанию, но им можно управлять, написав в вызове агрегатной функции предложение ORDER BY, как показано в подразделе Агрегатные выражения. Как вариант, также обычно срабатывает предоставление входных значений из подзапроса с сортировкой. Например:

SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;

Помните, что этот подход может дать сбой, если внешний уровень запроса содержит дополнительную обработку, например объединение, поскольку это может привести к переупорядочению выходных значений подзапроса перед вычислением агрегатной функции.

Примечание
Логические агрегаты bool_and и bool_or аналогичны описанным в стандарте SQL агрегатам every и any или some. QHB поддерживает every, но не any или some, поскольку в стандартном синтаксисе присутствует неоднозначность:

SELECT b1 = ANY((SELECT b2 FROM t2 ...)) FROM t1 ...;

Здесь ANY можно рассматривать и как добавление подзапроса, и как агрегатную функцию, если подзапрос возвращает одну строку с логическим значением. Таким образом, этим агрегатам нельзя было дать стандартные имена.

Примечание
Пользователи, привыкшие работать с другими СУБД SQL, могут быть разочарованы производительностью агрегата count, когда он применяется к целой таблице. Подобный запрос:

SELECT count(*) FROM sometable;

потребует затрат, пропорциональных размеру таблицы: QHB потребуется просканировать либо таблицу целиком, либо индекс, включающий все строки этой таблицы.

В Таблице 58 приведены агрегатные функции, обычно используемые в статистическом анализе. (Они отделены просто для того, чтобы избежать загромождения списка более часто используемых агрегатных функций). Функции, показанные как принимающие числовой_тип, имеются для типов smallint, integer, bigint, numeric, real и double precision. Когда в описании упоминается N, это означает количество входных строк, для которых все входные выражения отличны от NULL. Во всех случаях, когда вычисление не имеет смысла, например, когда N равно нулю, возвращается NULL.

Таблица 58. Агрегатные функции для статистики

Функция

Частичный режим

Описание

corr ( Y double precision, X double precision ) → double precision
Да

Вычисляет коэффициент корреляции.

covar_pop ( Y double precision, X double precision ) → double precision
Да

Вычисляет ковариацию совокупности.

covar_samp ( Y double precision, X double precision ) → double precision
Да

Вычисляет выборочную ковариацию.

regr_avgx ( Y double precision, X double precision ) → double precision
Да

Вычисляет среднее для независимой переменной, sum(X)/N.

regr_avgy ( Y double precision, X double precision ) → double precision
Да

Вычисляет среднее для зависимой переменной, sum(Y)/N.

regr_count ( Y double precision, X double precision ) → bigint
Да

Вычисляет число строк, в которых оба входных выражения отличны от NULL.

regr_intercept ( Y double precision, X double precision ) → double precision
Да

Вычисляет пересечение с осью y линейного уравнения, решенного подбором по методу наименьших квадратов по парам (X, Y).

regr_r2 ( Y double precision, X double precision ) → double precision
Да

Вычисляет квадрат коэффициента корреляции.

regr_slope ( Y double precision, X double precision ) → double precision
Да

Вычисляет наклон линейного уравнения, решенного подбором по методу наименьших квадратов по парам (X, Y).

regr_sxx ( Y double precision, X double precision ) → double precision
Да

Вычисляет «сумму квадратов» независимой переменной, sum(X^2) - sum(X)^2/N.

regr_sxy ( Y double precision, X double precision ) → double precision
Да

Вычисляет «сумму произведений» независимой и зависимой переменных, sum(X*Y) - sum(X) * sum(Y)/N.

regr_syy ( Y double precision, X double precision ) → double precision
Да

Вычисляет «сумму квадратов» зависимой переменной, sum(Y^2) - sum(Y)^2/N.

stddev ( числовой_тип ) → double precision для real или double precision, иначе numeric
Да

Это исторический псевдоним stddev_samp.

stddev_pop ( числовой_тип ) → double precision для real или double precision, иначе numeric
Да

Вычисляет стандартное отклонение по совокупности входных значений.

stddev_samp ( числовой_тип ) → double precision для real или double precision, иначе numeric
Да

Вычисляет стандартное отклонение по выборке входных значений.

variance ( числовой_тип ) → double precision для real или double precision, иначе numeric
Да

Это исторический псевдоним var_samp.

var_pop ( числовой_тип ) → double precision для real или double precision, иначе numeric
Да

Вычисляет дисперсию совокупности входных значений (квадрат стандартного отклонения по совокупности).

var_samp ( числовой_тип ) → double precision для real или double precision, иначе numeric
Да

Вычисляет дисперсию выборки входных значений (квадрат стандартного отклонения по выборке).

В Таблице 59 приведены некоторые агрегатные функции, использующие синтаксис упорядочивающих агрегатов. Эти функции иногда называют функциями «обратного распределения». Их агрегированные входные данные добавляются указанием ORDER BY, а кроме того они могут принимать непосредственный аргумент, который не агрегируется, но вычисляется только один раз. Все эти функции игнорируют значения NULL в агрегируемых входных данных. Для функций, принимающих параметр fraction, его значение должно быть между 0 и 1; в противном случае возникнет ошибка. Однако если fraction имеет значение NULL, то и результат будет NULL.

Таблица 59. Упорядочивающие функции

Функция

Частичный режим

Описание

mode () WITHIN GROUP ( ORDER BY anyelement ) → anyelement
Нет

Вычисляет моду, наиболее часто встречающееся значение в агрегируемом аргументе (если одинаково часто встречаются несколько значений, произвольно выбирая первое из них). Агрегируемый аргумент должен быть сортируемого типа.

percentile_cont ( дробь double precision ) WITHIN GROUP ( ORDER BY double precision ) → double precision
percentile_cont ( дробь double precision ) WITHIN GROUP ( ORDER BY interval ) → interval
Нет

Вычисляет непрерывный процентиль — значение, соответствующее заданной дроби в отсортированном множестве значений агрегируемого аргумента. При необходимости соседние входные элементы будут интерполироваться.

percentile_cont ( дроби double precision[] ) WITHIN GROUP ( ORDER BY double precision ) → double precision[]
percentile_cont ( дроби double precision[] ) WITHIN GROUP ( ORDER BY interval ) → interval[]
Нет

Вычисляет несколько непрерывных процентилей. Результатом является массив той же размерности, что и параметр дроби, а все отличные от NULL элементы в нем заменяются значением (возможно, интерполированным), соответствующим этому процентилю.

percentile_disc ( дробь double precision ) WITHIN GROUP ( ORDER BY anyelement ) → anyelement
Нет

Вычисляет дискретный процентиль, первое значение в упорядоченном множестве значений агрегируемого аргумента, позиция которого в этом множестве равна или превышает заданную дробь. Агрегируемый аргумент должен быть сортируемого типа.

percentile_disc ( дроби double precision[] ) WITHIN GROUP ( ORDER BY anyelement ) → anyarray
Нет

Вычисляет несколько дискретных процентилей. Результатом является массив той же размерности, что и параметр дроби, а все отличные от NULL элементы в нем заменяются входным значением, соответствующим этому процентилю. Агрегируемый аргумент должен быть сортируемого типа.

Все «гипотезирующие» агрегаты, перечисленные в Таблице 60, связаны с одноименными оконными функциями, определенными в разделе Оконные функции. Во всех случаях результат этих агрегатов представляет собой значение, которое связанная оконная функция вернула бы для «гипотетической» строки, сформированной из аргументов, если бы такая строка была добавлена в упорядоченную группу строк, представленную отсортированными_аргументами. Для каждой из этих функций список непосредственных аргументов, заданный в параметре аргументы, должен соответствовать числу и типу агрегируемых аргументов, заданных в параметре отсортированные_аргументы. В отличие от большинства встроенных агрегатов, эти агрегаты не являются строгими, то есть они не опускают входные строки, содержащие NULL. Значения NULL сортируются согласно правилу, заданному в предложении ORDER BY.

Таблица 60. Гипотезирующие агрегатные функции

Функция

Частичный режим

Описание

rank ( аргументы ) WITHIN GROUP ( ORDER BY отсортированные_аргументы ) → bigint
Нет

Вычисляет ранг гипотетической строки с пропусками, то есть номер первой строки в одноранговой с ней группе.

dense_rank ( аргументы ) WITHIN GROUP ( ORDER BY отсортированные_аргументы ) → bigint
Нет

Вычисляет ранг гипотетической строки без пропусков; в сущности, эта функция считает одноранговые группы.

percent_rank ( аргументы ) WITHIN GROUP ( ORDER BY отсортированные_аргументы ) → double precision
Нет

Вычисляет относительный ранг гипотетической строки, то есть (rank - 1) / (общее число строк - 1). Таким образом, это значение лежит в диапазоне от 0 до 1, включая границы.

cume_dist ( аргументы ) WITHIN GROUP ( ORDER BY отсортированные_аргументы ) → double precision
Нет

Вычисляет кумулятивное распределение, то есть (число строк, предшествующих или одноранговых с гипотетической строкой) / (общее число строк). Это значение лежит в диапазоне от 1/N до 1.

Таблица 61. Операции группировки

Функция

Описание

GROUPING ( выражение(я)_group_by ) → integer

Возвращает битовую маску, показывающую, какие выражения GROUP BY не включены в текущий группирующий набор. Биты присваиваются самому правому аргументу, соответствующему младшему значащему биту; каждый бит равен 0, если соответствующее выражение включено в критерий группировки группирующего набора, генерирующего текущую результирующую строку, и 1, если оно туда не включено.

Операции группировки, приведенные в Таблице 61, применяются совместно с группирующими наборами (см. подраздел GROUPING SETS, CUBE и ROLLUP) для различения результирующих строк. Аргументы функции GROUPING на самом деле не вычисляются, но они должны в точности соответствовать выражениям, заданным в предложении GROUP BY на их уровне запроса. Например:

=> SELECT * FROM items_sold;
 make  | model | sales
-------+-------+-------
 Foo   | GT    |  10
 Foo   | Tour  |  20
 Bar   | City  |  15
 Bar   | Sport |  5
(4 rows)

=> SELECT make, model, GROUPING(make,model), sum(sales) FROM items_sold GROUP BY ROLLUP(make,model);
 make  | model | grouping | sum
-------+-------+----------+-----
 Foo   | GT    |        0 | 10
 Foo   | Tour  |        0 | 20
 Bar   | City  |        0 | 15
 Bar   | Sport |        0 | 5
 Foo   |       |        1 | 30
 Bar   |       |        1 | 20
       |       |        3 | 50
(7 rows)

Здесь значение grouping, равное 0 в первых четырех строках, показывает, что эти строки были сгруппированы обычным образом, по обоим группирующим столбцам. Значение 1 показывает, что столбец model не был группирующим в двух предпоследних строках, а значение 3 показывает, что в последней строке не были группирующими ни make, ни model (то есть агрегирование проводилось по всем входным строкам).

Оконные функции

Оконные функции предоставляют возможность выполнять вычисления по наборам строк, связанных с текущей строкой запроса. Вводную информацию по этой функциональной возможности см. в разделе Оконные функции, а подробную информацию о синтаксисе — в подразделе Вызовы оконных функций.

Встроенные оконные функции перечислены в Таблице 62. Обратите внимание, что эти функции должны вызываться с использованием синтаксиса оконных функций, то есть необходимо предложение OVER.

В дополнение к этим функциям в качестве оконной функции может использоваться любой встроенный или обычный пользовательский агрегат (т. е. не упорядочивающие или гипотезирующие агрегаты); список встроенных агрегатов см. в разделе Агрегатные функции. Агрегатные функции действуют как оконные только тогда, когда за вызовом следует предложение OVER; в противном случае они ведут себя как простые агрегаты и возвращают единственную строку для всего набора.

Таблица 62. Универсальные оконные функции

Функция

Описание

row_number () → bigint

Возвращает номер текущей строки в ее партиции, считая с 1.

rank () → bigint

Возвращает ранг текущей строки с пропусками, то есть row_number первой строки в одноранговой с ней группе.

dense_rank () → bigint

Возвращает ранг текущей строки без пропусков; в сущности, эта функция считает одноранговые группы.

percent_rank () → double precision

Возвращает относительный ранг текущей строки, то есть (rank - 1) / (общее число строк партиции - 1). Таким образом, значение лежит в диапазоне от 0 до 1, включая границы.

cume_dist () → double precision

Возвращает кумулятивное распределение, то есть (число строк партиции, предшествующих или одноранговых с текущей строкой) / (общее число строк партиции). Таким образом, значение лежит в диапазоне от 1/N до 1.

ntile ( число_блоков integer ) → integer

Возвращает целое в диапазоне от 1 до значения аргумента, разделяя партицию на максимально равные по размеру части.

lag ( значение anycompatible [, смещение integer [, значение_по_умолчанию anycompatible ]] ) → anycompatible

Возвращает значение, вычисленное для строки, отстоящей на смещение строк от текущей к началу партиции; если такой строки нет, вместо этого возвращает значение_по_умолчанию (которое должно быть совместимого со значением типа). Как смещение, так и значение_по_умолчанию вычисляются относительно текущей строки. Если они опускаются, по умолчанию смещение равно 1, а значение_по_умолчанию — NULL.

lead ( значение anycompatible [, смещение integer [, значение_по_умолчанию anycompatible ]] ) → anycompatible

Возвращает значение, вычисленное для строки, отстоящей на смещение строк от текущей к концу партиции; если такой строки нет, вместо этого возвращает значение_по_умолчанию (которое должно быть совместимого со значением типа). Как смещение, так и значение_по_умолчанию вычисляются относительно текущей строки. Если они опускаются, по умолчанию смещение равно 1, а значение_по_умолчанию — NULL.

first_value ( значение anyelement ) → anyelement

Возвращает значение, вычисленное для первой строки в рамке окна.

last_value ( значение anyelement ) → anyelement

Возвращает значение, вычисленное для последней строки в рамке окна.

nth_value ( значение anyelement, n integer ) → anyelement

Возвращает значение, вычисленное для n-й строки в рамке окна (считая с 1); возвращает NULL, если такой строки нет.

Все функции, перечисленные в Таблице 62, зависят от порядка сортировки, заданного в предложении ORDER BY соответствующего определения окна. Строки, не различающиеся при рассмотрении только столбцов ORDER BY, называются одноранговыми. Четыре функции ранжирования (включая cume_dist) определены так, чтобы давать одинаковый ответ для всех строк одноранговой группы.

Обратите внимание, что функции first_value, last_value и nth_value учитывают только строки в «рамке окна», которая по умолчанию содержит строки от начала партиции до последней строки, одноранговой с текущей. Скорее всего, у last_value, а иногда и nth_value, это приведет к бесполезным результатам. Рамку можно переопределить, добавив подходящую спецификацию рамки (RANGE, ROWS или GROUPS) в предложение OVER. Дополнительную информацию о спецификациях рамок см. в подразделе Вызовы оконных функций.

Когда в качестве оконной функции используется агрегатная, она агрегирует по строкам в рамке окна текущей строки. Агрегат, используемый с ORDER BY и определением рамки окна по умолчанию, создает тип поведения «суммирование с нарастающим итогом», что может как соответствовать, так и не соответствовать желаемому результату. Чтобы получить агрегацию по всей партиции, опустите ORDER BY или воспользуйтесь ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING. Для получения других результатов можно использовать другие спецификации рамок.

Примечание
В стандарте SQL определены параметры RESPECT NULLS или IGNORE NULLS для функций lead, lag, first_value, last_value и nth_value. В QHB это не реализовано: поведение этих функций всегда соответствует описанному в стандарте как поведение по умолчанию, а именно, с параметром RESPECT NULLS. Аналогичным образом для функции nth_value не реализованы стандартные параметры FROM FIRST или FROM LAST: поддерживается только поведение по умолчанию — с параметром FROM FIRST. (Результата как у параметра FROM LAST можно добиться, изменив порядок ORDER BY на обратный.)

Выражения подзапросов

В этом разделе описываются SQL-совместимые выражения подзапросов, имеющиеся в QHB. Все формы выражений, рассмотренные в этом разделе, возвращают логические (true/false) результаты.

EXISTS

EXISTS (подзапрос)

Аргументом EXISTS является произвольный оператор SELECT или подзапрос. Подзапрос вычисляется, чтобы определить, возвращает ли он какие-либо строки. Если он возвращает хотя бы одну строку, результат EXISTS равен «true»; если подзапрос не возвращает строк, результатом EXISTS будет «false».

Подзапрос может обращаться к переменным из окружающего запроса, которые будут действовать как константы во время одного вычисления подзапроса.

Подзапрос, как правило, выполняется только до того момента, когда можно определить, возвращена ли хотя бы одна строка, а не до полного завершения. Неразумно писать подзапрос с побочными эффектами (например, вызовом функций последовательностей); возникновение побочных эффектов может быть непредсказуемым.

Поскольку результат зависит только от того, возвращаются ли какие-либо строки, а не от содержимого этих строк, выходной список подзапроса, как правило не важен. Обычное соглашение о кодировании — записывать все проверки EXISTS в форме EXISTS(SELECT 1 WHERE ...). Однако из этого правила есть исключения, такие как подзапросы с предложением INTERSECT.

Этот простой пример похож на внутреннее соединение по col2, но он генерирует не более одной выходной строки для каждой строки tab1, даже если есть несколько совпадающих строк tab2:

SELECT col1
FROM tab1
WHERE EXISTS (SELECT 1 FROM tab2 WHERE col2 = tab1.col2);

IN

выражение IN (подзапрос)

Правая часть представляет собой заключенный в скобки подзапрос, который должен возвращать ровно один столбец. Выражение слева вычисляется и сравнивается с каждой строкой результата подзапроса. Если находится какая-либо равная ему строка подзапроса, результатом IN будет «true». Если не найдено ни одной такой строки (включая случай, когда подзапрос не возвращает строк), результатом будет «false».

Обратите внимание, что если выражение слева возвращает NULL или если нет равных ему правых значений и хотя бы одна строка справа возвращает NULL, результатом конструкции IN будет NULL, а не false. Это соответствует обычным правилам SQL для логических комбинаций значений NULL.

Как и в случае с EXISTS, не стоит предполагать, что подзапрос будет вычислен полностью.

конструктор_строки IN (подзапрос)

Левая часть этой формы IN является конструктором строки, описанным в подразделе Конструкторы строк. Правая часть — это заключенный в скобки подзапрос, который должен возвращать ровно столько столбцов, сколько имеется выражений в строке слева. Выражения слева вычисляются и построчно сравниваются с каждой строкой результата подзапроса. Если находится какая-либо равная им строка подзапроса, результатом IN будет «true». Если же не не находится ни одной такой строки (включая случай, когда подзапрос не возвращает строк), результатом будет «false».

Как обычно, значения NULL в строках объединяются в соответствии с типичными правилами логических выражений SQL. Две строки считаются равными, если все их соответствующие члены отличны от NULL и равны между собой; строки являются неравными, если любые соответствующие члены отличны от NULL и не равны между собой; в противном случае результат сравнения строк неизвестен (NULL). Если все построчные результаты не равны между собой или равны NULL (достаточно и одного значения NULL), то результатом IN будет NULL.

NOT IN

выражение NOT IN (подзапрос)

Правая часть представляет собой заключенный в скобки подзапрос, который должен возвращать ровно один столбец. Выражение слева вычисляется и сравнивается с каждой строкой результата подзапроса. Если находятся только неравные ему строки подзапроса (включая случай, когда подзапрос не возвращает строк), результатом NOT IN будет «true». Если же находится какая-либо равная ему строка, то результатом будет «false».

Обратите внимание, что если выражение слева возвращает NULL или если справа нет равных ему значений и хотя бы одна правая строка выдает NULL, результатом конструкции NOT IN будет NULL, а не true. Это соответствует обычным правилам SQL для логических комбинаций значений NULL.

Как и в случае с EXISTS, не стоит предполагать, что подзапрос будет вычисляться полностью.

конструктор_строки NOT IN (подзапрос)

Левая часть этой формы NOT IN является конструктором строки, описанным в подразделе Конструкторы строк. Правая часть — это заключенный в скобки подзапрос, который должен возвращать ровно столько столбцов, сколько имеется выражений в строке слева. Выражения слева вычисляются и построчно сравниваются с каждой строкой результата подзапроса. Если находятся только неравные им строки подзапроса (включая случай, когда подзапрос не возвращает строк), то результатом NOT IN будет «true». Если же находится какая-либо равная им строка, результатом будет «false».

Как обычно, значения NULL в строках объединяются в соответствии с типичными правилами логических выражений SQL. Две строки считаются равными, если все их соответствующие члены отличны от NULL и равны между собой; строки являются неравными, если любые соответствующие члены отличны от NULL и не равны между собой; в противном случае результат сравнения строк неизвестен (NULL). Если все построчные результаты не равны между собой или равны NULL (достаточно и одного значения NULL) то результатом NOT IN будет NULL.

ANY/SOME

выражение оператор ANY (подзапрос)

выражение оператор SOME (подзапрос)

Правая часть представляет собой заключенный в скобки подзапрос, который должен возвращать ровно один столбец. Выражение слева вычисляется и сравнивается с каждой строкой результата подзапроса с помощью указанного оператора, который должен выдавать логический результат. Если какой-либо полученный результат является true, результатом ANY будет «true». Если такой результат не находится (включая случай, когда подзапрос не возвращает строк), его результатом будет «false».

Конструкция SOME является синонимом для ANY. Конструкция IN равнозначна = ANY.

Обратите внимание, что если нужных результатов не находится и хотя бы одна строка справа возвращает NULL для результата оператора, результатом конструкции ANY будет NULL, а не false. Это соответствует обычным правилам SQL для логических комбинаций значений NULL.

Как и в случае с EXISTS, не стоит предполагать, что подзапрос будет вычисляться полностью.

конcтруктор_строки оператор ANY (подзапрос)
конcтруктор_строки оператор SOME (подзапрос)

Левая часть этой формы ANY является конструктором строки, описанным в подразделе Конструкторы строк. Правая часть — это заключенный в скобки подзапрос, который должен возвращать ровно столько столбцов, сколько имеется выражений в строке слева. Выражения слева вычисляются и построчно сравниваются с каждой строкой результата подзапроса с помощью указанного оператора. Если сравнение возвращает true для любой строки подзапроса, результатом ANY будет «true». Если сравнение возвращает false для каждой строки подзапроса (включая случай, когда подзапрос не возвращает строк), то результатом будет «false». Если же ни одно сравнение со строкой подзапроса не возвращает true и хотя бы одно сравнение возвращает NULL, то результатом будет NULL.

Подробную информацию о семантике сравнения конструкторов строк см. в подразделе Сравнение конструкторов строк.

ALL

выражение оператор ALL (подзапрос)

Правая часть представляет собой заключенный в скобки подзапрос, который должен возвращать ровно один столбец. Выражение слева вычисляется и сравнивается с каждой строкой результата подзапроса с помощью указанного оператора, который должен выдавать логический результат. Если все строки выдают true (включая случай, когда подзапрос не возвращает строк), результатом ALL будет «true». Если находится какой-либо результат false, то результатом будет «false». Если же ни одно сравнение со строкой подзапроса не возвращает false и хотя бы одно сравнение возвращает NULL, результатом будет NULL.

Конструкция NOT IN равнозначна <> ALL.

Как и в случае с EXISTS, не стоит предполагать, что подзапрос будет вычисляться полностью.

конструктор_строки оператор ALL (подзапрос)

Левая часть этой формы ALL является конструктором строки, описанным в подразделе Конструкторы строк. Правая часть — это заключенный в скобки подзапрос, который должен возвращать ровно столько столбцов, сколько имеется выражений в строке слева. Выражения слева вычисляются и сравниваются построчно с каждой строкой результата подзапроса с помощью указанного оператора. Если сравнение возвращает true для всех строк подзапроса (включая случай, когда подзапрос не возвращает строк), результатом ALL будет «true». Если сравнение возвращает false для любой строки подзапроса, то результатом будет «false». Если же ни одно сравнение со строкой подзапроса не возвращает false и хотя бы одно сравнение возвращает NULL, результатом будет NULL

Подробную информацию о семантике сравнения конструкторов строк см. в подразделе Сравнение конструкторов строк.

Сравнение единичных строк

конструктор_строки оператор (подзапрос)

Левая часть является конструктором строки, описанным в подразделе Конструкторы строк. Правая часть — это заключенный в скобки подзапрос, который должен возвращать ровно столько столбцов, сколько имеется выражений в строке слева. Более того, подзапрос не может возвращать более одной строки. (Если он возвращает ноль строк, результат принимается равным NULL). Левая часть вычисляется и построчно сравнивается с единственной строкой результата подзапроса.

Подробную информацию о семантике сравнения конструкторов строк см. в подразделе Сравнение конструкторов строк.

Сравнение строк и массивов

В этом разделе описывается несколько специализированных конструкций для множественных сравнений между группами значений. Эти формы синтаксически связаны с формами подзапросов предыдущего раздела, но не включают подзапросы. Формы, включающие подвыражения массива, являются расширениями QHB; остальные SQL-совместимы. Все формы выражений, описанные в этом разделе, возвращают логические (true/false) результаты.

IN

выражение IN (значение [, ...])

Правая часть представляет собой заключенный в скобки список скалярных выражений. Если результат выражения слева равен любому из выражений справа, результатом будет «true». Это сокращенная запись для условия

выражение = значение1
OR
выражение = значение2
OR
...

Обратите внимание, что если выражение слева выдает NULL или если нет равных ему значений справа и хотя бы одно выражение справа выдает NULL, результатом конструкции IN будет NULL, а не false. Это соответствует обычным правилам SQL для логических комбинаций значений NULL.

NOT IN

выражение NOT IN (значение [, ...])

Правая часть представляет собой заключенный в скобки список скалярных выражений. Если результат выражения слева не равен всем выражениям справа, результатом будет «true». Это сокращенная запись для условия

выражение <> значение1
AND
выражение <> значение2
AND
...

Обратите внимание, что если выражение слева выдает NULL или если нет равных ему значений справа и хотя бы одно выражение справа выдает NULL, результатом конструкции NOT IN будет NULL, а не true , как можно было ожидать. Это соответствует обычным правилам SQL для логических комбинаций значений NULL.

Совет
x NOT IN y во всех случаях равнозначно NOT (x IN y). Тем не менее значения NULL гораздо чаще ставят в тупик новичков при работе с NOT IN, чем при работе с IN. Лучше всего по возможности выражать условие без отрицаний.

ANY/SOME (с массивом)

выражение оператор ANY (выражение массива)
выражение оператор SOME (выражение массива)

Правая часть представляет собой заключенное в скобки выражение, которое должно выдавать значение массива. Выражение слева вычисляется и сравнивается с каждым элементом массива с помощью указанного оператора, который должен выдавать логический результат. Если какой-либо полученный результат является true, результатом ANY будет «true». Если же результат true не находится (включая случай, когда массив имеет ноль элементов), то результатом будет «false».

Если выражение массива выдает массив NULL, результатом ANY будет NULL. Если выражение слева выдает NULL, то результат ANY обычно равен NULL (хотя оператор нестрогого сравнения может выдать и другой результат). Кроме того, если массив справа содержит какие-либо элементы NULL и не получено ни одного результата сравнения, равного true, результат ANY будет NULL, а не false (опять-таки, предполагая, что используется оператор строгого сравнения). Это соответствует обычным правилам SQL для логических комбинаций значений NULL.

Конструкция SOME является синонимом для ANY.

ALL (с массивом)

выражение оператор ALL (выражение массива)

Правая часть представляет собой заключенное в скобки выражение, которое должно выдавать значение массива. Выражение слева вычисляется и сравнивается с каждым элементом массива с помощью указанного оператора, который должен выдавать логический результат. Если все сравнения выдают true (включая случай, когда массив имеет ноль элементов), результатом ALL будет «true». Если же находится какой-либо результат false, то результатом будет «false».

Если выражение массива выдает массив NULL, результат ALL будет NULL. Если выражение слева выдает NULL, то результат ALL обычно равен NULL (хотя оператор нестрогого сравнения может выдать и другой результат). Кроме того, если массив справа содержит какие-либо элементы NULL и не получено ни одного результата сравнения, равного false, результат ALL будет NULL, а не true (опять-таки, предполагая, что используется оператор строгого сравнения). Это соответствует обычным правилам SQL для логических комбинаций значений NULL.

Сравнение конструкторов строк

конструктор_строки оператор конструктор_строки

Каждая сторона является конструктором строки, описанным в подразделе Конструкторы строк. Два значения строки должны иметь одинаковое количество полей. Каждая сторона вычисляется, а затем они построчно сравниваются. Сравнения конструкторов строк допускаются, когда оператором является =, <>, <, <=, > или >=. Каждый элемент строки должен иметь тип, для которого определен класс операторов B-дерева по умолчанию, иначе попытка сравнения может вызвать ошибку.

Примечание
Ошибки, связанные с количеством или типами элементов, могут не возникнуть, если сравнение разрешается со столбцами, полученными ранее.

В случае = и <> все работает немного по-другому. Две строки считаются равными, если все их соответствующие члены отличны от NULL и равны; строки являются неравными, если любые соответствующие члены отличны от NULL и не равны; в противном случае результат сравнения строк неизвестен (NULL).

Для случаев <, <=, > и >= элементы строки сравниваются слева направо, останавливаясь, как только обнаруживается неравная или равная NULL пара элементов. Если любой из этой пары элементов равен NULL, результат сравнения строк будет неизвестен (NULL); в ином случае сравнение этой пары элементов определяет результат. Например, ROW(1,2,NULL) < ROW(1,3,0) выдает значение true, а не NULL, поскольку третья пара элементов не рассматривается.

конструктор_строки IS DISTINCT FROM конструктор_строки

Эта конструкция похожа на сравнение строк <>, но она не выдает NULL для входных данных, равных NULL. Вместо этого любое значение NULL считается неравным (отличным от) любого значения не NULL, а любые два значения NULL считаются равными (не отличными). Таким образом, результат будет либо true, либо false, но не NULL.

конструктор_строки IS NOT DISTINCT FROM конструктор_строки

Эта конструкция похожа на сравнение строк =, но она не выдает NULL для входных данных, равных NULL. Вместо этого любое значение NULL считается неравным (отличным от) любого значения не NULL, а любые два значения NULL считаются равными (не отличными). Таким образом, результат будет либо true, либо false, но не NULL.

Сравнение составных типов

запись оператор запись

Спецификация SQL требует, чтобы построчное сравнение возвращало NULL, если результат зависит от сравнения двух значений NULL или значения NULL со значением не NULL. QHB делает это только при сравнении результатов двух конструкторов строк (как в подразделе Сравнение конструкторов строк) или при сравнении конструктора строки с выходными данными подзапроса (как в разделе Выражения подзапросов). В других контекстах, где сравниваются два значения составного типа, два значения поля NULL считаются равными, а NULL считается больше, чем не NULL. Это необходимо для согласованного поведения сортировки и индексации составных типов.

Каждая сторона вычисляется, а затем они сравниваются построчно. Сравнения составных типов допускаются, когда оператором является =, <>, <, <=, > или >=, либо его семантика аналогична одному из них. (Точнее говоря, оператор может быть оператором сравнения строк, если является членом класса операторов B-дерева или инвертен оператору = из класса операторов B-дерева). Поведение по умолчанию вышеупомянутых операторов такое же, как у выражениям IS [NOT] DISTINCT FROM для конструкторов строк (см. подраздел Сравнение конструкторов строк).

Для поддержки сопоставления строк, включающих в себя элементы, для которых не определен класс операторов B-дерева по умолчанию, определены следующие операторы сравнения составных типов: *=, *<>, *<, *<=, *> и *>=. Эти операторы сравнивают внутреннее двоичное представление двух строк. Две строки могут иметь различное двоичное представление, даже если при сравнении двух строк с оператором равенства результат равен true. Порядок строк в этих операторах сравнения детерминирован, но в остальном не имеет значения. Эти операторы применяются внутри системы для материализованных представлений и могут быть полезны для других специализированных целей, таких как репликация и дедупликация в B-дереве (см. подраздел Дедупликация), но не предназначены для общего использования при написании запросов.

Функции, возвращающие множества

В этом разделе описываются функции, которые могут возвращать более одной строки. Наиболее широко используемые функции в этом классе — это функции, генерирующие ряды значений, подробно описанные в Таблице 63 и Таблице 64. Другие, более специализированные функции, возвращающие множества, описаны в других разделах этого руководства. Способы объединения нескольких функций, возвращающих множества, см. в подразделе Табличные функции.

Таблица 63. Функции, генерирующие ряды значений

Функция

Описание

generate_series ( начало integer, конец integer [, шаг integer ] ) → setof integer
generate_series ( начало bigint, конец bigint [, шаг bigint ] ) → setof bigint
generate_series ( начало numeric, конец numeric [, шаг numeric ] ) → setof numeric

Генерирует ряд значений от начала до конца с заданным шагом. По умолчанию шаг равен 1.

generate_series ( начало timestamp, конец timestamp, шаг interval ) → setof timestamp
generate_series ( начало timestamp with time zone, конец timestamp with time zone, шаг interval ) → setof timestamp with time zone

Генерирует ряд значений от начала до конца с заданным шагом.

Если шаг положительный, а начало больше конца, возвращается ноль строк. И, наоборот, когда шаг отрицательный, ноль строк возвращается, если начало меньше конца. Ноль строк также возвращается, если какой-либо аргумент равен NULL. Если шаг равен нулю, возникает ошибка. Вот несколько примеров:

SELECT * FROM generate_series(2,4);
 generate_series
-----------------
               2
               3
               4
(3 rows)

SELECT * FROM generate_series(5,1,-2);
 generate_series
-----------------
               5
               3
               1
(3 rows)

SELECT * FROM generate_series(4,3);
 generate_series
-----------------
(0 rows)

SELECT generate_series(1.1, 4, 1.3);
 generate_series
-----------------
             1.1
             2.4
             3.7
(3 rows)

-- этот пример основан на операторе прибавления к дате целого числа
SELECT current_date + s.a AS dates FROM generate_series(0,14,7) AS s(a);
   dates
------------
 2004-02-05
 2004-02-12
 2004-02-19
(3 rows)

SELECT * FROM generate_series('2008-03-01 00:00'::timestamp,
                              '2008-03-04 12:00', '10 hours');
   generate_series
---------------------
 2008-03-01 00:00:00
 2008-03-01 10:00:00
 2008-03-01 20:00:00
 2008-03-02 06:00:00
 2008-03-02 16:00:00
 2008-03-03 02:00:00
 2008-03-03 12:00:00
 2008-03-03 22:00:00
 2008-03-04 08:00:00
(9 rows)

Таблица 64. Функции, генерирующие индексы массивов

Функция

Описание

generate_subscripts ( массив anyarray, размерность integer ) → setof integer

Генерирует ряд значений, содержащий все допустимые индексы в заданной размерности данного массива.

generate_subscripts ( массив anyarray, размерность integer, обратный_порядок boolean ) → setof integer

Генерирует ряд значений, содержащий все допустимые индексы в заданной размерности данного массива. Когда параметр обратный_порядок равен true, возвращает ряд значений в порядке от большего к меньшему.

generate_subscripts — это вспомогательная функция, которая генерирует набор допустимых индексов для указанной размерности данного массива. Для массивов, у которых нет запрошенной размерности, или если какой-либо аргумент равен NULL, возвращается ноль строк. Вот несколько примеров:

-- простое использование
SELECT generate_subscripts('{NULL,1,NULL,2}'::int[], 1) AS s;
 s
---
 1
 2
 3
 4
(4 rows)

-- для представления массива, индекса и индексированного
-- значения требуется подзапрос
SELECT * FROM arrays;
         a
--------------------
 {-1,-2}
 {100,200,300}
(2 rows)

SELECT a AS array, s AS subscript, a[s] AS value
FROM (SELECT generate_subscripts(a, 1) AS s, a FROM arrays) foo;
     array     | subscript | value
---------------+-----------+-------
 {-1,-2}       |         1 |    -1
 {-1,-2}       |         2 |    -2
 {100,200,300} |         1 |   100
 {100,200,300} |         2 |   200
 {100,200,300} |         3 |   300
(5 rows)

-- разворачивание двумерного массива
CREATE OR REPLACE FUNCTION unnest2(anyarray)
RETURNS SETOF anyelement AS $$
select $1[i][j]
   from generate_subscripts($1,1) g1(i),
        generate_subscripts($1,2) g2(j);
$$ LANGUAGE sql IMMUTABLE;
CREATE FUNCTION
SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
 unnest2
---------
       1
       2
       3
       4
(4 rows)

Когда к функции в предложении FROM добавляется суффикс WITH ORDINALITY, к выходным столбцам функции добавляется столбец bigint, начинающийся с 1 и увеличивающийся на 1 для каждой строки вывода функции. Это наиболее полезно в случае функций, возвращающих множества, например unnest().

-- функция, возвращающая множество, с нумерацией
SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
       ls        | n
-----------------+----
 pg_serial       |  1
 pg_twophase     |  2
 postmaster.opts |  3
 pg_notify       |  4
 qhb.conf        |  5
 pg_tblspc       |  6
 logfile         |  7
 base            |  8
 qhbmaster.pid   |  9
 qhb_ident.conf  | 10
 global          | 11
 pg_xact         | 12
 pg_snapshots    | 13
 pg_multixact    | 14
 QHB_VERSION     | 15
 pg_wal          | 16
 qhb_hba.conf    | 17
 pg_stat_tmp     | 18
 pg_subtrans     | 19
(19 rows)

Системные информационные функции и операторы

В Таблице 65 приведено несколько функций, которые извлекают информацию о сеансе и системе.

В дополнение к функциям, перечисленным в этом разделе, существует ряд функций, связанных с системой статистики, которые также предоставляют системную информацию. Дополнительную информацию см. в подразделе Просмотр статистики.

Таблица 65. Функции получения информации о сеансе

Функция

Описание

current_catalog → name
current_database () → name

Возвращает имя текущей базы данных. (В стандарте SQL базы данных называются «каталогами», поэтому стандартным написанием является current_catalog.)

current_query () → text

Возвращает текст запроса, выполняемого в данный момент, в том виде, в каком его прислал клиент (может содержать более одного оператора).

current_role → name

Это аналог current_user.

current_schema → name
current_schema () → name

Возвращает имя первой схемы в пути поиска (или значение NULL, если путь поиска пустой). Это схема, которая будет использоваться для всех таблиц или других именованных объектов, создающихся без указания целевой схемы.

current_schemas ( включить_неявные_схемы boolean ) → name[]

Возвращает массив имен всех схем, в настоящее время находящихся в действующем пути поиска, в порядке приоритетности. (Элементы в текущем значении search_path, которые не соответствуют существующим, пригодным для поиска схемам, опускаются). Если логический аргумент равен true, то в результат включаются неявно просматриваемые системные схемы, например pg_catalog.

current_user → name

Возвращает имя пользователя в текущем контексте выполнения.

inet_client_addr () → inet

Возвращает IP-адрес текущего клиента или NULL, если текущее соединение установлено через сокет домена Unix.

inet_client_port () → integer

Возвращает номер IP-порта текущего клиента или NULL, если текущее соединение установлено через сокет домена Unix.

inet_server_addr () → inet

Возвращает IP-адрес, через который сервер принял текущее подключение, или NULL, если текущее соединение установлено через сокет домена Unix.

inet_server_port () → integer

Возвращает номер IP-порта, через который сервер принял текущее подключение, или NULL, если текущее соединение установлено через сокет домена Unix.

pg_backend_pid () → integer

Возвращает идентификатор серверного процесса при текущем сеансе.

pg_blocking_pids ( integer ) → integer[]

Возвращает массив идентификаторов серверных процессов сеансов, которые блокируют серверный процесс с указанным идентификатором от получения блокировки, или пустой массив, если такого серверного процесса нет или он не заблокирован.
Один серверный процесс блокирует другой, если он либо удерживает блокировку, конфликтующую с запросом блокировки заблокированного процесса (жесткая блокировка), либо ожидает блокировку, которая может вызвать конфликт с запросом блокировки заблокированного процесса и находится перед ней в очереди ожидания (мягкая блокировка). При использовании параллельных запросов в результате всегда перечисляются видимые клиенту идентификаторы процессов (то есть результаты pg_backend_pid), даже если фактическая блокировка удерживается или ожидается дочерним рабочим процессом. Вследствие этого в результате могут быть дублирующиеся идентификаторы. Также обратите внимание, что когда конфликтующую блокировку удерживает подготовленная транзакция, она будет представлена нулевым идентификатором процесса.
Частые вызовы этой функции могут некоторым образом повлиять на производительность базы данных, поскольку ей на короткое время требуется эксклюзивный доступ к разделяемому состоянию менеджера блокировок.

pg_conf_load_time () → timestamp with time zone

Возвращает время, когда в последний раз загружались файлы конфигурации сервера. Если к этому времени текущий сеанс уже начался, она возвращает время, когда сам этот сеанс заново считал эти файлы конфигурации (поэтому в разных сеансах время прочтения будет немного различаться). В противном случае это будет время, когда файлы конфигурации повторно считал процесс qhbmaster.

pg_current_logfile ( [ text ] ) → text

Возвращает имя пути к файлу журнала, в данный момент используемому сборщиком упреждающих записей. Этот путь включает в себя каталог из log_directory и имя конкретного файла журнала. Если сборщик упреждающих записей выключен, результатом будет NULL. Если существует несколько файлов журналов в разных форматах, pg_current_logfile без аргумента возвращает путь к файлу, имеющему первый формат, из найденных в следующем упорядоченном списке: stderr, csvlog. Если файлов с любым из этих форматов нет, возвращается NULL. Чтобы запросить информацию о файле журнала в конкретном формате, передайте csvlog или stderr в качестве значения необязательного параметра. Если запрошенный формат журнала не сконфигурирован в log_destination, результатом будет NULL. Результат функции отражает содержимое файла current_logfiles.

pg_my_temp_schema () → oid

Возвращает OID временной схемы текущего сеанса или ноль, если такой схемы нет (потому что не создавались временные таблицы).

pg_is_other_temp_schema ( oid ) → boolean

Возвращает true, если заданный OID относится к временной схеме текущего сеанса. (Это может быть полезно, например, для исключения временных таблиц других сеансов из представления каталога.)

pg_jit_available () → boolean

Возвращает true, если имеется расширение JIT-компилятор (см. главу JIT-компиляция) и включен параметр конфигурации jit (установлен в on).

pg_listening_channels () → setof text

Возвращает набор имен каналов асинхронных уведомлений, которые прослушивает текущий сеанс.

pg_notification_queue_usage () → double precision

Возвращает долю (0–1) максимального размера очереди асинхронных уведомлений, которая в настоящий момент занята уведомлениями, ожидающими обработки. Дополнительную информацию см. на справочных страницах команд LISTEN и NOTIFY.

pg_qhbmaster_start_time () → timestamp with time zone

Возвращает время, когда был запущен сервер.

pg_safe_snapshot_blocking_pids ( integer ) → integer[]

Возвращает массив идентификаторов процессов сеансов, блокирующих серверный процесс с заданным идентификатором от получения безопасного снимка, или пустой массив, если такого серверного процесса нет или он не заблокирован.
Сеанс, выполняющий транзакцию уровня SERIALIZABLE, блокирует транзакцию уровня SERIALIZABLE READ ONLY DEFERRABLE от получения снимка, пока та не определит, что можно безопасно избежать принятия предикатных блокировок. Дополнительную информацию о сериализуемых и откладываемых транзакциях см. в подразделе Уровень изоляции Serializable.
Частые вызовы этой функции могут некоторым образом повлиять на производительность базы данных, поскольку ей на короткое время требуется доступ к разделяемому состоянию менеджера предикатных блокировок.

pg_trigger_depth () → integer

Возвращает текущий уровень вложенности триггеров QHB (0, если эта функция вызывается не изнутри триггера, напрямую или косвенно).

session_user → name

Возвращает имя пользователя сеанса.

user → name

Это аналог current_user.

version () → text

Возвращает строку, описывающую версию сервера QHB. Эту информацию также можно получить из параметра server_version или, если нужен вариант, читаемый машиной, из параметра server_version_num. Разработчикам программного обеспечения следует использовать параметр server_version_num, а не анализировать текстовую версию.

Примечание
Функции current_catalog, current_role, current_schema, current_user, session_user и user имеют специальный синтаксический статус в SQL: они должны вызываться без завершающих скобок. В QHB скобки можно использовать с current_schema, но не с остальными функциями.

Функция session_user обычно возвращает имя пользователя, инициировавшего текущее соединение с базой данных; но суперпользователи могут изменить эту настройку с помощью команды SET SESSION AUTHORIZATION. Функция current_user возвращает идентификатор пользователя, который применим для проверки прав доступа. Обычно это тот же пользователь, что и пользователь сеанса, но его можно сменить с помощью команды SET ROLE. Он также изменяется во время выполнения функций с атрибутом SECURITY DEFINER. На языке Unix пользователь сеанса — это «реальный пользователь», а текущий пользователь — «эффективный пользователь». Функции current_role и user являются синонимами функции current_user. (Стандарт SQL проводит различие между current_role и current_user, но QHB этого не делает, поскольку объединяет пользователей и роли в единый вид сущностей).

В Таблице 66 перечислены функции, позволяющие программно запрашивать информацию о правах доступа к объекту. (Дополнительную информацию о правах см. в разделе Права.) В этих функциях пользователя, для которого запрашиваются права, можно указать по имени или OID (pg_authid.oid), либо, если имя задано как public, будут проверяться права псевдороли PUBLIC. Кроме того, аргумент user можно полностью опустить, и тогда будет подразумеваться current_user. Объект, к которому запрашиваются права доступа, тоже можно указать по имени или OID. Когда указывается имя, при необходимости в него можно включить имя схемы. Интересующие права доступа указываются в виде текстовой строки, которая должна задавать одно из ключевых слов права, допустимых для типа объекта (например SELECT). Дополнительно к типу права можно добавить WITH GRANT OPTION для проверки, разрешено ли пользователю передавать это право другим. Кроме того, можно перечислить несколько типов прав через запятую, и в этом случае результатом будет true, если у пользователя есть какое-либо из перечисленных прав. (Регистр в строке с правами не имеет значения, а между именами прав (но не внутри) разрешены лишние пробельные символы.) Несколько примеров:

SELECT has_table_privilege('myschema.mytable', 'select');
SELECT has_table_privilege('joe', 'mytable',
  'INSERT, SELECT WITH GRANT OPTION');

Таблица 66. Функции для проверки прав доступа

Функция

Описание

has_any_column_privilege ( [ пользователь name или oid, ] таблица text или oid, право text ) → boolean

Имеет ли пользователь указанное право для какого-либо столбца таблицы? Ответ положительный, если право имеется для всей таблице или право дано на уровне столбцов хотя бы для одного столбца. Допустимые типы прав: SELECT, INSERT, UPDATE и REFERENCES.

has_column_privilege ( [ пользователь name или oid, ] таблица text или oid, столбец text или smallint, право text ) → boolean

Имеет ли пользователь указанное право для заданного столбца таблицы? Ответ положительный, если право имеется для всей таблице или право дано на уровне столбцов для этого столбца. Столбец можно задать по имени или номеру атрибута (pg_attribute.attnum). Допустимые типы прав: SELECT, INSERT, UPDATE и REFERENCES.

has_database_privilege ( [ пользователь name или oid, ] база_данных text или oid, право text ) → boolean

Имеет ли пользователь указанное право для базы данных? Допустимые типы прав: CREATE, CONNECT, TEMPORARY и TEMP (это синоним TEMPORARY).

has_foreign_data_wrapper_privilege ( [ пользователь name или oid, ] обертка_сторонних_данных text или oid, право text ) → boolean

Имеет ли пользователь указанное право для обертки сторонних данных? Единственный допустимый тип права — USAGE.

has_function_privilege ( [ пользователь name или oid, ] функция text или oid, право text ) → boolean

Имеет ли пользователь указанное право для функции? Единственный допустимый тип права — EXECUTE.
Когда функция задается по имени, а не по OID, допускаются те же входные значения, что и для типа данных regprocedure (см. раздел Типы идентификаторов объектов). Например:
SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');

has_language_privilege ( [ пользователь name или oid, ] язык text или oid, право text ) → boolean

Имеет ли пользователь указанное право для языка? Единственный допустимый тип права — USAGE.

has_schema_privilege ( [ пользователь name или oid, ] схема text или oid, право text ) → boolean

Имеет ли пользователь указанное право для схемы? Допустимые типы прав: CREATE и USAGE.

has_sequence_privilege ( [ пользователь name или oid, ] последовательность text или oid, право text ) → boolean

Имеет ли пользователь указанное право для последовательности? Допустимые типы прав: USAGE, SELECT и UPDATE.

has_server_privilege ( [ пользователь name или oid, ] сервер text или oid, право text ) → boolean

Имеет ли пользователь указанное право для стороннего сервера? Единственный допустимый тип права — USAGE.

has_table_privilege ( [ пользователь name или oid, ] таблица text или oid, право text ) → boolean

Имеет ли пользователь указанное право для таблицы? Допустимые типы прав: SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES и TRIGGER.

has_tablespace_privilege ( [ пользователь name или oid, ] табличное_пространство text или oid, право text ) → boolean

Имеет ли пользователь указанное право для табличного пространства? Единственный допустимый тип права — CREATE.

has_type_privilege ( [ пользователь name или oid, ] тип text или oid, право text ) → boolean

Имеет ли пользователь указанное право для типа данных? Единственный допустимый тип права — USAGE. Когда тип задается по имени, а не по OID, допускаются те же входные значения, что и для типа данных regtype (см. раздел Типы идентификаторов объектов).

pg_has_role ( [ пользователь name или oid, ] роль text или oid, право text ) → boolean

Имеет ли пользователь указанное право для роли? Допустимые типы прав: MEMBER и USAGE. Право MEMBER обозначает непосредственное или косвенное членство в роли (то есть право выполнять SET ROLE), тогда как право USAGE обозначает, что права роли становятся доступны для пользователя сразу, без выполнения SET ROLE. Эта функция не поддерживает особый случай с указанием для пользователя (user) значения public, поскольку псевдороль PUBLIC не может быть членом настоящих ролей.

row_security_active ( таблица text или oid ) → boolean

Действует ли защита на уровне строк для заданной таблицы в контексте текущего пользователя и текущей среды?

В Таблице 67 приведены имеющиеся операторы для типа aclitem, который представляет права доступа в каталоге. Информацию о том, как читать значения прав доступа, см. в разделе Права.

Таблица 67. Операторы для aclitem

Оператор

Описание
Пример(ы)

aclitem = aclitem → boolean

Значения aclitem равны? (Обратите внимание, что у типа aclitem отсутствует обычный набор операторов сравнения; для него имеется только проверка равенства. В свою очередь, массивы aclitem тоже можно сопоставлять только на равенство.)
'calvin=r*w/hobbes'::aclitem = 'calvin=r*w*/hobbes'::aclitem → f

aclitem[] @> aclitem → boolean

Содержит ли массив заданные права? (Ответом будет true, если в массиве есть запись, соответствующая правоприобретателю и праводателю из aclitem и имеющая как минимум заданный набор прав.)
'{calvin=r*w/hobbes,hobbes=r*w*/postgres}'::aclitem[] @> 'calvin=r*/hobbes'::aclitem → t

aclitem[] ~ aclitem → boolean

Это устаревший псевдоним для @>.
'{calvin=r*w/hobbes,hobbes=r*w*/postgres}'::aclitem[] ~ 'calvin=r*/hobbes'::aclitem → t

В Таблице 68 приведены некоторые дополнительные функции для управления типом aclitem.

Таблица 68. Функции для aclitem

Функция

Описание

acldefault ( тип "char", id_владельца oid ) → aclitem[]

Конструирует массив aclitem, содержащий действующие по умолчанию права доступа для объекта заданного типа, принадлежащего роли с OID id_владельца. Эта функция показывает права доступа, которые будут подразумеваться, когда запись ACL определенного объекта равна NULL. (Права доступа по умолчанию описаны в разделе Права.) Параметр тип может принимать одно из следующих значений: 'c' — столбец (COLUMN), 'r' — таблица (TABLE) и подобные таблице объекты, 's' — последовательность (SEQUENCE), 'd' — база данных (DATABASE), 'f' — функция (FUNCTION) или процедура (PROCEDURE), 'l' — язык (LANGUAGE), 'L' — большой объект (LARGE OBJECT), 'n' — схема (SCHEMA), 't' — табличное пространство (TABLESPACE), 'F' — обертка сторонних данных (FOREIGN DATA WRAPPER), 'S' — сторонний сервер (FOREIGN SERVER) или 'T' — тип (TYPE) или домен (DOMAIN).

aclexplode ( aclitem[] ) → setof record ( праводатель oid, правоприобретатель oid, тип_права text, предоставляемое boolean )

Возвращает массив aclitem в виде набора строк. Если правоприобретателем является псевдороль PUBLIC, в столбце правоприобретатель она представляется нулем. Все назначенные права представляются ключевыми словами SELECT, INSERT и т. д. Обратите внимание, что каждое право выводится отдельной строкой, поэтому в столбце тип_права находится только одно ключевое слово.

makeaclitem ( правоприобретатель oid, праводатель oid, права text, предоставляемые boolean ) → aclitem

Конструирует значение aclitem с заданными свойствами.

В Таблице 69 приведены функции, определяющие, является ли определенный объект видимым в текущем пути поиска схемы. Например, таблица считается видимой, если содержащая ее схема находится в пути поиска и таблица с таким именем не появляется ранее в пути поиска. Это равнозначно утверждению, что на таблицу можно ссылаться по имени без явного уточнения схемы. Таким образом, чтобы перечислить имена всех видимых таблиц, можно написать:

SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);

Для функций и операторов считается, что объект в пути поиска является видимым, если объект с таким именем и типом(ами) данных аргументов не появляется ранее в пути поиска. Для классов и семейств операторов учитывается не только имя, но и связанный индексный метод доступа.

Таблица 69. Функции проверки видимости схемы

Функция

Описание

pg_collation_is_visible ( правило_сортировки oid ) → boolean

Видимо ли правило сортировки в пути поиска?

pg_conversion_is_visible ( преобразование oid ) → boolean

Видимо ли преобразование в пути поиска?

pg_function_is_visible ( функция oid ) → boolean

Видима ли функция в пути поиска? (Также работает для процедур и агрегатов.)

pg_opclass_is_visible ( класс_операторов oid ) → boolean

Видим ли класс операторов в пути поиска?

pg_operator_is_visible ( оператор oid ) → boolean

Видим ли оператор в пути поиска?

pg_opfamily_is_visible ( семейство_операторов oid ) → boolean

Видимо ли семейство операторов в пути поиска?

pg_statistics_obj_is_visible ( объект_статистики oid ) → boolean

Видим ли объект статистики в пути поиска?

pg_table_is_visible ( таблица oid ) → boolean

Видима ли таблица в пути поиска? (Также работает для всех типов отношений, включая представления, материализованные представления, индексы, последовательности и сторонние таблицы.)

pg_ts_config_is_visible ( конфигурация oid ) → boolean

Видима ли конфигурация текстового поиска в пути поиска?

pg_ts_dict_is_visible ( словарь oid ) → boolean

Видим ли словарь текстового поиска в пути поиска?

pg_ts_parser_is_visible ( анализатор oid ) → boolean

Видим ли анализатор текстового поиска в пути поиска?

pg_ts_template_is_visible ( шаблон oid ) → boolean

Видим ли шаблон текстового поиска в пути поиска?

pg_type_is_visible ( тип oid ) → boolean

Видим ли тип (или домен) в пути поиска?

Всем этим функциям для идентификации подлежащего проверке объекта требуется его OID. Если нужно проверить объект по имени, удобно использовать типы-псевдонимы OID (regclass, regtype, regprocedure, regoperator, regconfig или regdictionary), например:

SELECT pg_type_is_visible('myschema.widget'::regtype);

Обратите внимание, что не имеет смысла проверять таким способом имя типа, не дополненное схемой — если имя вообще можно распознать, оно должно быть видимым.

В Таблице 70 перечислены функции, которые извлекают информацию из системных каталогов.

Таблица 70. Функции для получения информации из системных каталогов

Функция

Описание

format_type ( тип oid, модификатор_типа integer ) → text

Возвращает в формате SQL имя типа данных, идентифицируемого по OID и, возможно, модификатору типа. Если конкретный модификатор неизвестен, вместо него можно передать NULL.

pg_char_to_encoding ( кодировка name ) → integer

Преобразует предоставленное имя кодировки в целое число, представляющее внутренний идентификатор, используемый в некоторых системных таблицах. Возвращает -1, если предоставлено неизвестное имя кодировки.

pg_encoding_to_char ( encoding integer ) → name

Преобразует целое число, используемое в некоторых системных таблицах как внутренний идентификатор кодировки, в удобочитаемую для человека строку. Возвращает пустую строку, если предоставлен неверный номер кодировки.

pg_get_catalog_foreign_keys () → setof record ( ссылающ_таблица regclass, ссылающ_столбцы text[], ссылочн_таблица regclass, ссылочн_столбцы text[], является_массивом boolean, необязательно boolean )

Возвращает набор записей, описывающих отношения внешних ключей, существующие в системных каталогах QHB. Столбец ссылающ_таблица содержит имя ссылающегося каталога, а столбец ссылающ_столбцы — имена ссылающихся столбцов. Аналогичным образом столбец ссылочн_таблица содержит имя ссылочного каталога, а столбец ссылочн_столбцы — имена ссылочных столбцов. Если параметр является_массивом равен true, последний ссылающийся столбец является массивом, каждый из элементов которого должен соответствовать некоторой записи в ссылочном каталоге. Если параметр необязательно равен true, ссылочным столбцам можно содержать нули вместо действительной ссылки.

pg_get_constraintdef ( ограничение oid [, удобный_формат boolean ] ) → text

Восстанавливает команду, создающую ограничение. (Это декомпилированное восстановление, а не оригинальный текст команды.)

pg_get_expr ( выражение pg_node_tree, отношение oid [, удобный_формат boolean ] ) → text

Декомпилирует внутреннюю форму выражений, хранящихся в системных каталогах, например, значение по умолчанию для столбца. Если это выражение может содержать переменные (Var), задайте во втором параметре OID отношения, к которому они обращаются; если переменных не ожидается, достаточно передать ноль.

pg_get_functiondef ( функция oid ) → text

Восстанавливает команду, создающую функцию или процедуру. (Это декомпилированное восстановление, а не оригинальный текст команды.) Результатом является полноценный оператор CREATE OR REPLACE FUNCTION или CREATE OR REPLACE PROCEDURE.

pg_get_function_arguments ( функция oid ) → text

Восстанавливает список аргументов функции или процедуры в той форме, в какой он должен находиться в команде CREATE FUNCTION (включая значения по умолчанию).

pg_get_function_identity_arguments ( функция oid ) → text

Восстанавливает список аргументов, необходимый для идентификации функции или процедуры, в той форме, в какой он должен находиться, например, в команде ALTER FUNCTION. В этой форме значения по умолчанию опускаются.

pg_get_function_result ( функция oid ) → text

Восстанавливает в функции предложение RETURNS в той форме, в какой оно должно находиться в команде CREATE FUNCTION. Для процедуры возвращает NULL.

pg_get_indexdef ( индекс oid [, столбец integer, удобный_формат boolean ] ) → text

Восстанавливает команду, создающую индекс. (Это декомпилированное восстановление, а не оригинальный текст команды.) Если передается значение столбца и оно отлично от нуля, восстанавливается только определение этого столбца.

pg_get_keywords () → setof record ( слово text, код_кат "char", откр_метка boolean, описание_кат text, описание_метки text )

Возвращает набор записей, описывающих ключевые слова SQL, распознаваемые сервером. Столбец слово содержит ключевое слово. Столбец код_кат содержит код категории: U — незарезервированное ключевое слово, C — ключевое слово, которое может быть именем столбца, T — ключевое слово, которое может быть именем типа или функции, или R — полностью зарезервированное ключевое слово. Столбец откр_метка содержит true, если ключевое слово можно использовать в качестве «открытой» метки столбца в списках SELECT, или false, если его можно использовать только после AS. Столбец описание_кат содержит возможно локализованную строку, описывающую категорию ключевого слова. Столбец описание_метки содержит возможно локализованную строку, описывающую статус метки столбца с ключевым словом.

pg_get_ruledef ( правило oid [, удобный_формат boolean ] ) → text

Восстанавливает команду, создающую правило. (Это декомпилированное восстановление, а не оригинальный текст команды.)

pg_get_serial_sequence ( таблица text, столбец text ) → text

Возвращает имя последовательности, связанной со столбцом, или NULL, если с этим столбцом не связано никакой последовательности. Если это столбец идентификации, то связанная последовательность внутренне создана для этого столбца. Для столбцов, созданных с одним из последовательных типов (serial, smallserial, bigserial), это последовательность, созданная для объявления этого последовательного столбца. В последнем случае эту связь можно изменить или убрать с помощью команды ALTER SEQUENCE OWNED BY. (Возможно, эту функцию следовало назвать pg_get_owned_sequence; ее текущее название отражает тот факт, что исторически она использовалась со столбцами последовательных типов.) Первым параметром является имя таблицы (возможно, дополненное схемой), а вторым — имя столбца. Поскольку первый параметр потенциально содержит имена схемы и таблицы, он анализируется по обычным правилам SQL, подразумевающим, что он по умолчанию приводится к нижнему регистру. Второй параметр, являясь просто именем столбца, воспринимается буквально, поэтому его регистр сохраняется неизменным. Результат форматируется надлежащим образом для передачи функциям для последовательностей (см. раздел Функции для управления последовательностями).
Обычно эта функция используется для прочтения текущего значения последовательности для столбца идентификации или последовательного столбца, например: SELECT currval(pg_get_serial_sequence('sometable', 'id'));

pg_get_statisticsobjdef ( объект_статистики oid ) → text

Восстанавливает команду, создающую объект расширенной статистики. (Это декомпилированное восстановление, а не оригинальный текст команды.)

pg_get_triggerdef ( триггер oid [, удобный_формат boolean ] ) → text

Восстанавливает команду, создающую триггер. (Это декомпилированное восстановление, а не оригинальный текст команды.)

pg_get_userbyid ( роль oid ) → name

Возвращает имя роли по заданному OID.

pg_get_viewdef ( представление oid [, удобный_формат boolean ] ) → text

Восстанавливает нижележащую команду SELECT для представления или материализованного представления. (Это декомпилированное восстановление, а не оригинальный текст команды.)

pg_get_viewdef ( представление oid, число_столбцов integer ) → text

Восстанавливает нижележащую команду SELECT для представления или материализованного представления. (Это декомпилированное восстановление, а не оригинальный текст команды.) В этой форме всегда включен форматированный вывод, и длинные строки переносятся, чтобы текст умещался в заданное число столбцов.

pg_get_viewdef ( представление text [, удобный_формат boolean ] ) → text

Восстанавливает нижележащую команду SELECT для представления или материализованного представления, принимая вместо OID его текстовое имя. (Это декомпилированное восстановление, а не оригинальный текст команды.)

pg_index_column_has_property ( индекс regclass, столбец integer, свойство text ) → boolean

Проверяет, есть ли у столбца индекса именованное свойство. Типичные свойства индексных столбцов перечислены в Таблице 71. (Обратите внимание, что расширенные методы доступа могут определить для своих индексов дополнительные имена свойств.) Если имя свойства неизвестно или неприменимо к конкретному объекту, либо если OID или номер столбца не идентифицируют действительный объект, возвращается NULL.

pg_index_has_property ( индекс regclass, свойство text ) → boolean

Проверяет, есть ли у индекса именованное свойство. Типичные свойства индексов перечислены в Таблице 72. (Обратите внимание, что расширенные методы доступа могут определить для своих индексов дополнительные имена свойств.) Если имя свойства неизвестно или неприменимо к конкретному объекту, либо если OID не идентифицирует действительный объект, возвращается NULL.

pg_indexam_has_property ( метод_доступа oid, свойство text ) → boolean

Проверяет, есть ли у индексного метода доступа именованное свойство. Свойства методов доступа перечислены в Таблице 73. Если имя свойства неизвестно или неприменимо к конкретному объекту, либо если OID не идентифицирует действительный объект, возвращается NULL.

pg_options_to_table ( массив_параметров text[] ) → setof record ( имя_параметра text, значение_параметра text )

Возвращает набор параметров хранилища, представленных значением из pg_class.reloptions или pg_attribute.attoptions.

pg_tablespace_databases ( табличное_пространство oid ) → setof oid

Возвращает набор OID баз данных, объекты которых размещены в заданном табличном пространстве. Если эта функция возвращает какие-либо строки, значит, табличное пространство не пустое и не может быть удалено. Для идентификации конкретный объектов, наполняющих табличное пространство, понадобится подключиться к базам данных, идентифицированным функцией pg_tablespace_databases, и запросить их каталоги pg_class.

pg_tablespace_location ( табличное_пространство oid ) → text

Возвращает путь в файловой системе, в котором расположено табличное пространство.

pg_typeof ( "any" ) → regtype

Возвращает OID типа данных переданного ей значения. Это может быть полезно для решения проблем или динамического конструирования запросов SQL. Эта функция объявлена как возвращающая тип regtype, являющийся типом-псевдонимом OID (см. раздел Типы идентификаторов объектов); это означает, что значения этого типа сравниваются так же, как OID, но отображаются как имя типа.
Например:

SELECT pg_typeof(33);
     pg_typeof
    -----------
      integer

SELECT typlen FROM pg_type WHERE oid = pg_typeof(33);
     typlen
    --------
          4

COLLATION FOR ( "any" ) → text

Возвращает имя правила сортировки переданного ей значения. При необходимости это значение заключается в кавычки и дополняется схемой. Если для выражения аргумента не было получено ни одного правила сортировки, возвращается NULL. Если же правила сортировки не подходят для типа данных аргумента, возникает ошибка.
Например:

SELECT collation for (description) FROM pg_description LIMIT 1;
     pg_collation_for
    ------------------
      "default"

SELECT collation for ('foo' COLLATE "de_DE");
     pg_collation_for
    ------------------
     "de_DE"

to_regclass ( text ) → regclass

Переводит текстовое имя отношения в его OID. Похожий результат можно получить, приведя строку к типу regclass (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

to_regcollation ( text ) → regcollation

Переводит текстовое имя правила сортировки в его OID. Похожий результат можно получить, приведя строку к типу regcollation (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

to_regnamespace ( text ) → regnamespace

Переводит текстовое имя схемы в ее OID. Похожий результат можно получить, приведя строку к типу regnamespace (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

to_regoper ( text ) → regoper

Переводит текстовое имя оператора в его OID. Похожий результат можно получить, приведя строку к типу regoper (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено или окажется неоднозначным, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

to_regoperator ( text ) → regoperator

Переводит текстовое имя оператора (с типами параметров) в его OID. Похожий результат можно получить, приведя строку к типу regoperator (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

to_regproc ( text ) → regproc

Переводит текстовое имя функции или процедуры в ее OID. Похожий результат можно получить, приведя строку к типу regproc (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено или окажется неоднозначным, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

to_regprocedure ( text ) → regprocedure

Переводит текстовое имя функции или процедуры (с типами аргументов) в ее OID. Похожий результат можно получить, приведя строку к типу regprocedure (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

to_regrole ( text ) → regrole

Переводит текстовое имя роли в ее OID. Похожий результат можно получить, приведя строку к типу regrole (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

to_regtype ( text ) → regtype

Переводит текстовое имя типа в его OID. Похожий результат можно получить, приведя строку к типу regtype (см. раздел Типы идентификаторов объектов); однако если имя не будет найдено, эта функция вернет NULL, а не выдаст ошибку. Кроме того, в отличие от приведения, эта функция не принимает на вход числовые OID.

Большинство функций, восстанавливающих (декомпилирующих) объекты базы данных, принимают дополнительный флаг pretty, и если он равен true, то результат будет получен в форматированном виде. В форматированном выводе для удобочитаемости убираются лишние скобки и добавляются пробельные символы. Такой формат легче читается, но формат по умолчанию с большей вероятностью будет успешно восприниматься будущими версиями QHB, поэтому при дампировании рекомендуется воздержаться от использования форматированного вывода. Если в параметре pretty передается false, возвращается тот же результат, что и при отсутствии этого параметра.

Таблица 71. Свойства столбца индекса

Имя | Описание
asc | Сортируется ли столбец в порядке возрастания при сканировании вперед?
desc | Сортируется ли столбец в порядке убывания при сканировании вперед?
nulls_first | Сортируется ли столбец с выдачей NULL в начале при сканировании вперед?
nulls_last | Сортируется ли столбец с выдачей NULL в конце при сканировании вперед?
orderable | Имеет ли столбец какой-либо определенный порядок сортировки?
distance_orderable | Может ли столбец сканироваться по порядку оператором «расстояния», например, ORDER BY столбец <-> константа?
returnable | Может ли значение столбца быть возвращено при сканировании только индекса?
search_array | Обладает ли столбец внутренней поддержкой поиска столбец = ANY(массив)?
search_nulls | Поддерживает ли столбец поиск IS NULL и IS NOT NULL?

Таблица 72. Свойства индекса

Имя | Описание
clusterable | Можно ли использовать индекс в команде CLUSTER?
index_scan | Поддерживает ли индекс обычное сканирование (не по битовой карте)?
bitmap_scan | Поддерживает ли индекс сканирование по битовой карте?
backward_scan | Можно ли в процессе сканирования изменить его направление (для поддержки FETCH BACKWARD на курсоре без необходимости материализации)?

Таблица 73. Свойства индексного метода доступа

Имя | Описание
can_order | Поддерживает ли метод доступа ASC, DESC и связанные ключевые слова в команде CREATE INDEX?
can_unique | Поддерживает ли метод доступа уникальные индексы?
can_multi_col | Поддерживает ли метод доступа индексы с несколькими столбцами?
can_exclude | Поддерживает ли метод доступа исключающие ограничения?
can_include | Поддерживает ли метод доступа предложение INCLUDE в команде CREATE INDEX?

В Таблице 74 перечислены функции, связанные с идентификацией и адресацией объектов базы данных.

Таблица 74. Функции для получения информации и адресации объектов

Функция

Описание

pg_describe_object ( id_класса oid, id_объекта oid, id_подобъекта integer ) → text

Возвращает текстовое описание объекта базы данных, идентифицированного по OID каталога, OID объекта и ID подобъекта (например, номер столбца в таблице; ID подобъекта равен нулю при обращении к объекту в целом). Данное описание предназначено для прочтения человеком и может быть переведено, в зависимости от конфигурации сервера. Это особенно полезно для идентификации объекта, на который ссылается каталог pg_depend. Эта функция возвращает значения NULL для неопределенных объектов.

pg_identify_object ( id_класса oid, id_объекта oid, id_подобъекта integer ) → record ( тип text, схема text, имя text, идентификатор text )

Возвращает строку, содержащую достаточно информации для однозначной идентификации объекта базы данных по указанным OID каталога, OID объекта и ID подобъекта. Эта информация предназначена для прочтения машиной и поэтому никогда не переводится. Столбец тип определяет тип объекта базы данных; схема — это имя схемы, к которой относится объект, или NULL для типов объектов, не относящихся к схемам; имя — это имя объекта, при необходимости заключенное в кавычки, если этого имени (вместе с именем схемы, если применимо) достаточно для однозначной идентификации объекта, в иных случаях — NULL; идентификатор — это полный идентификатор объекта, точный формат которого зависит от типа объекта, а каждое имя в этом формате дополнено схемой и при необходимости заключено в кавычки. Для неопределенных объектов указываются значения NULL.

pg_identify_object_as_address ( id_класса oid, id_объекта oid, id_подобъекта integer ) → record ( тип text, имена_объектов text[], аргументы_объектов text[] )

Возвращает строку, содержащую достаточно информации для однозначной идентификации объекта базы данных по указанным OID каталога, OID объекта и ID подобъекта. Возвращаемая информация не зависит от текущего сервера, то есть ее можно использовать для идентификации одноименного объекта на другом сервере. Столбец тип определяет тип объекта базы данных; имена_объектов и аргументы_объектов представляют собой текстовые массивы, в совокупности формирующие ссылку на объект. Эти три значения можно передать функции pg_get_object_address, чтобы получить внутренний адрес объекта.

pg_get_object_address ( тип text, имена_объектов text[], аргументы_объектов text[] ) → record ( id_класса oid, id_объекта oid, id_подобъекта integer )

Возвращает строку, содержащую достаточно информации для однозначной идентификации объекта базы данных по указанным коду типа и массивам имен и аргументов объекта. Возвращаемые значения можно использовать в системных каталогах, например, в pg_depend; их можно передать другим системным функциям, например pg_describe_object или pg_identify_object. Столбец id_класса — это OID системного каталога, содержащего объект; id_объекта — OID самого объекта, а id_подобъекта — ID подобъекта или ноль, если такового нет. Эта функция обратна pg_identify_object_as_address. Для неопределенных объектов указываются значения NULL.

Функции, приведенные в Таблице 75, извлекают комментарии, ранее сохраненные командой COMMENT. Если найти комментарий для заданных параметров невозможно, возвращается NULL.

Таблица 75. Функции для получения комментариев

Функция

Описание

col_description ( таблица oid, столбец integer ) → text

Возвращает комментарий для столбца таблицы, заданного по OID таблицы и номеру самого столбца. (Функцию obj_description нельзя использовать для столбцов таблиц, поскольку столбцы не имеют собственных OID.)

obj_description ( объект oid, каталог name ) → text

Возвращает комментарий для объекта базы данных, заданного по OID и имени системного каталога, в котором этот объект находится. Например, при вызове obj_description(123456, 'pg_class') будет получен комментарий для таблицы с OID 123456.

obj_description ( объект oid ) → text

Возвращает комментарий для объекта базы данных, заданного только по OID. Эта функция является устаревшей, поскольку нет гарантии, что OID уникальны в разных системных каталогах; таким образом, может быть выдан неправильный комментарий.

shobj_description ( объект oid, каталог name ) → text

Возвращает комментарий для общего объекта базы данных, заданного по OID и имени системного каталога, в котором этот объект находится. Эта функция похожа на obj_description, за исключением того, что она используется для извлечения комментариев для общих объектов (то есть баз данных, ролей и табличных пространств). Некоторые системные каталоги являются глобальными для всех баз данных в каждом кластере, и описания объектов в них тоже хранятся глобально.

Функции, приведенные в Таблице 76, предоставляют информацию о серверных транзакциях в экспортируемой форме. В основном эти функции используются для определения того, какие транзакции были зафиксированы между двумя снимками состояния.

Таблица 76. Функции для получения информации об идентификаторах транзакций и снимках состояния

Функция

Описание

pg_current_xact_id () → xid8

Возвращает идентификатор текущей транзакции. Если у текущей транзакции еще нет идентификатора (поскольку она не осуществила никаких изменения в базе данных), он будет ей назначен.

pg_current_xact_id_if_assigned () → xid8

Возвращает идентификатор текущей транзакции или NULL, если он еще не назначен. (Если транзакция может быть только читающей, лучше воспользоваться этим вариантом, чтобы избежать излишнего потребления XID.)

pg_xact_status ( xid8 ) → text

Сообщает статус фиксации недавней транзакции. Результатом может быть одно из следующих значений: in progress (выполняется), committed (зафиксирована) или aborted (прервана), при условии, что транзакция выполнялась достаточно недавно, чтобы система еще хранила информацию о статусе ее фиксации. Если же транзакция достаточно стара, чтобы в системе не осталось о ней упоминаний, и информация о статусе фиксации была стерта, результатом будет NULL. Приложения могут использовать эту функцию, например, чтобы определить, была ли транзакция зафиксирована или прервана после потери соединения между приложением и сервером баз данных в процессе выполнения COMMIT. Обратите внимание, что для подготовленных транзакций выдается статус in progress; если приложениям нужно определить, является ли транзакция с заданным идентификатором подготовленной, они должны проверить каталог pg_prepared_xacts.

pg_current_snapshot () → pg_snapshot

Возвращает текущий снимок состояния — структуру данных, показывающую, какие транзакции сейчас выполняются.

pg_snapshot_xip ( pg_snapshot ) → setof xid8

Возвращает набор идентификаторов выполняющихся транзакций, содержащихся в снимке состояния.

pg_snapshot_xmax ( pg_snapshot ) → xid8

Возвращает значение xmax для снимка состояния.

pg_snapshot_xmin ( pg_snapshot ) → xid8

Возвращает значение xmin для снимка состояния.

pg_visible_in_snapshot ( xid8, pg_snapshot ) → boolean

Видима ли транзакция с заданным идентификатором согласно этому снимку состояния (то есть была ли она завершена до того, как был получен снимок)? Обратите внимание, что эта функция не выдаст правильный ответ, если задать идентификатор субтранзакции.

Внутренний тип идентификатора транзакции xid имеет ширину 32 бита и значения в нем повторяются каждые 4 миллиарда транзакций. Однако функции, перечисленные в Таблице 76, используют 64-битный тип xid8, значения которого не повторяются на протяжении жизни сервера и который при необходимости можно преобразовать в тип xid. Тип данных pg_snapshot хранит информацию о видимости транзакций в конкретный момент времени. Его компоненты описаны в Таблице 77. Текстовым представлением pg_snapshot является xmin:xmax:xip_list. Например, запись 10:20:10,14,15 означает xmin=10, xmax=20, xip_list=10, 14, 15.

Таблица 77. Компоненты снимка состояния

Имя
Описание

xmin
Минимальный идентификатор транзакции, который все еще активен. Все транзакции, идентификаторы которых меньше xmin, либо зафиксированы и видимы, либо отменены и неактивны.

xmax
Идентификатор на один больше максимального идентификатора завершенной транзакции. Все транзакции, идентификаторы которых больше или равны xmax, еще не завершены на момент получения снимка состояния и поэтому невидимы.

xip_list
Транзакции, выполняющиеся на момент получения снимка состояния. Транзакции с идентификаторами, для которых xmin <= id < xmax, не попавшие в этот список, были уже завершены к моменту получения снимка и поэтому либо видимы, либо неактивны, в зависимости от статуса фиксации. Идентификаторы субтранзакций в этот список не включены.

В более старых версиях QHB отсутствовал тип xid8, поэтому предоставлялись варианты этих функций, в которых для представления 64-битных XID использовался тип bigint, а также, соответственно, имелся отдельный тип для информации о снимке состояния — txid_snapshot. В названиях этих старых функций фигурирует txid. Они все еще поддерживаются в целях обратной совместимости, но могут быть удалены в будущем. См. Таблицу 78.

Таблица 78. Устаревшие функции для получения информации об идентификаторах транзакций и снимках состояния

Функция

Описание

txid_current () → bigint

См. pg_current_xact_id().

txid_current_if_assigned () → bigint

См. pg_current_xact_id_if_assigned().

txid_current_snapshot () → txid_snapshot

См. pg_current_snapshot().

txid_snapshot_xip ( txid_snapshot ) → setof bigint

См. pg_snapshot_xip().

txid_snapshot_xmax ( txid_snapshot ) → bigint

См. pg_snapshot_xmax().

txid_snapshot_xmin ( txid_snapshot ) → bigint

См. pg_snapshot_xmin().

txid_visible_in_snapshot ( bigint, txid_snapshot ) → boolean

См. pg_visible_in_snapshot().

txid_status ( bigint ) → text

См. pg_xact_status().

Функции, приведенные в Таблице 79, предоставляют информацию о том, когда были зафиксированы уже завершенные транзакции. Они выдают полезные данные только при включенном параметре конфигурации track_commit_timestamp и только для тех транзакций, которые были зафиксированы после его включения.

Таблица 79. Функции для получения информации о зафиксированных транзакциях

Функция

Описание

pg_xact_commit_timestamp ( xid ) → timestamp with time zone

Возвращает время фиксации транзакции.

pg_xact_commit_timestamp_origin ( xid ) → record ( метка_времени timestamp with time zone, id_ист_репл oid)

Возвращает время фиксации и источник репликации транзакции.

pg_last_committed_xact () → record ( id_транзакции xid, метка_времени timestamp with time zone, id_ист_репл oid )

Возвращает идентификатор, время фиксации и источник репликации последней зафиксированной транзакции.

Функции, приведенные в Таблице 80, выдают информацию, инициализированную во время qhb_bootstrap, например версию каталога. Они также показывают информацию о протоколировании в журнал упреждающей записи и обработке контрольных точек. Эта информация относится ко всему кластеру, а не к какой-либо одной базе данных. Эти функции по большей части предоставляют ту же информацию и из того же источника, что и приложение qhb_controldata.

Таблица 80. Функции для управления данными

Функция

Описание

pg_control_checkpoint () → record

Возвращает информацию о текущем состоянии контрольных точек, показанную в Таблице 81.

pg_control_system () → record

Возвращает информацию о текущем состоянии управляющего файла, показанную в Таблице 82.

pg_control_init () → record

Возвращает информацию о состоянии инициализации кластера, показанную в Таблице 83.

pg_control_recovery () → record

Возвращает информацию о состоянии восстановления, показанную в Таблице 84.

Таблица 81. Столбцы результата pg_control_checkpoint

Имя столбца | Тип данных
checkpoint_lsn | pg_lsn
redo_lsn | pg_lsn
redo_wal_file | text
timeline_id | integer
prev_timeline_id | integer
full_page_writes | boolean
next_xid | text
next_oid | oid
next_multixact_id | xid
next_multi_offset | xid
oldest_xid | xid
oldest_xid_dbid | oid
oldest_active_xid | xid
oldest_multi_xid | xid
oldest_multi_dbid | oid
oldest_commit_ts_xid | xid
newest_commit_ts_xid | xid
checkpoint_time | timestamp with time zone

Таблица 82. Столбцы результата pg_control_system

Имя столбца | Тип данных
pg_control_version | integer
catalog_version_no | integer
system_identifier | bigint
pg_control_last_modified | timestamp with time zone

Таблица 83. Столбцы результата pg_control_init

Имя столбца | Тип данных
max_data_alignment | integer
database_block_size | integer
blocks_per_segment | integer
wal_block_size | integer
bytes_per_wal_segment | integer
max_identifier_length | integer
max_index_columns | integer
max_toast_chunk_size | integer
large_object_chunk_size | integer
float8_pass_by_value | boolean
data_page_checksum_version | integer

Таблица 84. Столбцы результата pg_control_recovery

Имя столбца | Тип данных
min_recovery_end_lsn | pg_lsn
min_recovery_end_timeline | integer
backup_start_lsn | pg_lsn
backup_end_lsn | pg_lsn
end_of_backup_record_required | boolean

Функции для системного администрирования

Функции, описанные в этом разделе, используются для контроля и мониторинга сервера QHB.

Функции для параметров конфигурации

В Таблице 85 перечислены имеющиеся функции для запроса и изменения значений параметров конфигурации выполнения.

Таблица 85. Функции для параметров конфигурации

Функция

Описание
Пример(ы)

current_setting ( имя_параметра text [, может_отсутствовать boolean ] ) → text

Возвращает текущее значение параметра имя_параметра. Если такого параметра нет, current_setting выдает ошибку, если только не передан параметр может_отсутствовать со значением true (в этом случае возвращается NULL). Эта функция соответствует команде SQL SHOW.
current_setting('datestyle') → ISO, MDY

set_config ( имя_параметра text, новое_значение text, локальный boolean ) → text

Устанавливает для параметра имя_параметра новое_значение и возвращает это значение. Если параметр локальный равен true, новое значение будет применяться только в рамках текущей транзакции. Чтобы новое значение действовало до конца текущего сеанса, нужно задать этому параметру значение false. Эта функция соответствует команде SQL SET.
set_config('log_statement_stats', 'off', false) → off

Функции для передачи сигналов серверу

Функции, приведенные в Таблице 86, отправляют управляющие сигналы другим серверным процессам. По умолчанию пользоваться этими функциями могут только суперпользователи, но с помощью команды GRANT доступ к ним может быть предоставлен и другим пользователям, кроме отмеченных исключений.

Каждая из этих функция возвращает true, если сигнал был успешно передан, и false, если передача сигнала не удалась.

Таблица 86. Функции для передачи сигналов серверу

Функция

Описание

pg_cancel_backend ( pid integer ) → boolean

Отменяет текущий запрос в сеансе, который обслуживается серверным процессом с заданным PID. Эта операция также разрешена, если вызывающая функцию роль является членом роли, процесс которой отменяется, либо если вызывающей роли было дано право pg_signal_backend, однако только суперпользователи могут отменять запросы суперпользователей.

pg_log_backend_memory_contexts ( pid integer ) → boolean

Запрашивает протоколирование в журнал информации о контекстах памяти обслуживающего процесса с заданным PID. Эта информация будет выводиться в сообщениях уровня LOG, которые появятся в журнале сервера, исходя из заданной конфигурации журнала (подробную информацию см. в разделе Регистрация ошибок и протоколирование), но не будут передаваться клиенту, независимо от значения client_min_messages. Только суперпользователи могут запрашивать протоколирование информации о контекстах памяти.

pg_reload_conf () → boolean

Заставляет все процессы сервера QHB перезагрузить их файлы конфигурации. (Для запуска этой операции посылается сигнал SIGHUP процессу qhbmaster, который, в свою очередь, посылает SIGHUP всем своим дочерним процессам.) Перед перезагрузкой можно воспользоваться представлениями pg_file_settings и pg_hba_file_rules для проверки файлов конфигурации на предмет возможных ошибок.

pg_rotate_logfile () → boolean

Указывает менеджеру файла журнала немедленно переключиться на новый файл вывода. Это работает, только когда запущен встроенный сборщик сообщений, поскольку без него не запускается подпроцесс менеджера файла журнала.

pg_terminate_backend ( pid integer, время_ожидания bigint DEFAULT 0 ) → boolean

Завершает сеанс, который обслуживается внутренним процессом с заданным PID. Эта операция также разрешена, если вызывающая функцию роль является членом роли, процесс которой завершается, либо если вызывающей роли было дано право pg_signal_backend, однако только суперпользователи могут завершать обслуживающие процессы суперпользователей.
Если время_ожидания не задано или равно нулю, эта функция возвращает true независимо от того, действительно ли завершается процесс или нет, показывая только, что сигнал был передан успешно. Если время_ожидания задано (в миллисекундах) и больше нуля, функция ожидает, пока процесс фактически не завершится или пока не пройдет заданное время. Если процесс завершается, функция возвращает true. По окончании времени ожидания выдается предупреждение и возвращается false.

Функции pg_cancel_backend и pg_terminate_backend отправляют сигналы (SIGINT или SIGTERM соответственно) обслуживающим процессам, идентифицируемым по PID. PID активного процесса можно найти в столбце pid представления pg_stat_activity или в списке процессов qhb на сервере (используя ps в Unix). Роль активного процесса можно найти в столбце usename представления pg_stat_activity.

Функцию pg_log_backend_memory_contexts можно использовать для протоколирования в журнале информации о контекстах памяти обслуживающего процесса. Например:

qhb=# SELECT pg_log_backend_memory_contexts(pg_backend_pid());
 pg_log_backend_memory_contexts
--------------------------------
 t
(1 row)

Для каждого контекста памяти будет выводится одно сообщение. Например:

LOG:  logging memory contexts of PID 10377
STATEMENT:  SELECT pg_log_backend_memory_contexts(pg_backend_pid());
LOG:  level: 0; TopMemoryContext: 80800 total in 6 blocks; 14432 free (5 chunks); 66368 used
LOG:  level: 1; pgstat TabStatusArray lookup hash table: 8192 total in 1 blocks; 1408 free (0 chunks); 6784 used
LOG:  level: 1; TopTransactionContext: 8192 total in 1 blocks; 7720 free (1 chunks); 472 used
LOG:  level: 1; RowDescriptionContext: 8192 total in 1 blocks; 6880 free (0 chunks); 1312 used
LOG:  level: 1; MessageContext: 16384 total in 2 blocks; 5152 free (0 chunks); 11232 used
LOG:  level: 1; Operator class cache: 8192 total in 1 blocks; 512 free (0 chunks); 7680 used
LOG:  level: 1; smgr relation table: 16384 total in 2 blocks; 4544 free (3 chunks); 11840 used
LOG:  level: 1; TransactionAbortContext: 32768 total in 1 blocks; 32504 free (0 chunks); 264 used
...
LOG:  level: 1; ErrorContext: 8192 total in 1 blocks; 7928 free (3 chunks); 264 used
LOG:  Grand total: 1651920 bytes in 201 blocks; 622360 free (88 chunks); 1029560 used

Если у одного родителя более 100 дочерних контекстов, протоколируются первые 100 из них, а по остальным выдается краткая сводка. Обратите внимание, что частые вызовы этой функции могут спровоцировать значительную перегрузку, поскольку она может создавать в журнале огромное количество сообщений.

Функции для управления резервным копированием

Функции, перечисленные в Таблице 87, помогают создавать резервные копии в режиме реального времени. Эти функции нельзя выполнять во время восстановления (за исключением неэксклюзивного варианта pg_start_backup, неэксклюзивного варианта pg_stop_backup, а также функций pg_is_in_backup, pg_backup_start_time и pg_wal_lsn_diff).

Подробную информацию о надлежащем использовании этих функций см. в разделе Непрерывное архивирование и восстановление на момент времени (PITR).

Таблица 87. Функции для управления резервным копированием

Функция

Описание

pg_create_restore_point ( имя text ) → pg_lsn

Создает в журнале упреждающей записи именованный маркер, который позднее можно использовать как цель при восстановлении, и возвращает соответствующую ему позицию в журнале. Выданное имя затем можно использовать в параметре recovery_target_name, чтобы задать точку, до которой будет выполняться восстановление. Рекомендуется не создавать несколько точек восстановления с одним именем, поскольку восстановление остановится на первой же точке, имя которой соответствует цели восстановления.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_current_wal_flush_lsn () → pg_lsn

Возвращает текущую позицию сброса данных на диск в журнале упреждающей записи (см. примечания ниже).

pg_current_wal_insert_lsn () → pg_lsn

Возвращает текущую позицию добавления в журнале упреждающей записи (см. примечания ниже).

pg_current_wal_lsn () → pg_lsn

Возвращает текущую позицию записи в журнале упреждающей записи (см. примечания ниже).

pg_start_backup ( метка text [, быстро boolean [, эксклюзивный_режим boolean ]] ) → pg_lsn

Подготавливает сервер к началу резервного копирования в режиме реального времени. Единственный требуемый параметр — произвольная пользовательская метка для резервной копии. (Обычно это имя, под которым будет храниться файл дампа.) Если необязательный второй параметр имеет значение true, функция pg_start_backup должна выполняться как можно быстрее. При этом будет принудительно выполнена контрольная точка, что вызовет кратковременный всплеск операций ввода/вывода, замедлив тем самым параллельно выполняемые запросы. Необязательный третий параметр указывает, будет ли резервное копирование выполняться в эксклюзивном (по умолчанию) или неэксклюзивном режиме.
При копировании в эксклюзивном режиме эта функция записывает файл метки (backup_label) и, если есть ссылки в каталоге pg_tblspc/, файл карты табличных пространств (tablespace_map) в каталог данных кластера баз данных, выполняет процесс контрольной точки, а затем возвращает начальную позицию резервной копии в журнале упреждающей записи. (Это результирующее значение можно игнорировать, но в некоторых случаях оно может быть полезным.) При копировании в неэксклюзивном режиме содержимое этих файлов возвращается функцией pg_stop_backup и должно быть скопировано в область резервного копирования самим пользователем.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_stop_backup ( эксклюзивный_режим boolean [, ждать_архивирования boolean ] ) → setof record ( lsn pg_lsn, файл_метки text, файл_карты_табл_пространств text )

Завершает выполнение эксклюзивного или неэксклюзивного копирования в режиме реального времени. Значение параметра эксклюзивный_режим должно соответствовать тому, что было передано в предыдущем вызове функции pg_start_backup. При копировании в эксклюзивном режиме pg_stop_backup удаляет файл метки и файл карты табличных пространств (если он существует), созданные pg_start_backup. При копировании в неэксклюзивном режиме ожидаемое содержимое этих файлов возвращается в виде части результата этой функции и должно быть записано в файлы в области резервного копирования (а не в каталоге данных).
Также имеется необязательный второй параметр типа boolean. Если он равен false, функция вернет результат сразу после окончания резервного копирования, не дожидаясь архивирования WAL. Такое поведение полезно только для программ резервного копирования, независимо мониторящих архивирование WAL. В иных случаях архив WAL, требующийся для согласованности резервной копии, может отсутствовать, делая копию бесполезной. По умолчанию или когда этот параметр равен true, pg_stop_backup будет ждать завершения архивирования WAL, если оно включено. (Для резервного сервера это означает, что функция будет ждать, только при условии archive_mode = always. При низкой активности записи на основном сервере может быть полезно выполнить на нем pg_switch_wal, чтобы запустить немедленное переключение сегмента.)
При выполнении на основном сервере эта функция также создает файл истории резервного копирования в архивной области журнала упреждающей записи. В файле истории содержится метка, заданная при вызове pg_start_backup, начальная и конечная позиция копии в журнале, а также время начала и окончания копирования. После записи конечной позиции текущая позиция добавления в журнале упреждающей записи автоматически перемещается в следующий файл журнала, чтобы файл конечной позиции можно было немедленно заархивировать для завершения резервного копирования.
Результатом этой функции является единственная запись. Столбец lsn содержит позицию завершения резервного копирования в журнале упреждающей записи (которую, опять же, можно игнорировать). Второй и третий столбцы после окончания эксклюзивного копирования содержат NULL, а после неэксклюзивного — ожидаемое содержимое файлов метки и карты табличных пространств.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_stop_backup () → pg_lsn

Завершает выполнение эксклюзивного копирования в режиме реального времени. Эта упрощенная версия равнозначна вызову pg_stop_backup(true, true), за исключением того, что она возвращает в результате только pg_lsn.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_is_in_backup () → boolean

Возвращает true, если в данный момент выполняется эксклюзивное резервное копирование в режиме реального времени.

pg_backup_start_time () → timestamp with time zone

Возвращает время начала текущего эксклюзивного резервного копирования в режиме реального времени, если оно в данный момент выполняется, иначе — NULL.

pg_switch_wal () → pg_lsn

Производит принудительное переключение сервера на новый файл журнала упреждающей записи, что позволяет архивировать текущий файл (в предположении, что вы используете непрерывное архивирование). Результат — конечная позиция в только что законченном журнале упреждающей записи плюс 1. Если с момента последнего переключения файлов не было активности, pg_switch_wal ничего не делает и возвращает начальную позицию в журнале упреждающей записи, используемом в данный момент.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_walfile_name ( lsn pg_lsn ) → text

Преобразует позицию в журнале упреждающей записи в имя файла WAL, содержащего эту позицию.

pg_walfile_name_offset ( lsn pg_lsn ) → record ( имя_файла text, смещение_файла integer )

Преобразует позицию в журнале упреждающей записи в имя файла WAL и байтовое смещение в этом файле.

pg_wal_lsn_diff ( lsn1 pg_lsn, lsn2 pg_lsn ) → numeric

Вычисляет разницу в байтах (lsn1 - lsn2) между двумя позициями в журнале упреждающей записи. Эту разницу можно использовать с pg_stat_replication или с некоторыми из функций, приведенных в Таблице 87, для определения задержки репликации.

Функция pg_current_wal_lsn отображает текущую позицию записи в журнале упреждающей записи в том же формате, что и вышеперечисленные функции. Аналогичным образом функция pg_current_wal_insert_lsn отображает текущую позицию добавления, а pg_current_wal_flush_lsn — позицию сброса данных на диск. Позиция добавления является «логическим» концом журнала упреждающей записи в любой момент времени, тогда как позиция записи является концом данных, фактически выписанных из внутренних буферов сервера, а позиция сброса является последней позицией, которая считается записанной в долговременное хранилище. Позиция записи — это конец данных, которые можно проверить снаружи сервера, и обычно именно она требуется, если есть необходимость в архивировании частично заполненных файлов журнала. Позиции добавления и сброса предоставляются главным образом для отладки сервера. Все эти операции выполняются в режиме «только чтение» и не требуют прав суперпользователя.

Функцию pg_walfile_name_offset можно использовать для извлечения соответствующего имени файла журнала упреждающей записи и байтового смещения из значения pg_lsn. Например:

qhb=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        file_name         | file_offset
--------------------------+-------------
 00000001000000000000000D |     4039624
(1 row)

Аналогичным образом функция pg_walfile_name извлекает только имя файла журнала упреждающей записи. Когда заданная позиция в журнале находится точно на границе файла журнала, обе эти функции возвращают имя предыдущего файла. Обычно это желаемое поведение для управления архивированием журнала упреждающей записи, поскольку предыдущий файл является последним, который в настоящее время необходимо архивировать.

Функции для управления восстановлением

Функции, приведенные в Таблице 88, предоставляют информацию о текущем статусе резервного сервера. Эти функции могут выполняться как во время восстановления, так и в обычном режиме работы.

Таблица 88. Функции для получения информации о восстановлении

Функция

Описание

pg_is_in_recovery () → boolean

Возвращает true, если восстановление еще продолжается.

pg_last_wal_receive_lsn () → pg_lsn

Возвращает последнюю позицию в журнале упреждающей записи, полученную и синхронизированную с диском в процессе потоковой репликации. Пока выполняется потоковая репликация, это значение будет равномерно увеличиваться. Если восстановление было завершено, оно останется на позиции последней записи WAL, полученной и синхронизированной с диском во время восстановления. Если потоковая репликация отключена или еще не начиналась, функция возвращает NULL.

pg_last_wal_replay_lsn () → pg_lsn

Возвращает последнюю позицию в журнале упреждающей записи, воспроизведенную при восстановлении. Если восстановление еще продолжается, это значение будет равномерно увеличиваться. Если восстановление было завершено, оно останется на позиции последней восстановленной записи WAL. Если сервер запустился нормально, без восстановления, функция возвращает NULL.

pg_last_xact_replay_timestamp () → timestamp with time zone

Возвращает метку времени последней транзакции, воспроизведенной при восстановлении. Это время, когда на основном сервере произошла фиксация или откат записи WAL для этой транзакции. Если во время восстановления транзакции не воспроизводились, функция возвращает NULL. В противном случае, если восстановление еще продолжается это значение будет равномерно увеличиваться. Если восстановление было завершено, оно останется на метке времени последней восстановленной транзакции. Если сервер запустился нормально, без восстановления, функция возвращает NULL.

Функции, приведенные в Таблице 89, управляют процессом восстановления. Эти функции могут быть выполнены только во время восстановления.

Таблица 89. Функции для управления восстановлением

Функция

Описание

pg_is_wal_replay_paused () → boolean

Возвращает true, если запрошена приостановка восстановления.

pg_get_wal_replay_pause_state () → text

Возвращает состояние приостановки восстановления. Возвращаемые значения: not paused, если приостановка не запрашивалась, pause requested, если приостановка запрашивалась, но восстановление еще не приостановлено, и paused, если восстановление действительно приостановлено.

pg_promote ( ожидание boolean DEFAULT true, секунды_ожидания integer DEFAULT 60 ) → boolean

Повышает статус резервного сервера до основного. Если параметр ожидание равен true (по умолчанию), функция дожидается завершения повышения либо пока пройдут секунды_ожидания и возвращает true, если повышение завершилось успешно, и false в противном случае. Если параметр ожидание равен false, функция возвращает true сразу после отправки процессу qhbmaster сигнала SIGUSR1, запускающего повышение.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_wal_replay_pause () → void

Запрашивает приостановку восстановления. Запрос не означает, что восстановление сразу же остановится. Если вам нужна гарантия, что восстановление действительно приостановлено, следует проверить состояние приостановки восстановления, возвращаемое функцией pg_get_wal_replay_pause_state(). Обратите внимание, что pg_is_wal_replay_paused() показывает только, был ли сделан запрос. Пока восстановление стоит на паузе, запись дальнейших изменений в базе данных не производится. В режиме горячего резерва все новые запросы будут видеть один согласованный снимок состояния базы данных, и до возобновления восстановления больше никаких конфликтов запросов не произойдет.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_wal_replay_resume () → void

Возобновляет восстановление, если оно было приостановлено.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

Функции pg_wal_replay_pause и pg_wal_replay_resume нельзя выполнять в процессе повышения. Если повышение инициируется, когда восстановление приостановлено, оно снимается с паузы, и процесс повышения продолжается.

Если потоковая репликация отключена, состояние приостановки может без проблем длиться сколь угодно долго. Если же происходит процесс потоковой репликации, то записи WAL продолжат поступать и в конце концов заполнят все доступное дисковое пространство, в зависимости от длительности паузы, интенсивности генерирования записей WAL и объема доступного пространства на диске.

Функции синхронизации снимков

QHB позволяет сеансам базы данных синхронизировать свои снимки состояния. Снимок состояния определяет, какие данные видны для транзакции, использующей этот снимок. Синхронизированные снимки необходимы, когда двум или более сеансам нужно видеть идентичное содержимое базы данных. Если два сеанса просто начинают свои транзакции независимо, всегда существует вероятность того, что какая-то третья транзакция будет зафиксирована между выполнением этих двух команд START TRANSACTION, и поэтому один сеанс будет видеть результаты этой транзакции, а другой — нет.

Чтобы решить эту проблему, QHB позволяет транзакции экспортировать снимок состояния, который она использует. Пока экспортируемая транзакция остается открытой, другие транзакции могут импортировать ее снимок состояния, и тем самым гарантируется, что они видят точно такое же представление базы данных, что и первая транзакция. Но обратите внимание, что любые изменения в базе данных, сделанные любой из этих транзакций, остаются невидимыми для других транзакций, как это обычно происходит с изменениями, сделанными незафиксированными транзакциями. Таким образом, транзакции синхронизируются относительно ранее существующих данных, но относительно изменений, которые вносят они сами, ведут себя как обычно.

Снимки экспортируются с помощью функции pg_export_snapshot, показанной в Таблице 90, и импортируются с помощью команды SET TRANSACTION.

Таблица 90. Функции синхронизации снимков

Функция

Описание

pg_export_snapshot () → text

Сохраняет текущий снимок состояния транзакции и возвращает строку типа text, идентифицирующую этот снимок. Эта строка должна передаваться (вне базы данных) клиентам, желающим импортировать этот снимок. Снимок доступен для импортирования, только пока не завершится экспортирующая его транзакция.
При необходимости транзакция может экспортировать более одного снимка. Обратите внимание, что это может быть полезно только для транзакций уровня READ COMMITTED, поскольку транзакции уровня изоляции REPEATABLE READ и выше используют один снимок на протяжении всего своего существования. После того, как транзакция экспортировала какие-либо снимки, ее нельзя сделать подготовленной с помощью команды PREPARE TRANSACTION.

Функции для управления репликацией

Функции, приведенные в Таблице 91, предназначены для управления средствами репликации и взаимодействия с ними. Информацию об этих средствах см. в подразделах Потоковая репликация и Слоты репликации и в главе Отслеживание прогресса репликации. Использование функций, работающих с источниками репликации, по умолчанию разрешено только суперпользователям, но можно разрешить это и другим пользователям с помощью команды GRANT. Использование функций, работающих со слотами репликации, разрешено только суперпользователям и пользователям с правом REPLICATION.

Многим из этих функций равнозначны команды в протоколе репликации; см. раздел Протокол потоковой репликации.

Функции, описанные в подразделах Функции для управления резервным копированием, Функции для управления восстановлением и Функции синхронизации снимков, также имеют отношение к репликации.

Таблица 91. Функции для управления репликацией

Функция

Описание

pg_create_physical_replication_slot ( имя_слота name [, мгновенный_резерв boolean, временный boolean ] ) → record ( имя_слота name, lsn pg_lsn )

Создает новый физический слот репликации с именем имя_слота. Когда необязательный второй параметр равен true, это указывает, что LSN для этого слота репликации должен быть зарезервирован немедленно; в противном случае LSN резервируется при первом подключении клиента потоковой репликации. Передача изменений из физического слота возможна только по протоколу потоковой репликации. Когда необязательный третий параметр временный равен true, это указывает, что слот не должен постоянно храниться на диске и предназначен к использованию только в текущем сеансе. Временные слоты также освобождаются при любой ошибке. Эта функция соответствует команде протокола репликации CREATE_REPLICATION_SLOT ... PHYSICAL.

pg_drop_replication_slot ( имя_слота name ) → void

Удаляет физический или логический слот с именем имя_слота. Аналогична команде протокола репликации DROP_REPLICATION_SLOT. Для логических слотов эта функция должна вызываться при подключении к той же базе данных, в которой был создан слот.

pg_create_logical_replication_slot ( имя_слота name, плагин name [, временный boolean, две_фазы boolean ] ) → record ( имя_слота name, lsn pg_lsn )

Создает новый логический (декодирующий) слот репликации с именем имя_слота, используя плагин вывода плагин. Когда необязательный третий параметр временный равен true, это указывает, что слот не должен постоянно храниться на диске и предназначен к использованию только в текущем сеансе. Временные слоты также освобождаются при любой ошибке. Когда необязательный четвертый параметр две_фазы равен true, это указывает, что для этого слота включено декодирование подготовленных транзакций. Вызов этой функции равнозначен выполнению команды протокола репликации CREATE_REPLICATION_SLOT ... LOGICAL.

pg_copy_physical_replication_slot ( имя_исходного_слота name, имя_целевого_слота name [, временный boolean ] ) → record ( имя_слота name, lsn pg_lsn )

Копирует существующий физический слот репликации с именем имя_исходного_слота в физический слот репликации с именем имя_целевого_слота. Скопированный физический слот начинает резервировать WAL с того же LSN, что и исходный слот. Параметр временный является необязательным. Если этот параметр опущен, используется та же продолжительность существования, какую имеет исходный слот.

pg_copy_logical_replication_slot ( имя_исходного_слота name, имя_целевого_слота name [, временный boolean [, плагин name ]] ) → record ( имя_слота name, lsn pg_lsn )

Копирует существующий логический слот репликации с именем имя_исходного_слота в логический слот репликации с именем имя_целевого_слота, с возможностью смены плагина вывода и продолжительности существования. Скопированный логический слот начинает работу с того же LSN, что и исходный слот. Параметры временный и плагин являются необязательными; если они опущены, используются те же параметры, какие имеет исходный слот.

pg_logical_slot_get_changes ( имя_слота name, до_lsn pg_lsn, до_n_изменений integer, VARIADIC параметры text[] ) → setof record ( lsn pg_lsn, id_транзакции xid, данные text )

Возвращает изменения в слоте имя_слота, начиная с последней точки, с которой ранее были получены изменения. Если параметры до_lsn и до_n_изменений равны NULL, логическое декодирование будет продолжаться до конца WAL. Если до_lsn отличен от NULL, декодирование затронет только транзакции, зафиксированные до заданного LSN. Если до_n_изменений отличен от NULL, декодирование остановится, когда число строк, выданных при декодировании, превысит заданное значение. Однако обратите внимание, что фактическое число возвращенных строк может быть больше, поскольку это ограничение проверяется только после добавления строк, выданных при декодировании очередной зафиксированной транзакции.

pg_logical_slot_peek_changes ( имя_слота name, до_lsn pg_lsn, до_n_изменений integer, VARIADIC параметры text[] ) → setof record ( lsn pg_lsn, id_транзакции xid, данные text )

Ведет себя так же, как функция pg_logical_slot_get_changes(), за исключением того, что изменения не забираются; то есть они будут возвращены снова при следующих вызовах.

pg_logical_slot_get_binary_changes ( имя_слота name, до_lsn pg_lsn, до_n_изменений integer, VARIADIC параметры text[] ) → setof record ( lsn pg_lsn, id_транзакции xid, данные bytea )

Ведет себя так же, как функция pg_logical_slot_get_changes(), за исключением того, что изменения возвращаются в типе bytea.

pg_logical_slot_peek_binary_changes ( имя_слота name, до_lsn pg_lsn, до_n_изменений integer, VARIADIC параметры text[] ) → setof record ( lsn pg_lsn, id_транзакции xid, данные bytea )

Ведет себя так же, как функция pg_logical_slot_peek_changes(), за исключением того, что изменения возвращаются в типе bytea.

pg_replication_slot_advance ( имя_слота name, до_lsn pg_lsn ) → record ( имя_слота name, итоговый_lsn pg_lsn )

Продвигает текущую подтвержденную позицию слота репликации с именем имя_слота. Этот слот нельзя будет передвинуть назад, а также за текущую позицию добавления. Возвращает имя слота и фактическую позицию, до которой он был продвинут. Информация об изменившейся позиции слота, если он был продвинут, записывается при следующей контрольной точке. Поэтому в случае сбоя этот слот может вернуться в предыдущее положение.

pg_replication_origin_create ( имя_узла text ) → oid

Создает источник репликации с заданным внешним именем и возвращает назначенный ему внутренний идентификатор.

pg_replication_origin_drop ( имя_узла text ) → void

Удаляет ранее созданный источник репликации, включая всю связанную информацию о ходе выполнения воспроизведения.

pg_replication_origin_oid ( имя_узла text ) → oid

Ищет источник репликации по имени и возвращает внутренний идентификатор. Если такой источник не находится, возвращается NULL.

pg_replication_origin_session_setup ( имя_узла text ) → void

Помечает текущий сеанс как воспроизводящий изменения из заданного источника, позволяя отслеживать ход выполнения воспроизведения. Может применяться, только если в настоящий момент источник не выбран. Для отмены этого действия воспользуйтесь функцией pg_replication_origin_session_reset.

pg_replication_origin_session_reset () → void

Отменяет действие функции pg_replication_origin_session_setup().

pg_replication_origin_session_is_setup () → boolean

Возвращает true, если в текущем сеансе был выбран источник репликации.

pg_replication_origin_session_progress ( сброс_на_диск boolean ) → pg_lsn

Возвращает позицию воспроизведения для источника репликации, выбранного в текущем сеансе. Параметр сброс_на_диск определяет, будет ли гарантироваться сохранение локальной транзакции на диске.

pg_replication_origin_xact_setup ( исходный_lsn pg_lsn, исх_метка_времени timestamp with time zone ) → void

Помечает текущую транзакцию как воспроизводящую транзакцию, зафиксированную с заданными LSN и меткой времени. Может вызываться только после выбора источника репликации с помощью функции pg_replication_origin_session_setup.

pg_replication_origin_xact_reset () → void

Отменяет действие функции pg_replication_origin_xact_setup().

pg_replication_origin_advance ( имя_узла text, lsn pg_lsn ) → void

Устанавливает текущее состояние репликации для указанного узла в заданную позицию. Это в основном полезно для установки начальной позиции или новой позиции после изменений конфигурации и тому подобного. Имейте в виду, что неосторожное использование этой функции может привести к несогласованности реплицированных данных.

pg_replication_origin_progress ( имя_узла text, сброс_на_диск boolean ) → pg_lsn

Возвращает позицию воспроизведения для заданного источника репликации. Параметр сброс_на_диск определяет, будет ли гарантироваться сохранение соответствующей локальной транзакции на диске.

pg_logical_emit_message ( часть_транзакции boolean, префикс text, содержимое text ) → pg_lsn
pg_logical_emit_message ( часть_транзакции boolean, префикс text, содержимое bytea ) → pg_lsn

Генерирует сообщение логического декодирования. Эта функция может быть полезна для передачи универсальных сообщений плагинам логического декодирования через WAL. Параметр часть_транзакции определяет, должно ли сообщение быть частью текущей транзакции или оно должно записываться немедленно и декодироваться сразу после прочтения этой записи при логическом декодировании. Параметр префикс представляет собой текстовый префикс, по которому плагины логического декодирования могут с легкостью распознать интересующие их сообщения. Параметр содержимое представляет собой содержимое сообщения, заданное в текстовой или двоичной форме.

Функции для управления объектами базы данных

Функции, приведенные в Таблице 92, вычисляют объем дискового пространства, занимаемый объектами базы данных, или помогают представить или понять полученные результаты. Результаты типа bigint измеряются в байтах. Если любой из этих функций передается OID, не представляющий существующий объект, возвращается NULL.

Таблица 92. Функции для получения размеров объектов базы данных

Функция

Описание

pg_column_size ( "any" ) → integer

Показывает количество байтов, занимаемых при хранении отдельным значением данных. Если эта функция применяется непосредственно к значению столбца таблицы, результат отражает эффект произведенного сжатия.

pg_column_compression ( "any" ) → text

Показывает алгоритм сжатия, который использовался при сжатии отдельного значения переменной длины. Если значение не сжато, возвращает NULL.

pg_database_size ( name ) → bigint
pg_database_size ( oid ) → bigint

Вычисляет общий объем дискового пространства, занимаемый базой данных с указанным именем или OID. Чтобы воспользоваться этой функцией, нужно иметь право CONNECT для указанной базы данных (оно дается по умолчанию) или быть членом роли pg_read_all_stats.

pg_indexes_size ( regclass ) → bigint

Вычисляет общий объем дискового пространства, занимаемый индексами, связанными с указанной таблицей.

pg_relation_size ( отношение regclass [, ветвь text ] ) → bigint

Вычисляет объем дискового пространства, занимаемый одной «ветвью» указанного отношения. (Обратите внимание, что в большинстве случаев удобнее использовать более высокоуровневые функции pg_total_relation_size или pg_table_size, суммирующие размер всех ветвей.) С одним аргументом она выдает размер основной ветви данных отношения. Во втором аргументе можно передать название ветви, которую нужно проверить:

main возвращает размер основной ветви данных этого отношения.

fsm возвращает размер карты свободного пространства (см. раздел Карта свободного пространства), связанной с этим отношением.

vm возвращает размер карты видимости (см. раздел Карта видимости), связанной с этим отношением.

init возвращает размер ветви инициализации (если таковая имеется), связанной с этим отношением.

pg_size_bytes ( text ) → bigint

Преобразует размер в формате, удобном для восприятия человеком (который возвращает функция pg_size_pretty), в байты.

pg_size_pretty ( bigint ) → text
pg_size_pretty ( numeric ) → text

Преобразует размер в байтах в более удобный для восприятия человеком формат с единицами измерения размера (bytes, kB, MB, GB или TB соответственно). Обратите внимание, что эти единицы являются степенями 2, а не 10, поэтому 1kB — это 1024 байта, 1MB — это 1024² = 1048576 байта и т. д.

pg_table_size ( regclass ) → bigint

Вычисляет объем дискового пространства, занимаемый указанной таблицей, за исключением индексов (но включая ее таблицу TOAST (если таковая имеется), карту свободного пространства и карту видимости).

pg_tablespace_size ( name ) → bigint
pg_tablespace_size ( oid ) → bigint

Вычисляет общий объем дискового пространства, занимаемый табличным пространством с указанным именем или OID. Чтобы воспользоваться этой функцией, нужно иметь право CREATE для указанного табличного пространства или быть членом роли pg_read_all_stats, если только это не табличное пространство по умолчанию для текущей базы данных.

pg_total_relation_size ( regclass ) → bigint

Вычисляет общий объем дискового пространства, занимаемый указанной таблицей, включая все индексы и данные TOAST. Результат равен сумме результатов функций pg_table_size и pg_indexes_size.

Перечисленные выше функции, работающие с таблицами или индексами, принимают аргумент типа regclass, являющийся просто OID таблицы или индекса в системном каталоге pg_class. Однако нет нужды искать этот OID вручную, поскольку преобразователь ввода типа regclass сделает эту работу за вас. Подробную информацию см. в разделе Типы идентификаторов объектов.

Функции, приведенные в Таблице 93, помогают определить конкретные дисковые файлы, связанные с объектами базы данных.

Таблица 93. Функции для определения местоположения объектов базы данных

Функция

Описание

pg_relation_filenode ( отношение regclass ) → oid

Возвращает номер «файлового узла», связанного с указанным отношением. Файловый узел является основным компонентом имени файла, используемого для отношения (подробную информацию см. в разделе Структура файлов базы данных). Для большинства отношений результат совпадает с результатом функции pg_class.relfilenode, на для некоторых системных каталогов relfilenode равен нулю, и нужно использовать данную функцию, чтобы получить верное значение. Если передать этой функции отношение, не имеющее хранилища, например представление, она возвращает NULL.

pg_relation_filepath ( отношение regclass ) → text

Возвращает имя полного пути к файлу отношения (относительно каталога данных кластера баз данных — PGDATA).

pg_filenode_relation ( табличное_пространство oid, файловый_узел oid ) → regclass

Возвращает OID отношения по заданному OID табличного пространства и файловому узлу, в котором оно хранится. По сути эта функция является обратной к pg_relation_filepath. Для табличного пространства по умолчанию OID можно указать равным нулю. Если с заданными значениями в текущей базе данных не связано ни одно отношение, возвращает NULL.

В Таблице 94 перечислены функции, используемые для управления правилами сортировки.

Таблица 94. Функции для управления правилами сортировки

Функция

Описание

pg_collation_actual_version ( oid ) → text

Возвращает действительную версию объекта правила сортировки, которая в данный момент установлена в операционной системе. Если результат отличается от значения в pg_collation.collversion, может потребоваться перестройка объектов, зависящих от этого правила сортировки. См. также ALTER COLLATION.

pg_import_system_collations ( схема regnamespace ) → integer

Добавляет правила сортировки в системный каталог pg_collation с учетом всех локалей, которые она находит в операционной системе. Эту информацию использует qhb_bootstrap; подробную информацию см. в подразделе Управление правилами сортировки. Если позднее в операционную систему устанавливаются дополнительные локали, эту функцию можно запустить снова, чтобы добавить правила сортировки для новых локалей. Локали, соответствующие существующим записям в pg_collation, будут пропущены. (Но объекты правил сортировки, относящиеся к локалям, которых больше не существует в операционной системе, этой функцией не удаляются.) В параметре схема обычно передается pg_catalog, но это не обязательное требование; правила сортировки могут быть установлены и в какую-нибудь другую схему. Эта функция возвращает число созданных ей новых объектов правил сортировки. Вызывать эту функцию могут только суперпользователи.

В Таблице 95 перечислены функции, предоставляющие информацию о структуре партиционированных таблиц.

Таблица 95. Функции для получения информации о партиционировании

Функция

Описание

pg_partition_tree ( regclass ) → setof record ( id_отношения regclass, id_родителя regclass, является_листовой boolean, уровень integer )

Перечисляет таблицы или индексы в дереве партиционирования заданной партиционированной таблицы или партиционированного индекса, по одной строке на каждую партицию. Предоставляемая информация включает OID партиции, OID ее непосредственного родителя, логическое значение, указывающее, является ли партиция концевой вершиной структуры, и целое число, отражающее ее уровень в иерархии. Значение уровня 0 означает вводимую таблицу или индекс, 1 — непосредственные дочерние партиции, 2 — партиции этих партиций и т. д. Если заданное отношение не существует или не является партицией или партиционированной таблицей, возвращает пустой результат.

pg_partition_ancestors ( regclass ) → setof regclass

Перечисляет вышестоящие отношения для заданной партиции, включая ее саму. Если заданное отношение не существует или не является партицией или партиционированной таблицей, возвращает пустой результат.

pg_partition_root ( regclass ) → regclass

Возвращает самое верхнее родительское отношение в дереве партиционирования, к которому принадлежит заданное отношение. Если заданное отношение не существует или не является партицией или партиционированной таблицей, возвращает NULL.

Например, чтобы проверить общий размер данных, содержащихся в таблице measurement, можно использовать следующий запрос:

SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
  FROM pg_partition_tree('measurement');

Функции для обслуживания индексов

В Таблице 96 приведены имеющиеся функции для выполнения задач по обслуживанию индексов. (Обратите внимание, что эти задачи обычно выполняются автоматически в процессе автоочистки; использование этих функций требуется только в особых случаях.) Эти функции нельзя выполнять во время восстановления. Вызывать эти функции могут только суперпользователи и владелец заданного индекса.

Таблица 96. Функции для обслуживания индексов

Функция

Описание

brin_summarize_new_values ( индекс regclass ) → integer

Сканирует указанный индекс BRIN в поисках диапазонов страниц в базовой таблице, для которых к настоящему моменту в индексе не собрана сводная информация; для каждого такого диапазона она сканирует эти страницы таблицы, тем самым создавая новый индексный кортеж сводки. Возвращает число новых сводок по диапазонам страниц, добавленных в индекс.

brin_summarize_range ( индекс regclass, номер_блока bigint ) → integer

Рассчитывает сводку по диапазону страниц, охватывающую заданный блок, если эта сводка еще не была сделана. Эта функция похожа на brin_summarize_new_values, за исключением того, что она обрабатывает только тот диапазон страниц, который охватывает табличный блок с заданным номером.

brin_desummarize_range ( индекс regclass, номер_блока bigint ) → void

Удаляет из индекса BRIN кортеж, содержащий сводку по диапазону страниц, охватывающему указанный табличный блок, если такой кортеж существует.

gin_clean_pending_list ( индекс regclass ) → bigint

Очищает список ожидания указанного индекса GIN путем массового перемещения записей из него в основную структуру данных GIN. Возвращает число страниц, убранных из списка ожидания. Если аргументом является индекс GIN, построенный с выключенным параметром fastupdate, очистка не производится и результат равен нулю, поскольку у этого индекса нет списка ожидания. Подробную информацию о списке ожидания и параметре fastupdate см. в подразделе Методика быстрого обновления GIN и разделе Советы и рекомендации по применению GIN.

Функции доступа к типовым файлам

Функции, приведенные в Таблице 97, предоставляют прямой доступ к файлам на компьютере, на котором размещен сервер. За исключением случаев, когда пользователь включен в роль pg_read_server_files, обращение возможно только к файлам в каталоге кластера баз данных и в каталоге log_directory. Для файлов в каталоге кластера следует указывать относительный путь, а для файлов журнала — путь, соответствующий значению параметра конфигурации log_directory.

Обратите внимание, что предоставление пользователям права EXECUTE для pg_read_file () или связанных функций дает им возможность читать любой файл на сервере, который может прочитать серверный процесс базы данных; эти функции обходят все проверки прав внутри базы данных. Это означает, к примеру, что пользователь с таким доступом может читать содержимое таблицы pg_authid, в которой содержится информация по аутентификации, а также читать любые табличные данные в базе. Поэтому предоставление доступа к этим функциям должно быть тщательно продумано.

Некоторые из этих функций принимают необязательный параметр может_отсутствовать, который определяет их поведение в случае отсутствия файла или каталога. Если он равен true, функция возвращает NULL или пустой результат, в зависимости от конкретного случая. Если он равен false, возникает ошибка. Значение по умолчанию — false.

Таблица 97. Функции доступа к типовым файлам

Функция

Описание

pg_ls_dir ( имя_каталога text [, может_отсутствовать boolean, включать_каталоги_с_точками boolean ] ) → setof text

Возвращает имена всех файлов (а также каталогов и других специальных файлов) в указанном каталоге. Параметр включать_каталоги_с_точками показывает, будут ли в результирующий набор включаться каталоги «.» и «..»; по умолчанию они исключаются. Включить их имеет смысл, когда параметр может_отсутствовать равен true, чтобы пустой каталог можно было отличить от несуществующего.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_ls_logdir () → setof record ( имя text, размер bigint, модификация timestamp with time zone )

Возвращает имя, размер и время последнего изменения (mtime) всех обычных файлов в каталоге журналов сервера. Файлы с именами, начинающимися с точки, каталоги и другие специальные файлы исключаются.
По умолчанию эту функцию могут вызывать только суперпользователи и члены роли pg_monitor, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_ls_waldir () → setof record ( имя text, размер bigint, модификация timestamp with time zone )

Возвращает имя, размер и время последнего изменения (mtime) всех обычных файлов в каталоге журнала упреждающей записи (WAL) сервера. Файлы с именами, начинающимися с точки, каталоги и другие специальные файлы исключаются.
По умолчанию эту функцию могут вызывать только суперпользователи и члены роли pg_monitor, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_ls_archive_statusdir () → setof record ( имя text, размер bigint, модификация timestamp with time zone )

Возвращает имя, размер и время последнего изменения (mtime) всех обычных файлов в каталоге состояния архива WAL (pg_wal/archive_status) сервера. Файлы с именами, начинающимися с точки, каталоги и другие специальные файлы исключаются.
По умолчанию эту функцию могут вызывать только суперпользователи и члены роли pg_monitor, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_ls_tmpdir ( [ табличное_пространство oid ] ) → setof record ( имя text, размер bigint, модификация timestamp with time zone )

Возвращает имя, размер и время последнего изменения (mtime) всех обычных файлов во временном каталоге файлов для табличного_пространства. Если табличное_пространство не задано, проверяется табличное пространство pg_default. Файлы с именами, начинающимися с точки, каталоги и другие специальные файлы исключаются.
По умолчанию эту функцию могут вызывать только суперпользователи и члены роли pg_monitor, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_read_file ( имя_файла text [, смещение bigint, длина bigint [, может_отсутствовать boolean ]] ) → text

Возвращает текстовый файл, целиком или частично, начиная с байтового смещения и возвращая не более длины байт (длина может быть меньше, если файл кончится раньше). Если смещение отрицательное, смещение отсчитывается от конца файла. Если смещение и длина опущены, возвращается весь файл. Прочитанные из файла байты воспринимаются как строка в кодировке базы данных; если они недопустимы в этой кодировки, возникает ошибка.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

pg_read_binary_file ( имя_файла text [, смещение bigint, длина bigint [, может_отсутствовать boolean ]] ) → bytea

Возвращает текстовый файл, целиком или частично. Эта функция идентична pg_read_file, за исключением того, что она может читать произвольные двоичные данные, возвращая результат в значении типа bytea, а не text; соответственно, никакие проверки кодировки не осуществляются.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.
В сочетании с функцией convert_from эту функцию можно использовать для чтения текстового файла в заданной кодировке и преобразования в кодировку базы данных: SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');

pg_stat_file ( имя_файла text [, может_отсутствовать boolean ] ) → record ( размер bigint, обращение timestamp with time zone, модификация timestamp with time zone, изменение timestamp with time zone, является_каталогом boolean )

Возвращает запись, содержащую размер файла, время последнего обращения и последнего изменения, а также время последнего изменения состояния (только на платформах Unix), и флаг, показывающий, что это каталог.
По умолчанию эту функцию могут вызывать только суперпользователи, но право EXECUTE на ее выполнение можно дать и другим пользователям.

Функции рекомендательных блокировок

Функции, приведенные в Таблице 98, управляют рекомендательными блокировками. Подробную информацию о правильном использовании этих функций см. в подразделе Рекомендательные блокировки.

Все эти функции предназначены для работы с блокировками ресурсов, определяемых приложением, которые могут задаваться либо одним 64-битным, либо двумя 32-битными значениями ключей (обратите внимание, что пространства этих двух ключей не пересекаются). Если другой сеанс уже удерживает конфликтующую блокировку с тем же идентификатором ресурса, эти функции либо дожидаются, когда этот ресурс снова станет доступен, либо возвращает результат false, в зависимости от конкретной функции. Блокировки могут быть как совместными, так и эксклюзивными: совместная блокировка не конфликтует с другими совместными блокировками того же ресурса, только с эксклюзивными. Блокировки могут устанавливаться на уровне сеанса (таким образом они удерживаются до освобождения или до завершения сеанса) или на уровне транзакции (таким образом они удерживаются до завершения текущей транзакции; освободить их вручную возможности нет). Когда на блокировку уровня сеанса поступает несколько запросов, они накапливаются, поэтому если один идентификатор ресурса был заблокирован три раза, до завершения сеанса должно поступить три запроса на освобождение ресурса.

Таблица 98. Функции рекомендательных блокировок

Функция

Описание

pg_advisory_lock ( ключ bigint ) → void
pg_advisory_lock ( ключ1 integer, ключ2 integer ) → void

Получает эксклюзивную рекомендательную блокировку уровня сеанса, при необходимости ожидая ее.

pg_advisory_lock_shared ( ключ bigint ) → void
pg_advisory_lock_shared ( ключ1 integer, ключ2 integer ) → void

Получает совместную рекомендательную блокировку уровня сеанса, при необходимости ожидая ее.

pg_advisory_unlock ( ключ bigint ) → boolean
pg_advisory_unlock ( ключ1 integer, ключ2 integer ) → boolean

Освобождает полученную ранее эксклюзивную рекомендательную блокировку уровня сеанса. Если блокировка успешно освобождена, возвращает true. Если сеанс не удерживал эту блокировку, возвращается false, а кроме того сервер выдаст предупреждение SQL.

pg_advisory_unlock_all () → void

Освобождает все рекомендательные блокировки уровня сеанса, удерживаемые текущим сеансом. (Эта функция неявно вызывается при завершении сеанса, даже если внезапно прервалось соединение с клиентом.)

pg_advisory_unlock_shared ( ключ bigint ) → boolean
pg_advisory_unlock_shared ( ключ1 integer, ключ2 integer ) → boolean

Освобождает полученную ранее совместную рекомендательную блокировку уровня сеанса. Если блокировка успешно освобождена, возвращает true. Если сеанс не удерживал эту блокировку, возвращается false, а кроме того сервер выдаст предупреждение SQL.

pg_advisory_xact_lock ( ключ bigint ) → void
pg_advisory_xact_lock ( ключ1 integer, ключ2 integer ) → void

Получает эксклюзивную рекомендательную блокировку уровня транзакции, при необходимости ожидая ее.

pg_advisory_xact_lock_shared ( ключ bigint ) → void
pg_advisory_xact_lock_shared ( ключ1 integer, ключ2 integer ) → void

Получает совместную рекомендательную блокировку уровня транзакции, при необходимости ожидая ее.

pg_try_advisory_lock ( ключ bigint ) → boolean
pg_try_advisory_lock ( ключ1 integer, ключ2 integer ) → boolean

Получает эксклюзивную рекомендательную блокировку уровня сеанса, если она доступна. Эта функция либо немедленно получает блокировку и возвращает true, либо сразу возвращает false, если блокировку нельзя получить немедленно.

pg_try_advisory_lock_shared ( ключ bigint ) → boolean
pg_try_advisory_lock_shared ( ключ1 integer, ключ2 integer ) → boolean

Получает совместную рекомендательную блокировку уровня сеанса, если она доступна. Эта функция либо немедленно получает блокировку и возвращает true, либо сразу возвращает false, если блокировку нельзя получить немедленно.

pg_try_advisor_xact_lock ( ключ bigint ) → boolean
pg_try_advisory_xact_lock ( ключ1 integer, ключ2 integer ) → boolean

Получает эксклюзивную рекомендательную блокировку уровня транзакции, если она доступна. Эта функция либо немедленно получает блокировку и возвращает true, либо сразу возвращает false, если блокировку нельзя получить немедленно.

pg_try_advisory_xact_lock_shared ( ключ bigint ) → boolean
pg_try_advisory_xact_lock_shared ( ключ1 integer, ключ2 integer ) → boolean

Получает совместную рекомендательную блокировку уровня транзакции, если она доступна. Эта функция либо немедленно получает блокировку и возвращает true, либо сразу возвращает false, если блокировку нельзя получить немедленно.

Функции для управления метриками и аннотациями

В Таблице 99 приведены имеющиеся функции, предназначенные для работы с метриками и аннотациями.

Таблица 99. Функции для управления метриками и аннотациями

Функция

Описание

qhb_timer_report имя text значение int8

Отправляет метрику с заданным именем и заданным в наносекундах значением таймера.

qhb_counter_increase_by имя text значение int8

Увеличивает значение «счетчика» для метрики с указанным именем. Во втором аргументе передается число наносекунд, на которое увеличивается это значение.

qhb_gauge_add имя text значение int8

Увеличивает значение типа gauge для метрики с указанным именем. Во втором аргументе передается число наносекунд, на которое увеличивается это значение.

qhb_gauge_sub имя text значение int8

Уменьшает значение типа gauge для метрики с указанным именем. Во втором аргументе передается число наносекунд, на которое уменьшается это значение.

qhb_gauge_update имя text значение int8

Устанавливает значение типа gauge для метрики с указанным именем.

qhb_annotation текст text [тег ...]

Отправляет аннотации в Grafana. В аргументе текст передается текст этой аннотации. В необязательном аргументе тег указываются теги для аннотации; можно указать произвольное число тегов.

Триггерные функции

Хотя в большинстве случаев использование триггеров предусматривает триггерные функции, написанные пользователем, QHB предоставляет несколько встроенных триггерных функций, которые можно использовать непосредственно в пользовательских триггерах. Эти функции перечислены в Таблице 100. (Существуют и другие встроенные триггерные функции, реализующие ограничения внешнего ключа и отложенные индексные ограничения. Они здесь не приводятся, поскольку пользователям не нужно применять их напрямую.)

Подробную информацию о создании триггеров см. на справочной странице команды CREATE TRIGGER.

Таблица 100. Встроенные триггерные функции

Функция

Описание
Пример использования

suppress_redundant_updates_trigger ( ) → trigger

Подавляет фиктивные операции изменения. Подробную информацию см. ниже.
CREATE TRIGGER ... suppress_redundant_updates_trigger()

tsvector_update_trigger ( ) → trigger

Автоматически обновляет столбец tsvector из связанных столбцов с обычными текстовыми данными. Используемая конфигурация текстового поиска задается по имени в аргументе триггера. Подробную информацию см. в подразделе Триггеры для автоматических обновлений.
CREATE TRIGGER ... tsvector_update_trigger(tsvcol, 'pg_catalog.swedish', title, body)

tsvector_update_trigger_column ( ) → trigger

Автоматически обновляет столбец tsvector из связанных столбцов с обычными текстовыми данными. Используемая конфигурация текстового поиска берется из столбца regconfig целевой таблицы. Подробную информацию см. в подразделе Триггеры для автоматических обновлений.
CREATE TRIGGER ... tsvector_update_trigger_column(tsvcol, tsconfigcol, title, body)

Функция suppress_redundant_updates_trigger, применяемая в качестве триггера BEFORE UPDATE на уровне строк, предотвратит внесение любых изменений, при которых данные в строке фактически не меняются. Это переопределяет обычное поведение, когда изменение физической сроки происходит всегда, независимо от того, были ли изменены данные. (Обычное поведение ускоряет выполнение изменений, поскольку при этом не требуется сравнение данных, и в некоторых случаях это может быть полезно.)

В идеале следует избегать выполнения изменений, которые фактически не меняют данные в записи. На лишние изменения может уйти довольно много времени, особенно если требуется обновить множество индексов, а также пространства в неиспользуемых строках, от которых в итоге придется чистить базу данных. Однако выявить подобные ситуации в коде клиента не всегда просто или даже возможно, а при написании выражений для их обнаружения легко допустить ошибку. В качестве альтернативы можно использовать функцию suppress_redundant_updates_trigger, которая будет пропускать изменения, не меняющие данные. Однако пользоваться ей следует с осторожностью. Выполнение этого триггера для каждой записи происходит довольно быстро, но не мгновенно, поэтому если большинство затронутых изменениями записей действительно меняется, использование этого триггера в среднем замедлит операции изменения.

Функцию suppress_redundant_updates_trigger можно добавить к таблице следующим образом:

CREATE TRIGGER z_min_update
BEFORE UPDATE ON tablename
FOR EACH ROW EXECUTE FUNCTION suppress_redundant_updates_trigger();

В большинстве случаев этот триггер необходимо запускать для каждой строки последним, чтобы он не переопределил другие триггеры, которым может потребоваться изменить строку. Учитывая то, что триггеры запускаются в порядке сортировки имен, имя этого триггера следует выбрать так, чтобы оно шло после имен всех остальных триггеров, которые могут быть в таблице. (Поэтому в примере используется префикс «z».)

Функции триггеров событий

QHB предоставляет данные вспомогательные функции для извлечения информации из триггеров событий.

Дополнительную информацию о триггерах событий см. в главе Триггеры событий.

Сбор изменений в конце команды

pg_event_trigger_ddl_commands () → setof record

Функция pg_event_trigger_ddl_commands возвращает список команд DDL, выполняемых при каждым действии пользователя, при вызове в функции, связанной с триггером события ddl_command_end. Если она вызывается в любом другом контексте, возникает ошибка. Функция pg_event_trigger_ddl_commands возвращает по одной строке для каждой выполненной базовой команды; для некоторых команд, представляющих собой одно предложение SQL, может возвращаться более одной строки. Эта функция возвращает следующие столбцы:

Имя | Тип | Описание
classid | oid | OID каталога, к которому относится объект
objid | oid | OID самого объекта
objsubid | integer | Идентификатор подобъекта (например номер атрибута для столбца)
command_tag | text | Тег команды
object_type | text | Тип объекта
schema_name | text | Имя схемы, к которой относится объект, если таковая есть; в противном случае NULL. Заключение в кавычки не применяется.
object_identity | text | Текстовое отображение идентификатора объекта, дополненное схемой. Каждый входящий в отображение идентификатор при необходимости заключается в кавычки.
in_extension | boolean | True, если команда является частью скрипта расширения
command | pg_ddl_command | Полное представление команды, во внутреннем формате. Оно не может выводиться напрямую, но может быть передано другим функциям для получения различной информации о команде.

Обработка объектов, удаленных командой DDL

pg_event_trigger_dropped_objects () → setof record

Функция pg_event_trigger_dropped_objects возвращает список всех объектов, удаленных командой, для которой вызывается событие sql_drop. Если она вызывается в любом другом контексте, возникает ошибка. Функция pg_event_trigger_dropped_objects возвращает следующие столбцы:

Имя | Тип | Описание
classid | oid | OID каталога, к которому относился объект
objid | oid | OID самого объекта
objsubid | integer | Идентификатор подобъекта (например номер атрибута для столбца)
original | boolean | True, если это был один из корневых объектов удаления
normal | boolean | True, если в графе зависимостей, ведущей к этому объекту, было нормальное отношение зависимости
is_temporary | boolean | True, если это был временный объект
object_type | text | Тип объекта
schema_name | text | Имя схемы, к которой относился объект, если таковая есть; в противном случае NULL. Заключение в кавычки не применяется.
object_name | text | Имя объекта, если комбинация схемы и имени может использоваться в качестве уникального идентификатора для объекта; в противном случае NULL. Кавычки не применяются, а имя никогда не дополняется схемой.
object_identity | text | Текстовое отображение идентификатора объекта, дополненное схемой. Каждый входящий в отображение идентификатор при необходимости заключается в кавычки.
address_names | text[] | Массив, который вместе с object_type и address_args может использоваться функцией pg_get_object_address() для воссоздания адреса объекта на удаленном сервере, содержащем одноименный объект того же вида
address_args | text[] | Дополнение для address_names

Функцию pg_event_trigger_dropped_objects можно использовать в триггере событий следующим образом:

CREATE FUNCTION test_event_trigger_for_drops()
        RETURNS event_trigger LANGUAGE plpgsql AS $$
DECLARE
    obj record;
BEGIN
    FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
    LOOP
        RAISE NOTICE '% dropped object: % %.% %',
                     tg_tag,
                     obj.object_type,
                     obj.schema_name,
                     obj.object_name,
                     obj.object_identity;
    END LOOP;
END
$$;
CREATE EVENT TRIGGER test_event_trigger_for_drops
   ON sql_drop
   EXECUTE FUNCTION test_event_trigger_for_drops();

Обработка события перезаписи таблицы

Функции, приведенные в Таблице 101, предоставляют информацию о таблице, для которой только что было вызвано событие table_rewrite. Если они вызываются в любом другом контексте, возникает ошибка.

Таблица 101. Функции для получения информации о перезаписи таблицы

Функция

Описание

pg_event_trigger_table_rewrite_oid () → oid

Возвращает OID таблицы, которая будет перезаписана.

pg_event_trigger_table_rewrite_reason () → integer

Возвращает код, объясняющий причину(ы) перезаписи. Конкретные значения кодов зависят от версии сервера.

Эти функции можно использовать в триггере событий следующим образом:

CREATE FUNCTION test_event_trigger_table_rewrite_oid()
 RETURNS event_trigger
 LANGUAGE plpgsql AS
$$
BEGIN
  RAISE NOTICE 'rewriting table % for reason %',
                pg_event_trigger_table_rewrite_oid()::regclass,
                pg_event_trigger_table_rewrite_reason();
END;
$$;

CREATE EVENT TRIGGER test_table_rewrite_oid
                  ON table_rewrite
   EXECUTE FUNCTION test_event_trigger_table_rewrite_oid();

Функции статистической информации

QHB предоставляет функцию для проверки сложной статистики, определенной с помощью команды CREATE STATISTICS.

Проверка списков MCV

pg_mcv_list_items ( pg_mcv_list ) → setof record

Функция pg_mcv_list_items возвращает набор записей, описывающих все элементы, хранящиеся в многостолбцовом списке MCV. Она возвращает следующие столбцы:

Имя | Тип | Описание
index | int | индекс элемента в списке MCV
values | text[] | значения, хранящиеся в элементе MCV
nulls | boolean[] | флаги, идентифицирующие значения NULL
frequency | double precision | частота вхождения этого элемента MCV
base_frequency | double precision | базовая частота вхождения этого элемента MCV

Функцию pg_mcv_list_items можно использовать следующим образом:

SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
                pg_mcv_list_items(stxdmcv) m WHERE stxname = 'stts';

Значения типа pg_mcv_list можно получить только из столбца pg_statistic_ext_data.stxdmcv.

Преобразование типов

Операторы SQL, намеренно или нет, могут требовать смешения данных разных типов в одном выражении. У QHB имеются широкие возможности для вычисления выражений смешанного типа.

Во многих случаях пользователю необязательно понимать все тонкости механизма преобразования типов. Однако неявные преобразования, проводимые QHB, могут повлиять на результаты запроса. Поэтому при необходимости эти результаты можно оптимизировать с помощью явного преобразования типов.

В этой главе рассматриваются механизмы преобразования типов и соглашения, имеющиеся в QHB. Дополнительную информацию о конкретных типах данных и допустимых для них функциях и операторах см. в соответствующих разделах в главах Типы данных и Функции и операторы.

Обзор

SQL — это язык со строгой типизацией. То есть каждый элемент данных имеет связанный тип данных, определяющий его поведение и допустимое использование. QHB обладает расширяемой системой типов, которая является более универсальной и гибкой, чем другие реализации SQL. Как следствие, преобразование типов в QHB в основном подчиняется общими правилам, а не ситуативной эвристике. Это позволяет использовать в выражениях смешанного типа даже пользовательские типы.

Сканер/анализатор QHB разделяет лексические элементы на пять основных категорий: целые числа, числа, отличные от целых, строки, идентификаторы и ключевые слова. Константы большинства не числовых типов сначала классифицируются как строки. В определении языка SQL допускается указывать имена типов в строках, и этот механизм можно применять в QHB, чтобы направить анализатор по верному пути. К примеру, запрос:

SELECT text 'Origin' AS "label", point '(0,0)' AS "value";

 label  | value
--------+-------
 Origin | (0,0)
(1 row)

содержит две строковые константы, типа text и типа point. Если для строковой константы не указан тип, то изначально ей присваивается фиктивный тип unknown, который может быть разрешен на более поздних этапах, как описано ниже.

Существует четыре основных структурных компонента SQL, которым требуются различные правила преобразования типов в анализаторе QHB:

Вызовы функций
Система типов QHB во многом сформирована вокруг обширного набора функций. У функций может быть один или несколько аргументов. Поскольку QHB позволяет перегружать функции, само по себе имя функции не является уникальным идентификатором вызываемой функции; анализатор должен выбрать правильную функцию, исходя из типов данных переданных аргументов.

Операторы
QHB допускает выражения с префиксными (с одним аргументом) и инфиксными (с двумя аргументами) операторами. Как и функции, операторы можно перегружать, поэтому с ними тоже существует проблема выбора правильного оператора.

Сохранение значений
Операторы SQL INSERT и UPDATE помещают результаты выражений в таблицу. Выражения в операторе должны соответствовать типам целевых столбцов и, возможно, преобразовываться в них.

UNION, CASE и связанные конструкции
Поскольку все результаты запроса объединяющего оператора SELECT должны оказаться в одном наборе столбцов, типы результатов каждого предложения SELECT должны согласовываться и преобразовываться в единый набор. Аналогично результирующие выражения конструкции CASE должны преобразовываться в общий тип, чтобы выражение CASE в целом имело известный выходной тип. Схожим образом, для некоторых других конструкций, например ARRAY[] и функций GREATEST и LEAST, требуется определение общего типа для нескольких подвыражений.

Информация о том, какие преобразования, или приведения, существуют между определенными типами данных и как их выполнить, хранится в системных каталогах. Пользователь может добавить дополнительные приведения с помощью команды CREATE CAST. (Обычно это делается в связи с определением новых типов данных. Набор приведений между встроенными типами был тщательно продуман, и лучше его не менять.)

Дополнительная эвристика, предоставляемая анализатором, позволяет использовать улучшенное определение корректного приведения в группах типов, допускающих неявные приведения. Типы данных разделяются на несколько базовых категорий типов, включающих boolean, numeric, string, bitstring, datetime, timespan, geometric, network и пользовательские типы. (Полный список см. в таблице Коды typcategory; однако обратите внимание, что также имеется возможность создать свои категории типов.) В каждой категории может быть один или несколько предпочитаемых типов, которые будут иметь преимущество при выборе возможных типов. Тщательно отбирая предпочитаемые типы и доступные неявные приведения, можно добиться того, что неоднозначные выражения (с несколькими возможными решениями при синтаксическом анализе) будут разрешаться подходящим способом.

Все правила преобразования типов разработаны с учетом нескольких принципов:

Неявные преобразования никогда не должны иметь неожиданных или непредсказуемых результатов.

Если запросу не нужно неявное преобразование типа, у анализатора и исполнителя не должно возникать лишних издержек. То есть, если запрос хорошо сформулирован и типы уже совпадают, этот запрос должен выполняться без дополнительной обработки в анализаторе и без лишних вызовов неявных преобразований.

Кроме того, если обычно запрос требует неявного преобразования для функции, и если впоследствии пользователь определяет новую функцию с корректными типами аргументов, анализатор должен использовать эту новую функцию и больше не проводить неявное преобразование, чтобы воспользоваться старой функцией.

Операторы

Конкретный оператор, на который ссылается выражение оператора, определяется с помощью процедуры, описанной ниже. Обратите внимание, что на эту процедуру опосредованно влияют приоритеты задействованных операторов, поскольку это определит, какие подвыражения будут входными аргументами каких операторов. Более подробную информацию см. в подразделе Приоритеты операторов.

Разрешение типа оператора

Выбрать операторы для рассмотрения из системного каталога pg_operator. Если было использовано имя оператора, не дополненное схемой (обычно это так), рассматриваются все операторы с подходящим именем и числом аргументов, видимые в текущем пути поиска (см. подраздел Путь поиска схемы). Если было задано полное имя оператора, рассматриваются только операторы из указанной схемы.

Если в пути поиска обнаруживаются несколько операторов с одинаковыми типами аргументов, рассматриваются только те из них, которые находятся в пути раньше. Операторы с разными типами аргументов рассматриваются на равных правах независимо от их позиции в пути поиска.

Проверить, нет ли среди них оператора, принимающего точно совпадающие типы входных аргументов. Если таковой существует (в наборе рассматриваемых операторов может быть только одно точное совпадение), использовать его. Отсутствие точного совпадения создает угрозу безопасности при вызове с указанием полного имени¹ (нетипичным) любого оператора, найденного в схеме, где недоверенным пользователям разрешено создавать объекты. В таких ситуациях следует привести типы аргументов для получения точного совпадения.

Если один аргумент при вызове двоичного оператора имеет тип unknown, то для данной проверки предположить, что он имеет тот же тип, что и другой аргумент. При вызове оператора с двумя аргументами unknown или префиксного оператора с одним аргументом unknown совпадение на этом шаге найдено не будет.

Если один аргумент при вызове двоичного оператора имеет тип unknown, а другой — тип домена, проверить, есть ли оператор, принимающий конкретно базовый тип домена с обеих сторон; если таковой имеется, использовать его.

Искать наиболее подходящий.

Отбросить операторы-кандидаты, для которых входные типы не совпадают и не могут быть преобразованы (с помощью неявного преобразования) так, чтобы они совпали. В этом контексте предполагается, что литералы типа unknown можно преобразовать во что угодно. Если остается только один кандидат, использовать его; в противном случае перейти к следующему шагу.

Если какой-либо из входных аргументов имеет тип домена, на всех последующих шагах считать его имеющим базовый тип домена. Это гарантирует, что в контексте разрешения неоднозначных операторов домены будут вести себя подобно своим базовым типам.

Просмотреть всех кандидатов и оставить операторы с наиболее точно совпадающими входными типами. Оставить всех кандидатов, если ни у одного нет точных совпадений. Если остается только один кандидат, использовать его; в противном случае перейти к следующему шагу.

Просмотреть всех кандидатов и оставить операторы, принимающие предпочитаемые типы (из категории типов входных данных) в большинстве позиций, где потребуется преобразование типов. Оставить всех кандидатов, если ни один не принимает предпочитаемые типы. Если остается только один кандидат, использовать его; в противном случае перейти к следующему шагу.

Если какие-либо входные аргументы имеют тип unknown, проверить категории типов, принимаемых в этих позициях аргументов оставшимися кандидатами. Для каждой позиции выбрать категорию string, если какой-либо кандидат принимает эту категорию. (Эта склонность к строкам обоснована, поскольку литерал типа unknown выглядит как строка.) Иначе, если все оставшиеся кандидаты принимают одну категорию типов, выбрать эту категорию; в противном случае констатировать неудачу, поскольку сделать верный выбор без дополнительных подсказок нельзя. Затем отбросить кандидатов, не принимающих выбранную категорию типов. Далее, если какой-либо кандидат принимает предпочитаемый тип в этой категории, отбросить кандидатов, принимающих для этого аргумента не предпочитаемые типы. Оставить всех кандидатов, если ни один не прошел эти проверки. Если остается только один кандидат, использовать его; в противном случае перейти к следующему шагу.

Если имеются аргументы как типа unknown, так и известного типа, и все аргументы известного типа имеют один тип, предположить, что аргументы типа unknown также имеют этот тип, и проверить, какие кандидаты могут принимать этот тип в позициях аргумента unknown. Если эту проверку проходит ровно один кандидат, использовать его. В противном случае констатировать неудачу.

Ниже мы приводим несколько примеров.

Пример 1. Разрешение типа для оператора квадратного корня

В стандартном каталоге определен только один оператор квадратного корня (префиксный |/) и он принимает аргумент типа double precision. В данном выражении запроса лексический анализатор изначально присваивает аргументу тип integer:

SELECT |/ 40 AS "square root of 40";
 square root of 40
-------------------
 6.324555320336759
(1 row)

Поэтому синтаксический анализатор преобразует тип этого операнда, и запрос становится равнозначен:

SELECT |/ CAST(40 AS double precision) AS "square root of 40";

Пример 2. Разрешение типа для оператора конкатенации строк

Строкоподобный синтаксис используется при работе со строковыми типами и со сложными типами расширений. Строки с неуказанным типом сопоставляются с наиболее подходящими операторами-кандидатами.

Пример с одним неуказанным аргументом:

SELECT text 'abc' || 'def' AS "text and unknown";

 text and unknown
------------------
 abcdef
(1 row)

В этом случае синтаксический анализатор смотрит, есть ли оператор, принимающий в обоих аргументов тип text. Поскольку таковой имеется, анализатор предполагает, что второй аргумент следует воспринимать как аргумент типа text.

Ниже приведена конкатенация двух значений неуказанных типов:

SELECT 'abc' || 'def' AS "unspecified";

 unspecified
-------------
 abcdef
(1 row)

В этом случае изначально нет подсказки, какой тип использовать, поскольку в данном запросе никакие типы не указаны. Поэтому синтаксический анализатор просматривает всех операторов-кандидатов и находит операторов, принимающих аргументы категорий string и bit-string. Поскольку категория string является предпочтительной (при наличии), выбирается она, а затем в качестве конкретного типа для разрешения литералов неизвестного типа используется предпочитаемый для строк тип text.

Пример 3. Разрешение типа для оператора абсолютного значения и отрицания

В каталоге операторов QHB для префиксного оператора @ имеется несколько записей, реализующих операции абсолютного значения для различных числовых типов данных. Одна из этих записей предназначена для типа float8, который является предпочитаемым типом в категории числовых типов. Таким образом, столкнувшись с входным значением типа unknown, QHB будет использовать эту запись:

SELECT @ '-4.5' AS "abs";
 abs
-----
 4.5
(1 row)

Здесь система неявно разрешила литерал неизвестного типа как имеющий тип float8, прежде чем применить выбранный оператор. Мы можем удостовериться, что был применен именно float8, а не какой-то другой тип:

SELECT @ '-4.5e500' AS "abs";

ERROR:  "-4.5e500" is out of range for type double precision

С другой стороны, префиксный оператор ~ (побитовое отрицание) определяется только для целочисленных типов данных, но не для float8. Поэтому, если мы попытаемся сделать то же самое с ~, то получим:

SELECT ~ '20' AS "negation";

ERROR:  operator is not unique: ~ "unknown"
HINT:  Could not choose a best candidate operator. You might need to add
explicit type casts.
-- ОШИБКА: оператор не уникален: ~ "unknown"
-- ПОДСКАЗКА: Не удалось выбрать наилучшую кандидатуру оператора. Возможно, вам
-- нужно добавить явные приведения типов.

Это происходит из-за того, что система не может решить, какой из нескольких возможных операторов ~ следует предпочесть. Мы можем помочь ей, добавив явное приведение:

SELECT ~ CAST('20' AS int8) AS "negation";

 negation
----------
      -21
(1 row)

Пример 4. Разрешение типа для оператора включения в массив

Ниже приведен еще один пример разрешения оператора с одним известным и одним неизвестным аргументами:

SELECT array[1,2] <@ '{1,2,3}' as "is subset";

 is subset
-----------
 t
(1 row)

В каталоге операторов QHB имеется несколько записей для инфиксного оператора <@, но только два из них могут принять целочисленный массив слева: оператор включения массива (anyarray <@ anyarray) и оператор включения диапазона (anyelement <@ anyrange). Поскольку ни один из этих полиморфных псевдотипов (см. раздел Псевдотипы) не считается предпочитаемым, синтаксический анализатор не может разрешить неоднозначность, исходя из этого. Однако в шаге 3.6 говорится, что он должен предположить, что литерал неизвестного типа имеет тот же тип, что и другой аргумент, то есть тип целочисленного массива. Теперь соответствовать могут только два оператора, поэтому выбирается оператор включения массива. (Если бы был выбран оператор включения диапазона, мы бы получили ошибку, поскольку у строки нет подходящего формата, чтобы быть диапазонным литералом.)

Пример 5. Нестандартный оператор с типом домена

Иногда пользователи пытаются объявить операторы, применимые только к типу домена. Это возможно, но далеко не так полезно, как может показаться, поскольку правила разрешения операторов разработаны для выбора операторов, применимых к базовому типу домена. В качестве примера рассмотрим

CREATE DOMAIN mytext AS text CHECK(...);
CREATE FUNCTION mytext_eq_text (mytext, text) RETURNS boolean AS ...;
CREATE OPERATOR = (procedure=mytext_eq_text, leftarg=mytext, rightarg=text);
CREATE TABLE mytable (val mytext);

SELECT * FROM mytable WHERE val = 'foo';

В этом запросе не будет использоваться нестандартный оператор. Синтаксический анализатор сначала проверит, имеется ли оператор mytext = mytext (шаг 2.1), которого здесь нет, затем он рассмотрит базовый тип домена text и проверит, имеется ли оператор text = text (шаг 2.2), который здесь есть, так что он разрешит литерал типа unknown как text и использует оператор text = text. Единственный способ добиться применения нестандартного оператора — это явное приведение литерала:

SELECT * FROM mytable WHERE val = text 'foo';

чтобы оператор mytext = text был найден сразу, в соответствии с правилом точного совпадения. Если дело доходит до правил наилучшего соответствия, они активно дискриминируют операторы типов домена. Если бы они этого не делали, подобный оператор спровоцировал бы слишком много ошибок, связанных с неоднозначностью оператора, поскольку правила приведения всегда рассматривают домен как приводимый к базовому типу и наоборот, и поэтому оператор домена считался бы применимым во всех одинаковых случаях, где используется одноименный оператор с базовым типом.

Функции

Конкретная функция, на которую ссылается вызов функции, определяется с помощью процедуры, описанной ниже.

Разрешение типа функции

Выбрать функции для рассмотрения из системного каталога pg_proc. Если было использовано имя функции, не дополненное схемой, рассматриваются все функции с подходящим именем и числом аргументов, видимые в текущем пути поиска (см. подраздел Путь поиска схемы). Если было задано полное имя функции, рассматриваются только функции из указанной схемы.

Если в пути поиска обнаруживаются несколько функций с одинаковыми типами аргументов, рассматриваются только те из них, которые находятся в пути раньше. Функции с разными типами аргументов рассматриваются на равных правах независимо от их позиции в пути поиска.

Если функция объявлена с параметром-массивом VARIADIC и при вызове не используется ключевое слово VARIADIC, то функция обрабатывается так, как если бы параметр-массив был заменен одним или несколькими вхождениями параметров типа его элементов (число соответствует количеству аргументов при вызове). После такого расширения по фактическим типам аргументов эта функция может совпасть с некоторой функцией с постоянным количеством аргументов. В этом случае используется функция, находящаяся в пути поиска раньше, а если эти две функции находятся в одной схеме, то предпочитается функция с постоянным количеством аргументов.
Это создает угрозу безопасности при вызове с указанием полного имени¹ любой функции с переменным количеством аргументов, найденной в схеме, где недоверенным пользователям разрешено создавать объекты. Злонамеренный пользователь может захватить контроль и выполнять произвольные функции SQL, как будто их выполняете вы. Использование вместо этого вызова с ключевым словом VARIADIC устраняет эту угрозу. Однако для вызовов с параметрами VARIADIC "any" зачастую отсутствует равнозначная формулировка, содержащая ключевое слово VARIADIC. Чтобы такие вызовы выполнялись безопасно, создание объектов в схеме функции должно разрешаться только доверенным пользователям.

Функции, имеющие для параметров значения по умолчанию, считаются совпадающими с любым вызовом, в котором опущено ноль или более позиций параметров, где возможны значения по умолчанию. Если вызову соответствует больше одной такой функции, используется та, которая находится в пути поиска раньше. Если в одной схеме имеются две или более таких функций с идентичными типами параметров в позициях без значений по умолчанию (что возможно, если у них разные наборы параметров, устанавливаемых по умолчанию), система не сможет выбрать предпочитаемую и выдаст ошибку «неоднозначный вызов функции», если лучшее соответствие для вызова найти невозможно.
Это создает угрозу безопасности при вызове с указанием полного имени¹ любой функции, найденной в схеме, где недоверенным пользователям разрешено создавать объекты. Злонамеренный пользователь может создать функцию с именем уже существующей, продублировав параметры этой функции и добавив новые со значениями по умолчанию. Это исключит новые вызовы к исходной функции. Во избежание этой угрозы помещайте функции в схемы, где создавать объекты разрешено только доверенным пользователям.

Проверить, нет ли функция, принимающая в точности типы входных аргументов. Если таковая существует (в наборе рассматриваемых функций может быть только одно точное совпадение), использовать ее. Отсутствие точного совпадения создает угрозу безопасности при вызове с указанием полного имени¹ любой функции, найденной в схеме, где недоверенным пользователям разрешено создавать объекты. В таких ситуациях следует привести типы аргументов для получения точного совпадения. (В случаях с unknown совпадение на этом шаге найдено не будет.)

Если точное совпадение не найдено, проверить не похож ли вызов функции на запрос на специальный тип преобразования. Это происходит, если вызов функции содержит всего один аргумент, а имя функции совпадает с именем (внутренним) некоторого типа данных. Более того, аргумент функции должен быть либо литералом неизвестного типа, либо значением типа, двоично-приводимого к именованному типу данных, либо значением типа, который можно преобразовать в именованный тип данных, применив функции ввода/вывода этого типа (то есть преобразовав в стандартный строковый тип или из него). Когда эти условия выполняются, вызов функции обрабатывается как форма спецификации CAST. ²

Искать наиболее подходящий.

Отбросить функции-кандидаты, для которых входные типы не совпадают и не могут быть преобразованы (с помощью неявного преобразования) так, чтобы они совпали. В этом контексте предполагается, что литералы типа unknown можно преобразовать во что угодно. Если остается только один кандидат, использовать его; в противном случае перейти к следующему шагу.

Если какой-либо из входных аргументов имеет тип домена, на всех последующих шагах считать его имеющим базовый тип домена. Это гарантирует, что в контексте разрешения неоднозначных функций домены будут вести себя подобно своим базовым типам.

Просмотреть всех кандидатов и оставить функции с наиболее точно совпадающими входными типами. Оставить всех кандидатов, если ни у одного нет точных совпадений. Если остается только один кандидат, использовать его; в противном случае перейти к следующему шагу.

Просмотреть всех кандидатов и оставить функции, принимающие предпочитаемые типы (из категории типов входных данных) в большинстве позиций, где потребуется преобразование типов. Оставить всех кандидатов, если ни один не принимает предпочитаемые типы. Если остается только один кандидат, использовать его; в противном случае перейти к следующему шагу.

Если какие-либо входные аргументы имеют тип unknown, проверить категории типов, принимаемых в этих позициях аргументов оставшимися кандидатами. Для каждой позиции выбрать категорию string, если какой-либо кандидат принимает эту категорию. (Эта склонность к строкам обоснована, поскольку литерал типа unknown выглядит как строка.) Иначе, если все оставшиеся кандидаты принимают одну категорию типов, выбрать эту категорию; в противном случае констатировать неудачу, поскольку сделать верный выбор без дополнительных подсказок нельзя. Затем отбросить кандидатов, не принимающих выбранную категорию типов. Далее, если какой-либо кандидат принимает предпочитаемый тип в этой категории, отбросить кандидатов, принимающих для этого аргумента не предпочитаемые типы. Оставить всех кандидатов, если ни один не прошел эти проверки. Если остается только один кандидат, использовать его; в противном случае перейти к следующему шагу.

Если имеются аргументы как типа unknown, так и известного типа, и все аргументы известного типа имеют один тип, предположить, что аргументы типа unknown также имеют этот тип, и проверить, какие кандидаты могут принимать этот тип в позициях аргумента unknown. Если эту проверку проходит ровно один кандидат, использовать его. В противном случае констатировать неудачу.

Обратите внимания, что правила «наилучшего соответствия» для разрешения типа операторов и функций одинаковы. Ниже мы приводим несколько примеров.

Пример 6. Разрешение типа аргумента для функции округления

Существует только одна функция round, принимающая два аргумента: первый типа numeric, а второй — integer. Поэтому в следующем запросе первый аргумент типа integer автоматически преобразуется в numeric:

SELECT round(4, 4);

 round
--------
 4.0000
(1 row)

На самом деле этот запрос трансформируется анализатором в:

SELECT round(CAST (4 AS numeric), 4);

Поскольку числовым константам с десятичными точками изначально назначается тип numeric, для следующего запроса преобразование типов не понадобится, и поэтому он может быть немного эффективнее:

SELECT round(4.0, 4);

Пример 7. Разрешение функции с переменным количеством параметров

CREATE FUNCTION public.variadic_example(VARIADIC numeric[]) RETURNS int
  LANGUAGE sql AS 'SELECT 1';
CREATE FUNCTION

Эта функция принимает, но не требует ключевое слово VARIADIC. Она допускает как целочисленные, так и числовые аргументы:

SELECT public.variadic_example(0),
       public.variadic_example(0.0),
       public.variadic_example(VARIADIC array[0.0]);
 variadic_example | variadic_example | variadic_example
------------------+------------------+------------------
                1 |                1 |                1
(1 row)

Однако для первого и второго вызовов предпочтительнее окажутся более специфичные функции, если таковые имеются:

CREATE FUNCTION public.variadic_example(numeric) RETURNS int
  LANGUAGE sql AS 'SELECT 2';
CREATE FUNCTION

CREATE FUNCTION public.variadic_example(int) RETURNS int
  LANGUAGE sql AS 'SELECT 3';
CREATE FUNCTION

SELECT public.variadic_example(0),
       public.variadic_example(0.0),
       public.variadic_example(VARIADIC array[0.0]);
 variadic_example | variadic_example | variadic_example
------------------+------------------+------------------
                3 |                2 |                1
(1 row)

Если задана конфигурация по умолчанию и существует только первая функция, первый и второй вызовы являются небезопасными. Любой пользователь может перехватить их, создав вторую или третью функцию. Поскольку в третьем вызове тип аргумента соответствует в точности и используется ключевое слово VARIADIC, он безопасен.

Пример 8. Разрешение типа для функции извлечения подстроки

Существует несколько функций substr, одна из которых принимает типы text и integer. Если эта функция вызывается со строковой константой неуказанного типа, система выбирает функцию-кандидата, которая принимает аргумент предпочитаемой категории string (а именно, типа text).

SELECT substr('1234', 3);

 substr
--------
     34
(1 row)

Если строка объявляется как имеющая тип varchar, как может произойти, если она поступает из таблицы, анализатор попытается преобразовать ее в тип text:

SELECT substr(varchar '1234', 3);

 substr
--------
     34
(1 row)

Этот запрос трансформируется синтаксическим анализатором и по сути становится:

SELECT substr(CAST (varchar '1234' AS text), 3);

Примечание
Анализатор узнает из каталога pg_cast, что типы text и varchar двоично-совместимы, что означает, что один тип можно передать функции, принимающей другой, не выполняя физического преобразования. Таким образом, в этом случае преобразование типов на самом деле не добавляется.

И если функция вызывается с аргументом типа integer, анализатор попытается преобразовать его в text:

SELECT substr(1234, 3);
ERROR:  function substr(integer, integer) does not exist
HINT:  No function matches the given name and argument types. You might need
to add explicit type casts.
-- ОШИБКА: функция substr(integer, integer) не существует
-- ПОДСКАЗКА: Данному имени и типам аргументов не соответствует ни одна функция.
-- Возможно, вам понадобится добавить явные приведения типов.

Этот вызов не работает, поскольку для integer нет неявного приведения к text. Однако явное приведение сработает:

SELECT substr(CAST (1234 AS text), 3);

 substr
--------
     34
(1 row)

Хранимое значение

Значения, вставляемые в таблицу, преобразуются в тип данных целевого столбца в соответствии со следующей процедурой.

Преобразование типа для хранимого значения

Проверить точное соответствие целевому типу.

Если это не так, попытаться преобразовать выражение в целевой тип. Это возможно, если в каталоге pg_cast (см. CREATE CAST) зарегистрировано приведение присваивания между двумя типами. Иначе, если выражение является литералом неизвестного типа, содержимое этой строки будет передано на вход процедуре преобразования для целевого типа.

Проверить, имеется ли приведение размера для целевого типа. Приведение размера — это приведение типа к самому себе. Если такое приведение найдено в каталоге pg_cast, применить его к выражению, прежде чем сохранить в целевом столбце. Функция, реализующая такое приведение, всегда принимает дополнительный параметр типа integer, в котором передается значение atttypmod целевого столбца (обычно это его объявленная длина, хотя интерпретация atttypmod варьирует в зависимости от типа данных), и может принимать третий параметр типа boolean, который показывает, является ли приведение явным или неявным. Функция приведения отвечает за применение любой семантики, связанной с длиной, например, проверку размера или усечение.

Пример 9. Преобразование типа хранения character

Следующий оператор показывает, что хранимое значение правильно подгоняется под размер целевого столбца, объявленного как character(20):

CREATE TABLE vv (v character(20));
INSERT INTO vv SELECT 'abc' || 'def';
SELECT v, octet_length(v) FROM vv;

          v           | octet_length
----------------------+--------------
 abcdef               |           20
(1 row)

В действительности здесь происходит следующее: два неизвестных литерала по умолчанию разрешаются как тип text, позволяя оператору || разрешиться как оператор конкатенации значений text. Затем результат оператора с типом text преобразуется в тип bpchar («blank-padded char» (символы, дополненные пробелами), внутреннее имя типа данных character) в соответствии с типом целевого столбца. (Поскольку преобразование из text в bpchar является двоично-приводимым, это преобразование не добавляет реальный вызов функции.) Наконец, в системном каталоге находится функция изменения размера bpchar(bpchar, integer, boolean), которая применяется к результату оператора и длине столбца хранения. Эта типоспецифичная функция осуществляет требуемую проверку длины и добавление заполняющих пробелов.

UNION, CASE и связанные конструкции

SQL-конструкции UNION должны сочетать возможно неодинаковые типы, чтобы превратить их в единый результирующий набор. Алгоритм разрешения применяется независимо для каждого выходного столбца запроса на объединение. Конструкции INTERSECT и EXCEPT разрешают неодинаковые типы тем же способом, что и UNION. Тот же алгоритм используется и в некоторых других конструкциях, включая CASE, ARRAY, VALUES и функции GREATEST и LEAST, для сочетания составляющих их выражений и выбора результирующего типа данных.

Разрешение типов для UNION, CASE и связанных конструкций

Если все входные данные одного типа и это не unknown, разрешить как этот тип.

Если какое-либо из входных значений имеет тип домена, на всех последующих шагах обращаться с ним как с имеющим базовый тип домена. ³

Если все входные данные имеют тип unknown, разрешить их как тип text (предпочитаемый тип категории string). В противном случае входные значения unknown для остальных правил игнорируются.

Если входные данные известных типов не относятся к одной категории типов, констатировать неудачу.

Выбрать первый известный тип входных данных в качестве типа-кандидата, затем рассмотреть все остальные известные типы данных в порядке слева направо. ⁴ Если тип-кандидат можно неявно преобразовать в другой тип, но не наоборот, выбрать другой тип в качестве нового типа-кандидата. Затем продолжить рассматривать оставшиеся входные значения. Если на каком-либо шаге этого процесса выбирается предпочитаемый тип, прекратить дальнейшее рассмотрение значений.

Преобразовать все входные данные в итоговый тип-кандидат. Констатировать неудачу, если неявное преобразование из заданного входного типа в тип-кандидат невозможно.

Ниже мы приводим несколько примеров.

Пример 10. Разрешение типа с недостаточным определением в объединении

SELECT text 'a' AS "text" UNION SELECT 'b';

 text
------
 a
 b
(2 rows)

Здесь литерал 'b' неизвестного типа будет разрешен как тип text.

Пример 11. Разрешение типа в простом объединении

SELECT 1.2 AS "numeric" UNION SELECT 1;

 numeric
---------
       1
     1.2
(2 rows)

Литерал 1.2 имеет тип numeric, а целочисленное значение 1 можно неявно привести к типу numeric, поэтому используется этот тип.

Пример 12. Разрешение типа в объединении с переносом

SELECT 1 AS "real" UNION SELECT CAST('2.2' AS REAL);

 real
------
    1
  2.2
(2 rows)

Здесь тип real нельзя неявно привести к integer, но integer можно неявно привести к real, поэтому результирующий тип объединения разрешается как real.

Пример 13. Разрешение типа во вложенном объединении

SELECT NULL UNION SELECT NULL UNION SELECT 1;

ERROR:  UNION types text and integer cannot be matched
-- ОШИБКА: типы text и integer в UNION сопоставить нельзя

Эта ошибка возникает из-за того, что QHB обращается с множественными UNION как с вложенным множеством попарных операций; то есть для нее эта запись — то же самое, что

(SELECT NULL UNION SELECT NULL) UNION SELECT 1;

Внутренний UNION разрешается как выдающий тип text, в соответствии с приведенными выше правилами. Затем внешний UNION получает на вход типы text и integer, что и приводит к вышеуказанной ошибке. Эту проблему можно устранить, сделав так, чтобы самый левый UNION получил на вход хотя бы одно значение желаемого результирующего типа.

Операции INTERSECT и EXCEPT также разрешаются попарно. Однако другие конструкции, описанные в этом разделе, рассматривают все входные данные сразу за один шаг процедуры разрешения.

Выходные столбцы SELECT

Правила, описанные в предыдущих разделах, обеспечивают присвоение типов данных, отличных от unknown, во всех выражениях в запросах SQL, за исключением литералов с неуказанными типом, имеющих вид простых выходных столбцов команды SELECT. Например, в запросе

SELECT 'Hello World';

отсутствуют подсказки, позволяющие определить, какой тип должен принимать строковый литерал. В этой ситуации QHB прибегнет к разрешению типа литерала как text.

Когда SELECT является одной из ветвей конструкции UNION (либо INTERSECT или EXCEPT) или когда она находится внутри INSERT ... SELECT, это правило не применяется, поскольку приоритет имеют правила, описанные в предыдущих разделах. В первом случае тип литерала с неуказанным типом можно взять из другой ветви UNION, а во втором — из целевого столбца.

В этом контексте списки RETURNING воспринимаются так же, как выходные списки SELECT.

¹

Эта угроза не возникает, если имя не дополнено схемой, поскольку путь поиска, содержащий схемы, в которых недоверенным пользователям разрешено создавать объекты, не является шаблоном безопасного использования схем.

²

Этот шаг нужен для поддержки спецификаций приведений в стиле функции в случаях, когда на самом деле функции приведения нет. Если функция приведения есть, она для удобства называется именем выходного типа, и поэтому не требует особого подхода. Дополнительные комментарии см. на справочной странице команды CREATE CAST.

³

Примерно так же, как обработка значений-доменов для операторов и функций, это поведение позволяет сохранить тип домена в конструкции UNION или подобной, если пользователь позаботится о том, чтобы все входные данные явно или неявно приводились именно к этому типу. В противном случае будет использоваться базовый тип домена.

⁴

По историческим причинам CASE обрабатывает свое предложение ELSE (если таковое имеется) как «первое» входное значение, а предложения THEN рассматриваются после него. Во всех остальных случаях «слева направо» означает порядок, в котором выражения расположены в тексте запроса.

Параллельный запрос

QHB может разрабатывать планы запросов, которые способны задействовать несколько ЦП, чтобы быстрее получить ответы на эти запросы. Эта функциональность называется параллельным выполнением запросов. Для многих запросов параллельное выполнение не дает никаких преимуществ, либо из-за ограничений текущей реализации, либо из-за принципиального отсутствия плана запроса, который был бы быстрее последовательного. Тем не менее для запросов, способных выиграть от этого, ускорение вследствие параллельного выполнения зачастую весьма значительно. Многие запросы таким образом могут выполняться более чем вдвое быстрее, а некоторые — быстрее в четыре и даже более раз. Обычно наибольший выигрыш получают запросы, обрабатывающие большой объем данных, но возвращающие пользователю всего несколько строк. В этой главе рассматриваются некоторые подробности того, как работают параллельные запросы и в каких ситуациях их можно использовать, чтобы пользователи, желающие их применять, могли понять, чего ожидать.

Как работают параллельные запросы

Когда оптимизатор определяет, что параллельное выполнение является самой быстрой стратегией выполнения конкретного запроса, он создает план запроса, включающий узел Gather (Сбор) или Gather Merge (Сбор со слиянием). Вот простой пример:

EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
                                     QUERY PLAN
-------------------------------------------------------------------​------------------
 Gather  (cost=1000.00..217018.43 rows=1 width=97)
   Workers Planned: 2
   ->  Parallel Seq Scan on pgbench_accounts  (cost=0.00..216018.33 rows=1 width=97)
         Filter: (filler ~~ '%x%'::text)
(4 rows)

Во всех случаях узел Gather или Gather Merge будет иметь ровно один дочерний план, являющийся частью плана, которая будет выполняться параллельно. Если узел Gather или Gather Merge находится на самом верху дерева плана, то в параллельном режиме будет выполняться весь запрос. Если он находится где-то в другом месте дерева плана, то параллельно будет выполняться только часть плана ниже его. В приведенном выше примере запрос обращается только к одной таблице, поэтому помимо самого узла Gather имеется еще только один узел плана; поскольку этот узел плана является потомком узла Gather, он будет выполняться параллельно.

Используя EXPLAIN, вы можете узнать количество рабочих процессов, выбранное планировщиком. Когда во время выполнения запроса достигается узел Gather, процесс, реализующий сеанс пользователя, запрашивает фоновые рабочие процессов в этом количестве. Количество фоновых рабочих процессов, которое планировщик выберет для использования, ограничено значением max_parallel_workers_per_gather. Общее количество фоновых рабочих процессов, которые могут существовать одновременно, ограничено параметрами max_worker_processes и max_parallel_workers. Таким образом, вполне возможно, что параллельный запрос будет выполняться с меньшим количеством рабочих процессов, чем планировалось, или даже вообще без дополнительных рабочих процессов. Оптимальность плана может зависеть от количества доступных рабочих процессов, поэтому их нехватка может привести к низкой производительности запроса. Если это происходит часто, имеет смысл увеличить max_worker_processes и max_parallel_workers, чтобы одновременно могло выполняться больше рабочих процессов, или, как вариант, уменьшить max_parallel_workers_per_gather, чтобы планировщик запрашивал меньше рабочих процессов.

Каждый фоновый рабочий процесс, успешно запущенный для данного параллельного запроса, будет выполнять параллельную часть плана. Ведущий процесс также будет выполнять эту часть плана, но у него есть дополнительная ответственность: он должен также прочитать все кортежи, сгенерированные рабочими процессами. Когда параллельная часть плана генерирует небольшое количество кортежей, ведущий процесс часто ведет себя практически так же, как дополнительный рабочий процесс, ускоряя выполнение запроса. И наоборот, когда параллельная часть плана генерирует много кортежей, ведущий процесс может быть почти все время занят чтением сгенерированных рабочими процессами кортежей, и выполнять дальнейшие шаги обработки, нужные узлам плана выше уровня узла Gather или Gather Merge. В таких случаях ведущий процесс почти не будет работать над выполнением параллельной части плана.

Когда на вершине параллельной части плана находится Gather Merge, а не Gather, это означает, что каждый процесс, выполняющий параллельную часть плана, выдает кортежи в отсортированном порядке, и что ведущий процесс выполняет слияние с сохранением порядка. Gather же, наоборот, читает кортежи от рабочих процессов в произвольном удобном ему порядке, нарушая какой бы то ни было порядок сортировки, который мог существовать.

Когда может использоваться параллельное выполнение запросов?

Существует несколько значений параметров, которые могут заставить планировщик ни при каких обстоятельствах не генерировать параллельные планы запросов. Чтобы такие планы вообще генерировались, необходимо установить следующие параметры указанным образом.

max_parallel_workers_per_gather должен быть установлен в значение больше нуля. Это частный случай более общего принципа, согласно которому количество используемых рабочих процессов не должно превышать значение, заданное параметром max_parallel_workers_per_gather.

Кроме того, система не должна работать в однопользовательском режиме. Поскольку в такой ситуации вся СУБД работает как один процесс, фоновые рабочие процессы будут недоступны.

Даже когда в целом создание параллельных планов запросов возможно, планировщик не будет генерировать их для конкретного запроса при наличии одного из следующих обстоятельств:

Запрос записывает данные или блокирует строки в базе данных. Если запрос содержит операцию, модифицирующую данные на верхнем уровне или внутри CTE, параллельные планы для этого запроса генерироваться не будут. В виде исключения параллельный план для нижележащей команды SELECT внутри запроса могут использовать следующие команды, создающие таблицу и наполняющие ее:

CREATE TABLE ... AS

SELECT INTO

CREATE MATERIALIZED VIEW

REFRESH MATERIALIZED VIEW

Запрос может быть приостановлен в процессе выполнения. В любой ситуации, когда система думает, что может произойти частичное или инкрементальное выполнение, параллельный план не генерируется. Например, курсор, созданный командой DECLARE CURSOR, никогда не будет использовать параллельный план. Аналогично цикл PL/pgSQL вида FOR x IN query LOOP .. END LOOP никогда не будет использовать параллельный план, поскольку система параллельных запросов не способна проверить, безопасен ли для выполнения код внутри цикла во время параллельного выполнения запроса.

Запрос использует функцию, помеченную как PARALLEL UNSAFE (небезопасна для параллельного выполнения). Большинство системных функций помечены как PARALLEL SAFE (безопасны для параллельного выполнения), но пользовательские функции по умолчанию помечаются как PARALLEL UNSAFE. См. описание в разделе Безопасность параллельного выполнения.

Запрос выполняется внутри другого запроса, уже параллельного. Например, если функция, вызванная параллельным запросом, сама выполняет запрос SQL, этот запрос никогда не будет использовать параллельный план. Это ограничение текущей реализации, но убирать его, вероятно, нежелательно, поскольку это может привести к использованию одним запросом огромного количества процессов.

Даже когда для определенного запроса генерируется параллельный план, имеется ряд обстоятельств, при которых этот план будет невозможно выполнить в параллельном режиме во время выполнения. Если это происходит, ведущий процесс выполняет часть плана ниже узла Gather полностью самостоятельно, как если бы узла Gather не было. Это произойдет только при выполнении одного из следующих условий:

Нельзя получить ни одного фонового рабочего процесса из-за ограничения, гласящего, что общее количество фоновых рабочих процессов не может быть выше значения параметра max_worker_processes.

Нельзя получить ни одного фонового рабочего процесса из-за ограничения, гласящего, что общее количество фоновых рабочих процессов, запущенных для параллельного запроса, не может быть выше значения параметра max_parallel_workers.

Клиент передает сообщение Execute с ненулевым количеством выбираемых кортежей. См. описание расширенного протокола запросов. Поскольку в настоящее время libpq не предоставляет способа передавать такие сообщения, это может произойти только при использовании клиента, не задействующего libpq. Если это происходит часто, имеет смысл установить в max_parallel_workers_per_gather ноль в сеансах, где такой вариант возможен, во избежание создания планов запросов, которые могут быть неоптимальны при последовательном выполнении.

Параллельные планы

Поскольку каждый рабочий процесс выполняет параллельную часть плана до конца, нельзя просто взять обычный план запроса и запустить его с использованием нескольких рабочих процессов. Потому что тогда каждый рабочий процесс будет выдавать полную копию выходного результирующего набора, так что запрос выполнится не быстрее, чем обычно, но может выдать некорректные результаты. Вместо этого параллельной частью плана должно быть то, что оптимизатор запроса внутренне распознает как частичный план; то есть он должен быть сконструирован так, что каждый процесс, выполняющий этот план, генерировал только подмножество выходных строк, причем таким образом, чтобы каждая требующаяся выходная строка гарантированно генерировалась ровно одним из сотрудничающих процессов. В целом, это означает, что сканирование целевой таблицы запроса должно проводиться с учетом параллельного выполнения.

Параллельные сканирования

В настоящее время поддерживаются следующие типы сканирований таблиц, учитывающих параллельное выполнение.

При параллельном последовательном сканировании блоки таблицы будут разделены на диапазоны и распределены между сотрудничающими процессами. Каждый рабочий процесс сначала завершит сканирование выданного ему диапазона блоков, и только потом запросит следующий диапазон.

При параллельном сканировании кучи по битовой карте один процесс выбирается в качестве ведущего. Этот процесс проводит сканирование одного или нескольких индексов и строит битовую карту, показывающую, какие блоки таблицы нужно просмотреть. Затем эти блоки распределяются между сотрудничающими процессами, как и при параллельном последовательном сканировании. Другими словами, сканирование кучи проводится в параллельном режиме, а сканирование нижележащего индекса — нет.

При параллельном сканировании по индексу или параллельном сканировании только индекса сотрудничающие процессы читают данные из индекса по очереди. В настоящее время параллельное сканирование по индексу поддерживается только для индексов B-деревьев. Каждый процесс затребует один блок индекса и будет сканировать и возвращать все кортежи, на которые тот ссылается; другие процессы могут в то же время возвращать кортежи из другого блока индекса. От каждого рабочего процесса результаты параллельного сканирования B-дерева возвращаются в отсортированном порядке.

В будущем может появиться поддержка параллельного выполнения и для других типов сканирования, например, сканирования индексов, отличных от B-деревьев.

Параллельные соединения

Как и в непараллельном плане, целевую таблицу можно соединить с одной или несколькими другими таблицами, используя вложенный цикл, соединение по хешу или соединение слиянием. Внутренней стороной соединения может быть любой вид непараллельного плана, который в остальном поддерживается планировщиком, при условии, что он безопасен для выполнении в параллельном рабочем процессе. В зависимости от типа соединения, внутренней стороной может быть и параллельный план.

В соединении с вложенным циклом внутренняя сторона всегда непараллельная. Хотя она выполняется полностью, это эффективно, если внутренней стороной является сканирование по индексу, поскольку внешние кортежи, а следовательно и циклы, находящие значения в индексе, распределяются по сотрудничающим процессам.

В соединении со слиянием внутренняя сторона всегда является непараллельным планом и поэтому выполняется полностью. Это может быть неэффективно, если нужно произвести сортировку, поскольку работа и результирующие данные дублируются в каждом сотрудничающем процессе.

В соединении по хешу (без приставки «параллельное»), внутренняя сторона выполняется полностью каждым сотрудничающим процессом, чтобы собрать идентичные копии хеш-таблицы. Это может быть неэффективно, если хеш-таблица очень большая или план дорогой. В параллельном соединении по хешу внутренней стороной является параллельное хеширование, при котором работа по созданию общей хеш-таблицы распределяется между сотрудничающими процессами.

Параллельное агрегирование

QHB поддерживает параллельное агрегирование, проводя агрегирование в два этапа. Сначала каждый процесс в параллельной части плана выполняет этап агрегирования, выдавая частичный результат для каждой группы, о которой известно этому процессу. В плане это отражается как узел Partial Aggregate (Частичное агрегирование). Затем частичные результаты передаются ведущему процессу через узел Gather или Gather Merge. И наконец, ведущий процесс заново агрегирует результаты всех рабочих процессов, чтобы выдать итоговый результат. В плане это отражается как узел Finalize Aggregate (Итоговое агрегирование).

Поскольку узел Finalize Aggregate выполняется в ведущем процессе, запросы, выдающие относительно большое количество групп по сравнению с количеством входных строк, будут считаться планировщиком запросов менее предпочтительными. Например, при худшем сценарии количество групп, обнаруженных узлом Finalize Aggregate, может равняться количеству входных строк, обнаруженных всеми рабочими процессами на этапе Partial Aggregate. Очевидно, что в таких случаях при использовании параллельного агрегирования производительность не повысится. Планировщик запросов учитывает это в процессе планирования и вряд ли в такой ситуации выберет параллельное агрегирование.

Параллельное агрегирование поддерживается не во всех ситуациях. Для этого каждый агрегат должен быть безопасен для распараллеливания и иметь комбинирующую функцию. Если переходное состояние агрегата имеет тип internal, то он должен также иметь функции сериализации и десериализации. Подробную информацию см. на справочной странице команды CREATE AGGREGATE. Параллельное агрегирование не поддерживается, если вызов агрегатной функции содержит предложение DISTINCT или ORDER BY, а также не поддерживается для сортирующих агрегатов или когда в запросе есть предложение GROUPING SETS. Оно может применяться, только когда все задействованные в запросе соединения также входят в параллельную часть плана.

Параллельное присоединение

Когда QHB нужно объединить строки из нескольких источников в один результирующий набор, она использует узел плана Append (Присоединение) или MergeAppend (Присоединение со слиянием). Обычно это происходит при реализации UNION ALL или сканировании партиционированной таблицы. Такие узлы можно использовать как в параллельных планах, так и в любых других. Однако в параллельном плане планировщик может вместо них применить узел Parallel Append (Параллельное присоединение).

Когда в параллельном плане используется узел Append, каждый процесс выполняет дочерние планы в порядке их расположения, так что все участвующие процессы кооперируются и полностью выполняют первый дочерний план, а затем одновременно переходят ко второму плану. Когда вместо него используется Parallel Append, исполнитель по возможности равномерно распределяет участвующие процессы по дочерним планам, так что одновременно выполняется несколько дочерних планов. Это позволяет избежать конкуренции и не тратить ресурсы на запуск дочернего плана в тех процессах, которые не будут его выполнять.

Кроме того, в отличие от обычного узла Append, в котором при использовании внутри параллельного плана могут быть только частичные дочерние планы, в узле Parallel Append могут быть как частичные, так и не частичные дочерние планы. Не частичные дочерние планы будут сканироваться всего одним процессом, поскольку при многократном сканировании будут выдаваться дублирующиеся результаты. Таким образом, для планов, включающих соединение нескольких результирующих наборов, можно добиться крупномодульного распараллеливания, даже когда эффективные частичные планы недоступны. К примеру, рассмотрим запрос к партиционированной таблице, который может быть эффективно реализован только с помощью индекса, не поддерживающего параллельное сканирование. Планировщик может выбрать Parallel Append для параллельного присоединения обычных планов Index Scan; каждое отдельное сканирование индекса будет полностью выполняться одним процессом, но разные сканирования могут проводиться одновременно разными процессами.

Для отключения этой функциональности можно воспользоваться параметром enable_parallel_append.

Советы по параллельным планам

Если ожидается, что для запроса будет выдан параллельный план, но этого не происходит, можно попробовать уменьшить значение parallel_setup_cost или parallel_tuple_cost. Конечно, этот план может оказаться медленнее, чем последовательный, который предпочитает планировщик, но не всегда. Если вы не получаете параллельный план даже с очень маленькими значениями этих параметров (например, после установки обоих в ноль), возможно, имеется какая-то причина тому, что планировщик не способен сгенерировать для вашего запроса параллельный план. Информацию о возможных причинах этого см. в разделах Когда может использоваться параллельное выполнение запросов? и Безопасность параллельного выполнения.

При выполнении параллельного плана можно воспользоваться командой EXPLAIN (ANALYZE, VERBOSE), чтобы просмотреть статистику по рабочим процессам для каждого узла плана. Это может быть полезно для определения того, равномерно ли распределяется работа между всеми узлами плана, а на более общем уровне — для понимания характеристик производительности плана.

Безопасность параллельного выполнения

Планировщик классифицирует операции, задействованные в запросе, как либо безопасные для параллельного выполнения, либо с ограниченным параллельным выполнением, либо небезопасные для параллельного выполнения. Безопасная для параллельного выполнения операция — это операция, которая не конфликтует с выполняющимся параллельным запросом. Операция с ограниченным параллельным выполнением — это операция, которая не может выполняться в параллельном рабочем процессе, но может выполняться в ведущем процессе во время выполнения параллельного запроса. Таким образом, операции с ограниченным параллельным выполнением никогда не проводятся ниже узла Gather или Gather Merge, но могут встречаться в других местах плана, содержащего такой узел. Небезопасная для параллельного выполнения операция — это операция, которая не может осуществляться во время выполнения параллельного запроса, даже в ведущем процессе. Когда запрос содержит что-то небезопасное для параллельного выполнения, такое выполнение для этого запроса полностью выключается.

Имеющими ограниченное параллельное выполнение всегда являются следующие операции:

Сканирование общих табличных выражений (CTE).

Сканирование временных таблиц.

Сканирование сторонних таблиц, если только обертка сторонних данных не имеет API IsForeignScanParallelSafe, который указывает на безопасность параллельного выполнения.

Узлы плана, к которым присоединен узел InitPlan.

Узлы плана, ссылающиеся на связанный узел SubPlan.

Пометки параллельности для функций и агрегатов

Планировщик не может автоматически определить, является ли пользовательская обычная или агрегатная функция безопасной ограниченной или небезопасной для параллельного выполнения, поскольку это потребовало бы предсказания каждой операции, которую такая функция могла бы осуществить. В целом, это равнозначно решению проблемы остановки, а следовательно, невозможно. Даже для простых функций, где это гипотетически возможно, мы не пытаемся это делать, поскольку это было бы затратно и не защищено от ошибок. Вместо этого все пользовательские функции считаются небезопасными для параллельного выполнения, если не отмечено обратное. При выполнении команды CREATE FUNCTION или ALTER FUNCTION можно при необходимости установить метку, указав PARALLEL SAFE (безопасна для параллельного выполнения), PARALLEL RESTRICTED (с ограниченным параллельным выполнением) или PARALLEL UNSAFE (небезопасные для параллельного выполнения). При выполнении команды CREATE AGGREGATE для параметра PARALLEL можно задать SAFE, RESTRICTED или UNSAFE в качестве соответствующего значения.

Функции и агрегаты должны помечаться как PARALLEL UNSAFE, если они пишут в базу данных, обращаются к последовательностям, меняют состояние транзакции, даже временно (например, функция PL/pgSQL, устанавливающая блок EXCEPTION для перехвата ошибок), или производят постоянные изменения параметров. Схожим образом функции должны помечаться как PARALLEL RESTRICTED, если они обращаются к временным таблицам, состоянию клиентского подключения, курсорам, подготовленным операторам или разнообразному локальному состоянию обслуживающего процесса, которое система не может синхронизировать между рабочими процессами. Например, по этой последней причине ограниченно параллельными являются функции setseed и random.

В целом, если функция помечена как безопасная, когда в действительности она ограниченно используемая или небезопасная, или если она помечена как ограниченно используемая, когда в действительности она небезопасная, такая функция может вызывать ошибки или выдавать неверные ответы при использовании в параллельном запросе. Функции на языке C/RUST при неправильной пометке теоретически могут проявлять полностью неопределенное поведение, поскольку система никак не может защитить себя от произвольного кода C, но в большинстве случаев результат будет не хуже, чем у любой другой функции. При возникновении сомнений лучше всего помечать функции как UNSAFE.

Если функция, выполняемая в параллельном рабочем процессе, требует блокировки, которыми не владеет ведущий процесс, например, запрашивая таблицу, не фигурирующую в запросе, эти блокировки будут освобождены после завершения рабочего процесса, а не в конце транзакции. Если вы пишете функции, которая это делает, и для вас важна эта разница в поведении, помечайте такие функции как PARALLEL RESTRICTED, чтобы они гарантированно выполнялись только в ведущем процессе.

Обратите внимание, что планировщик запросов не рассматривает отсроченное вычисление ограниченно параллельных обычных или агрегатных функций, задействованных в запросе, чтобы получить лучший план. Поэтому, к примеру, если предложение WHERE, применяемое к конкретной таблице, является ограниченно параллельным, планировщик запроса не будет рассматривать проведение сканирования этой таблицы в параллельной части плана. В некоторых случаях возможно (и, вероятно, даже эффективно) включить сканирование этой таблицы в параллельную часть запроса и отложить вычисление предложения WHERE, чтобы оно происходило выше узла Gather. Однако планировщик этого не делает.

PL/pgSQL — процедурный язык SQL

Обзор

PL/pgSQL — это загружаемый процедурный язык для системы управления базами данных QHB. Цели разработки PL/pgSQL заключались в создании загружаемого процедурного языка, который

может быть использован для создания функций, процедур и триггеров,

добавляет структуры управления к языку SQL,

может выполнять сложные вычисления,

наследует все пользовательские типы, функции, процедуры и операторы,

может быть определен для сервера как доверенный язык,

прост в использовании.

Функции, созданные с помощью PL/pgSQL, можно использовать везде, где могут использоваться встроенные функции. Например, можно создавать сложные функции условного вычисления, а затем использовать их для определения операторов или в индексных выражениях.

В QHB PL/pgSQL устанавливается по умолчанию. Однако это все еще загружаемый модуль, поэтому администраторы, особенно заботящиеся о безопасности, могут при желании его удалить.

Преимущества использования PL/pgSQL

SQL — это язык QHB, и большинство других реляционных баз данных используют его в качестве языка запросов. Он переносим и легок в изучении. Но каждый оператор SQL должен выполняться сервером баз данных индивидуально.

Это означает, что ваше клиентское приложение должно отправлять каждый запрос на сервер баз данных, ждать его обработки, получать и обрабатывать результаты, выполнять некоторые вычисления, а затем отправлять дополнительные запросы на сервер. Все это требует межпроцессного взаимодействия, а также повлечет за собой нагрузку на сеть, если ваш клиент находится не на одном компьютере с сервером баз данных.

С PL/pgSQL вы можете сгруппировать блок вычислений и серию запросов внутри сервера баз данных, тем самым получая мощь процедурного языка и простоту использования SQL, но при этом значительно экономя накладные расходы на клиент- серверное взаимодействие.

Дополнительные обходы между клиентом и сервером исключены

Промежуточные результаты, которые не нужны клиенту, необязательно маршализовать или передавать между сервером и клиентом

Можно избежать нескольких циклов синтаксического анализа запроса

Это может дать значительное увеличение производительности по сравнению с приложением, которое не использует хранимые функции.

Кроме того, с PL/pgSQL можно использовать все типы данных, операторы и функции SQL.

Поддерживаемые типы данных аргумента и результата

Функции, написанные на PL/pgSQL, могут принимать в качестве аргументов любой скалярный тип данных или массив, поддерживаемый сервером, и возвращать результат любого из этих типов. Также они могут принимать или возвращать любой составной тип (тип строки), заданный по имени. Кроме того, возможно объявить функцию PL/pgSQL как принимающую record, то есть на вход будет приниматься любой составной тип, или как возвращающую record, то есть результатом является тип строки, столбцы которой определяются спецификацией в вызывающем запросе, как описано в подразделе Табличные функции.

С помощью маркера VARIADIC функции PL/pgSQL можно объявить как принимающие переменное число аргументов. Это работает точно так же, как и для функций SQL, как описано в разделе Функции SQL с переменным числом аргументов.

Также функции PL/pgSQL можно объявить как принимающие и возвращающие полиморфные типы, описанные в подразделе Полиморфные типы, вследствие чего фактические типы данных, обрабатываемые подобной функцией, могут варьироваться от вызова к вызову. Пример приводится в подразделе Объявление параметров функции.

Кроме того, функции PL/pgSQL можно объявить как возвращающие «множество» (или таблицу) любого типа данных, который может быть возвращен как один экземпляр. Такая функция генерирует свои выходные данные, выполняя RETURN NEXT для каждого желаемого элемента результирующего набора или используя RETURN QUERY для вывода результата вычисления запроса.

Наконец, можно объявить функцию PL/pgSQL как возвращающую void, если она не имеет полезного возвращаемого значения. (Как вариант, в этом случае ее можно написать в виде процедуры.)

Функции PL/pgSQL также могут быть объявлены с выходными параметрами вместо явной спецификации возвращаемого типа. Это не добавляет данному языку никаких фундаментальных возможностей, но часто бывает удобно, особенно для возврата нескольких значений. Кроме того, вместо RETURNS SETOF можно использовать нотацию RETURNS TABLE.

Конкретные примеры приведены в подразделах Объявление параметров функции и Возврат из функции.

Структура PL/pgSQL

Функции, написанные на PL/pgSQL, определяются на сервере путем выполнения команд CREATE FUNCTION. Такая команда обычно выглядит примерно так:

CREATE FUNCTION somefunc(integer, text) RETURNS integer
AS 'текст тела функции'
LANGUAGE plpgsql;

Если рассматривать тело функции относительно CREATE FUNCTION, то это просто строковый литерал. Часто для написания тела функции полезно использовать знаки доллара (см. подраздел Строковые константы с экранированием знаками доллара), а не обычный синтаксис с апострофами. Без использования знаков доллара любые апострофы или обратные слэши в теле функции следует экранировать путем их удвоения. Почти во всех примерах в этой главе для тел функций используются литералы в долларовых кавычках.

PL/pgSQL — это блочно-структурированный язык. Полный текст тела функции должен быть блоком. Блок определяется как:

[ <<метка>> ]
[ DECLARE
    объявления ]
BEGIN
    операторы
END [ метка ];

Каждое объявление и каждый оператор в блоке заканчиваются точкой с запятой. Блок, находящийся внутри другого блока, должен иметь точку с запятой после END, как показано выше; однако последний END, завершающий тело функции, точки с запятой не требует.

Совет
Распространенная ошибка — писать точку с запятой сразу после BEGIN. Это неверно и приведет к синтаксической ошибке.

метка нужна только в том случае, если вы хотите идентифицировать блок для использования в операторе EXIT или указать имена переменных, объявленных в этом блоке. Если метка задается после END, она должна совпадать с меткой в начале блока.

Все ключевые слова нечувствительны к регистру. Идентификаторы неявно переводятся в нижний регистр, если они не заключены в кавычки, как в обычных командах SQL.

Комментарии в коде PL/pgSQL работают так же, как и в обычном SQL. Комментарий начинается после двойного дефиса (--) и продолжается до конца строки. Блочный комментарий начинается с символов /* и продолжается до зеркальных символов */. Блочные комментарии могут быть вложенными.

Любой оператор в разделе операторов блока может быть субблоком. Субблоки можно использовать для логической группировки или для локализации переменных в небольшой группе операторов. Переменные, объявленные в субблоке, скрывают на время его выполнения любые переменные внешних блоков с теми же именами; но ко внешним переменным все равно можно обратиться, если дополнить их имена меткой их блока. Например:

CREATE FUNCTION somefunc() RETURNS integer AS $$
<< outerblock >>
DECLARE
    quantity integer := 30;
BEGIN
    RAISE NOTICE 'Quantity here is %', quantity;  -- Выводится 30
    quantity := 50;
    --
    -- Создать субблок
    --
    DECLARE
        quantity integer := 80;
    BEGIN
        RAISE NOTICE 'Quantity here is %', quantity;  -- Выводится 80
        RAISE NOTICE 'Outer quantity here is %', outerblock.quantity;  -- Выводится 50
    END;

    RAISE NOTICE 'Quantity here is %', quantity;  -- Выводится 50

    RETURN quantity;
END;
$$ LANGUAGE plpgsql;

Примечание
На самом деле существует скрытый «внешний блок», окружающий тело любой функции PL/pgSQL. Этот блок предоставляет объявления параметров функции (если таковые имеются), а также некоторые специальные переменные, например FOUND (см. подраздел Получение статуса результата). Внешний блок помечен именем функции, подразумевая, что параметры и специальные переменные могут быть дополнены именем функции.

Важно не путать использование BEGIN/END для группировки операторов в PL/pgSQL с одноименными командами SQL для управления транзакциями. В PL/pgSQL BEGIN/END предназначены только для группировки; они не начинают и не заканчивают транзакцию. Информацию об управлении транзакциями в PL/pgSQL см. в разделе Управление транзакциями. Кроме того, блок, содержащий предложение EXCEPTION, по сути образует субтранзакцию, которую можно откатить, не затрагивая внешнюю транзакцию. Подробнее это описано в подразделе Перехват ошибок.

Объявления

Все переменные, используемые в блоке, должны быть объявлены в разделе объявлений этого блока. (Единственное исключение состоит в том, что переменная цикла FOR, повторяющегося по диапазону целочисленных значений, автоматически объявляется как целочисленная переменная, а переменная цикла FOR, повторяющегося по результату курсора, аналогично автоматически объявляется как переменная записи.)

Переменные PL/pgSQL могут иметь любой тип данных SQL, например, integer, varchar и char.

Несколько примеров объявлений переменных:

user_id integer;
quantity numeric(5);
url varchar;
myrow tablename%ROWTYPE;
myfield tablename.columnname%TYPE;
arow RECORD;

Общий синтаксис объявления переменной:

имя [ CONSTANT ] тип [ COLLATE имя_правила_сортировки ] [ NOT NULL ] [ { DEFAULT | := | = } выражение ];

Предложение DEFAULT, если таковое имеется, задает начальное значение, назначенное переменной при вводе блока. Если предложение DEFAULT не задано, переменная инициализируется SQL-значением NULL. Параметр CONSTANT предотвращает присвоение переменной после инициализации, чтобы ее значение оставалось постоянным на протяжении всего блока. Параметр COLLATE задает правило сортировки, которое будет применяться для переменной (см. подраздел Сортировка переменных PL/pgSQL). Если указано NOT NULL, то присвоение значения NULL вызовет ошибку во время выполнения. Все переменные, объявленные как NOT NULL должны иметь значение по умолчанию, отличное от NULL. Вместо PL/SQL-совместимого сочетания := можно использовать просто знак равенства (=).

Значение переменной по умолчанию вычисляется и присваивается переменной каждый раз, когда вводится блок (а не только один раз за вызов функции). Так, например, присвоение функции now() переменной типа timestamp приведет к тому, что переменная будет содержать время текущего вызова функции, а не время, когда функция была предварительно скомпилирована.

Примеры:

quantity integer DEFAULT 32;
url varchar := 'http://mysite.com';
user_id CONSTANT integer := 10;

Объявление параметров функции

Параметры, передаваемые в функции, именуются с помощью идентификаторов $1, $2 и т. д. При желании с целью повышения удобочитаемости для $n имен параметров могут быть объявлены псевдонимы. Затем для обращения к значению параметра можно использовать псевдоним или числовой идентификатор.

Есть два способа создать псевдоним. Предпочтительным способом является присвоение имени параметру в команде CREATE FUNCTION, например:

CREATE FUNCTION sales_tax(subtotal real) RETURNS real AS $$
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

Другой способ — явно объявить псевдоним, используя следующий синтаксис объявления:

имя ALIAS FOR $n;

Тот же пример в этом стиле выглядит так:

CREATE FUNCTION sales_tax(real) RETURNS real AS $$
DECLARE
    subtotal ALIAS FOR $1;
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

Примечание
Эти два примера не вполне равнозначны. В первом случае на subtotal можно ссылаться как на sales_tax.subtotal, но во втором случае это невозможно. (Если бы мы прикрепили метку к внутреннему блоку, subtotal можно было бы дополнить этой меткой).

Еще несколько примеров:

CREATE FUNCTION instr(varchar, integer) RETURNS integer AS $$
DECLARE
    v_string ALIAS FOR $1;
    index ALIAS FOR $2;
BEGIN
    -- ряд вычислений, использующих v_string и index
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION concat_selected_fields(in_t sometablename) RETURNS text AS $$
BEGIN
    RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
END;
$$ LANGUAGE plpgsql;

Когда функция PL/pgSQL объявляется с выходными параметрами, им даются имена $n и необязательные псевдонимы точно так же, как обычным входным параметрам. Выходной параметр по сути является переменной, которая начинается с NULL; значение ей должно присваиваться во время выполнения функции. Возвращается конечное значение этого параметра. Например, расчет налога с продаж также можно сделать следующим образом:

CREATE FUNCTION sales_tax(subtotal real, OUT tax real) AS $$
BEGIN
    tax := subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

Обратите внимание, что мы пропустили RETURNS real — мы могли бы включить его, но это было бы излишним.

Чтобы вызвать функцию с параметрами OUT, при ее вызове опустите выходные параметры:

SELECT sales_tax(100.00);

Выходные параметры наиболее полезны при возврате нескольких значений. Тривиальный пример:

CREATE FUNCTION sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
BEGIN
    sum := x + y;
    prod := x * y;
END;
$$ LANGUAGE plpgsql;

SELECT * FROM sum_n_product(2, 4);
 sum | prod
-----+------
   6 |    8

Как было сказано в подразделе Функции SQL с выходными параметрами, это по сути создает анонимный тип записи для результатов функции. Если задано предложение RETURNS, оно должно выглядеть как RETURNS record.

Это работает и для процедур, например:

CREATE PROCEDURE sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
BEGIN
    sum := x + y;
    prod := x * y;
END;
$$ LANGUAGE plpgsql;

При вызове процедуры следует задавать все параметры. Вызывая процедуру из простого SQL, вместо выходных параметров можно указать NULL:

CALL sum_n_product(2, 4, NULL, NULL);
 sum | prod
-----+------
   6 |    8

Однако при вызове процедуры из PL/pgSQL для каждого выходного параметра нужно написать переменную; эта переменная будет получать результат вызова. Подробную информацию см. в подразделе Вызов процедуры.

Другой способ объявить функцию PL/pgSQL — использовать RETURNS TABLE, например:

CREATE FUNCTION extended_sales(p_itemno int)
RETURNS TABLE(quantity int, total numeric) AS $$
BEGIN
    RETURN QUERY SELECT s.quantity, s.quantity * s.price FROM sales AS s
                 WHERE s.itemno = p_itemno;
END;
$$ LANGUAGE plpgsql;

Это в точности эквивалентно объявлению одного или нескольких параметров OUT и указанию RETURNS SETOF некий_тип.

Когда возвращаемый тип функции PL/pgSQL объявлен как полиморфный (см. подраздел Полиморфные типы), создается специальный параметр $0. Его тип данных является фактическим типом возвращаемого значения функции, выведенным из фактических типов входных параметров. Это позволяет функции получить доступ к ее фактическому возвращаемому типу, как показано в подразделе Копирование типов. $0 инициализируется значением NULL и может быть изменен функцией, поэтому при желании его можно использовать для хранения возвращаемого значения, хотя это необязательно. $0 также можно присвоить псевдоним. Например, эта функция работает с любым типом данных, который имеет оператор +:

CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
RETURNS anyelement AS $$
DECLARE
    result ALIAS FOR $0;
BEGIN
    result := v1 + v2 + v3;
    RETURN result;
END;
$$ LANGUAGE plpgsql;

Тот же эффект можно получить, объявив один или несколько выходных параметров как имеющие полиморфный тип. В этом случае специальный параметр $0 не используется; той же цели служат сами выходные параметры. Например:

CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement,
                                 OUT sum anyelement)
AS $$
BEGIN
    sum := v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

На практике может быть более полезно объявить полиморфную функцию, используя семейство типов anycompatible, чтобы входные аргументы автоматически преобразовывались в общий тип. Например:

CREATE FUNCTION add_three_values(v1 anycompatible, v2 anycompatible, v3 anycompatible)
RETURNS anycompatible AS $$
BEGIN
    RETURN v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

С функцией из этого примера сработает следующий вызов:

SELECT add_three_values(1, 2, 4.7);

автоматически преобразовав целочисленные аргументы в числовые. В функции, использующей anyelement, потребовалось бы приводить все три аргумента к одному вручную.

ALIAS

новое_имя ALIAS FOR старое_имя;

Синтаксис ALIAS является более общим, чем предполагается в предыдущем подразделе: псевдоним можно объявить для любой переменной, а не только для параметров функции. Основное практическое применение этого состоит в назначении другого имени для переменных с предопределенными именами, например, NEW или OLD в триггерной функции.

Примеры:

DECLARE
  prior ALIAS FOR old;
  updated ALIAS FOR new;

Поскольку ALIAS создает два разных способа именования одного объекта, неограниченное использование может привести к путанице. Лучше всего использовать его только с целью замещения предопределенных имен.

Копирование типов

переменная%TYPE

%TYPE предоставляет тип данных переменной или столбца таблицы. Это можно использовать для объявления переменных, которые будут содержать значения базы данных. Например, предположим, что у вас есть столбец с именем user_id в таблице users. Чтобы объявить переменную с тем же типом данных, что и users.user_id вы пишете:

user_id users.user_id%TYPE;

Используя %TYPE, необязательно знать тип данных структуры, на которую вы ссылаетесь, и, самое главное, если тип данных ссылочного элемента в будущем изменится (например, вы поменяете тип user_id с integer на real), возможно, вам не понадобится менять определение функции.

Конструкция %TYPE особенно полезна в полиморфных функциях, поскольку типы данных, необходимые для внутренних переменных, могут меняться от вызова к вызову. Подходящие переменные можно создать путем применения %TYPE к аргументам функции или местозаполнителям результата.

Типы строки

имя имя_таблицы%ROWTYPE;
имя имя_составного_типа;

Переменная составного типа называется переменной строки (или переменной типа строки). Такая переменная может содержать целую строку результата запроса SELECT или FOR, если набор столбцов этого запроса соответствует объявленному типу переменной. Доступ к отдельным полям значения строки осуществляется с помощью обычной записи с точкой, например rowvar.field.

Переменную строки можно объявить с тем же типом, что и строки существующей таблицы или представления, используя нотацию имя_таблицы%ROWTYPE, либо путем указания имени составного типа. (Поскольку каждая таблица имеет связанный составной тип с тем же именем, в QHB не имеет значения, пишете ли вы %ROWTYPE или нет. Но форма с %ROWTYPE лучше переносится.)

Параметрами функции могут быть составные типы (полные строки таблицы). В этом случае переменной строки будет соответствующий идентификатор $n, и из него можно выбрать поля, например $1.user_id.

Далее идет пример использования составных типов. table1 и table2 являются существующими таблицами, имеющими как минимум упомянутые поля:

CREATE FUNCTION merge_fields(t_row table1) RETURNS text AS $$
DECLARE
    t2_row table2%ROWTYPE;
BEGIN
    SELECT * INTO t2_row FROM table2 WHERE ... ;
    RETURN t_row.f1 || t2_row.f3 || t_row.f5 || t2_row.f7;
END;
$$ LANGUAGE plpgsql;

SELECT merge_fields(t.*) FROM table1 t WHERE ... ;

Типы записи

имя RECORD;

Переменные записи аналогичны переменным типа строки, но не имеют предопределенной структуры. Они принимают фактическую структуру строки, назначенной им при выполнении команды SELECT или FOR. Подструктура переменной записи может меняться каждый раз, когда ей присваивается значение. Вследствие этого до тех пор, пока переменной записи не будет присвоено первое значение, она не имеет подструктуры, и любая попытка доступа к полю в ней вызовет ошибку во время выполнения.

Обратите внимание, что RECORD не является истинным типом данных, а только местозаполнителем. Следует также понимать, что когда функция PL/pgSQL объявляется как возвращающая тип record, это не совсем то же самое, что и переменная записи, хотя такая функция может использовать переменную записи для хранения своего результата. В обоих случаях фактическая структура строки неизвестна при написании функции, но для функции, возвращающей record, фактическая структура определяется при синтаксическом анализе вызывающего запроса, тогда как переменная записи может изменить свою структуру строки на ходу.

Сортировка переменных PL/pgSQL

Когда функция PL/pgSQL имеет один или несколько параметров с сортируемыми типами данных, правило сортировки определяется для каждого вызова функции в зависимости от правил сортировки, назначенных фактическим аргументам, как описано в разделе Поддержка правил сортировки. Если правило сортировки определено успешно (т. е. среди аргументов нет конфликтов между неявными правилами сортировки), то все сортируемые параметры обрабатываются как неявно имеющие это правило. Это повлияет на поведение зависящих от правила сортировки операций внутри функции. Например, рассмотрим следующую функцию:

CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b;
END;
$$ LANGUAGE plpgsql;

SELECT less_than(text_field_1, text_field_2) FROM table1;
SELECT less_than(text_field_1, text_field_2 COLLATE "C") FROM table1;

В первом случае less_than будет использовать для сравнения text_field_1 и text_field_2 общее правило сортировки, в то время как во втором случае этот оператор будет использовать правило сортировки C.

Более того, предполагается, что идентифицированное правило сортировки также будет использоваться при сортировке любых локальных переменных сортируемых типов. Таким образом, эта функция не будет работать по-другому, если написать ее как

CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
DECLARE
    local_a text := a;
    local_b text := b;
BEGIN
    RETURN local_a < local_b;
END;
$$ LANGUAGE plpgsql;

Если параметры с сортируемыми типами данных отсутствуют или для них нельзя определить общее правило сортировки, то параметры и локальные переменные используют правила сортировки по умолчанию для своего типа данных (обычно это правила сортировки по умолчанию для базы данных, но могут отличаться для переменных типа домена).

Локальная переменная сортируемого типа данных может иметь другое правило сортировки, связываемое с ней путем включения параметра COLLATE в ее объявление, например:

DECLARE
    local_a text COLLATE "en_US";

Этот параметр переопределяет правило сортировки, которое в противном случае было бы установлено для этой переменной в соответствии с приведенными выше правилами.

Кроме того, разумеется, если требуется применить в конкретной операции конкретное правило сортировки, то внутри функции можно написать явные предложения COLLATE. Например:

CREATE FUNCTION less_than_c(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b COLLATE "C";
END;
$$ LANGUAGE plpgsql;

Это переопределяет правила сортировки, связанные со столбцами таблицы, параметрами или локальными переменными, используемыми в выражении, аналогично тому, как это происходит в простой команде SQL.

Выражения

Все выражения, используемые в операторах PL/pgSQL, обрабатываются с использованием основного SQL-исполнителя сервера. Например, когда вы пишете оператор PL/pgSQL как

IF выражение THEN ...

то PL/pgSQL вычислит выражение, отправив на основной движок SQL следующий запрос:

SELECT выражение

При формировании команды SELECT любые вхождения имен переменных PL/pgSQL заменяются параметрами запроса, как описано в подразделе Подстановка переменных. Это позволяет составлять план запроса для SELECT только один раз, а затем повторно использовать его для последующих вычислений с различными значениями переменных. Таким образом, на самом деле при первом использовании выражения по сути происходит выполнение команды PREPARE. Например, если мы объявили две целочисленные переменные x и y, и мы напишем

IF x < y THEN ...

то, что происходит за кулисами, равнозначно

PREPARE имя_оператора(integer, integer) AS SELECT $1 < $2;

и затем этот подготовленный оператор выполняется с EXECUTE при каждом выполнении оператора IF с текущими значениями переменных PL/pgSQL, подставляемыми в качестве значений параметров. Обычно эти детали неважны для пользователя PL/pgSQL, но их полезно знать при попытке диагностировать проблему. Более подробная информация представлена в подразделе Кэширование плана.

Поскольку выражение преобразуется в команду SELECT, оно может содержать те же предложения, что и обычная команда SELECT, за исключением предложений UNION, INTERSECT и EXCEPT верхнего уровня. Таким образом, например, можно проверить, является ли таблица непустой:

IF count(*) > 0 FROM my_table THEN ...

так как выражение между IF и THEN анализируется, как если бы это была команда SELECT count(*) > 0 FROM my_table. SELECT должна выдавать один столбец и не более одной строки. (Если она не выдает ни одной строки, считается, что результат равен NULL.)

Основные операторы

В этом и следующих разделах описываются все типы операторов, которые явно понятны PL/pgSQL. Все, что не распознается как один из этих типов операторов, считается командой SQL и отправляется на выполнение главному движку базы данных, как описано в подразделе Выполнение команд SQL.

Присваивание

Присвоение значения переменной PL/pgSQL записывается следующим образом:

переменная { := | = } выражение;

Как говорилось ранее, выражение в таком операторе вычисляется с помощью команды SQL SELECT, передаваемой основному движку базы данных. Выражение должно выдавать одно значение (возможно, значение строки, если это переменная строки или записи). Целевая переменная может быть простой переменной (необязательно дополненной именем блока), полем строки или переменной записи либо элементом или срезом целевого массива. Вместо PL/SQL-совместимого сочетания := можно использовать просто знак равенства (=).

Если тип данных результата выражения не совпадает с типом данных переменной, это значение будет преобразовано, как если бы использовалось приведение присваивание (см. раздел Хранимое значение). Если для пары задействованных типов данных приведение присваивания неизвестно, интерпретатор PL/pgSQL попытается преобразовать значение результата в текстовом виде, то есть применяя функцию вывода типа результата, а следом функцию ввода типа переменной. Обратите внимание, что это может привести к ошибкам во время выполнения, сгенерированным функцией ввода, если строковая форма значения результата не приемлема для функции ввода.

Примеры:

tax := subtotal * 0.06;
my_record.user_id := 20;
my_array[j] := 20;
my_array[1:3] := array[1,2,3];
complex_array[n].realpart = 12.3;

Выполнение команд SQL

В целом, любую команду SQL, которая не возвращает строки, можно выполнить в функции PL/pgSQL, просто написав эту команду. Например, можно создать и заполнить таблицу, написав

CREATE TABLE mytable (id int primary key, data text);
INSERT INTO mytable VALUES (1,'one'), (2,'two');

Если же команда возвращает строки (например SELECT или INSERT/UPDATE/ DELETE с RETURNING), есть два способа ее выполнить. Если команда возвращает максимум одну строку или вам интересна только первая строка вывода, напишите команду как обычно, но добавьте предложение INTO для захвата вывода, как описано в подразделе Выполнение команды с результатом в одну строку. Чтобы обработать все строки вывода, напишите команду как источник данных для цикла FOR, как описано в подразделе Цикл по результатам запроса.

Обычно недостаточно просто выполнить статически определенные команды SQL. Как правило, желательно, чтобы команда использовала разные значения данных или даже менялась в более фундаментальном смысле, например каждый раз использовала разные имена таблиц. Опять же, это можно сделать двумя способами, в зависимости от ситуации.

Значения переменных PL/pgSQL могут автоматически вставляться в оптимизированные команды SQL: SELECT, INSERT, UPDATE, DELETE и определенные служебные команды, содержащие одну из вышеперечисленных команды, например EXPLAIN и CREATE TABLE ... AS SELECT. Любое имя переменной PL/pgSQL, встречающееся в тексте этих команд, заменяется параметром запроса, а затем текущее значение этой переменной подставляется в качестве значения параметра во время выполнения. Это полностью совпадает с описанной ранее обработкой для выражений; подробную информацию см. в подразделе Подстановка переменных.

При выполнении оптимизируемой команды SQL таким способом PL/pgSQL может кэшировать и повторно использовать план выполнения команды, как описано в подразделе Кэширование плана.

Неоптимизируемые команды SQL (также называемые служебными командами) неспособны принимать параметры запроса. Поэтому в таких командах автоматическая подстановка переменных PL/pgSQL не работает. Чтобы включить непостоянный текст в служебную команду, выполняемую из PL/pgSQL, следует собрать служебную команду в виде строки, а затем выполнить ее с EXECUTE, как описано в подразделе Выполнение динамических команд.

Также EXECUTE следует использовать, если вы хотите модифицировать команду иным способом, нежели просто передавая ей значения данных, например, изменить имя таблицы.

Иногда бывает полезно вычислить выражение или запрос SELECT, но отбросить результат, например, при вызове функции с побочными эффектами, не имеющей полезного значения результата. Чтобы сделать это в PL/pgSQL, воспользуйтесь оператором PERFORM:

PERFORM запрос;

Эта команда выполняет запрос и отбрасывает результат. Напишите запрос тем же способом, что и в команде SQL SELECT, но замените исходное ключевое слово SELECT на PERFORM. Для запросов WITH используйте PERFORM, а затем поместите запрос в скобки. (В этом случае запрос может вернуть только одну строку.) Переменные PL/pgSQL будут подставляться в запрос так же, как описано выше, и план кэшируется таким же образом. Кроме того, специальная переменная FOUND устанавливается в true, если запрос выдал хотя бы одну строку, или в false, если он не выдал ни одной строки (см. подраздел Получение статуса результата).

Примечание
Можно было бы ожидать, что к этому же результату приведет написание SELECT напрямую, но в настоящее время единственным приемлемым способом сделать это является PERFORM. Команда SQL, которая может возвращать строки, например SELECT, будет отклонена с ошибкой, если в ней нет предложения INTO, как описано в следующем подразделе.

Пример:

PERFORM create_mv('cs_session_page_requests_mv', my_query);

Выполнение команды с результатом в одну строку

Результат команды SQL, выдающей одну строку (возможно, из нескольких столбцов), может быть присвоен переменной записи, переменной типа строки или списку скалярных переменных. Это делается путем написания базовой команды SQL и добавления предложения INTO. Например,

SELECT выражения_select INTO [STRICT] цель FROM ...;
INSERT ... RETURNING выражения INTO [STRICT] цель;
UPDATE ... RETURNING выражения INTO [STRICT] цель;
DELETE ... RETURNING выражения INTO [STRICT] цель;

где цель может быть переменной записи, переменной строки или разделенным запятыми списком простых переменных и полей записи/строки. В остальную часть запроса (то есть везде, кроме предложения INTO) будут подставляться переменные PL/pgSQL, как описано выше, и план будет кэширован таким же образом. Это работает для SELECT, INSERT/UPDATE/DELETE с RETURNING и определенных служебных команд, которые возвращают наборы строк (например EXPLAIN). За исключением предложения INTO, это такая же команда SQL, как если бы она была написана вне PL/pgSQL.

Совет
Обратите внимание, что эта интерпретация SELECT с INTO сильно отличается от обычной команды SELECT INTO QHB, в которой целью INTO является вновь созданная таблица. Если вы хотите создать таблицу из результата SELECT внутри функции PL/pgSQL, используйте синтаксис CREATE TABLE... AS SELECT.

Если в качестве цели используется строка или список переменных, столбцы результата команды должны точно соответствовать структуре цели в отношении количества и типов данных, иначе произойдет ошибка во время выполнения. Когда целью является переменная записи, она автоматически конфигурируется в тип строки столбцов результата команды.

Предложение INTO может находиться практически в любом месте команды SQL. Обычно оно записывается либо непосредственно перед или сразу после списка выражения_select в команде SELECT, либо в конце команды для других типов команд. Рекомендуется следовать этому соглашению на случай, если анализатор PL/pgSQL в будущих версиях станет более строгим.

Если в предложении INTO не указан STRICT, то цели будет присвоена первая строка, возвращаемая запросом, или на NULL, если команда не вернула ни одной строки. (Обратите внимание, что «первая строка» не определяется четко, если вы не использовали ORDER BY). Все строки результата после первой отбрасываются. Чтобы определить, была ли возвращена строка, можно проверить специальную переменную FOUND (см. подраздел Получение статуса результата):

SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
    RAISE EXCEPTION 'employee % not found', myname;
END IF;

Если указан параметр STRICT, команда должна возвращать ровно одну строку, иначе будет выдана ошибка во время выполнения: либо NO_DATA_FOUND (нет строк), либо TOO_MANY_ROWS (более одной строки). Если хотите найти эту ошибку, можно использовать блок исключения, например:

BEGIN
    SELECT * INTO STRICT myrec FROM emp WHERE empname = myname;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE EXCEPTION 'employee % not found', myname;
        WHEN TOO_MANY_ROWS THEN
            RAISE EXCEPTION 'employee % not unique', myname;
END;

Успешное выполнение команды с помощью STRICT всегда устанавливает FOUND в true.

Для INSERT/UPDATE/DELETE с RETURNING PL/pgSQL выдает ошибку при более чем одной возвращенной строки, даже если STRICT не указан. Это связано с тем, что здесь нет параметра вроде ORDER BY, с помощью которого можно определить, какая затронутая строка должна быть возвращена.

Если для функции включен print_strict_params, то при возникновении ошибки из-за несоблюдения требований STRICT часть DETAIL в сообщении об ошибке будет содержать информацию о параметрах, переданных в команду. Изменить значение print_strict_params для всех функций можно, установив параметр plpgsql.print_strict_params, хотя это подействует только на последующие компиляции функций. Также его можно включить для каждой функции по отдельности, используя параметр компилятора, например:

CREATE FUNCTION get_userid(username text) RETURNS int
AS $$
#print_strict_params on
DECLARE
userid int;
BEGIN
    SELECT users.userid INTO STRICT userid
        FROM users WHERE users.username = get_userid.username;
    RETURN userid;
END
$$ LANGUAGE plpgsql;

При сбое эта функция может выдавать сообщение об ошибке, например:

ERROR:  query returned no rows
DETAIL:  parameters: $1 = 'nosuchuser'
CONTEXT:  PL/pgSQL function get_userid(text) line 6 at SQL statement
-- ОШИБКА: параметр не вернул ни одной строки
-- ДЕТАЛИЗАЦИЯ: параметры: $1 = 'пользователь не найден'
-- КОНТЕКСТ: строка 6 функции PL/pgSQL get_userid(text) в операторе SQL

Примечание
Параметр STRICT соответствует поведению SELECT INTO и связанных операторов в Oracle PL/SQL.

Выполнение динамических команд

Зачастую возникает необходимость генерировать динамические команды внутри функций PL/pgSQL, то есть команды, которые при каждом выполнении будут затрагивать разные таблицы или разные типы данных. Обычные попытки PL/pgSQL кэшировать планы для команд (как описано в подразделе Кэширование плана) в таких сценариях работать не будут. Для решения такого рода проблем предусмотрен оператор EXECUTE:

EXECUTE командная-строка [ INTO [STRICT] цель ] [ USING выражение [, ... ] ];

где командная-строка представляет собой выражение, выдающее строку (типа text), содержащую выполняемую команду. Необязательная цель является переменной записи, переменной строки или разделенным запятыми списком простых переменных и полей записи/строки, в которых будут сохраняться результаты команды. Необязательные выражения USING предоставляют значения для вставки в команду.

Подстановка переменных PL/pgSQL в вычисляемой командной строке не производится. Все требуемые значения переменных должны быть вставлены в командную строку при ее создании, либо можно использовать параметры, как описано ниже.

Также для команд, выполняемых через EXECUTE, не проводится кэширования плана. Вместо этого команда всегда планируется при каждом выполнении оператора. Таким образом, командная строка может быть динамически создана внутри функции для выполнения действий над различными таблицами и столбцами.

Предложение INTO указывает, куда должны быть назначены результаты команды SQL, возвращающей строки. Если указана переменная строки или список переменных, они должны точно соответствовать структуре результатов команды; если используется переменная записи, она автоматически конфигурируется, чтобы соответствовать структуре результатов. Если возвращается несколько строк, только первая из них будет назначена переменной INTO. Если не возвращается ни одной строки, переменной (или переменным) INTO присваивается NULL. Если предложение INTO не указано, результаты запроса отбрасываются.

Если задан параметр STRICT, то команда должна создать ровно одну строку, иначе выдается сообщение об ошибке.

В командной строке можно использовать значения параметров, ссылки на которые в команде обозначаются как $1, $2 и т. д. Эти символы обращаются к значениям, предоставленным в предложении USING. Этот метод часто предпочтительнее вставки значений данных в командную строку в виде текста: он позволяет избежать издержек во время выполнения при преобразовании значений в текст и обратно, и он гораздо менее подвержен атакам с использованием SQL-инъекций, поскольку нет необходимости использовать кавычки или экранирующие символы. Пример:

EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2'
   INTO c
   USING checked_user, checked_date;

Обратите внимание, что символы параметров могут использоваться только для значений данных — если вы хотите использовать динамически определяемые имена таблиц или столбцов, их следует вставлять в командную строку в текстовом виде. Например, если предыдущий запрос необходимо выполнить для динамически выбранной таблицы, можно сделать следующее:

EXECUTE 'SELECT count(*) FROM '
    || quote_ident(tabname)
    || ' WHERE inserted_by = $1 AND inserted <= $2'
   INTO c
   USING checked_user, checked_date;

Более чистый подход заключается в использовании указания %I функции format(), чтобы вставить имена таблиц или столбцов, которые будут автоматически заключаться в кавычки:

EXECUTE format('SELECT count(*) FROM %I '
   'WHERE inserted_by = $1 AND inserted <= $2', tabname)
   INTO c
   USING checked_user, checked_date;

(Этот пример основывается на правиле SQL, гласящем, что строковые литералы, разделенные символом перевода строки, неявно конкатенируются.)

Другое ограничение на символы параметров заключается в том, что они работают только в оптимизируемых командах SQL (SELECT, INSERT, UPDATE, DELETE и некоторые другие команды, содержащие одну из вышеперечисленных.) В других типах операторов (обычно называемых служебными операторами) следует вставлять значения в текстовом виде, даже если это просто значения данных.

EXECUTE с простой константой командной строки и некоторыми параметрами USING, как в первом примере выше, функционально равнозначна написанию команды непосредственно в PL/pgSQL и позволяет автоматической заменять значениями переменные PL/pgSQL. Важное отличие заключается в том, что EXECUTE будет заново планировать команду при каждом выполнении, генерируя план, специфичный для текущих значений параметров, тогда как PL/pgSQL может создать общий план и кэшировать его для повторного использования. В ситуациях, когда лучший план сильно зависит от значений параметров, может быть полезно использовать EXECUTE чтобы общий план гарантированно не выбирался.

В настоящее время команда SELECT INTO в EXECUTE не поддерживается; вместо этого выполните простую команду SELECT и укажите INTO как часть самой команды EXECUTE.

Примечание
Оператор PL/pgSQL EXECUTE не имеет отношения к оператору SQL EXECUTE, поддерживаемому сервером QHB. Оператор EXECUTE сервера нельзя напрямую использовать в функциях PL/pgSQL (и не нужно).

Пример 1. Заключение в кавычки значений в динамических запросах

При работе с динамическими командами часто приходится иметь дело с экранированием апострофов. Рекомендованный метод заключения в кавычки фиксированного текста в теле функции — это экранирование знаками доллара. (Если у вас есть устаревший код, который не использует долларовые кавычки, обратитесь к обзору в подразделе Обработка кавычек, который может сэкономить некоторые усилия при переводе этого кода в более разумную схему.)

Динамические значения требуют аккуратного обращения, поскольку могут содержать символы кавычек. Пример использования функции format() (предполагается, что вы заключили тело функции в долларовые кавычки, поэтому апострофы не нужно удваивать):

EXECUTE format('UPDATE tbl SET %I = $1 '
   'WHERE key = $2', colname) USING newvalue, keyvalue;

Также можно напрямую вызывать функции заключения в кавычки:

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = '
        || quote_literal(newvalue)
        || ' WHERE key = '
        || quote_literal(keyvalue);

Этот пример демонстрирует использование функций quote_ident и quote_literal (см. раздел Строковые функции и операторы). В целях безопасности выражения, содержащие идентификаторы столбцов или таблиц, должны перед вставкой в динамический запрос передаваться через quote_ident. Выражения, содержащие значения, которые должны быть литеральными строками в построенной команде, должны передаваться через quote_literal. Эти функции предпринимают соответствующие шаги для возврата входного текста, заключенного в кавычки или апострофы соответственно, с правильным экранированием всех содержащихся в нем специальных символов.

Поскольку функция quote_literal помечена как STRICT, она всегда будет возвращать NULL при вызове с аргументом NULL. Если бы в приведенном выше примере newvalue или keyvalue были равны NULL, то вся строка динамического запроса стала бы равна NULL, что привело бы к ошибке EXECUTE. Этой проблемы можно избежать, применив функцию quote_nullable, которая работает так же, как quote_literal, но при вызове с аргументом NULL возвращает строку NULL. Например:

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = '
        || quote_nullable(newvalue)
        || ' WHERE key = '
        || quote_nullable(keyvalue);

Если вы имеете дело со значениями, которые могут быть равны NULL, то, как правило, вместо quote_literal стоит использовать quote_nullable.

Как всегда, нужно позаботиться о том, чтобы значения NULL в запросе не давали непредвиденных результатов. Например, предложение WHERE

'WHERE key = ' || quote_nullable(keyvalue)

никогда не будет успешным, если keyvalue равно NULL, потому что результат использования оператора равенства = с операндом NULL всегда равен NULL. Если вы хотите, чтобы значение NULL работало как обычное значение ключа, необходимо переписать условие выше как

'WHERE key IS NOT DISTINCT FROM ' || quote_nullable(keyvalue)

(В настоящее время IS NOT DISTINCT FROM обрабатывается гораздо менее эффективно, чем =, поэтому не делайте этого без необходимости. Дополнительную информации о NULL и IS DISTINCT см. в разделе Функции и операторы сравнения.)

Обратите внимание, что знаки доллара полезны только для экранирования фиксированного текста. Было бы очень плохой идеей написать этот пример так:

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = $$'
        || newvalue
        || '$$ WHERE key = '
        || quote_literal(keyvalue);

потому что он бы сломался, если бы содержимое newvalue содержало $$. Та же проблема касается и любого другого разделителя в знаках доллара, который вы можете выбрать. Таким образом, для безопасного заключения к в кавычки текста, который не известен заранее, нужно использовать quote_literal, quote_nullable или quote_ident, в зависимости от ситуации.

Также динамические операторы SQL можно безопасно создавать с помощью функции format (см. подраздел format). Например:

EXECUTE format('UPDATE tbl SET %I = %L '
   'WHERE key = %L', colname, newvalue, keyvalue);

Указание %I равнозначно вызову quote_ident, а %L — quote_nullable. Функцию format можно использовать вместе с предложением USING:

EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
   USING newvalue, keyvalue;

Эта форма лучше, потому что переменные обрабатываются в их собственном формате типа данных, а не преобразуются безусловно в текст и затем заключаются в кавычки посредством %L Кроме того, она более эффективна.

Гораздо более объемный пример динамической команды и EXECUTE можно увидеть в Примере 10, где создается и выполняется команда CREATE FUNCTION для определения новой функции.

Получение статуса результата

Существует несколько способов определить эффект команды. Первый метод заключается в использовании команды GET DIAGNOSTICS, которая имеет вид:

GET [ CURRENT ] DIAGNOSTICS переменная { = | := } элемент [, ... ];

Эта команда позволяет получить системные индикаторы состояния. CURRENT — неучитываемое слово (но см. также описание GET STACKED DIAGNOSTICS в подразделе Получение информации об ошибке). Каждый элемент является ключевым словом, определяющим значение состояния, назначаемое указанной переменной (которая должна иметь правильный тип данных для его получения). Доступные в настоящее время элементы состояния показаны в Таблице 1. Вместо принятой в стандарте SQL синтаксической единицы = можно использовать сочетание двоеточия и знака равенства (:=). Пример:

GET DIAGNOSTICS integer_var = ROW_COUNT;

Таблица 1. Доступные элементы диагностики

Имя | Тип | Описание
ROW_COUNT | bigint | количество строк, обработанных самой последней командой SQL
PG_CONTEXT | text | строка(и) текста, описывающего текущий стек вызовов (см. подраздел Получение информации о месте выполнения)

Второй метод определения эффектов команды — это проверка специальной переменной с именем FOUND, которая имеет тип boolean. FOUND запускается с false при каждом вызове функции PL/pgSQL. Она устанавливается каждым из следующих типов операторов:

Оператор SELECT INTO устанавливает FOUND в true, если строка назначена, и в false, если не возвращается ни одной строки.

Оператор PERFORM устанавливает FOUND в true, если он создает (и отбрасывает) одну и более строк, и false, если не создается ни одной строки.

Операторы UPDATE, INSERT и DELETE устанавливают FOUND в true, если затронута хотя бы одна строка, и false, если не затрагивается ни одной строки.

Оператор FETCH устанавливает FOUND в true, если возвращает строку, и false, если не возвращается ни одной строки.

Оператор MOVE устанавливает FOUND в true, если он успешно перемещает курсор, в противном случае — false.

Оператор FOR или FOREACH устанавливает FOUND в true, если он повторяется один и более раз, в противном случае - false. FOUND устанавливается таким образом при выходе из цикла; внутри выполнения цикла оператор цикла не изменяет FOUND, хотя ее значение может изменить выполнение других операторов в теле цикла.

Операторы RETURN QUERY и RETURN QUERY EXECUTE устанавливают FOUND в true, если запрос возвращает хотя бы одну строку, и false, если не возвращается ни одной строки.

Другие операторы PL/pgSQL не изменяют состояние FOUND. В частности, обратите внимание, что EXECUTE изменяет вывод GET DIAGNOSTICS, но не меняет FOUND.

FOUND — локальная переменная в каждой функции PL/pgSQL; любые изменения в ней затрагивают только текущую функцию.

Вообще ничего не делать

Иногда бывает полезен оператор-заполнитель, который ничего не делает. Например, он может показать, что одна ветвь цепочки если/то/иначе намеренно оставлена пустой. Для этого используется оператор NULL:

NULL;

Например, следующие два фрагмента кода равнозначны:

BEGIN
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN
        NULL;  -- ошибка игнорируется
END;

BEGIN
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN  -- ошибка игнорируется
END;

Что предпочесть — дело вкуса.

Примечание
В Oracle PL/SQL пустые списки операторов недопустимы, поэтому для таких ситуаций требуются операторы NULL. PL/pgSQL позволяет просто ничего не писать.

Управляющие структуры

Управляющие структуры, вероятно, наиболее полезная (и важная) часть PL/pgSQL. С управляющими структурами PL/pgSQL можно манипулировать данными QHB очень гибким и высокопроизводительным образом.

Возврат из функции

Имеются две команды, которые позволяют возвращать данные из функции: RETURN и RETURN NEXT.

RETURN

RETURN выражение;

RETURN с выражением завершает функцию и возвращает значение выражения вызывающей функции. Эта форма используется для функций PL/pgSQL, не возвращающих множества.

В функции, которая возвращает скалярный тип, результат выражения будет автоматически преобразован в возвращаемый тип, как описано для назначений. Но чтобы вернуть составное (строковое) значение, следует написать выражение, доставляющее точно запрошенный набор столбцов. Это может потребовать использования явного приведения.

Если вы объявили функцию с выходными параметрами, напишите просто RETURN без выражения. Будут возвращены текущие значения переменных выходного параметра.

Если вы объявили функцию как возвращающую void, оператор RETURN можно использовать для досрочного выхода из функции; но не пишите выражение после RETURN.

Возвращаемое значение функции нельзя оставлять неопределенным. Если управление достигает конца блока верхнего уровня функции, не встретив оператор RETURN, произойдет ошибка времени выполнения. Однако это ограничение не распространяется на функции с выходными параметрами и на функции, возвращающие void. В этих случаях оператор RETURN выполняется автоматически по окончании блока верхнего уровня.

Несколько примеров:

-- функции, возвращающие скалярный тип
RETURN 1 + 2;
RETURN scalar_var;

-- функции, возвращающие составной тип
RETURN composite_type_var;
RETURN (1, 2, 'three'::text);  -- нужно привести столбцы к корректным типам

RETURN NEXT и RETURN QUERY

RETURN NEXT выражение;
RETURN QUERY запрос;
RETURN QUERY EXECUTE командная-строка [ USING выражение [, ... ] ];

Когда функция PL/pgSQL объявляется как возвращающая SETOF некий_тип, процедура, которой нужно следовать, немного отличается. В этом случае отдельные возвращаемые элементы задаются с помощью последовательности команд RETURN NEXT или RETURN QUERY, а затем используется последняя команда RETURN без аргументов, указывающая, что функция завершилась. Команду RETURN NEXT можно использовать как со скалярными, так и с составными типами данных; с составным типом результата будет возвращена вся «таблица» результатов. Команда RETURN QUERY добавляет результаты выполнения запроса к результирующему набору функции. RETURN NEXT и RETURN QUERY можно свободно смешивать в одной функции, возвращающей множество, и в этом случае их результаты будут конкатенированы.

На самом деле RETURN NEXT и RETURN QUERY не делают возврат из функции — они просто добавляют ноль или более строк в результирующий набор функции. Затем выполнение продолжается со следующего оператора в функции PL/pgSQL. По мере выполнения последовательных команд RETURN NEXT или RETURN QUERY формируется результирующий набор. Финальная команда RETURN, которая не должна иметь аргументов, заставляет элемент управления выйти из функции (или можно просто позволить элементу управления достигнуть конца функции).

У команды RETURN QUERY есть вариант RETURN QUERY EXECUTE, который указывает, что запрос должен выполняться динамически. Выражения параметров можно вставить в вычисляемую строку запроса через USING, точно так же, как в команде EXECUTE.

Если вы объявили функцию с выходными параметрами, напишите просто RETURN NEXT без выражения. При каждом выполнении текущие значения переменных выходных параметров будут сохраняться для последующего возврата в виде строки результата. Обратите внимание, что для создания функции, возвращающей множество, с выходными параметрами нужно объявить функцию как возвращающую SETOF record, когда есть несколько выходных параметров, или SETOF некий_тип, когда есть только один выходной параметр типа некий_тип.

Пример функции, использующей RETURN NEXT:

CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');

CREATE OR REPLACE FUNCTION get_all_foo() RETURNS SETOF foo AS
$BODY$
DECLARE
    r foo%rowtype;
BEGIN
    FOR r IN
        SELECT * FROM foo WHERE fooid > 0
    LOOP
        -- здесь можно выполнить обработку данных
        RETURN NEXT r; -- возвращается текущая строка SELECT
    END LOOP;
    RETURN;
END
$BODY$
LANGUAGE plpgsql;

SELECT * FROM get_all_foo();

Пример функции, использующей RETURN QUERY:

CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS
$BODY$
BEGIN
    RETURN QUERY SELECT flightid
                   FROM flight
                  WHERE flightdate >= $1
                    AND flightdate < ($1 + 1);

    -- Поскольку выполнение еще не закончено, можно проверить, были ли возвращены
    -- строки, и выдать исключение, если нет.
    IF NOT FOUND THEN
        RAISE EXCEPTION 'No flight at %.', $1;
    END IF;

    RETURN;
 END
$BODY$
LANGUAGE plpgsql;

-- Возвращает доступные рейсы или выдает исключение, если доступные рейсы
-- отсутствуют.
SELECT * FROM get_available_flightid(CURRENT_DATE);

Примечание
Текущая реализация RETURN NEXT и RETURN QUERY сохраняет весь результирующий набор перед возвратом из функции, как говорилось выше. Это означает, что если функция PL/pgSQL выдает очень большой результирующий набор, производительность может быть низкой: данные будут записаны на диск во избежание исчерпания памяти, но сама функция не вернется, пока не будет сгенерирован весь результирующий набор. Будущая версия PL/pgSQL может позволить пользователям определять функции, возвращающие множества, у которых нет этого ограничения. В настоящее время точка, с которой данные начинают записываться на диск, управляется переменной конфигурации work_mem. Администраторам, которые имеют в распоряжении достаточно памяти для хранения больших результирующих наборов, стоит рассмотреть возможность увеличения этого параметра.

Возврат из процедуры

Процедура не имеет возвращаемого значения. Поэтому она может завершиться без оператора RETURN. Если вы хотите использовать оператор RETURN для досрочного выхода из кода, напишите просто RETURN без выражения.

Если процедура имеет выходные параметры, итоговые значения переменных выходных параметров будут возвращены вызывающей функции.

Вызов процедуры

Функция, процедура или блок DO PL/pgSQL могут вызывать процедуру, используя оператор CALL. Выходные параметры обрабатываются не так, как CALL работает в простом SQL. Каждый параметр OUT или INOUT процедуры должен соответствовать переменной в операторе CALL, и все, что возвращает процедура, присваивается этой переменной после ее возврата. Например:

CREATE PROCEDURE triple(INOUT x int)
LANGUAGE plpgsql
AS $$
BEGIN
    x := x * 3;
END;
$$;

DO $$
DECLARE myvar int := 5;
BEGIN
  CALL triple(myvar);
  RAISE NOTICE 'myvar = %', myvar;  -- выводится 15
END
$$;

Переменная, соответствующая выходному параметру, может быть простой переменной или полем переменной составного типа. На данный момент она не может быть элементом массива.

Условные операторы

Операторы IF и CASE позволяют выполнять альтернативные команды в зависимости от определенных условий. В PL/pgSQL имеется три формы IF:

IF ... THEN ... END IF

IF ... THEN ... ELSE ... END IF

IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF

и две формы CASE:

CASE ... WHEN ... THEN ... ELSE ... END CASE

CASE WHEN ... THEN ... ELSE ... END CASE

IF-THEN

IF логическое-выражение THEN
    операторы
END IF;

Оператор IF-THEN является простейшей формой оператора IF. Операторы между THEN и END IF будут выполнены, если условие равно true. В противном случае они пропускаются.

Пример:

IF v_user_id <> 0 THEN
    UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;

IF-THEN-ELSE

IF логическое-выражение THEN
    операторы
ELSE
    операторы
END IF;

Операторы IF-THEN-ELSE добавляются к IF-THEN, позволяя задать альтернативный набор операторов, которые должны выполняться, если условие не равно true. (Обратите внимание, что это включает случай, когда условие вычисляется как равное NULL).

Примеры:

IF parentid IS NULL OR parentid = ''
THEN
    RETURN fullname;
ELSE
    RETURN hp_true_filename(parentid) || '/' || fullname;
END IF;

IF v_count > 0 THEN
    INSERT INTO users_count (count) VALUES (v_count);
    RETURN 't';
ELSE
    RETURN 'f';
END IF;

IF-THEN-ELSIF

IF логическое-выражение THEN
    операторы
[ ELSIF логическое-выражение THEN
    операторы
[ ELSIF логическое-выражение THEN
    операторы
    ...
]
]
[ ELSE
    операторы ]
END IF;

Иногда альтернатив бывает больше двух. IF-THEN-ELSIF предоставляет удобный метод поочередной проверки нескольких альтернатив. Условия IF проверяются последовательно, пока не будет найдено первое условие, равное true. Затем выполняются связанные операторы, после чего управление переходит к следующему оператору после END IF. (Любые последующие условия IF не проверяются). Если ни одно из условий IF не является истинным, выполняется блок ELSE (если таковой имеется).

Пример:

IF number = 0 THEN
    result := 'zero';
ELSIF number > 0 THEN
    result := 'positive';
ELSIF number < 0 THEN
    result := 'negative';
ELSE
    -- остается только один вариант — number равен NULL
    result := 'NULL';
END IF;

Ключевое слово ELSIF также можно написать как ELSEIF.

Альтернативный способ выполнения той же задачи — вложение операторов IF-THEN-ELSE, как в следующем примере:

IF demo_row.sex = 'm' THEN
    pretty_sex := 'man';
ELSE
    IF demo_row.sex = 'f' THEN
        pretty_sex := 'woman';
    END IF;
END IF;

Однако этот метод требует написания соответствующего END IF для каждого IF, поэтому при наличии нескольких альтернативах он будет гораздо более громоздким, чем использование ELSIF.

Простой CASE

CASE выражение-поиска
    WHEN выражение [, выражение [ ... ]] THEN
      операторы
  [ WHEN выражение [, выражение [ ... ]] THEN
      операторы
    ... ]
  [ ELSE
      операторы ]
END CASE;

Простая форма CASE обеспечивает условное выполнение, основанное на равенстве операндов. выражение-поиска вычисляется (однократно) и последовательно сравнивается с каждым выражением в предложениях WHEN. Если совпадение найдено, то выполняются соответствующие операторы, а затем управление переходит к следующему оператору после END CASE. (Последующие выражения WHEN не вычисляются.) Если совпадений не найдено, выполняются ELSE операторы, но если ELSE нет, то возникает исключение CASE_NOT_FOUND.

Простой пример:

CASE x
    WHEN 1, 2 THEN
        msg := 'один или два';
    ELSE
        msg := 'значение, отличное от один или два';
END CASE;

Поисковый CASE

CASE
    WHEN логическое-выражение THEN
      операторы
  [ WHEN логическое-выражение THEN
      операторы
    ... ]
  [ ELSE
      операторы ]
END CASE;

Поисковая форма CASE обеспечивает условное выполнение, основанное на истинности логических выражений. Каждое логическое-выражение предложения WHEN вычисляется по очереди, пока не будет найдено выдающее true. Затем выполняются соответствующие операторы, а затем управление переходит к следующему оператору после END CASE. (Последующие выражения WHEN не вычисляются.) Если истинный результат не найден, выполняются ELSE операторы, но если ELSE нет, то возникает исключение CASE_NOT_FOUND.

Пример:

CASE
    WHEN x BETWEEN 0 AND 10 THEN
        msg := 'значение между нулем и десятью';
    WHEN x BETWEEN 11 AND 20 THEN
        msg := 'значение между одиннадцатью и двадцатью';
END CASE;

Эта форма CASE полностью равнозначна IF-THEN-ELSIF, за исключением правила, согласно которому достижение пропущенного предложения ELSE приводит к ошибке, а не к бездействию.

Простые циклы

С помощью операторов LOOP, EXIT, CONTINUE, WHILE, FOR и FOREACH можно настроить функцию PL/pgSQL на повторение ряда команд.

LOOP

[ <<метка>> ]
LOOP
    операторы
END LOOP [ метка ];

Оператор LOOP определяет безусловный цикл, который повторяется бесконечно, пока не будет завершен с помощью оператора EXIT или RETURN. Необязательная метка может использоваться операторами EXIT и CONTINUE во вложенных циклах, чтобы указать, к какому циклу обращаются эти операторы.

EXIT

EXIT [ метка ] [ WHEN логическое-выражение ];

Если метка не указана, самый внутренний цикл завершается, а затем выполняется оператор, следующий за END LOOP. Если метка указывается, это должна быть метка текущего или некоторого внешнего уровня вложенного цикла или блока. Затем именованный цикл или блок завершается, и управление переходит к оператору после соответствующего END цикла/блока.

Если указано WHEN, выход из цикла происходит только в том случае, если логическое-выражение равно true. В противном случае управление передается оператору после EXIT.

EXIT может использоваться со всеми типами циклов; его использование не ограничено безусловными циклами.

При использовании с блоком BEGIN, EXIT передает управление следующему оператору после конца блока. Обратите внимание, что для этой цели должна использоваться метка; немаркированный EXIT никогда не считается совпадающим с блоком BEGIN.

Примеры:

LOOP
    -- некоторые вычисления
    IF count > 0 THEN
        EXIT;  -- выход из цикла
    END IF;
END LOOP;

LOOP
    -- некоторые вычисления
    EXIT WHEN count > 0;  -- тот же результат, что и в предыдущем примере
END LOOP;

<<ablock>>
BEGIN
    -- некоторые вычисления
    IF stocks > 100000 THEN
        EXIT ablock;  -- выход из блока BEGIN
    END IF;
    -- вычисления будут пропущены, если stocks > 100000
END;

CONTINUE

CONTINUE [ метка ] [ WHEN логическое-выражение ];

Если метка не указана, начинается следующая итерация самого внутреннего цикла. То есть все операторы, оставшиеся в теле цикла, пропускаются, и управление возвращается к управляющему выражению цикла (если оно есть), чтобы определить, нужна ли еще одна итерация цикла. Если метка имеется, она задает метку цикла, выполнение которого будет продолжено.

Если указано WHEN, следующая итерация цикла начинается только в том случае, если логическое-выражение равно true. В противном случае управление переходит к оператору после CONTINUE.

CONTINUE можно использовать со всеми типами циклов; его использование не ограничено безусловными циклами.

Примеры:

LOOP
    -- некоторые вычисления
    EXIT WHEN count > 100;
    CONTINUE WHEN count < 50;
    -- некоторые вычисления для count IN [50 .. 100]
END LOOP;

WHILE

[ <<метка>> ]
WHILE логическое-выражение LOOP
    операторы
END LOOP [ метка ];

Оператор WHILE повторяет последовательность операторов до тех пор, пока логическое-выражение вычисляется как true. Это выражение проверяется перед каждой записью в теле цикла.

Например:

WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
    -- некоторые вычисления
END LOOP;

WHILE NOT done LOOP
    -- некоторые вычисления
END LOOP;

FOR (целочисленный вариант)

[ <<метка>> ]
FOR имя IN [ REVERSE ] выражение .. выражение [ BY выражение ] LOOP
    операторы
END LOOP [ метка ];

Эта форма FOR создает цикл, который перебирает диапазон целых значений. имя переменной автоматически определяется с типом integer и существует только внутри цикла (любое существующее определение имени переменной внутри цикла игнорируется). Два выражения, выдающие нижнюю и верхнюю границы диапазона, вычисляются однократно при входе в цикл. Если предложение BY не указано, шаг итерации равен 1, в противном случае это значение, заданное в предложении BY, которое, опять же, вычисляется однократно при входе в цикл. Если указано REVERSE, то значение шага после каждой итерации вычитается, а не добавляется.

Некоторые примеры целочисленных циклов FOR:

FOR i IN 1..10 LOOP
    -- внутри цикла переменная i будет принимать значения 1,2,3,4,5,6,7,8,9,10
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- внутри цикла переменная i будет принимать значения 10,9,8,7,6,5,4,3,2,1
END LOOP;

FOR i IN REVERSE 10..1 BY 2 LOOP
    -- внутри цикла переменная i будет принимать значения 10,8,6,4,2
END LOOP;

Если нижняя граница больше верхней (или меньше, в случае REVERSE), тело цикла вообще не выполняется. Ошибка при этом не возникает.

Если к циклу FOR привязана метка, то с ее помощью к целочисленной переменной цикла можно обращаться по полному имени.

Цикл по результатам запроса

Используя цикл FOR другого типа, можно перебирать результаты запроса и соответствующим образом манипулировать этими данными. Синтаксис:

[ <<метка>> ]
FOR цель IN запрос LOOP
    операторы
END LOOP [ метка ];

цель — это переменная записи, переменная строки или разделенный запятыми список скалярных переменных. цель назначается последовательно каждой строке, полученной в результате запроса, и тело цикла выполняется для каждой строки. Пример:

CREATE FUNCTION refresh_mviews() RETURNS integer AS $$
DECLARE
    mviews RECORD;
BEGIN
    RAISE NOTICE 'Refreshing all materialized views...';

    FOR mviews IN
       SELECT n.nspname AS mv_schema,
              c.relname AS mv_name,
              pg_catalog.pg_get_userbyid(c.relowner) AS owner
         FROM pg_catalog.pg_class c
    LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
        WHERE c.relkind = 'm'
     ORDER BY 1
    LOOP

        -- Теперь "mviews" содержит одну запись с информацией о материализованном
        -- представлении

        RAISE NOTICE 'Refreshing materialized view %.% (owner: %)...',
                     quote_ident(mviews.mv_schema),
                     quote_ident(mviews.mv_name),
                     quote_ident(mviews.owner);
        EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name);
    END LOOP;

    RAISE NOTICE 'Done refreshing materialized views.';
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

Если цикл завершается оператором EXIT, последнее присвоенное значение строки будет доступно и после цикла.

запрос, используемый в операторе FOR этого типа, может быть любой командой SQL, которая возвращает строки вызывающей функции: чаще всего применяется SELECT, но также можно использовать INSERT, UPDATE или DELETE с предложением RETURNING. Некоторые служебные команды, например EXPLAIN, тоже подойдут.

Переменные PL/pgSQL заменяются параметрами запроса, и план запроса кэшируется для возможного повторного использования, как подробно описано в подразделах Подстановка переменных и Кэширование плана.

Еще один способ перебора строк — это оператор FOR-IN-EXECUTE:

[ <<метка>> ]
FOR цель IN EXECUTE текстовое_выражение [ USING выражение [, ... ] ] LOOP
    операторы
END LOOP [ метка ];

Это похоже на предыдущую форму, за исключением того, что исходный запрос указывается как строковое выражение, которое вычисляется и планируется заново для каждой записи в цикле FOR. Это позволяет программисту выбирать между скоростью заранее спланированного запроса и гибкостью динамического запроса, так же, как и с простым оператором EXECUTE. Как и в случае EXECUTE, значения параметров можно вставить в динамическую команду посредством USING.

Еще один способ указать запрос, по результатам которого необходимо выполнить перебор, — объявить его как курсор. Это описывается в подразделе Цикл по результату курсора.

Цикл по массивам

Цикл FOREACH во многом похож на цикл FOR, но вместо перебора по строкам, возвращаемым запросом SQL, он выполняет перебор по элементам значения-массива. (В целом, FOREACH предназначен для циклического прохождения по компонентам выражения составного типа; в будущем могут быть добавлены варианты циклического прохождения по значениям составного типа, отличным от массивов). Оператор FOREACH для цикла по массиву:

[ <<метка>> ]
FOREACH цель [ SLICE количество ] IN ARRAY выражение LOOP
    операторы
END LOOP [ метка ];

Без SLICE или при SLICE 0 цикл перебирает отдельные элементы массива, созданного путем вычисления выражения. Переменная цель присваивается каждому значению элемента в последовательности, и тело цикла выполняется для каждого элемента. Пример зацикливания элементов целочисленного массива:

CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
DECLARE
  s int8 := 0;
  x int;
BEGIN
  FOREACH x IN ARRAY $1
  LOOP
    s := s + x;
  END LOOP;
  RETURN s;
END;
$$ LANGUAGE plpgsql;

Элементы посещаются в порядке хранения, независимо от количества измерений массива. Хотя цель обычно представляет собой всего одну переменную, она может быть и списком переменных при циклическом просмотре массива значений (записей) составного типа. В этом случае для каждого элемента массива переменные назначаются из последовательных столбцов составного значения.

При положительном значении SLICE FOREACH выполняет перебор по фрагментам массива, а не по отдельным элементам. Значение SLICE должно быть целочисленной константой, не превышающей количество измерений массива. Переменная цель должна быть массивом, получающим последовательные срезы значения массива, где каждый срез имеет количество измерений, заданное в SLICE. Пример перебора по одномерным срезам:

CREATE FUNCTION scan_rows(int[]) RETURNS void AS $$
DECLARE
  x int[];
BEGIN
  FOREACH x SLICE 1 IN ARRAY $1
  LOOP
    RAISE NOTICE 'row = %', x;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

SELECT scan_rows(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);

NOTICE:  row = {1,2,3}
NOTICE:  row = {4,5,6}
NOTICE:  row = {7,8,9}
NOTICE:  row = {10,11,12}

Перехват ошибок

По умолчанию любая ошибка, возникающая в функции PL/pgSQL, прерывает выполнение этой функции и окружающей ее транзакции. Эти ошибки можно перехватывать и исправлять, используя блок BEGIN с предложением EXCEPTION. Синтаксис представляет собой расширение обычного синтаксиса для блока BEGIN:

[ <<метка>> ]
[ DECLARE
    объявления ]
BEGIN
    операторы
EXCEPTION
    WHEN условие [ OR условие ... ] THEN
        операторы_обработчика
    [ WHEN условие [ OR условие ... ] THEN
          операторы_обработчика
      ... ]
END;

Если ошибка не возникает, эта форма блока просто выполняет все операторы, а затем управление переходит к следующему оператору после END. Но если в операторах возникает ошибка, дальнейшая их обработка прекращается, и управление переходит в список EXCEPTION. В списке ищется первое условие, соответствующее возникшей ошибке. Если совпадение найдено, выполняются соответствующие операторы_обработчика, а затем управление переходит к следующему оператору после END. Если совпадение не найдено, ошибка передается наружу, как если бы предложения EXCEPTION вообще не было: ошибку может перехватить внешний блок с EXCEPTION, или, если этого предложения нет, блок прерывает обработку функции.

В качестве условий могут задаваться любые имена из указанных в разделе Коды ошибок QHB. Название категории соответствует любой ошибке в ее категории. Специальное имя условия OTHERS (другие) соответствует всем типам ошибок, кроме QUERY_CANCELED и ASSERT_FAILURE. (Отлавливать по имени эти два типа ошибок можно, но зачастую неразумно.) Имена условий нечувствительны к регистру. Кроме того, условие ошибки можно указать кодом SQLSTATE; например эти две записи равнозначны:

WHEN division_by_zero THEN ...
WHEN SQLSTATE '22012' THEN ...

Если в выбранных операторах_обработчика возникает новая ошибка, это предложение EXCEPTION не сможет ее перехватить, но ее можно передать наружу. Тогда эту ошибку сможет поймать внешнее предложение EXCEPTION.

Когда предложение EXCEPTION обнаруживает ошибку, локальные переменные функции PL/pgSQL остаются такими же, какими они были на момент возникновения ошибки, но все изменения в постоянном состоянии базы данных, выполненные в блоке, откатываются. В качестве примера рассмотрим этот фрагмент:

INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
BEGIN
    UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones';
    x := x + 1;
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE NOTICE 'caught division_by_zero';
        RETURN x;
END;

Когда управление доходит до присвоения значения y, произойдет сбой с ошибкой division_by_zero. Она будет поймана предложением EXCEPTION. Значение, возвращаемое в операторе RETURN, будет увеличенным значением x, но действия команды UPDATE будут отменены. Однако команда INSERT, предшествующая блоку, не откатывается, поэтому в результате база данных будет содержать Tom Jones, а не Joe Jones.

Совет
Блок, содержащий предложение EXCEPTION, значительно увеличивает затраты на вход и выход по сравнения с обычным блоком. Поэтому не используйте EXCEPTION без необходимости.

Пример 2. Исключения с UPDATE/INSERT

В этом примере используется обработка исключений, помогающая выполнению UPDATE или INSERT, в зависимости от ситуации. В приложениях рекомендуется применять вместо этого шаблона INSERT с ON CONFLICT DO UPDATE. Этот пример служит главным образом для иллюстрации использования структур потока управления PL/pgSQL:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- сначала попытаемся изменить ключ
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- такого ключа нет, поэтому попытаемся его добавить
        -- если кто-то параллельно добавляет такой же ключ,
        -- можно получить ошибку уникальности ключа
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- Ничего не делать, продолжить цикл, чтобы снова попробовать UPDATE.
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

В этом коде предполагается, что ошибка unique_violation вызвана самой командой INSERT, а не, скажем, INSERT в триггерной функции таблицы. Некорректную работу также может вызвать наличие в таблице более одного уникального индекса, поскольку команда будет повторять операцию независимо от того, какой индекс вызвал ошибку. Большую надежность можно было бы получить, используя возможности, рассматриваемые далее, которые позволяют проверить, была ли перехваченная ошибка ожидаемой.

Получение информации об ошибке

Обработчикам исключений часто требуется идентифицировать конкретную возникшую ошибку. В PL/pgSQL есть два способа получить информацию о текущем исключении: специальные переменные и команда GET STACKED DIAGNOSTICS.

В обработчике исключений специальная переменная SQLSTATE содержит код ошибки, соответствующий возникшему исключению (список возможных кодов ошибок приведен в Таблице 1 раздела Коды ошибок QHB). Специальная переменная SQLERRM содержит сообщение об ошибке, связанной с этим исключением. Эти переменные не определены вне обработчиков исключений.

Также в обработчике исключений можно получить информацию о текущем исключении с помощью команды GET STACKED DIAGNOSTICS, имеющей вид:

GET STACKED DIAGNOSTICS переменная { = | := } элемент [, ... ];

Каждый элемент является ключевым словом, определяющим значение состояния, присваиваемое указанной переменной (которая должно иметь правильный тип данных, чтобы принять его). Доступные в настоящее время элементы состояния приведены в Таблице 2.

Таблица 2. Элементы диагностики ошибок

Имя | Тип | Описание
RETURNED_SQLSTATE | text | код ошибки SQLSTATE исключения
COLUMN_NAME | text | имя столбца, связанного с исключением
CONSTRAINT_NAME | text | имя ограничения, связанного с исключением
PG_DATATYPE_NAME | text | имя типа данных, связанного с исключением
MESSAGE_TEXT | text | текст основного сообщения исключения
TABLE_NAME | text | имя таблицы, связанной с исключением
SCHEMA_NAME | text | имя схемы, связанной с исключением
PG_EXCEPTION_DETAIL | text | текст подробного сообщения об исключении, если таковое имеется
PG_EXCEPTION_HINT | text | текст сообщения с подсказкой к исключению, если таковое имеется
PG_EXCEPTION_CONTEXT | text | строка(и) текста, описывающего стек вызовов во время исключения (см. подраздел Получение информации о месте выполнения)

Если исключение не установило значение для элемента, будет возвращена пустая строка.

Пример:

DECLARE
  text_var1 text;
  text_var2 text;
  text_var3 text;
BEGIN
  -- некая обработка, которая может вызвать исключение
  ...
EXCEPTION WHEN OTHERS THEN
  GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
                          text_var2 = PG_EXCEPTION_DETAIL,
                          text_var3 = PG_EXCEPTION_HINT;
END;

Получение информации о месте выполнения

Команда GET DIAGNOSTICS, ранее описанная в подразделе Получение статуса результата, извлекает информацию о текущем состоянии выполнения (тогда как рассмотренная выше команда GET STACKED DIAGNOSTICS выводит информацию о состоянии выполнения на момент предыдущей ошибки). Ее элемент состояния PG_CONTEXT полезен для определения текущего местоположения выполнения. PG_CONTEXT возвращает текст со строками, описывающими стек вызовов. Первая строка ссылается на текущую функцию и текущую команду GET DIAGNOSTICS. Вторая и любые последующие строки ссылаются на вызывающие функции дальше по стеку вызовов. Например:

CREATE OR REPLACE FUNCTION outer_func() RETURNS integer AS $$
BEGIN
  RETURN inner_func();
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION inner_func() RETURNS integer AS $$
DECLARE
  stack text;
BEGIN
  GET DIAGNOSTICS stack = PG_CONTEXT;
  RAISE NOTICE E'--- Стек вызова ---\n%', stack;
  RETURN 1;
END;
$$ LANGUAGE plpgsql;

SELECT outer_func();

NOTICE:  --- Стек вызова ---
PL/pgSQL function inner_func() line 5 at GET DIAGNOSTICS
PL/pgSQL function outer_func() line 3 at RETURN
CONTEXT:  PL/pgSQL function outer_func() line 3 at RETURN
 outer_func
 ------------
           1
(1 row)

GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT возвращает похожую трассировку стека, но описывает не текущее местоположение, а то, в котором была обнаружена ошибка.

Курсоры

Вместо выполнения всего запроса целиком можно установить курсор, который инкапсулирует запрос, и затем прочитать результат запроса по несколько строк за раз. Одна из причин сделать это состоит в том, чтобы избежать переполнения памяти, когда результат содержит большое количество строк. (Хотя обычно пользователям PL/pgSQL не нужно беспокоиться об этом, поскольку в циклах FOR автоматически используются курсоры, чтобы избежать проблем с памятью). Более интересным вариантом применения является возврат ссылки на курсор, созданный функцией, что позволяет вызывающей функции прочитать строки запроса. Это обеспечивает эффективный способ возврата больших наборов строк из функций.

Объявление переменных курсора

Весь доступ к курсорам в PL/pgSQL осуществляется через переменные курсора, которые всегда имеют специальный тип данных refcursor. Один из способов создать переменную курсора — просто объявить ее как переменную типа refcursor. Другой способ — использовать синтаксис объявления курсора, который выглядит примерно так:

имя [ [ NO ] SCROLL ] CURSOR [ ( аргументы ) ] FOR запрос;

(Для совместимости с Oracle FOR можно заменить на IS.) Если указан SCROLL, курсор можно будет прокручивать назад; если указан NO SCROLL, выборки в обратном направлении будут отклонены; если этих указаний нет, возможность обратных выборок зависит от запроса. аргументы, если указаны, представляют собой разделенный запятыми список пар, состоящих из имени и типа_данных, которые определяют имена, заменяемые значениями параметров в данном запросе. Фактические значения для замены этих имен будут указаны позже, после открытия курсора.

Несколько примеров:

DECLARE
    curs1 refcursor;
    curs2 CURSOR FOR SELECT * FROM tenk1;
    curs3 CURSOR (key integer) FOR SELECT * FROM tenk1 WHERE unique1 = key;

Все эти три переменные имеют тип данных refcursor, но первую можно использовать с любым запросом, тогда как со второй уже связан полностью определенный запрос, а с последней связан параметризованный запрос. (key будет заменен на целочисленное значение параметра при открытии курсора.) Переменная curs1 называется несвязанной, поскольку она не связана ни с каким конкретным запросом.

Параметр SCROLL нельзя использовать, когда в запросе курсора применяется FOR UPDATE/SHARE. Кроме того, с запросом, включающим изменчивые функции, лучше всего использовать NO SCROLL. Реализация SCROLL предполагает, что повторное чтение вывода запроса даст согласованные результаты, которых изменчивая функция гарантировать не может.

Открытие курсоров

Прежде чем курсор можно будет использовать для извлечения строк, его необходимо открыть. (Это действие равнозначно команде SQL DECLARE CURSOR). В PL/pgSQL имеется три формы оператора OPEN, две из которых используют несвязанные переменные курсора, а третья — связанную.

Примечание
Связанные переменные курсора также можно использовать без явного открытия курсора, с помощью оператора FOR, описанного в подразделе Цикл по результату курсора.

OPEN FOR запрос

OPEN несвязанная_переменная_курсора [ [ NO ] SCROLL ] FOR запрос;

Переменная курсора открывается и получает заданный запрос для выполнения. Курсор уже не может быть открытым и должен быть объявлен как несвязанная переменная курсора (то есть как простая переменная refcursor ). Запрос должен быть командой SELECT или любой другой, что возвращает строки (например EXPLAIN). Запрос обрабатывается так же, как и другие команды SQL в PL/pgSQL: имена переменных PL/pgSQL заменяются, а план запроса кэшируется для возможного повторного использования. В запрос курсора подставляется то значение переменной PL/pgSQL, которое она имеет во время OPEN; последующие изменения в этой переменной на поведение курсора не влияют. Параметры SCROLL и NO SCROLL имеют тот же смысл, что и для связанного курсора.

Пример:

OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;

OPEN FOR EXECUTE

OPEN несвязанная_переменная_курсора [ [ NO ] SCROLL ] FOR EXECUTE строка_запроса
                                     [ USING выражение [, ... ] ];

Переменная курсора открывается и получает заданный запрос для выполнения. Курсор уже не может быть открытым и должен быть объявлен как несвязанная переменная курсора (то есть как простая переменная refcursor). Запрос задается как строковое выражение, так же, как в команде EXECUTE. Как обычно, это обеспечивает гибкость, поэтому план запроса может варьироваться от прогона к прогону (см. подраздел Кэширование плана), а также означает, что подстановка переменных производится не в командной строке. Как и в случае с EXECUTE, значения параметров можно вставить в динамическую команду посредством format() и USING. Параметры SCROLL и NO SCROLL имеют тот же смысл, что и для связанного курсора.

Пример:

OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;

В этом примере имя таблицы вставляется в запрос через format(). Сравниваемое значение для col1 вставляется через параметр USING, поэтому его не нужно заключать в апострофы.

Открытие связанного курсора

OPEN связанная_переменная_курсора [ ( [ имя_аргумента := ] значение_аргумента [, ...] ) ];

Эта форма OPEN используется для открытия переменной курсора, с которой при объявлении был связан ее запрос. Курсор уже не может быть открытым. Список фактических выражений значений аргументов должен присутствовать тогда и только тогда, когда курсор был объявлен как принимающий аргументы. Эти значения будут подставляться в запрос.

План запроса для связанного курсора всегда считается кэшируемым; в этом случае нет эквивалента EXECUTE. Обратите внимание, что в OPEN нельзя указывать SCROLL и NO SCROLL, поскольку поведение прокрутки курсора уже было определено.

Значения аргумента можно передать посредством позиционной или именованной нотации. В позиционной нотации все аргументы указываются по порядку. В именованной нотации имя каждого аргумента отделяется от выражения аргумента с помощью :=. Как и в случае вызова функций, описанного в разделе Вызов функции, также допускается смешивание позиционной и именованной нотации.

Примеры (здесь используются объявления курсора из примеров выше):

OPEN curs2;
OPEN curs3(42);
OPEN curs3(key := 42);

Поскольку подстановка переменных выполняется в запросе связанного курсора, на самом деле существует два способа передачи значений в курсор: либо с явным аргументом в OPEN, либо неявно ссылаясь на переменную PL/pgSQL в запросе. Однако в связанный курсор будут подставлены только те переменные, которые были объявлены до его объявления. В любом случае передаваемое значение определяется во время OPEN. Например, вот другой способ получить тот же эффект, что и в приведенном выше примере с curs3:

DECLARE
    key integer;
    curs4 CURSOR FOR SELECT * FROM tenk1 WHERE unique1 = key;
BEGIN
    key := 42;
    OPEN curs4;

Использование курсоров

Когда курсор открыт, им можно манипулировать с помощью описанных здесь операторов.

Эти манипуляции не обязательно должны выполняться в той же функции, которая изначально открыла курсор. Можно вернуть из функции значение refcursor и позволить вызывающей функции работать с курсором. (Внутренне значение refcursor — это просто строковое имя так называемого портала, содержащего активный запрос для курсора. Это имя можно передавать, присваивать другим переменным refcursor и т. д., не нарушая портал.)

Все порталы неявно закрываются в конце транзакции. Поэтому значение refcursor можно использовать для обращения к открытому курсору только до конца транзакции.

FETCH

FETCH [ направление { FROM | IN } ] курсор INTO цель;

FETCH извлекает следующую строку из курсора в цель, которая может быть переменной строки, переменной записи или списком разделенных запятыми простых переменных, подобно SELECT INTO. Если следующей строки нет, в цели устанавливается NULL. Как и в случае с SELECT INTO, чтобы увидеть, была ли получена строка или нет, можно проверить специальную переменную FOUND.

Предложение направление может быть любым вариантом, разрешенным в команде SQL FETCH, кроме тех, которые могут извлекать более одной строки, а именно: NEXT, PRIOR, FIRST, LAST, ABSOLUTE число, RELATIVE число, FORWARD или BACKWARD. Отсутствие направления означает то же самое, что и указание NEXT. В формах, использующих число, оно может быть любым целочисленным выражением (в отличие от команды SQL FETCH, которая допускает только целочисленную константу). Значения направления, требующие прокрутку назад, могут вызвать ошибку, если курсор не был объявлен или открыт с параметром SCROLL.

курсор должен быть именем переменной типа refcursor, ссылающейся на портал открытого курсора.

Примеры:

FETCH curs1 INTO rowvar;
FETCH curs2 INTO foo, bar, baz;
FETCH LAST FROM curs3 INTO x, y;
FETCH RELATIVE -2 FROM curs4 INTO x;

MOVE

MOVE [ направление { FROM | IN } ] курсор;

MOVE перемещает курсор без извлечения каких-либо данных. MOVE работает точно так же, как команда FETCH, за исключением того, что он только перемещает курсор и не возвращает строку, к которой перемещается. Как и в случае с SELECT INTO, чтобы увидеть, имелась ли следующая строка для перемещения, можно проверить специальную переменную FOUND.

Примеры:

MOVE curs1;
MOVE LAST FROM curs3;
MOVE RELATIVE -2 FROM curs4;
MOVE FORWARD 2 FROM curs4;

UPDATE/DELETE WHERE CURRENT OF

UPDATE таблица SET ... WHERE CURRENT OF курсор;
DELETE FROM таблица WHERE CURRENT OF курсор;

Когда курсор расположен в строке таблицы, эту строку можно изменить или удалить, используя курсор для идентификации строки. Существуют ограничения на то, каким может быть запрос курсора (в частности, в нем не должно быть группировки), и лучше всего использовать в курсоре FOR UPDATE. Дополнительную информацию см. на справочной странице DECLARE.

Пример:

UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;

CLOSE

CLOSE курсор;

CLOSE закрывает портал, лежащий в основе открытого курсора. Это можно использовать для освобождения ресурсов до завершения транзакция, или для освобождения переменной курсора, чтобы снова ее открыть.

Пример:

CLOSE curs1;

Возврат курсоров

Функции PL/pgSQL могут возвращать курсоры вызывающей функции. Это полезно для возврата нескольких строк или столбцов, особенно с очень большими результирующими наборами. Для этого функция открывает курсор и возвращает имя курсора вызывающему коду (или просто открывает курсор, используя имя портала, указанное или иным образом известное вызывающей функции). Затем вызывающая функция может извлечь строки из курсора. Курсор может закрыть вызывающую функцию, или она будет закрыта автоматически по завершении транзакции.

Имя портала, используемое для курсора, может быть указано программистом или сгенерировано автоматически. Чтобы указать имя портала, просто присвойте строку переменной refcursor перед его открытием. Строковое значение переменной refcursor будет использоваться OPEN в качестве имени нижележащего портала. Однако если переменная refcursor имеет значение NULL, OPEN автоматически генерирует имя, которое не конфликтует ни с каким существующим порталом, и присваивает его переменной refcursor.

Примечание
Связанная переменная курсора инициализируется в строковое значение, представляющее ее имя, поэтому имя портала совпадает с именем переменной курсора, если только программист не переопределит его присваиванием перед открытием курсора. Но несвязанная переменная курсора по умолчанию изначально имеет значение NULL, поэтому она получит автоматически сгенерированное уникальное имя, если не будет переопределена.

В следующем примере показан один из способов, которым вызывающая функция может предоставить имя курсора:

CREATE TABLE test (col text);
INSERT INTO test VALUES ('123');

CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS '
BEGIN
    OPEN $1 FOR SELECT col FROM test;
    RETURN $1;
END;
' LANGUAGE plpgsql;

BEGIN;
SELECT reffunc('funccursor');
FETCH ALL IN funccursor;
COMMIT;

В следующем примере используется автоматическая генерация имени курсора:

CREATE FUNCTION reffunc2() RETURNS refcursor AS '
DECLARE
    ref refcursor;
BEGIN
    OPEN ref FOR SELECT col FROM test;
    RETURN ref;
END;
' LANGUAGE plpgsql;

-- для использования курсоров нужно быть в транзакции.
BEGIN;
SELECT reffunc2();

      reffunc2
--------------------
 <unnamed cursor 1>
(1 row)

FETCH ALL IN "<unnamed cursor 1>";
COMMIT;

В следующем примере показан один из способов вернуть несколько курсоров из одной функции:

CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$
BEGIN
    OPEN $1 FOR SELECT * FROM table_1;
    RETURN NEXT $1;
    OPEN $2 FOR SELECT * FROM table_2;
    RETURN NEXT $2;
END;
$$ LANGUAGE plpgsql;

-- для использования курсоров нужно быть в транзакции.
BEGIN;

SELECT * FROM myfunc('a', 'b');

FETCH ALL FROM a;
FETCH ALL FROM b;
COMMIT;

Цикл по результату курсора

Существует вариант оператора FOR, позволяющий перебирать строки, возвращаемые курсором. Синтаксис:

[ <<метка>> ]
FOR переменная_записи IN связанная_переменная_курсора [ ( [ имя_аргумента := ] значение_аргумента [, ...] ) ] LOOP
    операторы
END LOOP [ метка ];

Переменная курсора при объявлении должна быть привязана к какому-либо запросу, и уже не может быть открытой. Оператор FOR автоматически открывает курсор и снова закрывает его при выходе из цикла. Список фактических выражений значений аргументов должен присутствовать тогда и только тогда, когда курсор был объявлен как принимающий аргументы. Эти значения будут подставлены в запросе точно так же, как во время OPEN (см. подраздел Открытие связанного курсора).

Здесь переменная_записи автоматически определяется как имеющая тип record и существует только внутри цикла (любое существующее определение имени переменной в цикле игнорируется). Каждая строка, возвращаемая курсором, последовательно присваивается этой переменной записи, и выполняется тело цикла.

Управление транзакциями

В процедурах, вызываемых командой CALL, а также в блоках анонимного кода (команда DO), можно завершать транзакции с помощью команд COMMIT и ROLLBACK. После завершения транзакции с этими командами новая транзакция запускается автоматически, поэтому отдельной команды START TRANSACTION нет. (Обратите внимание, что BEGIN и END в PL/pgSQL имеют другой смысл.)

Простой пример:

CREATE PROCEDURE transaction_test1()
LANGUAGE plpgsql
AS $$
BEGIN
    FOR i IN 0..9 LOOP
        INSERT INTO test1 (a) VALUES (i);
        IF i % 2 = 0 THEN
            COMMIT;
        ELSE
            ROLLBACK;
        END IF;
    END LOOP;
END
$$;

CALL transaction_test1();

Новая транзакция начинается с характеристик по умолчанию, таких как уровень изоляции. В случаях, когда транзакции фиксируются в цикле, может быть удобнее автоматически начинать новые транзакции с теми же характеристиками, что и предыдущая. Это реализуется командами COMMIT AND CHAIN и ROLLBACK AND CHAIN.

Управление транзакциями возможно только в вызовах CALL или DO из верхнего уровня или во вложенных CALL или DO без какой-либо промежуточной команды. Например, если стеком вызовов является CALL proc1() → CALL proc2() → CALL proc3(), то вторая и третья процедуры могут выполнять действия по управлению транзакциями. Но если стеком вызовов является CALL proc1() → SELECT func2() → CALL proc3(), то последняя процедура не может осуществлять управление транзакциями из-за промежуточного SELECT.

Циклы с курсорами требуют особого внимания. Рассмотрим этот пример:

CREATE PROCEDURE transaction_test2()
LANGUAGE plpgsql
AS $$
DECLARE
    r RECORD;
BEGIN
    FOR r IN SELECT * FROM test2 ORDER BY x LOOP
        INSERT INTO test1 (a) VALUES (r.x);
        COMMIT;
    END LOOP;
END;
$$;

CALL transaction_test2();

Обычно курсоры автоматически закрываются при фиксации транзакции. Однако курсор, созданный как часть цикла, подобного этому, автоматически преобразуется в удерживаемый курсор с помощью первой COMMIT или ROLLBACK. Это означает, что курсор полностью вычисляется в первой COMMIT или ROLLBACK а не построчно. Курсор по-прежнему автоматически удаляется после цикла, поэтому в большинстве случаев невидим для пользователя.

Команды транзакций не допускаются в циклах с курсорами, управляемых командами, которые предназначены не только для чтения (например UPDATE ... RETURNING).

Транзакция не может завершаться внутри блока с обработчиками исключений.

Ошибки и сообщения

Вывод ошибок и сообщений

Для вывода расширенных сообщений и обработки ошибок используйте оператор RAISE.

RAISE [ уровень ] 'формат' [, выражение [, ... ]] [ USING параметр = выражение [, ... ] ];
RAISE [ уровень ] имя_условия [ USING параметр = выражение [, ... ] ];
RAISE [ уровень ] SQLSTATE 'код_ошибки' [ USING параметр = выражение [, ... ] ];
RAISE [ уровень ] USING параметр = выражение [, ... ];
RAISE ;

В параметре уровень задается серьезность ошибки. Допустимые уровни: DEBUG, LOG, INFO, NOTICE, WARNING и EXCEPTION; по умолчанию используется EXCEPTION. EXCEPTION вызывает ошибку (которая обычно прерывает текущую транзакцию); другие уровни просто генерируют сообщения с различными уровнями приоритета. Будут ли сообщения определенного приоритета передаваться клиенту, записываться в журнал сервера, или и то, и другое, зависит от переменных конфигурации log_min_messages и client_min_messages. Дополнительную информацию см. в главе Конфигурация сервера.

После уровня, если он имеется, можно задать строку формата (которая должна быть простым строковым литералом, а не выражением). Строка формата определяет текст выводимого сообщения об ошибке. За строкой формата могут идти необязательные выражения аргументов, которые будут вставлены в сообщение. Внутри строки формата % заменяется строковым представлением значения следующего необязательного аргумента. Чтобы получить буквальный знак %, напишите %%. Количество аргументов должно соответствовать количеству заполнителей % в строке формата, иначе при компиляции функции возникает ошибка.

В этом примере знак % в строке будет заменен на значение v_job_id:

RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
           -- Вызов функции cs_create_job(%)

Написав USING, а затем элементы параметр = выражение, можно прикрепить к отчету об ошибке дополнительную информацию. Каждое выражение может быть любым строковым выражением. Допустимые ключевые слова параметра:

MESSAGE
Устанавливает текст сообщения об ошибке. Этот параметр нельзя использовать в форме RAISE, включающей строку формата перед USING.

DETAIL
Предоставляет подробное сообщение об ошибке.

HINT
Предоставляет подсказку.

ERRCODE
Задает выводимый код ошибки (SQLSTATE) либо по имени условия, как показано в разделе Коды ошибок QHB, либо непосредственно в виде пятизначного кода SQLSTATE.

COLUMN
CONSTRAINT
DATATYPE
TABLE SCHEMA
Предоставляют имя объекта, связанного с ошибкой.

Этот пример прервет транзакцию с заданным сообщением об ошибке и подсказкой:

RAISE EXCEPTION 'Nonexistent ID --> %', user_id
              -- Несуществующий ID --> %
      USING HINT = 'Please check your user ID';
                 -- Пожалуйста, проверьте ваш пользовательский ID

Эти два примера показывают равнозначные способы установки SQLSTATE:

RAISE 'Duplicate user ID: %', user_id USING ERRCODE = 'unique_violation';
RAISE 'Duplicate user ID: %', user_id USING ERRCODE = '23505';
-- Дубликат пользовательского ID

Существует второй синтаксис RAISE, в котором основным аргументом является имя условия или выводимый SQLSTATE, например:

RAISE division_by_zero;
RAISE SQLSTATE '22012';

В этом синтаксисе для предоставления пользователю сообщения об ошибке, детализации или подсказки можно использовать USING. Другой способ выполнить предыдущий пример:

RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;

Еще один вариант — написать RAISE USING или RAISE уровень USING и поместить все остальное в список USING.

Последний вариант RAISE вообще не имеет параметров. Эту форму можно использовать только внутри предложения EXCEPTION блока BEGIN; с ее помощью можно повторно вызвать ошибку, обрабатываемую в данный момент.

Если в команде RAISE EXCEPTION не указаны ни имя условия, ни SQLSTATE, по умолчанию используется ERRCODE_RAISE_EXCEPTION (P0001). Если не задан текст сообщения, по умолчанию в качестве текста сообщения используется имя условия или SQLSTATE.

Примечание
При указании кода ошибки с помощью кода SQLSTATE вы не ограничены предопределенными кодами ошибок. Можно выбрать любой код ошибки, состоящий из пяти цифр и/или букв ASCII в верхнем регистре, кроме 00000. Не рекомендуется использовать коды ошибок, заканчивающиеся тремя нулями, потому что это коды категорий, которые можно перехватить, только перехватив всю категорию.

Проверка утверждений

Оператор ASSERT представляет собой удобное сокращение для вставки отладочных проверок в функции PL/pgSQL.

ASSERT условие [, сообщение ];

условие является логическим выражением, которое, как ожидается, всегда вычисляется как true; если это так, оператор ASSERT больше ничего не делает. При результате false или NULL выдается исключение ASSERT_FAILURE. (Если ошибка возникает при вычислении условия, она выводится как обычная ошибка.)

Если предоставляется необязательное сообщение, то результат этого выражения (если это не NULL) заменяет стандартный текст сообщения об ошибке «assertion failed» (утверждение не выполнено), если условие выдает ошибку. В обычном случае, когда утверждение успешно выполняется, выражение сообщения не вычисляется.

Тестирование утверждений можно включить или выключить с помощью параметра конфигурации plpgsql.check_asserts, который принимает логическое значение; значение по умолчанию on (включен). Если этот параметр равен off (выключен), то операторы ASSERT ничего не делают.

Обратите внимание, что ASSERT предназначен для обнаружения программных ошибок, а не для сообщения об обычных ошибках. Для этого используется описанный выше оператор RAISE.

Триггерные функции

PL/pgSQL можно использовать для определения триггерных функций при изменении данных или событиях базы данных. Триггерная функция создается с помощью команды CREATE FUNCTION, объявляющей ее как функцию без аргументов и возвращающей тип trigger (для триггеров изменения данных) или event_trigger (для триггеров событий базы данных). Для описания условия, провоцирующего вызов триггерной функции, автоматически определяются специальные локальные переменные с именами вида TG_некое_имя.

Триггеры при изменении данных

Триггер изменения данных объявляется как функция без аргументов и возвращающая тип trigger. Обратите внимание, что функция должна быть объявлена без аргументов, даже если ожидается, что она будет получать некоторые аргументы, указанные в CREATE TRIGGER, — такие аргументы передаются через TG_ARGV, как описано ниже.

Когда в качестве триггера вызывается функция PL/pgSQL, в блоке верхнего уровня автоматически создается несколько специальных переменных:

NEW
Тип данных RECORD; переменная, содержащая новую строку базы данных для операций INSERT/UPDATE в триггерах уровня строки. Эта переменная равна NULL в триггерах уровня оператора и для операций DELETE.

OLD
Тип данных RECORD; переменная, содержащая старую строку базы данных для операций UPDATE/DELETE в триггерах уровня строки. Эта переменная равна NULL в триггерах уровня оператора и для операций INSERT.

TG_NAME
Тип данных name; переменная, содержащая имя фактически сработавшего триггера.

TG_WHEN
Тип данных text; строка BEFORE, AFTER или INSTEAD OF, в зависимости от определения триггера.

TG_LEVEL
Тип данных text; строка ROW или STATEMENT, в зависимости от определения триггера.

TG_OP
Тип данных text; строка INSERT, UPDATE, DELETE или TRUNCATE, указывающая, для какой операции сработал триггер.

TG_RELID
Тип данных oid; идентификатор объекта таблицы, для которой сработал триггер.

TG_RELNAME
Тип данных name; имя таблицы, для которой сработал триггер. Сейчас эта переменная устарела и в будущих релизах может исчезнуть. Используйте вместо нее TG_TABLE_NAME.

TG_TABLE_NAME
Тип данных name; имя таблицы, для которой сработал триггер.

TG_TABLE_SCHEMA
Тип данных name; имя схемы таблицы, для которой сработал триггер.

TG_NARGS
Тип данных integer; количество аргументов, переданных триггерной функции в операторе CREATE TRIGGER.

TG_ARGV[]
Тип данных массив text; аргументы из оператора CREATE TRIGGER. Индекс массива начинается с 0. При недопустимых индексах (меньше 0 либо больше или равны tg_nargs) возвращается NULL.

Триггерная функция должна возвращать либо NULL, либо значение записи/строки, точно соответствующее структуре таблицы, для которой был запущен триггер.

Триггеры BEFORE уровня строки могут вернуть NULL, чтобы указать менеджеру триггеров пропустить оставшуюся часть операции для этой строки (т. е. последующие триггеры не запускаются, и INSERT/UPDATE/DELETE для этой строки не выполняется). Если возвращается значение, отличное от NULL, то операция продолжается с этим значением строки. Возвращение значения строки, отличного от исходного значения NEW, изменяет строку, которая будет добавлена или изменена. Таким образом, если триггерной функции нужно, чтобы инициирующее действие успешно выполнялось без изменения значения строки, необходимо вернуть NEW (или эквивалентное ей значение). Чтобы изменить сохраняемую строку, можно заменить отдельные значения непосредственно в NEW и вернуть измененную NEW или создать и вернуть полностью новую запись/строку. В случае триггера BEFORE для DELETE возвращаемое значение не имеет прямого эффекта, но оно должно быть отличным от NULL, чтобы триггер мог продолжить обработку. Обратите внимание, что в триггерах DELETE NEW равна NULL, поэтому обычно возвращать ее не имеет смысла. Обычной идиомой в триггерах DELETE является возврат OLD.

Триггеры INSTEAD OF (которые всегда являются триггерами уровня строки и могут использоваться только в представлениях) могут возвращать NULL, показывая, что они не выполняли никаких изменений и что оставшуюся часть операции для этой строки следует пропустить (т. е. последующие триггеры не срабатывают, и строка не учитывается в счетчике затронутых строк для окружающей команды INSERT/UPDATE/ DELETE). В противном случае должно быть возвращено значение, отличное от NULL, показывающее, что триггер выполнил запрошенную операцию. Для операций INSERT и UPDATE возвращаемое значение должно быть NEW, которое триггерная функция может изменить для передачи командам INSERT RETURNING и UPDATE RETURNING (это также повлияет на значение строки, передаваемое любым последующим триггерам или в специальный псевдоним для обращения EXCLUDED в операторе INSERT с предложением ON CONFLICT DO UPDATE). Для операций DELETE возвращаемое значение должно быть OLD.

Возвращаемое значение триггера AFTER уровня строки или триггера BEFORE или AFTER уровня оператора всегда игнорируется; оно тоже может быть равно NULL. Однако любой из этих типов триггеров все равно может прервать всю операцию, вызвав ошибку.

В Примере 3 показан пример триггерной функции в PL/pgSQL.

Пример 3. Триггерная функция PL/pgSQL

Этот пример триггера гарантирует, что каждый раз, когда в таблице добавляется или изменяется строка, в этой строке указываются имя текущего пользователя и метка времени. Также этот триггер проверяет, что имя сотрудника задано, а зарплата является положительным значением.

CREATE TABLE emp (
    empname text,
    salary integer,
    last_date timestamp,
    last_user text
);

CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Проверить, что указаны имя сотрудника и зарплата
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname cannot be null';
        END IF;
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% cannot have null salary', NEW.empname;
        END IF;

        -- Кто будет работать на нас, если им придется за это платить?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
        END IF;

        -- Запомнить, кто и когда изменил платежную ведомость
        NEW.last_date := current_timestamp;
        NEW.last_user := current_user;
        RETURN NEW;
    END;
$emp_stamp$ LANGUAGE plpgsql;

CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
    FOR EACH ROW EXECUTE FUNCTION emp_stamp();

Другой способ регистрации изменений в таблице заключается в создании новой таблицы, содержащей строку для каждого случая добавления, изменения или удаления. Этот подход можно рассматривать как аудит изменений в таблице. В Примере 4 показан пример триггерной функции аудита в PL/pgSQL.

Пример 4. Триггерная функция PL/pgSQL для аудита

Этот пример триггера гарантирует, что любое добавление, изменение или удаление строки в таблице emp записано (т. е. аудировано) в таблице emp_audit. Текущее время и имя пользователя указываются в строке вместе с типом выполняемой операции.

CREATE TABLE emp (
    empname           text NOT NULL,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    stamp             timestamp NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary integer
);

CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
    BEGIN
        --
        -- Создать строку в emp_audit для отображения операции, проведенной в emp,
        -- использовать специальную переменную TG_OP для определения типа операции.
        --
        IF (TG_OP = 'DELETE') THEN
            INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*;
        ELSIF (TG_OP = 'UPDATE') THEN
            INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*;
        END IF;
        RETURN NULL; -- результат игнорируется, поскольку это триггер AFTER
    END;
$emp_audit$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
    FOR EACH ROW EXECUTE FUNCTION process_emp_audit();

Разновидность предыдущего примера использует представление, соединяющее основную таблицу с таблицей аудита, для отображения времени последнего изменения каждой записи. При таком подходе по-прежнему записывается полный маршрут аудита изменений в таблице, но также предоставляется упрощенное представление маршрута аудита, отображая только временную метку последнего изменения, полученную из маршрута аудита для каждой записи. В Примере 5 показан пример триггера с представлением для аудита в PL/pgSQL.

Пример 5. Триггерная функция PL/pgSQL для аудита с представлением

В этом примере используется триггер с представлением, который делает это представление изменяемым и гарантирует, что любое добавление, изменение или удаление строки в представлении записывается (т. е. аудируется) в таблице emp_audit. Текущее время и имя пользователя записываются вместе с типом выполненной операции, и представление отображает время последнего изменения каждой строки.

CREATE TABLE emp (
    empname           text PRIMARY KEY,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary            integer,
    stamp             timestamp NOT NULL
);

CREATE VIEW emp_view AS
    SELECT e.empname,
           e.salary,
           max(ea.stamp) AS last_updated
      FROM emp e
      LEFT JOIN emp_audit ea ON ea.empname = e.empname
     GROUP BY 1, 2;

CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
    BEGIN
        --
        -- Выполнить требуемую операцию в emp и создать строку в emp_audit
        -- для отображения изменения, совершенного в emp.
        --
        IF (TG_OP = 'DELETE') THEN
            DELETE FROM emp WHERE empname = OLD.empname;
            IF NOT FOUND THEN RETURN NULL; END IF;

            OLD.last_updated = now();
            INSERT INTO emp_audit VALUES('D', user, OLD.*);
            RETURN OLD;
        ELSIF (TG_OP = 'UPDATE') THEN
            UPDATE emp SET salary = NEW.salary WHERE empname = OLD.empname;
            IF NOT FOUND THEN RETURN NULL; END IF;

            NEW.last_updated = now();
            INSERT INTO emp_audit VALUES('U', user, NEW.*);
            RETURN NEW;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp VALUES(NEW.empname, NEW.salary);

            NEW.last_updated = now();
            INSERT INTO emp_audit VALUES('I', user, NEW.*);
            RETURN NEW;
        END IF;
    END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit
INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view
    FOR EACH ROW EXECUTE FUNCTION update_emp_view();

Одним из применений триггеров является ведение сводной таблицы для другой таблицы. Результирующую сводку можно использовать вместо исходной таблицы для определенных запросов — зачастую со значительным сокращением времени выполнения. Этот метод обычно используется в хранилищах данных, где таблицы измеренных или наблюдаемых данных (так называемые таблицы фактов) могут быть чрезвычайно большими. В Примере 6 показан пример триггерной функции в PL/pgSQL, ведущей сводную таблицу для таблицы фактов в хранилище данных.

Пример 6. Триггерная функция PL/pgSQL для ведения сводной таблицы

Схема, подробно описанная здесь, частично основана на примере Grocery Store из книги «The Data Warehouse Toolkit» Ральфа Кимбалла (Ralph Kimball).

--
-- Основные таблицы - временные периоды и факты продажи.
--
CREATE TABLE time_dimension (
    time_key                    integer NOT NULL,
    day_of_week                 integer NOT NULL,
    day_of_month                integer NOT NULL,
    month                       integer NOT NULL,
    quarter                     integer NOT NULL,
    year                        integer NOT NULL
);
CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);

CREATE TABLE sales_fact (
    time_key                    integer NOT NULL,
    product_key                 integer NOT NULL,
    store_key                   integer NOT NULL,
    amount_sold                 numeric(12,2) NOT NULL,
    units_sold                  integer NOT NULL,
    amount_cost                 numeric(12,2) NOT NULL
);
CREATE INDEX sales_fact_time ON sales_fact(time_key);

--
-- Сводная таблица - продажи по периодам.
--
CREATE TABLE sales_summary_bytime (
    time_key                    integer NOT NULL,
    amount_sold                 numeric(15,2) NOT NULL,
    units_sold                  numeric(12) NOT NULL,
    amount_cost                 numeric(15,2) NOT NULL
);
CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);

--
-- Функция и триггер для корректировки столбцов сводки при выполнении команд
-- UPDATE, INSERT, DELETE.
--
CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER
AS $maint_sales_summary_bytime$
    DECLARE
        delta_time_key          integer;
        delta_amount_sold       numeric(15,2);
        delta_units_sold        numeric(12);
        delta_amount_cost       numeric(15,2);
    BEGIN

        -- Определить увеличение/уменьшенние сумм(ы).
        IF (TG_OP = 'DELETE') THEN

            delta_time_key = OLD.time_key;
            delta_amount_sold = -1 * OLD.amount_sold;
            delta_units_sold = -1 * OLD.units_sold;
            delta_amount_cost = -1 * OLD.amount_cost;

        ELSIF (TG_OP = 'UPDATE') THEN

            -- запретить изменения, затрагивающие time_key -
            -- (вероятно, это не слишком обременительно, поскольку большинство
            -- изменений будет выполняться по схеме DELETE + INSERT).
            IF ( OLD.time_key != NEW.time_key) THEN
                RAISE EXCEPTION 'Update of time_key : % -> % not allowed',
                                                      OLD.time_key, NEW.time_key;
            END IF;

            delta_time_key = OLD.time_key;
            delta_amount_sold = NEW.amount_sold - OLD.amount_sold;
            delta_units_sold = NEW.units_sold - OLD.units_sold;
            delta_amount_cost = NEW.amount_cost - OLD.amount_cost;

        ELSIF (TG_OP = 'INSERT') THEN

            delta_time_key = NEW.time_key;
            delta_amount_sold = NEW.amount_sold;
            delta_units_sold = NEW.units_sold;
            delta_amount_cost = NEW.amount_cost;

        END IF;

        -- Внести новые значения, изменив строку сводки, или добавив новую.
        <<insert_update>>
        LOOP
            UPDATE sales_summary_bytime
                SET amount_sold = amount_sold + delta_amount_sold,
                    units_sold = units_sold + delta_units_sold,
                    amount_cost = amount_cost + delta_amount_cost
                WHERE time_key = delta_time_key;

            EXIT insert_update WHEN found;

            BEGIN
                INSERT INTO sales_summary_bytime (
                            time_key,
                            amount_sold,
                            units_sold,
                            amount_cost)
                    VALUES (
                            delta_time_key,
                            delta_amount_sold,
                            delta_units_sold,
                            delta_amount_cost
                           );

                EXIT insert_update;

            EXCEPTION
                WHEN UNIQUE_VIOLATION THEN
                    -- ничего не делать
            END;
        END LOOP insert_update;

        RETURN NULL;

    END;
$maint_sales_summary_bytime$ LANGUAGE plpgsql;

CREATE TRIGGER maint_sales_summary_bytime
AFTER INSERT OR UPDATE OR DELETE ON sales_fact
    FOR EACH ROW EXECUTE FUNCTION maint_sales_summary_bytime();

INSERT INTO sales_fact VALUES(1,1,1,10,3,15);
INSERT INTO sales_fact VALUES(1,2,1,20,5,35);
INSERT INTO sales_fact VALUES(2,2,1,40,15,135);
INSERT INTO sales_fact VALUES(2,3,1,10,1,13);
SELECT * FROM sales_summary_bytime;
DELETE FROM sales_fact WHERE product_key = 1;
SELECT * FROM sales_summary_bytime;
UPDATE sales_fact SET units_sold = units_sold * 2;
SELECT * FROM sales_summary_bytime;

Триггеры AFTER также могут использовать таблицы переходов для проверки всего набора строк, измененных оператором, вызвавшим срабатывание триггера. Команда CREATE TRIGGER присваивает имена одной или обеим таблицам переходов, а затем функция может обращаться к ним по этим именам, как если бы они были временными таблицами только для чтения. Это показано в Примере 7.

Пример 7. Аудит с таблицами переходов

Этот пример выдает те же результаты, что и Пример 4, но вместо использования триггера, срабатывающего для каждой строки, здесь используется триггер, срабатывающий один раз для каждого оператора, после сбора соответствующей информации в таблице переходов. Это может быть значительно быстрее способа с триггером строки, когда оператор, вызвавший срабатывание триггера, изменил много строк. Обратите внимание, что нужно сделать отдельное объявление триггера для каждого типа события, так как предложения REFERENCING для каждого случая должны быть разными. Но это не мешает при желании использовать одну функцию триггера. (Возможно, на практике было бы лучше использовать три отдельные функции и избегать проверок TG_OP во время выполнения.)

CREATE TABLE emp (
    empname           text NOT NULL,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    stamp             timestamp NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary integer
);

CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
    BEGIN
        --
        -- Создать строки в emp_audit для отображения операций, проведенных в emp,
        -- использовать специальную переменную TG_OP для определения типа операций.
        --
        IF (TG_OP = 'DELETE') THEN
            INSERT INTO emp_audit
                SELECT 'D', now(), user, o.* FROM old_table o;
        ELSIF (TG_OP = 'UPDATE') THEN
            INSERT INTO emp_audit
                SELECT 'U', now(), user, n.* FROM new_table n;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp_audit
                SELECT 'I', now(), user, n.* FROM new_table n;
        END IF;
        RETURN NULL; -- результат игнорируется, поскольку это триггер AFTER
    END;
$emp_audit$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit_ins
    AFTER INSERT ON emp
    REFERENCING NEW TABLE AS new_table
    FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_upd
    AFTER UPDATE ON emp
    REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
    FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_del
    AFTER DELETE ON emp
    REFERENCING OLD TABLE AS old_table
    FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();

Триггеры событий

PL/pgSQL можно использовать для определения триггеров событий. QHB требует, чтобы функция, вызываемая как триггер события, была объявлена как функция без аргументов и возвращающая тип event_trigger.

Когда функция PL/pgSQL вызывается как триггер события, в блоке верхнего уровня автоматически создается несколько специальных переменных:

TG_EVENT
Тип данных text; строка, представляющая событие, для которого срабатывает триггер.

TG_TAG
Тип данных text; переменная, содержащая тег команды, для которого срабатывает триггер.

В Примере 8 показан пример триггерной функции события в PL/pgSQL.

Пример 8. Триггерная функция события PL/pgSQL

Этот пример триггера просто вызывает сообщение NOTICE каждый раз, когда выполняется поддерживаемая команда.

CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
BEGIN
    RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
END;
$$ LANGUAGE plpgsql;

CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();

Внутренняя структура PL/pgSQL

В этом разделе рассматриваются некоторые детали реализации, которые зачастую важно знать пользователям PL/pgSQL.

Подстановка переменных

Операторы SQL и выражения в функции PL/pgSQL могут ссылаться на переменные и параметры этой функции. За кулисами PL/pgSQL заменяет параметры запроса для таких ссылок. Параметры будут заменены только в тех местах, где они синтаксически разрешены. В качестве крайнего случая рассмотрим пример плохого стиля программирования:

INSERT INTO foo (foo) VALUES (foo(foo));

Первое вхождение foo синтаксически должно быть именем таблицы, поэтому оно не будет заменено, даже если функция имеет переменную с именем foo. Второе вхождение должно быть именем столбца этой таблицы, поэтому оно также не будет заменено. Аналогичным образом третье вхождение должно быть именем функции, поэтому заменено не будет. Только последнее вхождение является кандидатом на ссылку на переменную функции PL/pgSQL.

Еще один способ понять это — подстановка переменной может вставлять в команду SQL только значение данных; она не может динамически менять объекты базы данных, на которые ссылается команда. (Для этого нужно собрать командную строку динамически, как описано в подразделе Выполнение динамических команд.)

Поскольку имена переменных синтаксически ничем не отличаются от имен столбцов таблицы, в выражениях, которые также ссылаются на таблицы, может возникнуть неоднозначность: данное имя является ссылкой на столбец таблицы или на переменную? Давайте изменим предыдущий пример на

INSERT INTO dest (col) SELECT foo + bar FROM src;

Здесь dest и src должны быть именами таблиц, а col должен быть столбцом dest, но foo и bar вполне могут быть либо переменными функции, либо столбцами src.

По умолчанию PL/pgSQL сообщит об ошибке, если имя в операторе SQL может ссылаться либо на переменную, либо на столбец таблицы. Эту проблему можно решить, переименовав переменную или столбец, или дополнив неоднозначную ссылку, или указав PL/pgSQL, какую интерпретацию выбрать.

Самое простое решение — переименовать переменную или столбец. Общим правилом кодирования является использование для переменных PL/pgSQL соглашения об именах, отличного от соглашения для имен столбцов. Например, если вы последовательно называете переменные функции v_имя, тогда как ни одно из имен столбцов не начинается с v_, никаких конфликтов не будет.

В качестве альтернативы можно для ясности дополнить неоднозначные ссылки. В приведенном выше примере src.foo будет однозначной ссылкой на столбец таблицы. Чтобы создать однозначную ссылку на переменную, объявите ее в помеченном блоке и используйте эту метку (см. раздел Структура PL/pgSQL). Например,

<<block>>
DECLARE
    foo int;
BEGIN
    foo := ...;
    INSERT INTO dest (col) SELECT block.foo + bar FROM src;

Здесь block.foo означает переменную, даже если в src есть столбец foo. Параметры функции, а также специальные переменные, например FOUND, можно дополнить именем функции, поскольку они неявно объявляются во внешнем блоке, помеченном именем функции.

Иногда нецелесообразно исправлять все неоднозначные ссылки в большом теле кода PL/pgSQL. В таких случаях можно указать, что PL/pgSQL должен разрешать неоднозначные ссылки как переменные или как столбцы таблицы (последнее совместимо с некоторыми другими системами, например с Oracle).

Чтобы изменить это поведение в масштабе всей системы, установите для параметра конфигурации plpgsql.variable_conflict один из параметров: error, use_variable или use_column (где error — заводская установка по умолчанию). Этот параметр влияет на последующие компиляции операторов в функциях PL/pgSQL, но не на операторы, уже скомпилированные в текущем сеансе. Поскольку изменение этого параметра может привести к неожиданным изменениям в поведении функций PL/pgSQL, его может изменить только суперпользователь.

Также можно установить поведение для каждой функции, вставив в начале текста функции одну из этих специальных команд:

#variable_conflict error
#variable_conflict use_variable
#variable_conflict use_column

Эти команды влияют только на функцию, в которой они написаны, и переопределяют значение plpgsql.variable_conflict. Например:

CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
    #variable_conflict use_variable
    DECLARE
        curtime timestamp := now();
    BEGIN
        UPDATE users SET last_modified = curtime, comment = comment
          WHERE users.id = id;
    END;
$$ LANGUAGE plpgsql;

В команде UPDATE curtime, comment и id будут ссылаться на переменную и параметры функции независимо от того, есть ли у users столбцы с этими именами. Обратите внимание, что нам пришлось дополнить ссылку на users.id в предложении WHERE, чтобы она ссылалась на столбец таблицы. Но нам не нужно было дополнять ссылку на comment как цель в списке UPDATE, потому что синтаксически это должен быть столбец users. Мы можем написать ту же функцию, но не зависящую от значения variable_conflict, следующим образом:

CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
    <<fn>>
    DECLARE
        curtime timestamp := now();
    BEGIN
        UPDATE users SET last_modified = fn.curtime, comment = stamp_user.comment
          WHERE users.id = stamp_user.id;
    END;
$$ LANGUAGE plpgsql;

Подстановка переменных не происходит в командной строке, переданной EXECUTE или одному из ее вариантов. Если вам нужно вставить значение переменной в подобную команду, сделайте это при построении строкового значения или используйте USING, как показано в подразделе Выполнение динамических команд.

В настоящее время замена переменных работает только в командах SELECT, INSERT, UPDATE и DELETE, а также в командах, содержащих одну из них (например, EXPLAIN и CREATE TABLE ... AS SELECT), поскольку основной механизм SQL допускает параметры запроса только в этих командах. Чтобы использовать непостоянное имя или значение в других типах операторов (обычно называемых служебными операторами), необходимо создать служебный оператор в виде строки и выполнить его (EXECUTE).

Кэширование плана

Интерпретатор PL/pgSQL разбирает исходный текст функции и создает внутреннее двоичное дерево инструкций при первом вызове функции (в каждом сеансе). Дерево инструкций полностью переводит структуру операторов PL/pgSQL, но отдельные выражения и команды SQL, используемые в функции, переводятся не сразу.

Поскольку каждое выражение и команда SQL сначала выполняются в функции, интерпретатор PL/pgSQL разбирает и анализирует команду для создания подготовленного оператора, используя функцию SPI_prepare менеджера SPI. Последующие посещения этого выражения или команды повторно используют подготовленный оператор. Таким образом, функция с редко посещаемыми условными путями кода никогда не будет нести затраты на анализ тех команд, которые никогда не выполняются в текущем сеансе. Недостатком является то, что ошибки в определенном выражении или команде нельзя обнаружить, пока выполнение не дойдет до этой части функции. (Тривиальные синтаксические ошибки будут обнаружены во время первого разбора, но что-то более серьезное будет выявлено только во время выполнения.)

Кроме того, PL/pgSQL (или, точнее, менеджер SPI) может попытаться кэшировать план выполнения, связанный с любым конкретным подготовленным оператором. Если кэшированный план не используется, то при каждом посещении оператора генерируется новый план выполнения, и для оптимизации выбранного плана можно использовать текущие значения параметров (то есть значения переменных PL/pgSQL). Если у оператора нет параметров или он выполняется многократно, менеджер SPI рассмотрит возможность создания общего плана, не зависящего от конкретных значений параметров, и кэширования его для повторного использования. Обычно это происходит, только если план выполнения не очень зависит от значений переменных PL/pgSQL, на которые в нем есть ссылки. Если это так, каждый раз генерировать новый план крайне выгодно. Дополнительную информацию о поведении подготовленных операторов см. на справочной странице команды PREPARE.

Поскольку PL/pgSQL сохраняет подготовленные операторы и иногда планы выполнения таким образом, команды SQL, которые находятся непосредственно в функции PL/pgSQL, должны при каждом выполнении ссылаться на одни и те же таблицы и столбцы; то есть в команде SQL нельзя использовать параметр в качестве имени таблицы или столбца. Чтобы обойти это ограничение, можно создать динамические команды с помощью оператора PL/pgSQL EXECUTE — ценой проведения нового разбора и синтаксического анализа и построения нового плана при каждом выполнении.

Изменчивая природа переменных записи представляет другую проблему в этой связи. Когда поля переменной записи используются в выражениях или операторах, типы данных этих полей не должны изменяться от одного вызова функции к следующему, поскольку каждое выражение будет анализироваться с использованием типа данных, который присутствует при первой обработке выражения. При необходимости, чтобы обойти эту проблему, можно воспользоваться командой EXECUTE.

Если одна функция используется как триггер для нескольких таблиц, PL/pgSQL подготавливает и кэширует операторы независимо для каждой такой таблицы — то есть существует кэш для каждой комбинации триггерной функции и таблицы, а не только для каждой функции. Это устраняет некоторые проблемы, касающиеся различных типов данных; например, триггерная функция сможет успешно работать со столбцом с именем key, даже если он имеет разные типы в разных таблицах.

Аналогично функции с полиморфными типами аргументов имеют отдельный кэш операторов для каждой комбинации фактических типов аргументов, для которых они были вызваны, так что различия типов данных не вызывают неожиданных сбоев.

Кэширование операторов иногда может оказать неожиданное влияние на интерпретацию чувствительных ко времени значений. Например, есть разница между тем, что делают эти две функции:

CREATE FUNCTION logfunc1(logtxt text) RETURNS void AS $$
    BEGIN
        INSERT INTO logtable VALUES (logtxt, 'now');
    END;
$$ LANGUAGE plpgsql;

и:

CREATE FUNCTION logfunc2(logtxt text) RETURNS void AS $$
    DECLARE
        curtime timestamp;
    BEGIN
        curtime := 'now';
        INSERT INTO logtable VALUES (logtxt, curtime);
    END;
$$ LANGUAGE plpgsql;

В случае logfunc1 главный анализатор QHB при анализе INSERT знает, что строка 'now' должна интерпретироваться как timestamp, потому что целевой столбец logtable относится к этому типу. Таким образом, при анализе INSERT 'now' будет преобразована в постоянную timestamp, а затем будет использоваться во всех вызовах logfunc1 в течение всего сеанса. Несомненно, это не то, что хотел программист. Было бы лучше использовать функцию now() или current_timestamp.

В случае logfunc2 основной анализатор QHB не знает, каким должен быть тип 'now', и поэтому возвращает значение типа text, содержащее строку now. Во время последующего присвоения локальной переменной curtime интерпретатор PL/pgSQL приводит эту строку к типу timestamp, вызывая для преобразования функции text_out и timestamp_in. Таким образом, вычисленная метка времени обновляется при каждом выполнении, как и ожидает программист. Но, несмотря на то, что все работает должным образом, это не очень эффективно, поэтому все равно было бы лучше воспользоваться функцией now().

Советы по разработке на PL/pgSQL

Хороший способ разработки в PL/pgSQL состоит в том, чтобы создавать функции с помощью выбранного текстового редактора, а в другом окне загружать и тестировать эти функции с помощью psql. Если делать это таким способом, хорошей идеей будет написать функцию, используя CREATE OR REPLACE FUNCTION. При этом для изменения определения функции достаточно просто перезагрузить файл. Например:

CREATE OR REPLACE FUNCTION testfunc(integer) RETURNS integer AS $$
          ....
$$ LANGUAGE plpgsql;

Во время работы psql можно загрузить или перезагрузить такой файл определения функции с помощью:

\i filename.sql

а затем немедленно выполнить команды SQL для проверки этой функции.

Еще один хороший способ разработки на PL/pgSQL заключается в применении инструмента доступа к базе данных с ГПИ, упрощающего разработку на процедурном языке. Одним из примеров такого инструмента является pgAdmin, хотя существуют и другие. Эти инструменты часто предоставляют удобные функции, например экранирование апострофов и упрощение воссоздания и отладки функций.

Обработка кавычек

Код функции PL/pgSQL задается в CREATE FUNCTION как строковый литерал. Если вы пишете строковый литерал обычным способом, заключая его в апострофы, то все апострофы внутри тела функции следует дублировать; аналогичным образом нужно дублировать все обратные слэши (при условии использования синтаксиса с экранированием строки). Дублирование кавычек в лучшем случае утомительно, а в более сложных случаях код может стать совершенно непонятным, потому что нередко может потребоваться поставить пять или более кавычек подряд. Вместо этого рекомендуется написать тело функции в виде строкового литерала, заключенного в «долларовые кавычки» (см. подраздел Строковые константы с экранированием знаками доллара). При подходе с заключением в знаки доллара кавычки не нужно дублировать; вместо этого следует выбирать разные разделители долларовых кавычек для каждого необходимого уровня вложенности. Например, можно написать команду CREATE FUNCTION как:

CREATE OR REPLACE FUNCTION testfunc(integer) RETURNS integer AS $PROC$
          ....
$PROC$ LANGUAGE plpgsql;

В рамках этого можно использовать кавычки для простых литеральных строк в командах SQL и $$ для разделения фрагментов команд SQL, которые вы собираете в виде строк. Если нужно заключить в кавычки текст, включающий $$, можно использовать $Q$, и так далее.

Следующая схема показывает, что следует делать, когда вы пишете кавычки без экранирования знаками доллара. Это может быть полезно при переводе кода с обычными кавычками в нечто более понятное.

1 кавычка (апостроф)

Чтобы начать и закончить тело функции, например:

CREATE FUNCTION foo() RETURNS integer AS '
          ....
' LANGUAGE plpgsql;

Везде в теле функции, заключенном в апострофы, кавычки должны дублироваться.

2 кавычки

Для строковых литералов внутри тела функции, например:

a_output := ''Blah'';
SELECT * FROM users WHERE f_name=''foobar'';

При подходе с экранированием знаками доллара вы бы написали просто:

a_output := 'Blah';
SELECT * FROM users WHERE f_name='foobar';

и именно это увидит анализатор PL/pgSQL в любом случае.

4 кавычки

Когда нужен апостроф в строковой константе внутри тела функции, например:

a_output := a_output || '' AND name LIKE ''''foobar'''' AND xyz''

Значением, фактически добавляемым к a_output, будет: AND name LIKE 'foobar' AND xyz.

В подходе с экранированием знаками доллара вы бы написали:

a_output := a_output || $$ AND name LIKE 'foobar' AND xyz$$

внимательно следите, чтобы любые разделители долларовых кавычек вокруг этого не были просто $$.

6 кавычек

Когда апострофы в строке внутри тела функции находятся в конце этой строковой константы, например:

a_output := a_output || '' AND name LIKE ''''foobar''''''

Значением, добавляемым к a_output будет: AND name LIKE 'foobar'.

В подходе с экранированием знаками доллара это превращается в:

a_output := a_output || $$ AND name LIKE 'foobar'$$

10 кавычек

Когда в строковой константе требуется два апострофа (это уже 8 кавычек), примыкающих к концу этой строковой константы (это еще 2). Вероятно, подобное может понадобиться только при написании функции, генерирующей другие функции, как в Примере 10. Например:

a_output := a_output || '' if v_'' ||
    referrer_keys.kind || '' like ''''''''''
    || referrer_keys.key_string || ''''''''''
    then return ''''''  || referrer_keys.referrer_type
    || ''''''; end if;'';

Тогда значением a_output будет:

if v_... like ''...'' then return ''...''; end if;

В подходе с экранированием знаками доллара это превращается в:

a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
    || referrer_keys.key_string || $$'
    then return '$$  || referrer_keys.referrer_type
    || $$'; end if;$$;

где мы предполагаем, что достаточно только поставить апострофы в a_output, потому что она будет повторно взята в кавычки перед использованием.

Дополнительные проверки во время компиляции и во время выполнения

Чтобы помочь пользователю найти отдельные случаи простых, но распространенных проблем до того, как они причинят вред, PL/pgSQL предоставляет дополнительные проверки. Когда они включены, в зависимости от конфигурации их можно использовать для выдачи сообщения WARNING или ERROR во время компиляции функции. Функция, получившая WARNING, может быть выполнена без выдачи дополнительных сообщений, поэтому рекомендуется тестировать ее в отдельной среде разработки.

При необходимости в средах разработки и/или тестирования рекомендуется установить plpgsql.extra_warnings или plpgsql.extra_errors в значение "all".

Эти дополнительные проверки включаются через переменные конфигурации plpgsql.extra_warnings для предупреждений и plpgsql.extra_errors для ошибок. В обеих переменных можно установить список проверок, разделенных запятыми, "none" или "all". По умолчанию установлено значение "none". В настоящее время список доступных проверок включает в себя:

shadowed_variables
Проверяет, не скрывает ли объявление новой переменной определенную ранее.

strict_multi_assignment
Некоторые команды PL/pgSQL, например SELECT INTO, позволяют присваивать значения более чем одной переменной за раз. Как правило, количество целевых и исходных переменных должно совпадать, хотя пропущенных значений PL/pgSQL будет использовать NULL, а лишние переменные игнорируются. Включение этой проверки приведет к тому, что PL/pgSQL будет выдавать WARNING или ERROR всякий раз, когда количество целевых и исходных переменных различается.

too_many_rows
Включение этой проверки заставит PL/pgSQL проверять, возвращает ли данный запрос более одной строки при использовании предложения INTO. Поскольку оператор INTO всегда будет использовать только одну строку, запрос, возвращающий несколько строк, как правило, неэффективен и/или недетерминирован, поэтому, скорее всего, является ошибочным.

В следующем примере показано влияние установки plpgsql.extra_warnings в значение shadowed_variables:

SET plpgsql.extra_warnings TO 'shadowed_variables';

CREATE FUNCTION foo(f1 int) RETURNS int AS $$
DECLARE
f1 int;
BEGIN
RETURN f1;
END
$$ LANGUAGE plpgsql;
WARNING:  variable "f1" shadows a previously defined variable
-- ПРЕДУПРЕЖДЕНИЕ: переменная "f1" скрывает переменную, определенную ранее
LINE 3: f1 int;
        ^
CREATE FUNCTION

В приведенном ниже примере показано влияние установки plpgsql.extra_warnings в значение strict_multi_assignment:

SET plpgsql.extra_warnings TO 'strict_multi_assignment';

CREATE OR REPLACE FUNCTION public.foo()
 RETURNS void
 LANGUAGE plpgsql
AS $$
DECLARE
  x int;
  y int;
BEGIN
  SELECT 1 INTO x, y;
  SELECT 1, 2 INTO x, y;
  SELECT 1, 2, 3 INTO x, y;
END;
$$;

SELECT foo();
WARNING:  number of source and target fields in assignment does not match
DETAIL:  strict_multi_assignment check of extra_warnings is active.
HINT:  Make sure the query returns the exact list of columns.
-- ПРЕДУПРЕЖДЕНИЕ: количество исходных и целевых полей в assignment не совпадает
-- ДЕТАЛИЗАЦИЯ: проверка strict_multi_assignment в extra_warnings активна.
-- ПОДСКАЗКА: Убедитесь, что запрос возвращает точный список столбцов.

 foo
-----

(1 row)

Портирование из Oracle PL/SQL

В этом разделе объясняются различия между языком PL/pgSQL QHB и языком PL/SQL Oracle, чтобы помочь разработчикам, которые переносят приложения из Oracle® в QHB.

PL/pgSQL во многом похож на PL/SQL. Это блочно-структурированный императивный язык, где все переменные должны быть объявлены. Присвоения, циклы и условия схожи. Основные различия, которые следует учитывать при переносе с PL/SQL на PL/pgSQL, следующие:

Если имя, фигурирующее в команде SQL, может быть как именем столбца таблицы, используемой в этой команде, так и ссылкой на переменную функции, PL/SQL обрабатывает его как имя столбца. По умолчанию PL/pgSQL выдаст ошибку, предъявляя претензию о неоднозначности имени. Чтобы изменить это поведение на принятое в PL/SQL, можно указать plpgsql.variable_conflict = use_column, как описано в подразделе Подстановка переменных. Как правило, лучше вообще избегать подобных неоднозначностей, но если вам нужно портировать большой объем кода, зависящего от этого поведения, установка variable_conflict может быть лучшим решением.

В QHB тело функции должно быть записано как строковый литерал. Поэтому следует использовать долларовые кавычки или экранировать апострофы в теле функции. (См. подраздел Обработка кавычек.)

Имена типов данных часто нуждаются в преобразовании. Например, в Oracle строковые значения обычно объявляются как имеющие тип varchar2, не являющийся стандартным типом SQL. В QHB вместо него следует использовать тип varchar или text. Аналогичным образом нужно будет заменить тип number на numeric или использовать другой числовой тип, если есть более подходящий.

Для организации функций в группы используйте схемы вместо пакетов.

Поскольку пакетов нет, переменных уровня пакета тоже нет. Это несколько раздражает. Вместо этого можно хранить состояние сеанса во временных таблицах.

Целочисленные циклы FOR с REVERSE работают по-разному: PL/SQL ведет обратный отсчет от второго числа к первому, тогда как PL/pgSQL ведет обратный его от первого числа ко второму, поэтому при переносе границы цикла нужно поменять местами. Это неприятная несовместимость, но вряд ли что-то изменится. (См. подраздел FOR (целочисленный вариант).)

Циклы FOR по запросам (кроме курсоров) также работают по-разному: целевые переменные должны быть объявлены, тогда как PL/SQL всегда объявляет их неявно. Преимущество этого состоит в том, что значения переменных доступны и после выхода из цикла.

Существуют различные нотационные различия при использовании переменных курсора.

Примеры портирования

В Примере 9 показано, как портировать простую функцию из PL/SQL в PL/pgSQL.

Пример 9. Портирование простой функции из PL/SQL в PL/pgSQL

Функция Oracle PL/SQL:

CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar2,
                                                  v_version varchar2)
RETURN varchar2 IS
BEGIN
    IF v_version IS NULL THEN
        RETURN v_name;
    END IF;
    RETURN v_name || '/' || v_version;
END;
/
show errors;

Давайте рассмотрим эту функцию и посмотрим на различия по сравнению с PL/pgSQL:

Имя типа varchar2 нужно заменить на varchar или text. В примерах в этом подразделе мы будем использовать varchar, но зачастую text является лучшим выбором, если вам не нужны определенные ограничения длины строк.

Ключевое слово RETURN в прототипе функции (но не в ее теле) в QHB становится RETURNS. Кроме того, IS становится AS, и нужно добавить предложение LANGUAGE, поскольку PL/pgSQL не является единственным возможным языком функций.

В QHB тело функции считается строковым литералом, поэтому его нужно заключать в кавычки или знаки доллара. Это заменяет завершающий / в подходе Oracle.

В QHB не существует команды show errors, но она и не нужна, поскольку ошибки выводятся автоматически.

Вот как будет выглядеть эта функция при портировании на QHB:

CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar,
                                                  v_version varchar)
RETURNS varchar AS $$
BEGIN
    IF v_version IS NULL THEN
        RETURN v_name;
    END IF;
    RETURN v_name || '/' || v_version;
END;
$$ LANGUAGE plpgsql;

В Примере 10 показано, как портировать функцию, создающую другую функцию, и как справиться с возникающими проблемами с кавычками.

Пример 10. Портирование функции, создающей другую функцию, из PL/SQL в PL/pgSQL

Следующая процедура извлекает строки из SELECT и строит большую функцию, результаты которой ради эффективности возвращаются в операторах IF.

Это версия Oracle:

CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS
    CURSOR referrer_keys IS
        SELECT * FROM cs_referrer_keys
        ORDER BY try_order;
    func_cmd VARCHAR(4000);
BEGIN
    func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR2,
                 v_domain IN VARCHAR2, v_url IN VARCHAR2) RETURN VARCHAR2 IS BEGIN';

    FOR referrer_key IN referrer_keys LOOP
        func_cmd := func_cmd ||
          ' IF v_' || referrer_key.kind
          || ' LIKE ''' || referrer_key.key_string
          || ''' THEN RETURN ''' || referrer_key.referrer_type
          || '''; END IF;';
    END LOOP;

    func_cmd := func_cmd || ' RETURN NULL; END;';

    EXECUTE IMMEDIATE func_cmd;
END;
/
show errors;

А вот как эта функция может выглядеть в QHB:

CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc() AS $func$
DECLARE
    referrer_keys CURSOR IS
        SELECT * FROM cs_referrer_keys
        ORDER BY try_order;
    func_body text;
    func_cmd text;
BEGIN
    func_body := 'BEGIN';

    FOR referrer_key IN referrer_keys LOOP
        func_body := func_body ||
          ' IF v_' || referrer_key.kind
          || ' LIKE ' || quote_literal(referrer_key.key_string)
          || ' THEN RETURN ' || quote_literal(referrer_key.referrer_type)
          || '; END IF;' ;
    END LOOP;

    func_body := func_body || ' RETURN NULL; END;';

    func_cmd :=
      'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host varchar,
                                                        v_domain varchar,
                                                        v_url varchar)
        RETURNS varchar AS '
      || quote_literal(func_body)
      || ' LANGUAGE plpgsql;' ;

    EXECUTE func_cmd;
END;
$func$ LANGUAGE plpgsql;

Обратите внимание, что тело функции строится отдельно и передается через quote_literal для удвоения всех кавычек. Этот метод необходим, потому что мы не можем безопасно использовать долларовые кавычки для определения новой функции: мы не знаем наверняка, какие строки будут интерполированы из поля referrer_key.key_string. (Здесь предполагается, что referrer_key.kind точно всегда имеет значение host, domain или url, но referrer_key.key_string может быть чем угодно, в частности, содержать знаки доллара). На самом деле, эта функция улучшена по сравнению с оригиналом на Oracle, потому что она не будет генерировать неработающий код, если в полях referrer_key.key_string или referrer_key.referrer_type содержатся кавычки.

В Примере 11 показано, как портировать функцию с выходными параметрами (OUT), которая манипулирует строками. QHB не имеет встроенной функции instr, но ее можно создать, используя комбинацию других функций. В подразделе Приложение описана реализация instr в PL/pgSQL, которой можно воспользоваться для упрощения портирования.

Пример 11. Портирование процедуры с манипулированием строками и параметрами OUT из PL/SQL в PL/pgSQL

Следующая процедура Oracle PL/SQL используется для синтаксического анализа URL-адреса и возврата нескольких элементов (хост, путь и запрос).

Это версия Oracle:

CREATE OR REPLACE PROCEDURE cs_parse_url(
    v_url IN VARCHAR2,
    v_host OUT VARCHAR2,  -- Это будет возвращено
    v_path OUT VARCHAR2,  -- И это тоже
    v_query OUT VARCHAR2) -- И это
IS
    a_pos1 INTEGER;
    a_pos2 INTEGER;
BEGIN
    v_host := NULL;
    v_path := NULL;
    v_query := NULL;
    a_pos1 := instr(v_url, '//');

    IF a_pos1 = 0 THEN
        RETURN;
    END IF;
    a_pos2 := instr(v_url, '/', a_pos1 + 2);
    IF a_pos2 = 0 THEN
        v_host := substr(v_url, a_pos1 + 2);
        v_path := '/';
        RETURN;
    END IF;

    v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
    a_pos1 := instr(v_url, '?', a_pos2 + 1);

    IF a_pos1 = 0 THEN
        v_path := substr(v_url, a_pos2);
        RETURN;
    END IF;

    v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
    v_query := substr(v_url, a_pos1 + 1);
END;
/
show errors;

Вот возможная конвертация в PL/pgSQL:

CREATE OR REPLACE FUNCTION cs_parse_url(
    v_url IN VARCHAR,
    v_host OUT VARCHAR,  -- Это будет возвращено
    v_path OUT VARCHAR,  -- И это тоже
    v_query OUT VARCHAR) -- И это
AS $$
DECLARE
    a_pos1 INTEGER;
    a_pos2 INTEGER;
BEGIN
    v_host := NULL;
    v_path := NULL;
    v_query := NULL;
    a_pos1 := instr(v_url, '//');

    IF a_pos1 = 0 THEN
        RETURN;
    END IF;
    a_pos2 := instr(v_url, '/', a_pos1 + 2);
    IF a_pos2 = 0 THEN
        v_host := substr(v_url, a_pos1 + 2);
        v_path := '/';
        RETURN;
    END IF;

    v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
    a_pos1 := instr(v_url, '?', a_pos2 + 1);

    IF a_pos1 = 0 THEN
        v_path := substr(v_url, a_pos2);
        RETURN;
    END IF;

    v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
    v_query := substr(v_url, a_pos1 + 1);
END;
$$ LANGUAGE plpgsql;

Эту функцию можно использовать так:

SELECT * FROM cs_parse_url('http://foobar.com/query.cgi?baz');

В Примере 12 показано, как портировать процедуру, использующую многочисленные функциональные возможности, характерные для Oracle.

Пример 12. Портирование процедуры из PL/SQL в PL/pgSQL

Версия Oracle:

CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id IN INTEGER) IS
    a_running_job_count INTEGER;
BEGIN
    LOCK TABLE cs_jobs IN EXCLUSIVE MODE;

    SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL;

    IF a_running_job_count > 0 THEN
        COMMIT; -- освободить блокировку
        raise_application_error(-20000,
                 'Unable to create a new job: a job is currently running.');
    END IF;

    DELETE FROM cs_active_job;
    INSERT INTO cs_active_job(job_id) VALUES (v_job_id);

    BEGIN
        INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
    EXCEPTION
        WHEN dup_val_on_index THEN NULL; -- ничего не делать, если оно уже существует
    END;
    COMMIT;
END;
/
show errors

Вот как можно портировать эту процедуру в PL/pgSQL:

CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id integer) AS $$
DECLARE
    a_running_job_count integer;
BEGIN
    LOCK TABLE cs_jobs IN EXCLUSIVE MODE;

    SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL;

    IF a_running_job_count > 0 THEN
        COMMIT; -- освободить блокировку
        RAISE EXCEPTION 'Unable to create a new job: a job is currently running'; -- (1)
    END IF;

    DELETE FROM cs_active_job;
    INSERT INTO cs_active_job(job_id) VALUES (v_job_id);

    BEGIN
        INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
    EXCEPTION
        WHEN unique_violation THEN -- (2)
            -- ничего не делать, если оно уже существует
    END;
    COMMIT;
END;
$$ LANGUAGE plpgsql;

(1): Синтаксис RAISE значительно отличается от оператора Oracle, хотя основной вариант RAISE имя_исключения работает аналогично.

(2): Имена исключений, поддерживаемые PL/pgSQL, отличаются от имен Oracle. Набор встроенных имен исключений намного больше (см. раздел Коды ошибок QHB). В настоящее время нет способа объявить пользовательские имена исключений, хотя вместо них можно вызывать ошибку с выбранными пользователем значениями SQLSTATE.

На что еще обратить внимание

В этом разделе описываются еще несколько вещей, на которые нужно обратить внимание при портировании функций Oracle PL/SQL в QHB.

Неявный откат после исключений

В PL/pgSQL при захвате исключения предложением EXCEPTION все изменения в базе данных, начиная с блока BEGIN, автоматически откатываются. То есть поведение равнозначно тому, что вы получите в Oracle при:

BEGIN
    SAVEPOINT s1;
    ... code here ...
EXCEPTION
    WHEN ... THEN
        ROLLBACK TO s1;
        ... code here ...
    WHEN ... THEN
        ROLLBACK TO s1;
        ... code here ...
END;

Если вы конвертируете процедуру Oracle, использующую SAVEPOINT и ROLLBACK TO в этом стиле, задача проста: просто опустите эти операторы. Если же у вас есть процедура, использующая SAVEPOINT и ROLLBACK TO по-другому, потребуется хорошенько поразмыслить.

EXECUTE

Версия EXECUTE для PL/pgSQL работает аналогично версии PL/SQL, но не забывайте использовать quote_literal и quote_ident, как описано в подразделе Выполнение динамических команд. Без этих функций конструкции типа EXECUTE 'SELECT * FROM $1'; не будут надежно работать.

Оптимизация функций PL/pgSQL

QHB предоставляет два модификатора при создании функции для оптимизации выполнения: «изменчивость» (всегда ли функция возвращает один и тот же результат, если задавать ей одни и те же аргументы) и «строгость» (возвращает ли функция NULL, если какой-либо аргумент равен NULL). Подробную информацию см. на справочной странице команды CREATE FUNCTION.

При использовании этих атрибутов оптимизации оператор CREATE FUNCTION может выглядеть примерно так:

CREATE FUNCTION foo(...) RETURNS integer AS $$
...
$$ LANGUAGE plpgsql STRICT IMMUTABLE;

Приложение

В этом подразделе содержится код для установки Oracle-совместимых функций instr которые можно использовать для упрощения процесса портирования.

-
-- функции instr, имитирующие аналоги в Oracle
-- Синтаксис: instr(string1, string2 [, n [, m]])
-- где [] означают необязательные параметры.
--
-- Искать в string1, начиная с n-го символа, m-е вхождение
-- string2.  Если n — отрициальное число, искать в обратном направлении, начиная с
-- abs(n)-го символа от конца string1.
-- Если n не передано, предполагать 1 (поиск начинается с первого символа).
-- Если m не передано, предполагать 1 (найти первое вхождение).
-- Возвращает начальный индекс string2 в string1 или 0, если string2 не найдена.
--

CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS $$
BEGIN
    RETURN instr($1, $2, 1);
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;

CREATE FUNCTION instr(string varchar, string_to_search_for varchar,
                      beg_index integer)
RETURNS integer AS $$
DECLARE
    pos integer NOT NULL DEFAULT 0;
    temp_str varchar;
    beg integer;
    length integer;
    ss_length integer;
BEGIN
    IF beg_index > 0 THEN
        temp_str := substring(string FROM beg_index);
        pos := position(string_to_search_for IN temp_str);

        IF pos = 0 THEN
            RETURN 0;
        ELSE
            RETURN pos + beg_index - 1;
        END IF;
    ELSIF beg_index < 0 THEN
        ss_length := char_length(string_to_search_for);
        length := char_length(string);
        beg := length + 1 + beg_index;

        WHILE beg > 0 LOOP
            temp_str := substring(string FROM beg FOR ss_length);
            IF string_to_search_for = temp_str THEN
                RETURN beg;
            END IF;

            beg := beg - 1;
        END LOOP;

        RETURN 0;
    ELSE
        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;

CREATE FUNCTION instr(string varchar, string_to_search_for varchar,
                      beg_index integer, occur_index integer)
RETURNS integer AS $$
DECLARE
    pos integer NOT NULL DEFAULT 0;
    occur_number integer NOT NULL DEFAULT 0;
    temp_str varchar;
    beg integer;
    i integer;
    length integer;
    ss_length integer;
BEGIN
    IF occur_index <= 0 THEN
        RAISE 'argument ''%'' is out of range', occur_index
          USING ERRCODE = '22003';
    END IF;

    IF beg_index > 0 THEN
        beg := beg_index - 1;
        FOR i IN 1..occur_index LOOP
            temp_str := substring(string FROM beg + 1);
            pos := position(string_to_search_for IN temp_str);
            IF pos = 0 THEN
                RETURN 0;
            END IF;
            beg := beg + pos;
        END LOOP;

        RETURN beg;
    ELSIF beg_index < 0 THEN
        ss_length := char_length(string_to_search_for);
        length := char_length(string);
        beg := length + 1 + beg_index;

        WHILE beg > 0 LOOP
            temp_str := substring(string FROM beg FOR ss_length);
            IF string_to_search_for = temp_str THEN
                occur_number := occur_number + 1;
                IF occur_number = occur_index THEN
                    RETURN beg;
                END IF;
            END IF;

            beg := beg - 1;
        END LOOP;

        RETURN 0;
    ELSE
        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;

Команды SQL

Эта часть содержит справочную информацию по командам SQL, поддерживаемым QHB. Под «SQL» подразумевается язык в целом; информацию о соответствии стандартам и совместимости каждой команды можно найти на соответствующей справочной странице.

Содержание

ABORT — прервать текущую транзакцию

ALTER AGGREGATE — изменить определение агрегатной функции

ALTER COLLATION — изменить определение правила сортировки

ALTER CONVERSION — изменить определение перекодировки

ALTER DATABASE — изменить базу данных

ALTER DEFAULT PRIVILEGES — определить права доступа по умолчанию

ALTER DOMAIN — изменить определение домена

ALTER EVENT TRIGGER — изменить определение триггера события

ALTER EXTENSION — изменить определение расширения

ALTER FOREIGN DATA WRAPPER — изменить определение обертки сторонних данных

ALTER FOREIGN TABLE — изменить определение сторонней таблицы

ALTER FUNCTION — изменить определение функции

ALTER GROUP — изменить имя роли или членство

ALTER INDEX — изменить определение индекса

ALTER LANGUAGE — изменить определение процедурного языка

ALTER LARGE OBJECT — изменить определение большого объекта

ALTER MATERIALIZED VIEW — изменить определение материализованного представления

ALTER OPERATOR — изменить определение оператора

ALTER OPERATOR CLASS — изменить определение класса операторов

ALTER OPERATOR FAMILY — изменить определение семейства операторов

ALTER POLICY — изменить определение политики защиты на уровне строк

ALTER PROCEDURE — изменить определение процедуры

ALTER PUBLICATION — изменить определение публикации

ALTER ROLE — изменить роль в базе данных

ALTER ROUTINE — изменить определение подпрограммы

ALTER RULE — изменить определение правила

ALTER SCHEMA — изменить определение схемы

ALTER SEQUENCE — изменить определение генератора последовательности

ALTER SERVER — изменить определение стороннего сервера

ALTER STATISTICS — изменить определение объекта расширенной статистики

ALTER SUBSCRIPTION — изменить определение подписки

ALTER SYSTEM — изменить параметр конфигурации сервера

ALTER TABLE — изменить определение таблицы

ALTER TABLESPACE — изменить определение табличного пространства

ALTER TEXT SEARCH CONFIGURATION — изменить определение конфигурации текстового поиска

ALTER TEXT SEARCH DICTIONARY — изменить определение словаря текстового поиска

ALTER TEXT SEARCH PARSER — изменить определение анализатора текстового поиска

ALTER TEXT SEARCH TEMPLATE — изменить определение шаблона текстового поиска

ALTER TRIGGER — изменить определение триггера

ALTER TYPE — изменить определение типа

ALTER USER — изменить роль в базе данных

ALTER USER MAPPING — изменить определение сопоставления пользователя

ALTER VIEW — изменить определение представления

ANALYZE — собрать статистику о базе данных

BEGIN — начать блок транзакции

CALL — вызвать процедуру

CHECKPOINT — принудительно выполнить контрольную точку в журнале упреждающей записи

CLOSE — закрыть курсор

CLUSTER — кластеризовать таблицу согласно индексу

COMMENT — определить или изменить комментарий объекта

COMMIT — зафиксировать текущую транзакцию

COMMIT PREPARED — зафиксировать транзакцию, которая ранее была подготовлена для двухфазной фиксации

COPY — копировать данные между файлом и таблицей

CREATE ACCESS METHOD — определить новый метод доступа

CREATE AGGREGATE — определить новую агрегатную функцию

CREATE CAST — определить новое приведение

CREATE COLLATION — определить новое правило сортировки

CREATE CONVERSION — определить новую перекодировку

CREATE DATABASE — создать новую базу данных

CREATE DOMAIN — определить новый домен

CREATE EVENT TRIGGER — определить новый триггер события

CREATE EXTENSION — определить новое расширение

CREATE FOREIGN DATA WRAPPER — определить новую обертку сторонних данных

CREATE FOREIGN TABLE — определить новую стороннюю таблицу

CREATE FUNCTION — определить новую функцию

CREATE GROUP — определить новую роль в базе данных

CREATE INDEX — определить новый индекс

CREATE LANGUAGE — определить новый процедурный язык

CREATE MATERIALIZED VIEW — определить новое материализованное представление

CREATE OPERATOR — определить новый оператор

CREATE OPERATOR CLASS — определить новый класс операторов

CREATE OPERATOR FAMILY — определить новое семейство операторов

CREATE POLICY — определить для таблицы новую политику защиты на уровне строк

CREATE PROCEDURE — определить новую процедуру

CREATE PUBLICATION — определить новую публикацию

CREATE ROLE — определить новую роль в базе данных

CREATE RULE — определить новое правило перезаписи

CREATE SCHEMA — определить новую схему

CREATE SEQUENCE — определить новый генератор последовательности

CREATE SERVER — определить новый сторонний сервер

CREATE STATISTICS — определить новую расширенную статистику

CREATE SUBSCRIPTION — определить новую подписку

CREATE TABLE — определить новую таблицу

CREATE TABLE AS — определить новую таблицу из результатов запроса

CREATE TABLESPACE — определить новое табличное пространство

CREATE TEXT SEARCH CONFIGURATION — определить новую конфигурацию текстового поиска

CREATE TEXT SEARCH DICTIONARY — определить новый словарь текстового поиска

CREATE TEXT SEARCH PARSER — определить новый анализатор текстового поиска

CREATE TEXT SEARCH TEMPLATE — определить новый шаблон текстового поиска

CREATE TRANSFORM — определить новую трансформацию

CREATE TRIGGER — определить новый триггер

CREATE TYPE — определить новый тип данных

CREATE USER — определить новую роль в базе данных

CREATE USER MAPPING — определить новое сопоставление пользователя для стороннего сервера

CREATE VIEW — определить новое представление

DEALLOCATE — освободить подготовленный оператор

DECLARE — определить курсор

DELETE — удалить строки таблицы

DISCARD — сбросить состояние сеанса

DO — выполнить анонимный блок кода

DROP ACCESS METHOD — удалить метод доступа

DROP AGGREGATE — удалить агрегатную функцию

DROP CAST — удалить приведение

DROP COLLATION — удалить правило сортировки

DROP CONVERSION — удалить перекодировку

DROP DATABASE — удалить базу данных

DROP DOMAIN — удалить домен

DROP EVENT TRIGGER — удалить триггер события

DROP EXTENSION — удалить расширение

DROP FOREIGN DATA WRAPPER — удалить обертку сторонних данных

DROP FOREIGN TABLE — удалить стороннюю таблицу

DROP FUNCTION — удалить функцию

DROP GROUP — удалить роль в базе данных

DROP INDEX — удалить индекс

DROP LANGUAGE — удалить процедурный язык

DROP MATERIALIZED VIEW — удалить материализованное представление

DROP OPERATOR — удалить оператор

DROP OPERATOR CLASS — удалить класс операторов

DROP OPERATOR FAMILY — удалить семейство операторов

DROP OWNED — удалить объекты базы данных, принадлежащие роли базы данных

DROP POLICY — удалить политику защиты на уровне строк из таблицы

DROP PROCEDURE — удалить процедуру

DROP PUBLICATION — удалить публикацию

DROP ROLE — удалить роль в базе данных

DROP ROUTINE — удалить подпрограмму

DROP RULE — удалить правило перезаписи

DROP SCHEMA — удалить схему

DROP SEQUENCE — удалить последовательность

DROP SERVER — удалить дескриптор стороннего сервера

DROP STATISTICS — удалить расширенную статистику

DROP SUBSCRIPTION — удалить подписку

DROP TABLE — удалить таблицу

DROP TABLESPACE — удалить табличное пространство

DROP TEXT SEARCH CONFIGURATION — удалить конфигурацию текстового поиска

DROP TEXT SEARCH DICTIONARY — удалить словарь текстового поиска

DROP TEXT SEARCH PARSER — удалить анализатор текстового поиска

DROP TEXT SEARCH TEMPLATE — удалить шаблон текстового поиска

DROP TRANSFORM — удалить трансформацию

DROP TRIGGER — удалить триггер

DROP TYPE — удалить тип данных

DROP USER — удалить роль в базе данных

DROP USER MAPPING — удалить сопоставление пользователя для стороннего сервера

DROP VIEW — удалить представление

END — зафиксировать текущую транзакцию

EXECUTE — выполнить подготовленный оператор

EXPLAIN — показать план выполнения оператора

FETCH — получить результат запроса с помощью курсора

GRANT — определить права доступа

IMPORT FOREIGN SCHEMA — импортировать определения таблиц со стороннего сервера

INSERT — создать новые строки в таблице

LISTEN — перехватывать уведомления

LOAD — загрузить файл разделяемой библиотеки

LOCK — заблокировать таблицу

MOVE — переместить курсор

NOTIFY — сгенерировать уведомление

PREPARE — подготовить оператор для выполнения

PREPARE TRANSACTION — подготовить текущую транзакцию для двухфазной фиксации

REASSIGN OWNED — сменить владельца объектов базы данных, принадлежащих роли базы данных

REFRESH MATERIALIZED VIEW — заменить содержимое материализованного представления

REINDEX — перестроить индексы

RELEASE SAVEPOINT — уничтожить определенную ранее точку сохранения

RESET — восстановить значение параметра времени выполнения до значения по умолчанию

REVOKE — отозвать права доступа

ROLLBACK — прервать текущую транзакцию

ROLLBACK PREPARED — отменить транзакцию, которая ранее была подготовлена для двухфазной фиксации

ROLLBACK TO SAVEPOINT — откатить к точке сохранения

SAVEPOINT — определить новую точку сохранения в текущей транзакции

SECURITY LABEL — определить или изменить примененную к объекту метку безопасности

SELECT — выбрать строки из таблицы или представления

SELECT INTO — определить новую таблицу из результатов запроса

SET — изменить параметр времени выполнения

SET CONSTRAINTS — установить время проверки ограничений для текущей транзакции

SET ROLE — установить идентификатор текущего пользователя для текущего сеанса

SET SESSION AUTHORIZATION — установить идентификатор пользователя сеанса и идентификатор текущего пользователя для текущего сеанса

SET TRANSACTION — установить характеристики текущей транзакции

SHOW — показать значение параметра времени выполнения

START TRANSACTION — начать блок транзакции

TRUNCATE — опустошить таблицу или набор таблиц

UNLISTEN — перестать перехватывать уведомления

UPDATE — изменить строки таблицы

VACUUM — убрать мусор и при необходимости проанализировать базу данных

VALUES — вычислить набор строк

ABORT

ABORT — прервать текущую транзакцию

Синтаксис

ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]

Описание

Команда ABORT откатывает текущую транзакцию и отменяет все внесенные ей изменения. По своему поведению ABORT идентична стандартной SQL-команде ROLLBACK и присутствует только по историческим причинам.

Параметры

WORK TRANSACTION

Необязательные ключевые слова, не влияют на результат.

AND CHAIN

Если указывается AND CHAIN, то сразу после окончания текущей транзакции начинается новая с теми же характеристиками, что и только что завершенная (см. SET TRANSACTION). В противном случае новая транзакция не начинается.

Примечания

Для успешного завершения транзакции используйте команду COMMIT.

При выполнении ABORT вне блока транзакции будет выдано предупреждение и больше ничего не произойдет.

Примеры

Отмена всех изменений:

ABORT;

Совместимость

Эта команда является расширением QHB и присутствует по историческим причинам. Ей равнозначна стандартная команда SQL ROLLBACK.

См. также

BEGIN, COMMIT, ROLLBACK

ALTER AGGREGATE

ALTER AGGREGATE — изменить определение агрегатной функции

Синтаксис

ALTER AGGREGATE имя ( сигнатура_агрегата ) RENAME TO новое_имя
ALTER AGGREGATE имя ( сигнатура_агрегата )
                OWNER TO { новый_владелец | CURRENT_USER | SESSION_USER }
ALTER AGGREGATE имя ( сигнатура_агрегата ) SET SCHEMA новая_схема

Где сигнатура_агрегата:

* |
[ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] |
[ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] ] ORDER BY [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ]

Описание

Команда ALTER AGGREGATE изменяет определение агрегатной функции.

Чтобы использовать команду ALTER AGGREGATE, нужно быть владельцем соответствующей агрегатной функции. Чтобы изменить схему агрегатной функции, помимо этого необходимо иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме агрегатной функции. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания агрегатной функции. Однако суперпользователь все равно может сменить владельца любой агрегатной функции.)

Параметры

имя

Имя существующей агрегатной функции (может быть дополнено схемой).

режим_аргумента

Режим аргумента: IN или VARIADIC. Если этот параметр не указан, значением по умолчанию является IN.

имя_аргумента

Название аргумента. Обратите внимание, что ALTER AGGREGATE фактически не обращает никакого внимания на имена аргументов, поскольку для идентификации агрегатной функции требуются только типы данных аргументов.

тип_аргумента

Тип входных данных, с которыми работает агрегатная функция. Чтобы сослаться на агрегатную функцию без аргументов, вместо списка определений аргументов напишите *. Чтобы сослаться на сортирующую агрегатную функцию, напишите ORDER BY между указаниями непосредственных и агрегируемых аргументов.

новое_имя

Новое имя агрегатной функции.

новый_владелец

Новый владелец агрегатной функции.

новая_схема

Новая схема для агрегатной функции.

Примечания

Рекомендуемый синтаксис для ссылки на сортирующий агрегат — написать ORDER BY между спецификациями непосредственных и агрегируемых аргументов, как в CREATE AGGREGATE. Однако команда будет выполнена и без ORDER BY, если просто перечислить спецификации непосредственных и агрегирующих аргументов в одном списке. В такой сокращенной форме, если VARIADIC "any" использовался как в списке непосредственных, так и в списке агрегирующих аргументов, достаточно указать VARIADIC "any" только один раз.

Примеры

Переименование агрегатной функции myavg с типом integer в my_average:

ALTER AGGREGATE myavg(integer) RENAME TO my_average;

Смена владельца агрегатной функции myavg с типом integer на joe:

ALTER AGGREGATE myavg(integer) OWNER TO joe;

Перемещение сортирующего агрегата mypercentile с непосредственным аргументом типа float8 и агрегируемым аргументом типа integer в схему myschema:

ALTER AGGREGATE mypercentile(float8 ORDER BY integer) SET SCHEMA myschema;

Так тоже будет работать:

ALTER AGGREGATE mypercentile(float8, integer) SET SCHEMA myschema;

Совместимость

В стандарте SQL нет команды ALTER AGGREGATE.

См. также

CREATE AGGREGATE, DROP AGGREGATE

ALTER COLLATION

ALTER COLLATION — изменить определение правила сортировки

Синтаксис

ALTER COLLATION имя RENAME TO новое_имя
ALTER COLLATION имя OWNER TO { новый_владелец | CURRENT_USER | SESSION_USER }
ALTER COLLATION имя SET SCHEMA новая_схема

Описание

Команда ALTER COLLATION изменяет определение правила сортировки.

Чтобы использовать команду ALTER COLLATION, нужно быть владельцем соответствующего правила сортировки. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме правила сортировки. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания правила сортировки. Однако суперпользователь все равно может изменить владельца любого правила сортировки.)

Параметры

имя

Имя существующего правила сортировки (может быть дополнено схемой).

новое_имя

Новое имя правила сортировки.

новый_владелец

Новый владелец правила сортировки.

новая_схема

Новая схема для правила сортировки.

REFRESH VERSION

Обновить версию правила сортировки. См. параграф Примечания ниже.

Примечания

Во время применения правил сортировки, которые предоставляются библиотекой ICU, при создании объекта для данного правила в системный каталог будет записана внутренняя версия сортировщика ICU. При использовании правила сортировки текущая версия будет сверяться с записанной, и в случае несоответствия будет выдано предупреждение, например:

WARNING:  collation "xx-x-icu" has version mismatch
DETAIL:  The collation in the database was created using version 1.2.3.4, but the operating system provides version 2.3.4.5.
HINT:  Rebuild all objects affected by this collation and run ALTER COLLATION pg_catalog."xx-x-icu" REFRESH VERSION, or build PostgreSQL with the right library version.
-- ПРЕДУПРЕЖДЕНИЕ: версия правила сортировки "xx-x-icu" не совпадает
-- ДЕТАЛИЗАЦИЯ: Правило сортировки в базе данных было создано с помощью версии 1.2.3.4, а операционная система предоставляет версию 2.3.4.5.
-- ПОДСКАЗКА: Пересоберите все объекты, затрагиваемые этим правилом сортировки и выполните ALTER COLLATION pg_catalog."xx-x-icu" REFRESH VERSION или соберите QHB с корректной версией библиотеки

Изменение определений правил сортировки может привести к разрушению индексов и другим проблемам, поскольку СУБД рассчитывает, что хранимые объекты имеют определенный порядок сортировки. В целом, подобного следует избегать, но это может произойти в легитимных обстоятельствах, например при обновлении с помощью qhb_upgrade двоичных файлов сервера, скомпилированных с более новой версией ICU. Когда это происходит, все объекты, зависящие от правил сортировки, должны быть перестроены, например, с помощью REINDEX. После этого можно обновить версию правил сортировки, выполнив команду ALTER COLLATION ... REFRESH VERSION. Это обновит системный каталог, и в него будет записана текущая версия сортировщика, а предупреждение исчезнет. Обратите внимание, что при выполнении команды фактически не проверяется, все ли затронутые объекты были перестроены правильно.

Когда применяются правила сортировки, предоставляемые libc, в системах, использующих библиотеку GNU C (большинство систем Linux systems) и FreeBSD, информация о версии записывается.

Примечание
При использовании для правил сортировки библиотеки GNU C, версия библиотеки C служит показателем версии правила. Во многих дистрибутивах Linux определения правил сортировки меняются только при обновлении библиотеки C, но такой подход неидеален, поскольку авторы дистрибутива вполне могут перенести обновленные определения правил сортировки обратно в старые версии библиотеки C.

В настоящее время версия правила сортировки, по умолчанию установленного для базы данных, не отслеживается.

Для определения всех правил сортировки в текущей базе данных, которые необходимо обновить, а также объектов, которые зависят от них, можно использовать следующий запрос:

SELECT pg_describe_object(refclassid, refobjid, refobjsubid) AS "Collation",
       pg_describe_object(classid, objid, objsubid) AS "Object"
  FROM pg_depend d JOIN pg_collation c
       ON refclassid = 'pg_collation'::regclass AND refobjid = c.oid
  WHERE c.collversion <> pg_collation_actual_version(c.oid)
  ORDER BY 1, 2;

Примеры

Переименование правила сортировки de_DE в german:

ALTER COLLATION "de_DE" RENAME TO german;

Изменение владельца правила сортировки en_US на joe:

ALTER COLLATION "en_US" OWNER TO joe;

Совместимость

В стандарте SQL нет команды ALTER COLLATION.

См. также

CREATE COLLATION, DROP COLLATION

ALTER CONVERSION

ALTER CONVERSION — изменить определение перекодировки

Синтаксис

ALTER CONVERSION имя RENAME TO новое_имя
ALTER CONVERSION имя OWNER TO { новый_владелец | CURRENT_USER | SESSION_USER }
ALTER CONVERSION имя SET SCHEMA новая_схема

Описание

Команда ALTER CONVERSION изменяет определение перекодировки.

Чтобы использовать команду ALTER CONVERSION, нужно быть владельцем соответствующей перекодировки. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме перекодировки. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания перекодировки. Однако суперпользователь все равно может изменить владельца любой перекодировки.)

Параметры

имя

Имя существующей перекодировки (может быть дополнено схемой).

новое_имя

Новое имя перекодировки.

новый_владелец

Новый владелец перекодировки.

новая_схема

Новая схема для перекодировки.

Примеры

Переименование перекодировки iso_8859_1_to_utf8 в latin1_to_unicode:

ALTER CONVERSION iso_8859_1_to_utf8 RENAME TO latin1_to_unicode;

Смена владельца перекодировки iso_8859_1_to_utf8 на joe:

ALTER CONVERSION iso_8859_1_to_utf8 OWNER TO joe;

Совместимость

В стандарте SQL нет команды ALTER CONVERSION.

См. также

CREATE CONVERSION, DROP CONVERSION

ALTER DATABASE

ALTER DATABASE — изменить базу данных

Синтаксис

ALTER DATABASE имя [ [ WITH ] параметр [ ... ] ]

Где параметр может быть:

    ALLOW_CONNECTIONS разрешение_подключения
    CONNECTION LIMIT предел_подключений
    IS_TEMPLATE это_шаблон

ALTER DATABASE имя RENAME TO новое_имя

ALTER DATABASE имя OWNER TO { новый_владелец | CURRENT_USER | SESSION_USER }

ALTER DATABASE имя SET TABLESPACE новое_табличное_пространство

ALTER DATABASE имя SET параметр_конфигурации { TO | = } { значение | DEFAULT }
ALTER DATABASE имя SET параметр_конфигурации FROM CURRENT
ALTER DATABASE имя RESET параметр_конфигурации
ALTER DATABASE имя RESET ALL

Описание

Команда ALTER DATABASE изменяет атрибуты базы данных.

Первая форма команды изменяет определенные параметры на уровне базы данных. (Подробное описание см. ниже). Изменять эти параметры может только владелец соответствующей базы данных или суперпользователь.

Вторая форма команды изменяет имя базы данных. Переименовать базу данных может только владелец базы данных или суперпользователь (не суперпользователи должны обладать правом CREATEDB). Переименовать текущую базу данных нельзя. (Если это нужно сделать, подключитесь к другой базе данных.)

Третья форма команды изменяет владельца базы данных. Для смены владельца текущий пользователь должен быть ее владельцем, а также непосредственным или опосредованным членом новой роли-владельца и иметь право CREATEDB. (Обратите внимание, что суперпользователи получают все эти права автоматически.)

Четвертая форма команды изменяет у базы данных табличное пространство по умолчанию. Это может сделать только владелец базы данных или суперпользователь; кроме того, необходимо иметь право на создание нового табличного пространства. Эта команда физически перемещает все таблицы или индексы из старого табличного пространства по умолчанию в новое. Новое табличное пространство по умолчанию должно быть пустым, и никто не должен быть подключен к базе данных. Таблицы и индексы из нестандартных табличных пространств не затрагиваются.

Остальные формы данной команды изменяют сеансовое значение по умолчанию у конфигурационной переменной периода выполнения для базы данных QHB. При последующем запуске нового сеанса в этой базе данных указанное значение становится значением по умолчанию. Заданные для базы данных значения по умолчанию переопределяют любые значения из qhb.conf или полученные из командной строки qhb. Только владелец базы данных и суперпользователь могут изменить для базы данных сеансовые значения по умолчанию. Некоторые переменные таким образом установить нельзя, либо их может установить только суперпользователь.

Параметры

имя

Имя базы данных, атрибуты которой подлежат изменению.

разрешение_подключения

При значении false к этой базе данных никто не сможет подключиться.

предел_подключений

Устанавливает, сколько одновременных подключений может быть сделано к этой базе данных. -1 означает отсутствие ограничений.

это_шаблон

При значении true эта база данных может быть клонирована любым пользователем с правами CREATEDB; при значении false клонировать базу данных могут только суперпользователи или ее владелец.

новое_имя

Новое имя базы данных.

новый_владелец

Новый владелец базы данных.

новое_табличное_пространство

Новое табличное пространство по умолчанию для базы данных.

Эту форму команды нельзя выполнить внутри блока транзакции.

параметр_конфигурации
значение

Устанавливает сеансовое значение по умолчанию для указанного параметра конфигурации. Если значение равно DEFAULT (или используется равнозначный вариант RESET), то предыдущее значение параметра удаляется, и в новых сеансах будет действовать значение, взятое из системных настроек по умолчанию. Для сброса всех значений параметров базы данных выполните RESET ALL. SET FROM CURRENT устанавливает значение параметра базы данных из его значения в текущем сеансе.

Дополнительную информацию о допустимых именах и значениях параметров см. на справочной странице команды SET и в главе Конфигурация сервера.

Примечания

Также существует возможность привязать параметры сеанса по умолчанию к определенной роли, а не к базе данных; см. справочную страницу команды ALTER ROLE. При возникновении конфликта ролевые параметры переопределяют параметры для конкретной базы данных.

Примеры

Выключение сканирования индекса по умолчанию в базе данных test:

ALTER DATABASE test SET enable_indexscan TO off;

Совместимость

Команда ALTER DATABASE является расширением QHB.

См. также

CREATE DATABASE, DROP DATABASE, SET, CREATE TABLESPACE

ALTER DEFAULT PRIVILEGES

ALTER DEFAULT PRIVILEGES — определить права доступа по умолчанию

Синтаксис

ALTER DEFAULT PRIVILEGES
    [ FOR { ROLE | USER } целевая_роль [, ...] ]
    [ IN SCHEMA имя_схемы [, ...] ]
    предложение_GRANT_или_REVOKE

Где предложение_GRANT_или_REVOKE может быть следующим:

GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
    [, ...] | ALL [ PRIVILEGES ] }
    ON TABLES
    TO { [ GROUP ] имя_роли | PUBLIC } [, ...] [ WITH GRANT OPTION ]

GRANT { { USAGE | SELECT | UPDATE }
    [, ...] | ALL [ PRIVILEGES ] }
    ON SEQUENCES
    TO { [ GROUP ] имя_роли | PUBLIC } [, ...] [ WITH GRANT OPTION ]

GRANT { EXECUTE | ALL [ PRIVILEGES ] }
    ON { FUNCTIONS | ROUTINES }
    TO { [ GROUP ] имя_роли | PUBLIC } [, ...] [ WITH GRANT OPTION ]

GRANT { USAGE | ALL [ PRIVILEGES ] }
    ON TYPES
    TO { [ GROUP ] имя_роли | PUBLIC } [, ...] [ WITH GRANT OPTION ]

GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
    ON SCHEMAS
    TO { [ GROUP ] имя_роли | PUBLIC } [, ...] [ WITH GRANT OPTION ]

REVOKE [ GRANT OPTION FOR ]
    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
    [, ...] | ALL [ PRIVILEGES ] }
    ON TABLES
    FROM { [ GROUP ] имя_роли | PUBLIC } [, ...]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { { USAGE | SELECT | UPDATE }
    [, ...] | ALL [ PRIVILEGES ] }
    ON SEQUENCES
    FROM { [ GROUP ] имя_роли | PUBLIC } [, ...]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { EXECUTE | ALL [ PRIVILEGES ] }
    ON { FUNCTIONS | ROUTINES }
    FROM { [ GROUP ] имя_роли | PUBLIC } [, ...]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { USAGE | ALL [ PRIVILEGES ] }
    ON TYPES
    FROM { [ GROUP ] имя_роли | PUBLIC } [, ...]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { USAGE | CREATE | ALL [ PRIVILEGES ] }
    ON SCHEMAS
    FROM { [ GROUP ] имя_роли | PUBLIC } [, ...]
    [ CASCADE | RESTRICT ]

Описание

Команда ALTER DEFAULT PRIVILEGES позволяет задавать права, которые будут применяться к объектам, создаваемым в будущем. (Это не влияет на права, назначенные уже существующим объектам.) В настоящее время права можно задавать только для схем, таблиц (включая представления и сторонние таблицы), последовательностей, функций и типов (включая домены). Применительно к данной команде функциями считаются также агрегаты и процедуры. Слова FUNCTIONS и ROUTINES в этой команде равнозначны. (ROUTINES предпочтительнее в перспективе как стандартный термин, охватывающий и функции, и процедуры. Задать права по умолчанию для функций и процедур по отдельности нельзя.)

Изменить права по умолчанию можно только для объектов, которые будут созданы текущим пользователем или ролями, членом которых он является. Права доступа могут быть установлены глобально (т. е. для всех объектов, создаваемых в текущей базе данных) или только для объектов, создаваемых в определенных схемах.

Как объяснено в разделе Права, права по умолчанию для любого типа объекта обычно предоставляют все назначаемые разрешения владельцу объекта и могут также предоставлять некоторые права роли PUBLIC. Однако это поведение можно изменить путем смены глобальных прав по умолчанию командой ALTER DEFAULT PRIVILEGES.

Права по умолчанию на уровне схемы добавляются к любым глобальным правам по умолчанию, применимым к определенному типу объекта. Иными словами, нельзя отозвать права на уровне схемы, если они назначены глобально (либо по умолчанию, либо в соответствии с предыдущей командой ALTER DEFAULT PRIVILEGES, где схема не указана). На уровне схемы команда REVOKE может быть полезна только для того, чтобы отменять действия предыдущей команды GRANT для этой же схемы.

Параметры

целевая_роль

Имя существующей роли, членом которой является текущая роль. Если FOR ROLE не указано, предполагается текущая роль.

имя_схемы

Имя существующей схемы. Если этот параметр указан, то права доступа по умолчанию изменяются для объектов, которые будут создаваться в этой схеме позднее. Если IN SCHEMA не указано, меняются глобальные права по умолчанию. Нельзя указывать IN SCHEMA при установлении прав для схем, так как схемы не могут быть вложенными.

имя_роли

Имя существующей роли, для которой необходимо предоставить или отозвать права. Этот параметр, а также все остальные параметры в предложении_grant_или_revoke действуют так, как описано на страницах команды GRANT или REVOKE, за исключением того, что они устанавливают разрешения не для каких-то конкретных объектов, а для целого класса объектов.

Примечания

Для получения информации о текущих назначениях прав по умолчанию, воспользуйтесь командой \ddp в psql. Интерпретация отображения данных о правах такая же, как приведенная в описании команды \dp в разделе Права.

Если вы хотите удалить роль, права по умолчанию для которой были изменены, необходимо отменить изменения прав по умолчанию или воспользоваться командой DROP OWNED BY для упразднения назначенных для этой роли прав по умолчанию.

Примеры

Добавление права SELECT всем пользователям на все таблицы (и представления), которые будут созданы в будущем в схеме myschema, и добавление роли webuser права INSERT для этих же таблиц:

ALTER DEFAULT PRIVILEGES IN SCHEMA myschema GRANT SELECT ON TABLES TO PUBLIC;
ALTER DEFAULT PRIVILEGES IN SCHEMA myschema GRANT INSERT ON TABLES TO webuser;

Отмена предыдущих изменений, чтобы для таблиц, которые будут созданы в будущем, были определены только обычные права, без дополнительных:

ALTER DEFAULT PRIVILEGES IN SCHEMA myschema REVOKE SELECT ON TABLES FROM PUBLIC;
ALTER DEFAULT PRIVILEGES IN SCHEMA myschema REVOKE INSERT ON TABLES FROM webuser;

Отзыв из общего доступа права EXECUTE (которое обычно выдается для функций) для всех функций, которые будут созданы ролью admin:

ALTER DEFAULT PRIVILEGES FOR ROLE admin REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;

Однако обратите внимание, что тот же эффект нельзя получить с помощью команды, ограниченной одной схемой. Эта команда будет действовать, только если она отменяет соответствующую команду GRANT:

ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;

Это связано с тем, что права по умолчанию на уровне схемы можно только добавить к правам, назначенным глобально, а отозвать последние нельзя.

Совместимость

В стандарте SQL нет команды ALTER DEFAULT PRIVILEGES.

См. также

GRANT, REVOKE

ALTER DOMAIN

ALTER DOMAIN — изменить определение домена

Синтаксис

ALTER DOMAIN имя
    { SET DEFAULT выражение | DROP DEFAULT }
ALTER DOMAIN имя
    { SET | DROP } NOT NULL
ALTER DOMAIN имя
    ADD ограничение_домена [ NOT VALID ]
ALTER DOMAIN имя
    DROP CONSTRAINT [ IF EXISTS ] имя_ограничения [ RESTRICT | CASCADE ]
ALTER DOMAIN имя
     RENAME CONSTRAINT имя_ограничения TO имя_нового_ограничения
ALTER DOMAIN имя
    VALIDATE CONSTRAINT имя_ограничения
ALTER DOMAIN имя
    OWNER TO { новый_владелец | CURRENT_USER | SESSION_USER }
ALTER DOMAIN имя
    RENAME TO новое_имя
ALTER DOMAIN имя
    SET SCHEMA новая_схема

Описание

Команда ALTER DOMAIN изменяет определение существующего домена. Она имеет несколько форм:

SET/DROP DEFAULT

Эти формы устанавливают или удаляют значение по умолчанию для домена. Обратите внимание, что значения по умолчанию применяются только к последующим командам INSERT; они не влияют на строки c данным доменом, уже находящиеся в таблице.

SET/DROP NOT NULL

Эти формы определяют, будут ли разрешены значения NULL. Установить SET NOT NULL можно, только если столбцы, использующие домен, не содержат значений NULL.

ADD ограничение_домена [ NOT VALID ]

Эта форма добавляет новое ограничение к домену, используя тот же синтаксис, что описан в CREATE DOMAIN. При добавлении нового ограничения домена, все столбцы, использующие этот домен, будут проверены на соответствие вновь добавленному ограничению. Эти проверки можно пропустить, если добавить указание NOT VALID; а потом активировать ограничение с помощью ALTER DOMAIN ... VALIDATE CONSTRAINT. При добавлении и изменении строк они всегда проверяются по всем ограничениям, даже тем, которые отмечены NOT VALID. NOT VALID принимается только для ограничений CHECK.

DROP CONSTRAINT [ IF EXISTS ]

Эта форма удаляет ограничения домена. Если указывается IF EXISTS, а ограничение не существует, это не считается ошибкой. В этом случае выдается только замечание.

RENAME CONSTRAINT

Эта форма изменяет имя ограничения домена.

VALIDATE CONSTRAINT

Эта форма включает проверку ограничения, ранее добавленного как NOT VALID, то есть проверяет, что все значения в табличных столбцах типа домена удовлетворяют указанному ограничению.

OWNER

Эта форма меняет владельца домена на указанного пользователя.

RENAME

Эта форма изменяет имя домена.

SET SCHEMA

Эта форма изменяет схему домена. Все связанные с доменом ограничения также перемещаются в новую схему.

Чтобы использовать команду ALTER DOMAIN, нужно быть владельцем соответствующего домена. Чтобы изменить схему домена, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме домена. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания домена. Однако суперпользователь все равно может сменить владельца любого домена.)

Параметры

имя

Имя существующего домена, подлежащего изменению (может быть дополнено схемой).

ограничение_домена

Новое ограничение домена.

имя_ограничения

Имя существующего ограничения, которое будет удалено или переименовано.

NOT VALID

Не проверять существующие сохраненные данные на соответствие ограничению.

CASCADE

Автоматически удалять объекты, зависящие от ограничения, и в свою очередь все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Запретить удаление ограничения, если есть какие-либо зависимые от него объекты. Это поведение по умолчанию.

новое_имя

Новое имя домена.

имя_нового_ограничения

Новое имя ограничения.

новый_владелец

Имя пользователя, который станет новым владельцем домена.

новая_схема

Новая схема домена.

Примечания

Несмотря на то, что команда ALTER DOMAIN ADD CONSTRAINT пытается проверить, что существующие данные удовлетворяют новому ограничению, эта проверка не очень надежна, поскольку команда не может «видеть» строки таблицы, которые были только что добавлены или изменены и еще не зафиксированы. Если существует опасность того, что параллельные операции могут добавлять неверные данные, можно применить следующий подход: создайте ограничения с указанием NOT VALID, зафиксируйте эту команду, подождите, пока завершатся все начатые до фиксирования транзакции, а затем выполните ALTER DOMAIN VALIDATE CONSTRAINT для поиска данных, нарушающих ограничение. Данный метод надежен, так как после фиксирования ограничения он будет гарантированно действовать на все новые значения типа домена, вносимые последующими транзакциями.

В настоящее время ALTER DOMAIN ADD CONSTRAINT, ALTER DOMAIN VALIDATE CONSTRAINT и ALTER DOMAIN SET NOT NULL завершаются ошибкой, если в какой-либо таблице базы данных указанный домен или любой производный от него домен используется в столбце контейнерного типа (составном, диапазонном или массиве). В дальнейшем эти команды будут доработаны, с тем чтобы проверять новое ограничение и для таких вложенных значений.

Примеры

Добавление ограничения NOT NULL к домену:

ALTER DOMAIN zipcode SET NOT NULL;

Удаление ограничения NOT NULL из домена:

ALTER DOMAIN zipcode DROP NOT NULL;

Добавление проверочного ограничения к домену:

ALTER DOMAIN zipcode ADD CONSTRAINT zipchk CHECK (char_length(VALUE) = 5);

Удаление проверочного ограничения из домена:

ALTER DOMAIN zipcode DROP CONSTRAINT zipchk;

Переименование проверочного ограничения в домене:

ALTER DOMAIN zipcode RENAME CONSTRAINT zipchk TO zip_check;

Перемещение домена в другую схему:

ALTER DOMAIN zipcode SET SCHEMA customers;

Совместимость

Команда ALTER DOMAIN соответствует стандарту SQL, за исключением форм OWNER, RENAME, SET SCHEMA и VALIDATE CONSTRAINT, которые являются расширениями QHB. Указание NOT VALID формы ADD CONSTRAINT также является расширением QHB.

См. также

CREATE DOMAIN, DROP DOMAIN

ALTER EVENT TRIGGER

ALTER EVENT TRIGGER — изменить определение триггера события

Синтаксис

ALTER EVENT TRIGGER имя DISABLE
ALTER EVENT TRIGGER имя ENABLE [ REPLICA | ALWAYS ]
ALTER EVENT TRIGGER имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER EVENT TRIGGER имя RENAME TO новое_имя

Описание

Команда ALTER EVENT TRIGGER изменяет свойства существующего триггера события.

Чтобы изменить триггер события, нужно быть суперпользователем.

Параметры

имя

Имя изменяемого существующего триггера.

новый_владелец

Имя пользователя, который станет новым владельцем триггера события.

новое_имя

Новое имя триггера события.

DISABLE/ENABLE [ REPLICA | ALWAYS ] TRIGGER

Эти формы настраивают запуск триггера события. Выключенный триггер сохраняется в системе, но не выполняется, когда происходит событие, которое его запускает. См. также session_replication_role.

Совместимость

В стандарте SQL нет команды ALTER EVENT TRIGGER.

См. также

CREATE EVENT TRIGGER, DROP EVENT TRIGGER

ALTER EXTENSION

ALTER EXTENSION — изменить определение расширения

Синтаксис

ALTER EXTENSION имя UPDATE [ TO новая_версия ]
ALTER EXTENSION имя SET SCHEMA новая_схема
ALTER EXTENSION имя ADD член_объект
ALTER EXTENSION имя DROP член_объект

где членом_объект может быть:

  ACCESS METHOD имя_объекта |
  AGGREGATE имя_агрегата ( сигнатура_агрегата ) |
  CAST (исходный_тип AS целевой_тип) |
  COLLATION имя_объекта |
  CONVERSION имя_объекта |
  DOMAIN имя_объекта |
  EVENT TRIGGER имя_объекта |
  FOREIGN DATA WRAPPER имя_объекта |
  FOREIGN TABLE имя_объекта |
  FUNCTION имя_функции [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  MATERIALIZED VIEW имя_объекта |
  OPERATOR имя_оператора (тип_слева, тип_справа) |
  OPERATOR CLASS имя_объекта USING индексный_метод |
  OPERATOR FAMILY имя_объекта USING индексный_метод |
  [ PROCEDURAL ] LANGUAGE имя_объекта |
  PROCEDURE имя_процедуры [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  ROUTINE имя_подпрограммы [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  SCHEMA имя_объекта |
  SEQUENCE имя_объекта |
  SERVER имя_объекта |
  TABLE имя_объекта |
  TEXT SEARCH CONFIGURATION имя_объекта |
  TEXT SEARCH DICTIONARY имя_объекта |
  TEXT SEARCH PARSER имя_объекта |
  TEXT SEARCH TEMPLATE имя_объекта |
  TRANSFORM FOR имя_типа LANGUAGE имя_языка |
  TYPE имя_объекта |
  VIEW имя_объекта

и сигнатура_агрегата:

* |
[ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] |
[ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] ] ORDER BY [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ]

Описание

Команда ALTER EXTENSION изменяет определение установленного расширения. Она имеет несколько форм:

UPDATE

Эта форма обновляет расширение до более новой версии. Расширение должно предоставить подходящий скрипт (или набор скриптов) обновления, который сможет изменить текущую установленную версию на требуемую.

SET SCHEMA

Эта форма перемещает объекты расширения в другую схему. Для успешного выполнения этой команды расширение должно быть перемещаемым.

ADD член_объект

Эта форма добавляет к расширению существующий объект. Указание в основном используется в скриптах обновления расширений. Объект впоследствии будет рассматриваться как часть расширения; в частности, он может быть удален только вместе с расширением.

DROP член_объект

Эта форма удаляет объект из расширения. Указание в основном используется в скриптах обновления расширений. Объект при этом не удаляется, а только отделяется от расширения.

Дополнительную информацию об этих операциях см. в разделе Упаковка связанных объектов в расширение.

Чтобы использовать команду ALTER EXTENSION, нужно быть владельцем соответствующего расширения. Для форм ADD/DROP требуется также быть владельцем добавляемого/ удаляемого объекта.

Параметры

имя

Имя установленного расширения.

новая_версия

Запрашиваемая новая версия расширения. Версию можно указать в виде идентификатора или строки. Если версия не указана, ALTER EXTENSION UPDATE попытается обновить расширение до версии по умолчанию в управляющем файле расширения.

новая_схема

Новая схема расширения.

имя_объекта
имя_агрегата
имя_функции
имя_оператора
имя_процедуры
имя_подпрограммы

Имя объекта, который будет добавлен или удален из расширения. Имена таблиц, агрегатов, доменов, сторонних таблиц, функций, операторов, классов операторов, семейств операторов, процедур, подпрограмм, последовательностей, объектов текстового поиска, типов и представлений могут дополняться именем схемы.

исходный_тип

Имя исходного типа данных для приведения.

целевой_тип

Имя целевого типа данных для приведения.

режим_аргумента

Режим аргумента функции, процедуры или агрегатной функции: IN, OUT, INOUT или VARIADIC. Если этот параметр опущен, значением по умолчанию является IN. Обратите внимание, что фактически ALTER EXTENSION не обращает никакого внимания на аргументы OUT, так как для определения идентичности функции требуются только входные аргументы. Поэтому достаточно перечислить аргументы IN, INOUT и VARIADIC.

имя_аргумента

Имя аргумента функции, процедуры или агрегатной функции. Обратите внимание, что фактически ALTER EXTENSION не обращает никакого внимания на имена аргументов, поскольку для определения идентичности функции необходимы только типы данных аргументов.

тип_аргумента

Тип данных аргумента функции, процедуры или агрегатной функции.

тип_слева
тип_справа

Типы данных аргументов оператора (могут быть дополнены схемой). При отсутствии аргумента префиксного или постфиксного оператора укажите вместо типа NONE.

PROCEDURAL

Пропускаемое слово.

имя_типа

Имя типа данных, для которого делается трансформация.

имя_языка

Имя языка, для которого делается трансформация.

Примеры

Обновление расширения hstore до версии 2.0:

ALTER EXTENSION hstore UPDATE TO '2.0';

Смена схемы расширения hstore на utils:

ALTER EXTENSION hstore SET SCHEMA utils;

Добавление существующей функции в расширение hstore:

ALTER EXTENSION hstore ADD FUNCTION populate_record(anyelement, hstore);

Совместимость

Команда ALTER EXTENSION является расширением QHB.

См. также

CREATE EXTENSION, DROP EXTENSION

ALTER FOREIGN DATA WRAPPER

ALTER FOREIGN DATA WRAPPER — изменить определение обертки сторонних данных

Синтаксис

ALTER FOREIGN DATA WRAPPER имя
    [ HANDLER функция_обработки | NO HANDLER ]
    [ VALIDATOR функция_проверки | NO VALIDATOR ]
    [ OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ]) ]
ALTER FOREIGN DATA WRAPPER имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER FOREIGN DATA WRAPPER имя RENAME TO новое_имя

Описание

Команда ALTER FOREIGN DATA WRAPPER изменяет определение обертки сторонних данных. Первая форма команды изменяет вспомогательные функции или общие параметры обертки сторонних данных (требуется по крайней мере одно предложение). Вторая форма меняет владельца обертки сторонних данных.

Только суперпользователи могут изменять обертки сторонних данных и быть их владельцами.

Параметры

имя

Имя существующей обертки сторонних данных.

HANDLER функция_обработки

Задает новую функцию обработки для обертки сторонних данных.

NO HANDLER

Используется, чтобы указать, что обертка сторонних данных больше не имеет функции обработки.

Обратите внимание, что к сторонним таблицам, использующим обертки сторонних данных без функции обработки, обратиться нельзя.

VALIDATOR функция_проверки

Задает новую функцию проверки для обертки сторонних данных.

Обратите внимание, что возможна ситуация, когда предыдущие параметры обертки сторонних данных, зависимых серверов, сопоставлений пользователей или сторонних таблиц, окажутся недопустимыми для новой функции проверки. QHB это не проверяет. Пользователь должен самостоятельно убедиться, что эти параметры являются правильными, прежде чем использовать измененную обертку сторонних данных. Однако все параметры, изменяемые в данной команде ALTER FOREIGN DATA WRAPPER, будут проверены с помощью новой функции проверки.

NO VALIDATOR

Указывает, что обертка сторонних данных больше не имеет функции проверки.

OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ] )

Эта форма настраивает параметры обертки сторонних данных. ADD, SET и DROP указывают, какое действие будет выполнено (добавление, установка и удаление соответственно). Если явно не указана никакая операция, по умолчанию подразумевается ADD. Имена параметров должны быть уникальными: они вместе со значениями проверяются функцией проверки, если та установлена.

новый_владелец

Имя пользователя, который станет новым владельцем обертки сторонних данных.

новое_имя

Новое имя обертки сторонних данных.

Примеры

Изменение параметров обертки сторонних данных dbi: добавление параметра foo, удаление bar:

ALTER FOREIGN DATA WRAPPER dbi OPTIONS (ADD foo '1', DROP 'bar');

Установление для обертки сторонних данных dbi новой функции проверки bob.myvalidator:

ALTER FOREIGN DATA WRAPPER dbi VALIDATOR bob.myvalidator;

Совместимость

Команда ALTER FOREIGN DATA WRAPPER соответствует стандарту ISO/IEC 9075-9 (SQL/MED), за исключением предложений HANDLER, VALIDATOR, OWNER TO и RENAME, являющихся расширениями.

См. также

CREATE FOREIGN DATA WRAPPER, DROP FOREIGN DATA WRAPPER

ALTER FOREIGN TABLE

ALTER FOREIGN TABLE — изменить определение сторонней таблицы

Синтаксис

ALTER FOREIGN TABLE [ IF EXISTS ] [ ONLY ] имя [ * ]
    действие [, ... ]
ALTER FOREIGN TABLE [ IF EXISTS ] [ ONLY ] имя [ * ]
    RENAME [ COLUMN ] имя_столбца TO новое_имя_столбца
ALTER FOREIGN TABLE [ IF EXISTS ] имя
    RENAME TO новое_имя
ALTER FOREIGN TABLE [ IF EXISTS ] имя
    SET SCHEMA новая_схема

где действием может быть:

    ADD [ COLUMN ] имя_столбца тип_данных [ COLLATE правило_сортировки ] [ ограничение_столбца [ ... ] ]
    DROP [ COLUMN ] [ IF EXISTS ] имя_столбца [ RESTRICT | CASCADE ]
    ALTER [ COLUMN ] имя_столбца [ SET DATA ] TYPE тип_данных [ COLLATE правило_сортировки ]
    ALTER [ COLUMN ] имя_столбца SET DEFAULT выражение
    ALTER [ COLUMN ] имя_столбца DROP DEFAULT
    ALTER [ COLUMN ] имя_столбца { SET | DROP } NOT NULL
    ALTER [ COLUMN ] имя_столбца SET STATISTICS целое_число
    ALTER [ COLUMN ] имя_столбца SET ( атрибут = значение [, ... ] )
    ALTER [ COLUMN ] имя_столбца RESET ( атрибут [, ... ] )
    ALTER [ COLUMN ] имя_столбца SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
    ALTER [ COLUMN ] имя_столбца OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ])
    ADD ограничение_таблицы [ NOT VALID ]
    VALIDATE CONSTRAINT имя_ограничения
    DROP CONSTRAINT [ IF EXISTS ]  имя_ограничения [ RESTRICT | CASCADE ]
    DISABLE TRIGGER [ имя_триггера | ALL | USER ]
    ENABLE TRIGGER [ имя_триггера | ALL | USER ]
    ENABLE REPLICA TRIGGER имя_триггера
    ENABLE ALWAYS TRIGGER имя_триггера
    SET WITHOUT OIDS
    INHERIT таблица_родитель
    NO INHERIT таблица_родитель
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
    OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ])

Описание

Команда ALTER FOREIGN TABLE изменяет определение существующей сторонней таблицы. Существует несколько форм этой команды:

ADD COLUMN

Эта форма добавляет новый столбец в стороннюю таблицу, используя тот же синтаксис, что и CREATE FOREIGN TABLE. В отличие от случая, когда столбец добавляется в обычную таблицу, при данной операции в базовом хранилище ничего не происходит: это действие просто объявляет, что некоторый новый столбец теперь доступен через стороннюю таблицу.

DROP COLUMN [ IF EXISTS ]

Эта форма удаляет столбец из сторонней таблицы. Если от этого столбца зависит что-то вне таблицы (например представление), нужно написать указание CASCADE. Если указано IF EXISTS и столбца не существует, это не считается ошибкой. В этом случае выдается только замечание.

SET DATA TYPE

Эта форма изменяет тип столбца сторонней таблицы. Опять же, это не оказывает никакого влияния на базовое хранилище: это действие просто изменяет тип, который, по мнению QHB, имеет этот столбец.

SET/DROP DEFAULT

Эти формы устанавливают или удаляют значение по умолчанию для столбца. Значения по умолчанию применяются только в последующих командах INSERT или UPDATE; они не вызывают изменения уже существующих строк в таблице.

SET/DROP NOT NULL

Устанавливает, будет ли столбец принимать значения NULL или нет.

SET STATISTICS

Эта форма задает цель сбора статистики для каждого столбца для последующих операций ANALYZE. Дополнительную информацию см. в описании аналогичной формы команды ALTER TABLE.

SET ( атрибут = значение [, ... ] )
RESET ( атрибут [, ... ] )

Эта форма устанавливает или сбрасывает значения для атрибута. Дополнительную информацию см. в описании аналогичной формы команды ALTER TABLE.

SET STORAGE

Эта форма задает режим хранения для столбца. Дополнительную информацию см. в описании аналогичной формы команды ALTER TABLE. Обратите внимание, что режим хранения не имеет никакого влияния, если обертка сторонних данных таблицы будет его игнорировать.

ADD ограничение_таблицы [ NOT VALID ]

Эта форма добавляет новое ограничение в стороннюю таблицу, используя тот же синтаксис, что и CREATE FOREIGN TABLE. В настоящее время поддерживаются только ограничения CHECK.

В отличие от случая, когда ограничение добавляется в обычную таблицу, ограничение сторонней таблицы никак не проверяется; скорее, это действие просто объявляет, что некоторое новое условие должно выполняться для всех строк в сторонней таблице. (См. описание на справочной странице команды CREATE FOREIGN TABLE) Если ограничение помечено как NOT VALID, то предполагается, что оно не поддерживается, а записывается для возможного использования в будущем.

VALIDATE CONSTRAINT

Эта форма помечает ограничение, которое ранее было помечено NOT VALID, как проверенное. Никаких действий для проверки ограничения не предпринимается, но будущие запросы будут предполагать, что оно поддерживается.

DROP CONSTRAINT [ IF EXISTS ]

Эта форма удаляет указанное ограничение для сторонней таблицы. Если указано IF EXISTS и ограничение не существует, это не считается ошибкой. В этом случае выдается только замечание.

DISABLE/ENABLE [ REPLICA | ALWAYS ] TRIGGER

Эти формы настраивают срабатывание триггера (или триггеров), принадлежащего сторонней таблице. Дополнительную информацию см. в описании аналогичной формы команды ALTER TABLE.

SET WITHOUT OIDS

Синтаксис обратной совместимости для удаления системного столбца oid. Так как добавить системные столбцы oid теперь невозможно, это указание не действует.

INHERIT таблица_родитель

Эта форма добавляет целевую стороннюю таблицу в качестве нового дочернего элемента указанной родительской таблицы. Дополнительную информацию см. в описании аналогичной формы команды ALTER TABLE.

NO INHERIT таблица_родитель

Эта форма удаляет целевую стороннюю таблицу из списка дочерних элементов указанной родительской таблицы.

OWNER

Эта форма меняет владельца сторонней таблицы на указанного пользователя.

OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ] )

Изменяет параметры для сторонней таблицы или одного из ее столбцов. ADD, SET и DROP указывают на действие, которое необходимо выполнить. Если явно не указана никакая операция, по умолчанию подразумевается ADD. Повторяющиеся имена параметров не допускаются (хотя иметь одно и то же имя для параметра таблицы и параметра столбца нормально). Имена и значения параметров также проверяются с помощью библиотеки обертки сторонних данных.

RENAME

Формы RENAME изменяют имя сторонней таблицы или имя ее столбца.

SET SCHEMA

Эта форма перемещает стороннюю таблицу в другую схему.

Все действия, кроме RENAME и SET SCHEMA, могут быть объединены в один список и выполнены одновременно. Например, в одной команде можно добавить несколько столбцов и/или изменить тип нескольких столбцов.

Если команда написана в виде ALTER FOREIGN TABLE IF EXISTS ... и сторонняя таблица не существует, это не считается ошибкой. В этом случае выдается только замечание.

Чтобы использовать команду ALTER FOREIGN TABLE, нужно быть владельцем соответствующей сторонней таблицы. Чтобы изменить схему сторонней таблицы, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме сторонней таблицы. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания сторонней таблицы. Однако суперпользователь все равно может сменить владельца любой сторонней таблицы.) Чтобы добавить столбец или изменить тип столбца, помимо этого необходимо иметь право USAGE для его типа данных.

Параметры

имя

Имя изменяемой существующей сторонней таблицы (может быть дополнено схемой). Если перед именем таблицы указывается ONLY, изменяется только эта таблица. Если ONLY не указано, будут изменены таблица и все ее потомки (если таковые имеются). После имени таблицы можно также добавить необязательное указание *, чтобы явно обозначить, что изменению подлежат все дочерние таблицы.

имя_столбца

Имя нового или существующего столбца.

новое_имя_столбца

Новое имя существующего столбца.

новое_имя

Новое имя таблицы.

тип_данных

Тип данных нового столбца или новый тип данных для существующего столбца.

ограничение_таблицы

Новое ограничение уровня таблицы для сторонней таблицы.

имя_ограничения

Имя существующего ограничения, подлежащего удалению.

CASCADE

Автоматически удалять объекты, зависящие от удаляемого столбца или ограничения (например ссылающиеся на столбец представления), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Запретить удаление столбца или ограничения, если есть какие-либо зависимые от них объекты. Это поведение по умолчанию.

имя_триггера

Имя одного триггера, который нужно выключить или включить.

ALL

Выключает или включает все триггеры, принадлежащие сторонней таблице. (Если какие-либо из триггеров являются внутрисистемными, то потребуются права суперпользователя. Сама система не добавляет такие триггеры в сторонние таблицы, но дополнительный код может это сделать.)

USER

Выключает или включает все триггеры, принадлежащие сторонней таблице, кроме внутрисистемных

таблица_родитель

Родительская таблица, с которой будет установлена или разорвана связь у этой сторонней таблицы.

новый_владелец

Имя пользователя, который станет новым владельцем таблицы.

новая_схема

Имя схемы, в которую будет перемещена таблица.

Примечания

Ключевое слово COLUMN не несет смысловой нагрузки, и его можно опустить.

Согласованность со сторонним сервером при добавлении или удалении столбца с помощью форм ADD COLUMN/DROP COLUMN, или добавлении ограничений NOT NULL или CHECK, или изменении типа данных SET DATA TYPE не проверяется. Ответственность за то, чтобы определение таблицы соответствовало удаленной стороне, лежит на пользователе.

Более полное описание допустимых параметров см. на справочной станице команды CREATE FOREIGN TABLE.

Примеры

Установление для столбца ограничения NOT NULL:

ALTER FOREIGN TABLE distributors ALTER COLUMN street SET NOT NULL;

Изменение параметров сторонней таблицы:

ALTER FOREIGN TABLE myschema.distributors
	OPTIONS (ADD opt1 'value', SET opt2 'value2', DROP opt3 'value3');

Совместимость

Формы ADD, DROP и SET DATA TYPE соответствуют стандарту SQL. Другие формы являются расширениями QHB. Кроме того, возможность указать в одной команде ALTER FOREIGN TABLE несколько операций также является расширением.

Команда ALTER FOREIGN TABLE DROP COLUMN позволяет удалить единственный столбец сторонней таблицы и оставить таблицу без столбцов. Это является расширением стандарта SQL, который не допускает существование сторонних таблиц без столбцов.

См. также

CREATE FOREIGN TABLE, DROP FOREIGN TABLE

ALTER FUNCTION

ALTER FUNCTION — изменить определение функции

Синтаксис

ALTER FUNCTION имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    действие [ ... ] [ RESTRICT ]
ALTER FUNCTION имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    RENAME TO новое_имя
ALTER FUNCTION имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER FUNCTION имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    SET SCHEMA новая_схема
ALTER FUNCTION имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    [ NO ] DEPENDS ON EXTENSION имя_расширения

где действием может быть:

    CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
    IMMUTABLE | STABLE | VOLATILE
    [ NOT ] LEAKPROOF
    [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
    PARALLEL { UNSAFE | RESTRICTED | SAFE }
    COST стоимость_выполнения
    ROWS строк_в_результате
    SUPPORT вспомогательная_функция
    SET параметр_конфигурации { TO | = } { значение | DEFAULT }
    SET параметр_конфигурации FROM CURRENT
    RESET параметр_конфигурации
    RESET ALL

Описание

Команда ALTER FUNCTION изменяет определение функции.

Чтобы использовать команду ALTER FUNCTION, нужно быть владельцем соответствующей функции. Чтобы изменить схему функции, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме функции. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания функции. Однако суперпользователь все равно может сменить владельца любой функции.)

Параметры

имя

Имя существующей функции (может быть дополнено схемой). Если не указан список аргументов, имя должно быть уникальным в своей схеме.

режим_аргумента

Режим аргумента: IN (входной), OUT (выходной), INOUT (входной-выходной) или VARIADIC (переменный). Если этот параметр не указан, значением по умолчанию является IN. Обратите внимание, что ALTER FUNCTION фактически не обращает никакого внимания на аргументы OUT, так как для определения идентичности функции нужны только входные аргументы. Поэтому достаточно перечислить аргументы IN, INOUT и VARIADIC.

имя_аргумента

Имя аргумента. Обратите внимание, что ALTER FUNCTION фактически не обращает никакого внимания на имена аргументов, так как для определения идентичности функции необходимы только типы данных аргументов.

тип_аргумента

Типы данных аргументов функции (могут быть дополнены схемой), если таковые имеются.

новое_имя

Новое имя функции.

новый_владелец

Новый владелец функции. Обратите внимание, что если функция помечена как SECURITY DEFINER, в дальнейшем она будет выполняться от имени нового владельца.

новая_схема

Новая схема функции.

DEPENDS ON EXTENSION имя_расширения
NO DEPENDS ON EXTENSION имя_расширения

Эта форма помечает функцию как зависящую от расширения или, если указано NO, как более не зависящую от этого расширения. Функция, помеченная как зависящая от расширения, удаляется при удалении этого расширения, даже если не указано CASCADE. Функция может зависеть от нескольких расширений и будет удалена при удалении любого из них.

CALLED ON NULL INPUT
RETURNS NULL ON NULL INPUT
STRICT

CALLED ON NULL INPUT изменяет функцию таким образом, чтобы она вызывалась, когда некоторые или все ее аргументы равны NULL. RETURNS NULL ON NULL INPUT или STRICT изменяет функцию таким образом, чтобы она не вызывалась, если какой-либо из ее аргументов равен NULL, а вместо этого автоматически предполагался результат NULL. Дополнительную информацию см. на справочной странице команды CREATE FUNCTION.

IMMUTABLE
STABLE
VOLATILE

Устанавливает заданный вариант изменчивости функции. Дополнительную информацию см. на справочной странице команды CREATE FUNCTION.

[EXTERNAL] SECURITY INVOKER
[EXTERNAL] SECURITY DEFINER

Устанавливает, является ли функция определяющей контекст безопасности. Ключевое слово EXTERNAL игнорируется для соответствия стандарту SQL. Дополнительную информацию об этой возможности см. на справочной странице команды CREATE FUNCTION.

PARALLEL

Устанавливает, будет ли функция считаться безопасной для распараллеливания. Дополнительную информацию см. на справочной странице команды CREATE FUNCTION.

LEAKPROOF

Устанавливает, будет ли функция рассматриваться как герметичная. Дополнительную информацию об этой возможности см. на справочной странице команды CREATE FUNCTION.

COST стоимость_выполнения

Изменяет расчетную стоимость выполнения функции. Дополнительную информацию см. на справочной странице команды CREATE FUNCTION.

ROWS строк_в_результате

Изменяет расчетное число строк в результате функции, возвращающей множество. Дополнительную информацию см. на справочной странице команды CREATE FUNCTION.

SUPPORT вспомогательная_функция

Устанавливает или меняет вспомогательную функцию планировщика, которая будет использоваться с этой функцией. Дополнительную информацию см. в разделе Информация по оптимизации функций. Чтобы использовать эту функцию, нужно быть суперпользователем.

Имя новой вспомогательной функции является обязательным, поэтому данное указание нельзя использовать для полного удаления вспомогательной функции. Если понадобится ее удалить, используйте CREATE OR REPLACE FUNCTION.

параметр_конфигурации
значение

Добавляет или изменяет установку параметра конфигурации, выполняемую при вызове функции. Если значение равно DEFAULT или использовано равнозначное указание RESET, локальное переопределение для функции удаляется, вследствие чего функция выполняется со значением, установленным в ее среде. Для удаления всех установок параметров этой функции укажите RESET ALL. SET FROM CURRENT устанавливает для последующих вызовов функции значение параметра, действующее в момент выполнения ALTER FUNCTION.

Дополнительную информацию о допустимых именах и значениях параметров см. на справочной странице команды SET и в главе Конфигурация сервера.

RESTRICT

Игнорируется для соответствия стандарту SQL.

Примеры

Переименование функции sqrt для типа integer в square_root:

ALTER FUNCTION sqrt(integer) RENAME TO square_root;

Смена владельца функции sqrt для типа integer на joe:

ALTER FUNCTION sqrt(integer) OWNER TO joe;

Смена схемы функции sqrt для типа integer на maths:

ALTER FUNCTION sqrt(integer) SET SCHEMA maths;

Обозначение функции sqrt для типа integer как зависимой от расширения mathlib:

ALTER FUNCTION sqrt(integer) DEPENDS ON EXTENSION mathlib;

Изменение пути поиска, который устанавливается автоматически для функции:

ALTER FUNCTION check_password(text) SET search_path = admin, pg_temp;

Отмена автоматического определения search_path для функции:

ALTER FUNCTION check_password(text) RESET search_path;

Теперь функция будет выполняться с тем путем, который задан в момент вызова.

Совместимость

Эта команда частично совместим с командой ALTER FUNCTION в стандарте SQL. Стандарт позволяет менять больше свойств функции, но не предоставляет возможности переименовывать функцию, сделать функцию определяющей контекст безопасности, связывать с функцией значения параметров конфигурации или менять владельца, схему и тип изменчивости функции. Кроме того, стандарт SQL требует обязательного наличия слова RESTRICT, тогда как в QHB оно необязательно.

См. также

CREATE FUNCTION, DROP FUNCTION, ALTER PROCEDURE, ALTER ROUTINE

ALTER GROUP

ALTER GROUP — изменить имя роли или членство

Синтаксис

ALTER GROUP описание_роли ADD USER имя_пользователя [, ... ]
ALTER GROUP описание_роли DROP USER имя_пользователя [, ... ]

где описанием_роли может быть:

    имя_роли
  | CURRENT_ROLE
  | CURRENT_USER
  | SESSION_USER

ALTER GROUP имя_группы RENAME TO новое_имя

Описание

Команда ALTER GROUP изменяет атрибуты группы пользователей. Это устаревшая команда, хотя и все еще поддерживаемая для обратной совместимости, так как группы (и пользователи) были заменены на более общую концепцию ролей.

Первые два варианта команды добавляют пользователей в группу или удаляют их из группы. (При этом любая роль может фигурировать либо как «пользователь», либо как «группа».) Эти варианты практически равнозначны командам разрешающим/запрещающим членство в роли «группа», поэтому вместо них рекомендуется использовать команды GRANT и REVOKE.

Третий вариант команды изменяет название группы. Он полностью аналогичен команде ALTER ROLE, выполняющей переименование роли.

Параметры

имя_группы

Имя изменяемой группы (роли).

имя_пользователя

Пользователи (роли), которые должны быть добавлены в группу или удалены из нее. Пользователи уже должны существовать: ALTER GROUP не создает и не удаляет пользователей.

новое_имя

Новое имя группы.

Примеры

Добавление пользователей в группу:

ALTER GROUP staff ADD USER karl, john;

Удаление пользователей из группы:

ALTER GROUP workers DROP USER beth;

Совместимость

В стандарте SQL нет команды ALTER GROUP.

См. также

GRANT, REVOKE, ALTER ROLE

ALTER INDEX

ALTER INDEX — изменить определение индекса

Синтаксис

ALTER INDEX [ IF EXISTS ] имя RENAME TO новое_имя
ALTER INDEX [ IF EXISTS ] имя SET TABLESPACE имя_табличного_пространства
ALTER INDEX имя ATTACH PARTITION имя_индекса
ALTER INDEX имя DEPENDS ON EXTENSION имя_расширения
ALTER INDEX [ IF EXISTS ] имя SET ( параметр_хранения = значение [, ... ] )
ALTER INDEX [ IF EXISTS ] имя RESET ( параметр_хранения [, ... ] )
ALTER INDEX [ IF EXISTS ] имя ALTER [ COLUMN ] номер_столбца
    SET STATISTICS целое_число
ALTER INDEX ALL IN TABLESPACE имя [ OWNED BY имя_роли [, ... ] ]
    SET TABLESPACE новое_табличное_пространство [ NOWAIT ]

Описание

Команда ALTER INDEX изменяет определение существующего индекса. Она имеет несколько форм, которые описаны ниже. Обратите внимание, что требуемый уровень блокировки может отличаться для каждой формы. Если явно не указано, то по умолчанию запрашивается блокировка ACCESS EXCLUSIVE. Если перечислено несколько подкоманд, то запрашивается самая строгая блокировка из требуемых подкомандами.

RENAME

Форма RENAME изменяет имя индекса. Если индекс связан с ограничением таблицы (UNIQUE, PRIMARY KEY или EXCLUDE), то ограничение также переименовывается. Это не оказывает никакого влияния на сохраненные данные.

Переименование индекса требует блокировки SHARE UPDATE EXCLUSIVE.

SET TABLESPACE

Эта форма изменяет табличное пространство индекса на указанное и перемещает файл (или файлы) данных, связанный с индексом, в новое табличное пространство. Чтобы изменить табличное пространство индекса, текущий пользователь должен быть владельцем индекса и иметь право CREATE в новом табличном пространстве. Все индексы в текущей базе данных в табличном пространстве можно переместить с помощью формы ALL IN TABLESPACE. При этом индексы блокируются для перемещения, а затем перемещается каждый индекс. Эта форма также поддерживает OWNED BY, с которым будут перемещены только индексы, принадлежащие заданным ролям. Если указан параметр NOWAIT, то команда завершится ошибкой, если не сможет сразу получить все необходимые блокировки. Обратите внимание, что системные каталоги этой командой перемещены не будут; при необходимости используйте вызов ALTER DATABASE или явный вызов ALTER INDEX. См. также CREATE TABLESPACE.

ATTACH PARTITION

Эта форма присоединяет указанный индекс к изменяемому. Указанный индекс должен находиться в партиции таблицы, содержащей изменяемый индекс, и иметь аналогичное определение. Присоединенный индекс не может быть удален сам по себе и будет автоматически удален при удалении родительского индекса.

DEPENDS ON EXTENSION имя_расширения
NO DEPENDS ON EXTENSION имя_расширения

Эта форма помечает индекс как зависящий или, если указано NO, как более не зависящий от данного расширения. Индекс, помеченный как зависящий от расширения, при его удалении также будет автоматически удален.

SET ( параметр_хранения = значение [, ... ] )

Эта форма изменяет один или несколько специфичных для индексного метода параметров хранения. Дополнительную информацию о доступных параметрах см. на справочной странице команды CREATE INDEX. Обратите внимание, что содержимое индекса не будет изменено немедленно; в зависимости от параметров, для получения желаемого эффекта может потребоваться перестроить индекс командой REINDEX.

RESET ( параметр_хранения [, ... ] )

Эта форма сбрасывает один или несколько специфичных для индексного метода параметров хранения к значениям по умолчанию. Как и в случае с SET, для полного обновления индекса может потребоваться выполнить REINDEX.

ALTER [ COLUMN ] номер_столбца SET STATISTICS целое_число

Эта форма задает целевой показатель сбора статистики по столбцу для последующих операций ANALYZE, хотя ее можно использовать только для индексируемых столбцов, определенных как выражение. Поскольку выражения не имеют уникального имени, к ним нужно обращаться по порядковому номеру столбца в индексе. Целевой показатель можно задать в диапазоне от 0 до 10000; как вариант, при установке -1 применяется его системное значение по умолчанию (default_statistics_target). Дополнительную информацию об использовании статистики планировщиком запросов QHB см. в разделе Статистика, используемая планировщиком.

Параметры

IF EXISTS

Не считать ошибкой, если индекс не существует. В этом случае выдается только замечание.

номер_столбца

Число, указывающее на порядковый номер столбца в индексе (слева направо).

имя

Имя изменяемого существующего индекса (может быть дополнено схемой).

новое_имя

Новое имя индекса.

имя_табличного_пространства

Табличное пространство, в которое будет перемещен индекс.

имя_расширения

Имя расширения, от которого будет зависеть индекс.

параметр_хранения

Имя специфичного для индексного метода параметра хранения.

значение

Новое значение специфичного для индексного метода параметра хранения. Это может быть число или слово, в зависимости от параметра.

Примечания

Эти операции также можно выполнить с помощью команды [ALTER TABLE]. ALTER INDEX фактически является просто синонимом форм команды ALTER TABLE, которые применяются к индексам.

Ранее существовала форма ALTER INDEX OWNER, но теперь она игнорируется (с предупреждением). Владелец индекса должен быть и владельцем таблицы. Смена владельца таблицы автоматически меняет владельца индекса.

Изменение любой части индекса системного каталога не допускается.

Примеры

Переименование существующего индекса:

ALTER INDEX distributors RENAME TO suppliers;

Перемещение индекса в другое табличное пространство:

ALTER INDEX distributors SET TABLESPACE fasttablespace;

Изменение коэффициента заполнения индекса (предполагается, что индексный метод его поддерживает):

ALTER INDEX distributors SET (fillfactor = 75);
REINDEX INDEX distributors;

Установка целевого показателя статистики для индекса по выражению:

CREATE INDEX coord_idx ON measured (x, y, (z + t));
ALTER INDEX coord_idx ALTER COLUMN 3 SET STATISTICS 1000;

Совместимость

Команда ALTER INDEX является расширением QHB.

См. также

CREATE INDEX, REINDEX

ALTER LANGUAGE

ALTER LANGUAGE — изменить определение процедурного языка

Синтаксис

ALTER [ PROCEDURAL ] LANGUAGE имя RENAME TO новое_имя
ALTER [ PROCEDURAL ] LANGUAGE имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

Описание

Команда ALTER LANGUAGE изменяет определение процедурного языка. Единственная ее функция — это переименовать язык или назначить нового владельца. Чтобы использовать команду ALTER LANGUAGE, нужно быть суперпользователем или владельцем соответствующего языка.

Параметры

имя

Имя языка

новое_имя

Новое имя языка

новый_владелец

Новый владелец языка

Совместимость

В стандарте SQL нет команды ALTER LANGUAGE.

См. также

CREATE LANGUAGE, DROP LANGUAGE

ALTER LARGE OBJECT

ALTER LARGE OBJECT — изменить определение большого объекта

Синтаксис

ALTER LARGE OBJECT oid_большого_объекта OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

Описание

Команда ALTER LARGE OBJECT изменяет определение большого объекта.

Чтобы использовать команду ALTER LARGE OBJECT, нужно быть владельцем соответствующего большого объекта. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца. (Однако суперпользователь все равно может сменить владельца любого большого объекта.) В настоящее время единственная функция данной команды — это назначение нового владельца, поэтому всегда действуют оба ограничения.

Параметры

oid_большого_объекта

OID изменяемого большого объекта

новый_владелец

Новый владелец большого объекта

Совместимость

В стандарте SQL нет команды ALTER LARGE OBJECT.

См. также

Большие объекты

ALTER MATERIALIZED VIEW

ALTER MATERIALIZED VIEW — изменить определение материализованного представления

Синтаксис

ALTER MATERIALIZED VIEW [ IF EXISTS ] имя
    действие [, ... ]
ALTER MATERIALIZED VIEW имя
    [ NO ] DEPENDS ON EXTENSION имя_расширения
ALTER MATERIALIZED VIEW [ IF EXISTS ] имя
    RENAME [ COLUMN ] имя_столбца TO новое_имя_столбца
ALTER MATERIALIZED VIEW [ IF EXISTS ] имя
    RENAME TO новое_имя
ALTER MATERIALIZED VIEW [ IF EXISTS ] имя
    SET SCHEMA новая_схема
ALTER MATERIALIZED VIEW ALL IN TABLESPACE имя [ OWNED BY имя_роли [, ... ] ]
    SET TABLESPACE новое_табличное_пространство [ NOWAIT ]

где действием может быть:

    ALTER [ COLUMN ] имя_столбца SET STATISTICS целое_число
    ALTER [ COLUMN ] имя_столбца SET ( атрибут = значение [, ... ] )
    ALTER [ COLUMN ] имя_столбца RESET ( атрибут [, ... ] )
    ALTER [ COLUMN ] имя_столбца SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
    ALTER [ COLUMN ] имя_столбца SET COMPRESSION метод_сжатия
    CLUSTER ON имя_индекса
    SET WITHOUT CLUSTER
    SET TABLESPACE новое_табличное_пространство
    SET ( параметр_хранения = значение [, ... ] )
    RESET ( параметр_хранения [, ... ] )
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

Описание

Команда ALTER MATERIALIZED VIEW изменяет различные расширенные свойства существующего материализованного представления.

Чтобы использовать команду ALTER MATERIALIZED VIEW, нужно быть владельцем соответствующего материализованного представления. Чтобы изменить схему материализованного представления, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли, и эта роль должна иметь право CREATE в схеме материализованного представления. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания материализованного представления. Однако суперпользователь все равно может сменить владельца любого материализованного представления.)

Формы и указания, доступные для команды ALTER MATERIALIZED VIEW, являются подмножеством тех, что доступны для команды ALTER TABLE, и при использовании для материализованных представлений имеют то же значение. Дополнительную информацию см. на справочной странице команды ALTER TABLE.

Параметры

имя

Имя существующего материализованного представления (может быть дополнено схемой).

имя_столбца

Имя нового или существующего столбца.

имя_расширения

Имя расширения, от которого будет зависеть материализованное представление (или больше не будет зависеть, если указано NO). Материализованное представление, помеченное как зависящее от расширения, будет автоматически удалено при удалении этого расширения.

новое_имя_столбца

Новое имя существующего столбца.

новый_владелец

Имя пользователя, который станет новым владельцем материализованного представления.

новое_имя

Новое имя материализованного представления.

новая_схема

Новая схема материализованного представления.

Примеры

Переименование материализованного представления foo в bar:

ALTER MATERIALIZED VIEW foo RENAME TO bar;

Совместимость

Команда ALTER MATERIALIZED VIEW является расширением QHB.

См. также

CREATE MATERIALIZED VIEW, DROP MATERIALIZED VIEW, REFRESH MATERIALIZED VIEW

ALTER OPERATOR CLASS

ALTER OPERATOR CLASS — изменить определение класса операторов

Синтаксис

ALTER OPERATOR CLASS имя USING индексный_метод
    RENAME TO новое_имя

ALTER OPERATOR CLASS имя USING индексный_метод
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

ALTER OPERATOR CLASS имя USING индексный_метод
    SET SCHEMA новая_схема

Описание

Команда ALTER OPERATOR CLASS изменяет определение класса операторов.

Чтобы использовать команду ALTER OPERATOR CLASS, нужно быть владельцем соответствующего класса оператора. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме класса оператора. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания класса оператора. Однако суперпользователь все равно может сменить владельца любого класса оператора.)

Параметры

имя

Имя существующего класса операторов (может быть дополнено схемой).

индексный_метод

Имя индексного метода, для которого предназначен этот класс операторов.

новое_имя

Новое имя класса операторов.

новый_владелец

Новый владелец класса операторов.

новая_схема

Новая схема класса операторов.

Совместимость

В стандарте SQL нет команды ALTER OPERATOR CLASS.

См. также

CREATE OPERATOR CLASS, DROP OPERATOR CLASS, ALTER OPERATOR FAMILY

ALTER OPERATOR FAMILY

ALTER OPERATOR FAMILY — изменить определение семейства операторов

Синтаксис

ALTER OPERATOR FAMILY имя USING индексный_метод ADD
  {  OPERATOR номер_стратегии имя_оператора ( тип_операнда, тип_операнда )
              [ FOR SEARCH | FOR ORDER BY имя_семейства_сортировки ]
   | FUNCTION номер_вспомогательной_функции [ ( тип_операнда [ , тип_операнда ] ) ]
              имя_функции [ ( тип_аргумента [, ...] ) ]
  } [, ... ]

ALTER OPERATOR FAMILY имя USING индексный_метод DROP
  {  OPERATOR номер_стратегии ( тип_операнда [ , тип_операнда ] )
   | FUNCTION номер_вспомогательной_функции ( тип_операнда [ , тип_операнда ] )
  } [, ... ]

ALTER OPERATOR FAMILY имя USING индексный_метод
    RENAME TO новое_имя

ALTER OPERATOR FAMILY имя USING индексный_метод
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

ALTER OPERATOR FAMILY имя USING индексный_метод
    SET SCHEMA новая_схема

Описание

Команда ALTER OPERATOR FAMILY изменяет определение семейства операторов. Можно добавлять в семейство операторы и вспомогательные функции, удалять их оттуда, изменять имя или владельца семейства операторов.

Когда операторы и вспомогательные функции добавляются в семейство операторов командой ALTER OPERATOR FAMILY, они не становятся частью какого-либо конкретного класса операторов в семействе, а просто считаются «слабосвязанными» с семейством. Это свидетельствует о том, что данные операторы и функции семантически совместимы с семейством, но не требуются для правильного функционирования какого-либо конкретного индекса. (Операторы и функции, которые действительно требуются для этого, должны быть объявлены как часть класса операторов, см. CREATE OPERATOR CLASS.) QHB позволяет в любое время удалить из семейства слабосвязанные члены, но нельзя удалить члены класса операторов без удаления всего класса и всех зависящих от него индексов. Как правило, в классы операторов включаются операторы и функции одного типа данных, поскольку они необходимы для поддержки индекса для этого конкретного типа данных, тогда как операторы и функции перекрестных типов данных становятся слабосвязанными членами семейства.

Чтобы использовать команду ALTER OPERATOR FAMILY, нужно быть суперпользователем. (Эти ограничения направлены на то, чтобы ошибочное определение семейства операторов не вызвало нарушение или даже сбой в работе сервера.)

В настоящее время ALTER OPERATOR FAMILY не проверяет, включает ли определение семейства операторов все операторы и функции, требуемые для индексного метода, и образуют ли они целостный набор. Ответственность за определение допустимого семейства операторов несет пользователь.

Дополнительную информацию см. в разделе Интерфейсные расширения для индексов.

Параметры

имя

Имя существующего семейства операторов (может быть дополнено схемой).

индексный_метод

Имя индексного метода, для которого предназначено это семейство операторов.

номер_стратегии

Номер стратегии индексного метода для оператора, связанного с этим семейством операторов.

имя_оператора

Имя оператора, связанного с этим семейством операторов (может быть дополнено схемой).

тип_операнда

В предложении OPERATOR указываются типы данных операнда оператора или NONE, если это левый или правый унарный оператор. В отличие от сопоставимого синтаксиса в CREATE OPERATOR CLASS, типы данных операнда всегда должны быть указаны.

В предложении ADD FUNCTION указываются типы данных операнда оператора, который должна поддерживать функция, если они отличаются от входных типов данных этой функции. Для функций сравнения B-деревьев и хеш-функций указывать тип_операнда необязательно, так как их входные типы данных всегда будут верными. Однако для вспомогательных функций сортировки B-деревьев и всех функций в классах операторов GiST, SP-GiST и GIN необходимо указать типы операндов, с которыми будут использоваться эти функции.

В предложении DROP FUNCTION должны быть указаны типы операндов, для поддержки которых предназначена функция.

имя_семейства_сортировки

Имя существующего семейства операторов btree, описывающего порядок сортировки, связанный с оператором сортировки (может быть дополнено схемой).

Если не указано ни FOR SEARCH (для поиска), ни FOR ORDER BY (для сортировки), то значение по умолчанию равно FOR SEARCH.

номер_вспомогательной_функции

Номер вспомогательной функции индексного метода для функции, связанной с данным семейством операторов

имя_функции

Имя функции, являющейся вспомогательной функцией индексного метода для данного семейства операторов (может быть дополнено схемой). Если список аргументов отсутствует, имя функции должно быть уникальным в ее схеме.

тип_аргумента

Тип(ы) данных параметра функции.

новое_имя

Новое имя семейства операторов.

новый_владелец

Новый владелец семейства операторов.

новая_схема

Новая схема для семейства операторов.

Предложения OPERATOR и FUNCTION можно указывать в любом порядке.

Примечания

Обратите внимание, что в синтаксисе DROP указывается только «слот» в семействе операторов (по номеру стратегии или вспомогательной функции) и входные типы данных. Имя оператора или функции, занимающей слот, не упоминается. Кроме того, для DROP FUNCTION необходимо указать тип (или типы) входных данных, который должна поддерживать функция; для индексов GiST, SP-GiST и Gin это может не иметь ничего общего с фактическими типами входных аргументов функции.

Поскольку механизм индексирования не проверяет права на доступ к функциям перед их использованием, включение функции или оператора в семейство операторов равносильно предоставлению права на их выполнение. Это обычно не является проблемой для тех видов функций, которые полезны в семействе операторов.

Операторы не должны реализовываться в функциях на языке SQL. SQL-функция, скорее всего, будет встроена в вызывающий запрос, что помешает оптимизатору распознать, что запрос соответствует индексу.

Примеры

Следующий пример добавляет вспомогательные функции и операторы смешанных типов данных в семейство операторов, уже содержащее классы операторов B-дерева для типов данных int4 и int2.

ALTER OPERATOR FAMILY integer_ops USING btree ADD

  -- int4 и int2
  OPERATOR 1 < (int4, int2) ,
  OPERATOR 2 <= (int4, int2) ,
  OPERATOR 3 = (int4, int2) ,
  OPERATOR 4 >= (int4, int2) ,
  OPERATOR 5 > (int4, int2) ,
  FUNCTION 1 btint42cmp(int4, int2) ,

  -- int2 и int4
  OPERATOR 1 < (int2, int4) ,
  OPERATOR 2 <= (int2, int4) ,
  OPERATOR 3 = (int2, int4) ,
  OPERATOR 4 >= (int2, int4) ,
  OPERATOR 5 > (int2, int4) ,
  FUNCTION 1 btint24cmp(int2, int4) ;

Удаление этих же элементов:

ALTER OPERATOR FAMILY integer_ops USING btree DROP

  -- int4 и int2
  OPERATOR 1 (int4, int2) ,
  OPERATOR 2 (int4, int2) ,
  OPERATOR 3 (int4, int2) ,
  OPERATOR 4 (int4, int2) ,
  OPERATOR 5 (int4, int2) ,
  FUNCTION 1 (int4, int2) ,

  -- int2 и int4
  OPERATOR 1 (int2, int4) ,
  OPERATOR 2 (int2, int4) ,
  OPERATOR 3 (int2, int4) ,
  OPERATOR 4 (int2, int4) ,
  OPERATOR 5 (int2, int4) ,
  FUNCTION 1 (int2, int4) ;

Совместимость

В стандарте SQL нет команды ALTER OPERATOR FAMILY.

См. также

CREATE OPERATOR FAMILY, DROP OPERATOR FAMILY, CREATE OPERATOR CLASS, ALTER OPERATOR CLASS, DROP OPERATOR CLASS

ALTER OPERATOR

ALTER OPERATOR — изменить определение оператора

Синтаксис

ALTER OPERATOR имя ( { тип_слева | NONE } , тип_справа )
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

ALTER OPERATOR имя ( { тип_слева | NONE } , тип_справа )
    SET SCHEMA новая_схема

ALTER OPERATOR имя ( { тип_слева | NONE } , тип_справа )
    SET ( {  RESTRICT = { процедура_ограничения | NONE }
           | JOIN = { процедура_соединения | NONE }
         } [, ... ] )

Описание

Команда ALTER OPERATOR изменяет определение оператора.

Чтобы использовать команду ALTER OPERATOR, нужно быть владельцем соответствующего оператора. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме оператора. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания оператора. Однако суперпользователь все равно может сменить владельца любого оператора.)

Параметры

имя

Имя существующего оператора (может быть дополнено схемой).

тип_слева

Тип данных левого операнда оператора; если у оператора нет левого операнда, укажите NONE.

тип_справа

Тип данных правого операнда оператора.

новый_владелец

Новый владелец оператора.

новая_схема

Новая схема оператора.

процедура_ограничения

Функция оценки избирательности ограничения для этого оператора; чтобы удалить существующую функцию оценки, укажите NONE.

процедура_соединения

Функция оценки избирательности соединения для этого оператора; чтобы удалить существующую функцию оценки, укажите NONE.

Примеры

Смена владельца нестандартного оператора a @@ b для типа text:

ALTER OPERATOR @@ (text, text) OWNER TO joe;

Смена функций оценки избирательности ограничения и соединения для нестандартного оператора a && b для типа int[]:

ALTER OPERATOR && (_int4, _int4) SET (RESTRICT = _int_contsel, JOIN = _int_contjoinsel);

Совместимость

В стандарте SQL нет команды ALTER OPERATOR.

См. также

CREATE OPERATOR, DROP OPERATOR

ALTER POLICY

ALTER POLICY — изменить определение политики защиты на уровне строк

Синтаксис

ALTER POLICY имя ON имя_таблицы RENAME TO новое_имя

ALTER POLICY имя ON имя_таблицы
    [ TO { имя_роли | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ]
    [ USING ( выражение_использования ) ]
    [ WITH CHECK ( выражение_проверки ) ]

Описание

Команда ALTER POLICY изменяет определение существующей политики защиты на уровне строк. Обратите внимание, что ALTER POLICY разрешает изменить только набор ролей, к которым применяется политика, а также выражения USING и WITH CHECK. Чтобы изменить другие свойства политики, например команду, к которой она применяется, или свойство разрешительная/ограничительная, политика следует удалить и создать заново.

Чтобы использовать команду ALTER POLICY, нужно быть владельцем таблицы, к которой применяется политика.

Во второй форме ALTER POLICY список ролей, выражение_использования и выражение_проверки (если указаны) заменяются независимо. Если одно из этих условий опущено, соответствующая часть политики остается неизменной.

Параметры

имя

Имя изменяемой существующей политики.

имя_таблицы

Имя таблицы, к которой применяется политика (может быть дополнено схемой).

новое_имя

Новое имя для политики.

имя_роли

Роль, на которую действует политика. Одновременно можно указать несколько ролей. Чтобы применить политику ко всем ролям, используйте PUBLIC.

выражение_использования

Выражение USING для политики. Дополнительную информацию см. на справочной странице команды CREATE POLICY.

выражение_проверки

Выражение WITH CHECK для политики. Дополнительную информацию см. на справочной странице команды CREATE POLICY.

Совместимость

Команда ALTER POLICY является расширением QHB.

См. также

CREATE POLICY, DROP POLICY

ALTER PROCEDURE

ALTER PROCEDURE — изменить определение процедуры

Синтаксис

ALTER PROCEDURE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    действие [ ... ] [ RESTRICT ]
ALTER PROCEDURE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    RENAME TO новое_имя
ALTER PROCEDURE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PROCEDURE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    SET SCHEMA новая_схема
ALTER PROCEDURE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    [ NO ] DEPENDS ON EXTENSION имя_расширения

Где действие может быть следующим:

    [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
    SET параметр_конфигурации { TO | = } { значение | DEFAULT }
    SET параметр_конфигурации FROM CURRENT
    RESET параметр_конфигурации
    RESET ALL

Описание

Команда ALTER PROCEDURE изменяет определение процедуры.

Чтобы использовать команду ALTER PROCEDURE, нужно быть владельцем соответствующей процедуры. Чтобы изменить схему процедуры, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме процедуры. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания процедуры. Однако суперпользователь все равно может сменить владельца любой процедуры.)

Параметры

имя

Имя существующей процедуры (может быть дополнено схемой). Если не указан список аргументов, имя должно быть уникальным в ее схеме.

режим_аргумента

Режим аргумента: IN (входной) или VARIADIC (переменный). Если опущен, по умолчанию подразумевается IN.

имя_аргумента

Имя аргумента. Обратите внимание, что ALTER PROCEDURE фактически не обращает никакого внимания на имена аргументов, поскольку для определения идентичности процедуры необходимы только типы данных аргументов.

тип_аргумента

Тип (или типы) данных аргументов процедуры (возможно, дополненный схемой), если аргументы имеются. Подробную информацию о том, как нужная процедура находится с помощью типов данных аргументов, см. на справочной странице команды DROP PROCEDURE.

новое_имя

Новое имя процедуры.

новый_владелец

Новый владелец функции. Обратите внимание, что если процедура помечена как SECURITY DEFINER, в дальнейшем она будет выполняться от имени нового владельца.

новая_схема

Новая схема для процедуры.

имя_расширения

Эта форма помечает процедуру как зависящую от расширения или, если указано NO, как более не зависящую от этого расширения. Процедура, помеченная как зависящая от расширения, удаляется при удалении этого расширения, даже если не указано CASCADE. Процедура может зависеть от нескольких расширений и будет удалена при удалении любого из них.

[EXTERNAL] SECURITY INVOKER
[EXTERNAL] SECURITY DEFINER

Устанавливает, является ли процедура определяющей контекст безопасности. Ключевое слово EXTERNAL игнорируется для соответствия стандарту SQL. Дополнительную информацию об этой возможности см. на справочной странице команды CREATE PROCEDURE.

параметр_конфигурации
значение

Добавляет или изменяет установку параметра конфигурации, выполняемую при вызове процедуры. Если значение равно DEFAULT или указано равнозначное RESET, локальное переопределение для процедуры удаляется, и функция выполняется со значением, установленным в ее среде. Для удаления всех установок параметров процедуры укажите RESET ALL. SET FROM CURRENT устанавливает для последующих вызовов процедуры значение параметра, действующее в момент выполнения команды ALTER FUNCTION.

Дополнительную информацию о допустимых именах и значениях параметров см. на справочной странице команды SET и в главе Конфигурация сервера.

RESTRICT

Игнорируется для соответствия стандарту SQL.

Примеры

Переименование процедуры insert_data с двумя аргументами типа integer в insert_record:

ALTER PROCEDURE insert_data(integer, integer) RENAME TO insert_record;

Смена владельца процедуры insert_data с двумя аргументами типа integer на joe:

ALTER PROCEDURE insert_data(integer, integer) OWNER TO joe;

Изменение схемы процедуры insert_data с двумя аргументами типа integer на accounting:

ALTER PROCEDURE insert_data(integer, integer) SET SCHEMA accounting;

Обозначение процедуры insert_data(integer, integer) как зависящей от расширения myext:

ALTER PROCEDURE insert_data(integer, integer) DEPENDS ON EXTENSION myext;

Изменение пути поиска, который устанавливается автоматически для процедуры:

ALTER PROCEDURE check_password(text) SET search_path = admin, pg_temp;

Отмена автоматического определения search_path для процедуры:

ALTER PROCEDURE check_password(text) RESET search_path;

Теперь процедура будет выполняться с тем путем, который задан в момент вызова.

Совместимость

Эта команда частично совместима с командой ALTER PROCEDURE в стандарте SQL. Стандарт позволяет изменять больше свойств процедуры, но не предоставляет возможности переименовать процедуру, сделать процедуру определяющей контекст безопасности, связать с процедурой значения параметров конфигурации или менять владельца, схему или тип изменчивости процедуры. Стандарт также требует обязательного наличия слова RESTRICT, которое является необязательным в QHB.

См. также

CREATE PROCEDURE, DROP PROCEDURE, ALTER FUNCTION, ALTER ROUTINE

ALTER PUBLICATION

ALTER PUBLICATION — изменить определение публикации

Синтаксис

ALTER PUBLICATION имя ADD TABLE [ ONLY ] имя_таблицы [ * ] [, ...]
ALTER PUBLICATION имя SET TABLE [ ONLY ] имя_таблицы [ * ] [, ...]
ALTER PUBLICATION имя DROP TABLE [ ONLY ] имя_таблицы [ * ] [, ...]
ALTER PUBLICATION имя SET ( параметр_публикации [= значение] [, ... ] )
ALTER PUBLICATION имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION имя RENAME TO новое_имя

Описание

Команда ALTER PUBLICATION может изменять атрибуты публикации.

Первые три варианта команды управляют вхождением таблиц в публикации. Предложение SET TABLE заменяет список таблиц в публикации заданным. Предложения ADD TABLE и DROP TABLE добавляют и удаляют таблицы в публикации. Обратите внимание, что для добавления таблиц в публикацию, на которую уже оформлена подписка, потребуется выполнить действие ALTER SUBSCRIPTION ... REFRESH PUBLICATION на стороне подписчика, чтобы это изменение вступило в силу.

Четвертый указанный в синтаксисе вариант этой команды может изменять все свойства публикации, указанные на справочной странице команды CREATE PUBLICATION. Свойства, не упомянутые в команде, сохраняют свои предыдущие значения.

Остальные варианты меняют владельца и название публикации.

Чтобы использовать команду ALTER PUBLICATION, нужно быть владельцем соответствующей публикации. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в базе данных. Кроме того, новым владельцем публикации FOR ALL TABLES должен быть суперпользователь. Однако суперпользователь может сменить владельца публикации, обходя эти ограничения.

Параметры

имя

Название существующей публикации, определение которой подлежит изменению.

имя_таблицы

Имя существующей таблицы. Если перед именем таблицы указывается ONLY, затрагивается только эта таблица. Если ONLY не указано, затрагиваются и эта таблица, и все ее потомки (если таковые имеются). После имени таблицы можно добавить необязательное указание *, чтобы явно обозначить, что должны затрагиваться и все дочерние таблицы.

SET ( параметр_публикации [= значение] [, ... ] )

Это предложение изменяет параметры публикации, первоначально заданные командой CREATE PUBLICATION (более подробную информацию см. на справочной странице этой команды).

новый_владелец

Имя пользователя, который станет новым владельцем публикации.

новое_имя

Новое имя для публикации.

Примеры

Изменение публикации, чтобы публиковались только удаления и изменения:

ALTER PUBLICATION noinsert SET (publish = 'update, delete');

Добавление таблиц в публикацию:

ALTER PUBLICATION mypublication ADD TABLE users, departments;

Совместимость

Команда ALTER PUBLICATION является расширением QHB.

См. также

CREATE PUBLICATION, DROP PUBLICATION, CREATE SUBSCRIPTION, ALTER SUBSCRIPTION

ALTER ROLE

ALTER ROLE — изменить роль в базе данных

Синтаксис

ALTER ROLE описание_роли [ WITH ] параметр [ ... ]

Где параметром может быть:

      SUPERUSER | NOSUPERUSER
    | CREATEDB | NOCREATEDB
    | CREATEROLE | NOCREATEROLE
    | INHERIT | NOINHERIT
    | LOGIN | NOLOGIN
    | REPLICATION | NOREPLICATION
    | BYPASSRLS | NOBYPASSRLS
    | CONNECTION LIMIT предел_подключений
    | [ ENCRYPTED ] PASSWORD 'пароль' | PASSWORD NULL
    | VALID UNTIL 'метка_времени'

ALTER ROLE имя RENAME TO новое_имя

ALTER ROLE { описание_роли | ALL } [ IN DATABASE имя_базы_данных ] SET параметр_конфигурации { TO | = } { значение | DEFAULT }
ALTER ROLE { описание_роли | ALL } [ IN DATABASE имя_базы_данных ] SET параметр_конфигурации FROM CURRENT
ALTER ROLE { описание_роли | ALL } [ IN DATABASE имя_базы_данных ] RESET параметр_конфигурации
ALTER ROLE { описание_роли | ALL } [ IN DATABASE имя_базы_данных ] RESET ALL

где описанием_роли может быть:

    имя_роли
  | CURRENT_ROLE
  | CURRENT_USER
  | SESSION_USER

Описание

Команда ALTER ROLE изменяет атрибуты роли QHB.

Первый указанный в синтаксисе вариант этой команды может изменить большинство атрибутов роли, которые можно задать в CREATE ROLE. (Покрываются все возможные атрибуты, за исключением возможности добавления/удаления членов роли; для этого нужно использовать команды GRANT и REVOKE.) Атрибуты, не упомянутые в команде, сохраняют свои предыдущие параметры. Суперпользователи базы данных могут изменять любые из этих параметров для любой роли. Роли с правом CREATEROLE могут изменять любые из этих параметров, за исключением SUPERUSER, REPLICATION и BYPASSRLS, но только не для ролей суперпользователя и репликации. Обычные роли могут изменить только свой пароль.

Второй вариант меняет название роли. Суперпользователи базы данных могут переименовать любую роль. Роли с правом CREATEROLE могут переименовывать роли, не являющиеся суперпользователями. Текущего пользователя сеанса переименовать нельзя. (Если нужно это сделать, подключитесь как другой пользователь.) Так как в паролях с MD5-шифрованием имя роли используется в качестве криптографической соли, то при переименовании роли ее пароль очищается, если он был зашифрован MD5.

Остальные варианты изменяют значение по умолчанию конфигурационной переменной, которое будет распространятся на сеансы роли либо для всех баз данных, либо, если указано IN DATABASE, только для сеансов в указанной базе данных. Если вместо имени роли указывается ALL, то значение переменной распространяется на все роли. Использование ALL с IN DATABASE по сути равносильно использованию команды ALTER DATABASE ... SET ....

Когда роль впоследствии запускает новый сеанс, указанное значение становится значением по умолчанию, переопределяя все настройки, присутствующие в qhb.conf или полученные из командной строки qhb. Это происходит только во время входа в систему; выполнение SET ROLE или SET SESSION AUTHORIZATION не вызывает установки новых значений конфигурации. Параметры, заданные для всех баз данных, переопределяются установленными для роли параметрами уровня базы данных. Параметры для конкретных баз данных или ролей могут переопределять только суперпользователи.

Суперпользователи могут менять сеансовые значения по умолчанию для любых ролей. Роли с правом CREATEROLE могут изменять значения по умолчанию для ролей, не являющихся суперпользователями. Обычные роли могут устанавливать значения по умолчанию только для себя. Некоторые переменные конфигурации таким способом установить нельзя или можно установить только в том случае, если команду выполняет суперпользователь. Только суперпользователи могут изменять настройки для всех ролей во всех базах данных.

Параметры

имя

Имя роли, атрибуты которой подлежат изменению.

CURRENT_ROLE
CURRENT_USER

Изменяет текущего пользователя вместо явно определенной роли.

SESSION_USER

Изменяет текущего пользователя сеанса вместо явно определенной роли.

SUPERUSER
NOSUPERUSER
CREATEDB
NOCREATEDB
CREATEROLE
NOCREATEROLE
INHERIT
NOINHERIT
LOGIN
NOLOGIN
REPLICATION
NOREPLICATION
BYPASSRLS
NOBYPASSRLS
CONNECTION LIMIT предел_подключений
[ ENCRYPTED ] PASSWORD 'пароль'
PASSWORD NULL
VALID UNTIL 'метка_времени'

Эти предложения изменяют атрибуты, изначально заданные с помощью команды CREATE ROLE. Дополнительную информацию см. на справочной странице команды CREATE ROLE.

новое_имя

Новое имя роли.

имя_базы_данных

Имя базы данных, в которой должна быть установлена переменная конфигурации.

параметр_конфигурации
значение

Устанавливает заданное значение по умолчанию в сеансах этой роли для указанного параметра конфигурации. Если значение равно DEFAULT или используется равнозначное указание RESET, установка переменной на уровне роли удаляется, то есть в новых сеансах роль будет наследовать системную установку по умолчанию. Чтобы очистить все настройки роли, используйте указание RESET ALL. SET FROM CURRENT сохраняет текущее значение параметра сеанса в качестве значения для роли. Если указано IN DATABASE, параметр конфигурации устанавливается или удаляется только для заданной роли и базы данных.

Настройки переменных для роли применяются только при входе в систему; команды SET ROLE и SET SESSION AUTHORIZATION эти определения не обрабатывают.

Дополнительную информацию о допустимых именах и значениях параметров см. на справочной странице команды SET и в главе Конфигурация сервера.

Примечания

Для добавления новых ролей используйте команду CREATE ROLE, а для удаления — DROP ROLE.

ALTER ROLE не управляет членством роли, для этого применяются команды GRANT и REVOKE.

При указании с помощью этой команды незашифрованного пароля необходимо соблюдать осторожность. Пароль будет передан на сервер в виде открытого текста, а также может быть зарегистрирован в истории команд клиента или журнале сервера. Приложение psql содержит команду \password, которую можно использовать для изменения пароля роли без предоставления открытого текста пароля.

Кроме того, можно привязать сеанс по умолчанию к определенной базе данных, а не к роли; см. справочную страницу команды ALTER DATABASE. В случае возникновения конфликта параметры для базы данных и роли переопределяют параметры только для роли, которые в свою очередь переопределяют параметры только для базы данных.

Примеры

Изменение пароля роли:

ALTER ROLE davide WITH PASSWORD 'hu8jmn3';

Удаление пароля роли:

ALTER ROLE davide WITH PASSWORD NULL;

Изменение срока действия пароля указанием, что пароль должен перестать действовать в полдень 4 мая 2015 г. в часовом поясе UTC+1:

ALTER ROLE chris VALID UNTIL 'May 4 12:00:00 2015 +1';

Установка бесконечного срока действия пароля:

ALTER ROLE fred VALID UNTIL 'infinity';

Добавление роли прав на создание других ролей и новых баз данных:

ALTER ROLE miriam CREATEROLE CREATEDB;

Определение для роли нестандартного значения параметра maintenance_work_mem:

ALTER ROLE worker_bee SET maintenance_work_mem = 100000;

Определение для роли нестандартного значения параметра client_min_messages, специфичного для заданной базы данных:

ALTER ROLE fred IN DATABASE devel SET client_min_messages = DEBUG;

Совместимость

Команда ALTER ROLE является расширением QHB.

См. также

CREATE ROLE, DROP ROLE, ALTER DATABASE, SET

ALTER ROUTINE

ALTER ROUTINE — изменить определение подпрограммы

Синтаксис

ALTER ROUTINE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    действие [ ... ] [ RESTRICT ]
ALTER ROUTINE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    RENAME TO новое_имя
ALTER ROUTINE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER ROUTINE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    SET SCHEMA новая_схема
ALTER ROUTINE имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ]
    [ NO ] DEPENDS ON EXTENSION имя_расширения

Где действие может быть следующим:

    IMMUTABLE | STABLE | VOLATILE
    [ NOT ] LEAKPROOF
    [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
    PARALLEL { UNSAFE | RESTRICTED | SAFE }
    COST стоимость_выполнения
    ROWS строк_в_результате
    SET параметр_конфигурации { TO | = } { значение | DEFAULT }
    SET параметр_конфигурации FROM CURRENT
    RESET параметр_конфигурации
    RESET ALL

Описание

Команда ALTER ROUTINE изменяет определение подпрограммы, которая может быть агрегатной функцией, обычной функцией или процедурой. Описание параметров, дополнительные примеры и сведения см. на справочных страницах команд ALTER AGGREGATE, ALTER FUNCTION и ALTER PROCEDURE.

Примеры

Переименование подпрограммы foo для типа integer в foobar:

ALTER ROUTINE foo(integer) RENAME TO foobar;

Эта команда будет работать независимо от того, является ли foo агрегатом, функцией или процедурой.

Совместимость

Эта команда частично совместима с командой ALTER ROUTINE в стандарте SQL. Дополнительную информацию см. на справочных страницах команд ALTER FUNCTION и ALTER PROCEDURE. Возможность сослаться по имени подпрограммы на агрегатную функцию является расширением QHB.

См. также

ALTER AGGREGATE, ALTER FUNCTION, ALTER PROCEDURE, DROP ROUTINE
Обратите внимание, что команды CREATE ROUTINE нет.

ALTER RULE

ALTER RULE — изменить определение правила

Синтаксис

ALTER RULE имя ON имя_таблицы RENAME TO новое_имя

Описание

Команда ALTER RULE изменяет свойства существующего правила. В настоящее время единственным доступным действием является изменение имени правила.

Чтобы использовать команду ALTER RULE, нужно быть владельцем таблицы или представления, к которым применяется правило.

Параметры

имя

Имя изменяемого существующего правила.

имя_таблицы

Имя таблицы или представления, к которым применяется правило (может быть дополнено схемой).

новое_имя

Новое имя правила.

Примеры

Переименование существующего правила:

ALTER RULE notify_all ON emp RENAME TO notify_me;

Совместимость

Команда ALTER RULE является расширением QHB, как и вся система перезаписи запросов.

См. также

CREATE RULE, DROP RULE

ALTER SCHEMA

ALTER SCHEMA — изменить определение схемы

Синтаксис

ALTER SCHEMA имя RENAME TO новое_имя
ALTER SCHEMA имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

Описание

Команда ALTER SCHEMA изменяет определение схемы.

Чтобы использовать команду ALTER SCHEMA, нужно быть владельцем соответствующей схемы. Чтобы переименовать схему, необходимо помимо этого иметь право CREATE в базе данных. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в базы данных. (Обратите внимание, что суперпользователи имеют все эти права автоматически.)

Параметры

имя

Имя существующей схемы.

новое_имя

Новое имя схемы. Новое имя не может начинаться с pg_, поскольку такие имена зарезервированы для системных схем.

новый_владелец

Новый владелец схемы.

Совместимость

В стандарте SQL нет команды ALTER SCHEMA.

См. также

CREATE SCHEMA, DROP SCHEMA

ALTER SEQUENCE

ALTER SEQUENCE — изменить определение генератора последовательности

Синтаксис

ALTER SEQUENCE [ IF EXISTS ] имя
    [ AS тип_данных ]
    [ INCREMENT [ BY ] шаг ]
    [ MINVALUE мин_значение | NO MINVALUE ] [ MAXVALUE макс_значение | NO MAXVALUE ]
    [ START [ WITH ] начало ]
    [ RESTART [ [ WITH ] перезапуск ] ]
    [ CACHE кэш ] [ [ NO ] CYCLE ]
    [ OWNED BY { имя_таблицы.имя_столбца | NONE } ]
ALTER SEQUENCE [ IF EXISTS ] имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER SEQUENCE [ IF EXISTS ] имя RENAME TO новое_имя
ALTER SEQUENCE [ IF EXISTS ] имя SET SCHEMA новая_схема

Описание

Команда ALTER SEQUENCE изменяет параметры существующего генератора последовательности. Все параметры, не заданные в ALTER SEQUENCE, сохраняют свои предыдущие установки.

Чтобы использовать команду ALTER SEQUENCE, нужно быть владельцем соответствующей последовательности. Чтобы изменить схему последовательности, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме последовательности. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания последовательности. Однако суперпользователь все равно может сменить владельца любой последовательности.)

Параметры

имя

Имя изменяемой последовательности (может быть дополнено схемой).

IF EXISTS

Не считать ошибкой, если последовательности не существует. В этом случае выдается только замечание.

тип_данных

Необязательное предложение AS тип_данных меняет тип данных последовательности. Допустимые типы: smallint, integer и bigint.

Изменение типа данных автоматически изменяет минимальное и максимальное значения последовательности тогда и только тогда, когда предыдущие минимальное и максимальное значения были минимальным или максимальным значением старого типа данных (другими словами, если последовательность была создана с использованием NO MINVALUE или NO MAXVALUE, явно или неявно). В противном случае минимальное и максимальное значения сохраняются, если в рамках этой же команды не заданы новые значения. Если минимальное и максимальное значения не укладываются в новый тип данных, будет выдана ошибка.

шаг

Предложение INCREMENT BY шаг является необязательным. Положительное значение шага генерирует возрастающую последовательность, отрицательное — убывающую. Если не указано, будет сохранено старое значение шага.

мин_значение
NO MINVALUE

Необязательное предложение MINVALUE мин_значение определяет минимальное значение, которое может генерировать последовательность. Если указано NO MINVALUE, то для возрастающей последовательности по умолчанию будет использоваться 1, а для убывающей — минимальное число для ее типа данных. Если ничего не указано, будет сохранено текущее минимальное значение.

макс_значение
NO MAXVALUE

Необязательное предложение MAXVALUE макс_значение определяет максимальное значение для последовательности. Если указано NO MAXVALUE, то для возрастающей последовательности по умолчанию будет использоваться максимальное число для ее типа данных, а для убывающей — -1. Если ничего не указано, будет сохранено текущее максимальное значение.

начало

Необязательное предложение START WITH начало меняет записанное начальное значение последовательности. Это не влияет на текущее значение последовательности; оно просто задает значение, которое будут использовать будущие команды ALTER SEQUENCE RESTART.

перезапуск

Необязательное предложение RESTART [ WITH перезапуск ] меняет текущее значение последовательности. Это похоже на вызов функции setval с параметром is_called = false: указанное значение будет возвращено при следующем вызове функции nextval. Указание RESTART без значения перезапуска равнозначно установке начального значения, которое было записано при выполнении CREATE SEQUENCE или установлено последним при выполнении ALTER SEQUENCE START WITH.

В отличие от вызова функции setval, операция RESTART над последовательностью является транзакционной и блокирует параллельным транзакциям возможность получать числа из той же последовательности. Если этот режим работы не устраивает, следует воспользоваться функцией setval.

кэш

Предложение CACHE кэш позволяет заранее распределять и хранить в памяти числа последовательности для быстрого доступа. Минимальное значение равно 1 (за один раз может быть сгенерировано только одно значение, т. е. кэширования нет). Если не указано, будет сохранено старое значение кэша.

CYCLE

Необязательный параметр CYCLE можно использовать для того, чтобы зациклить последовательность при достижении макс_значения или мин_значения для возрастающей и убывающей последовательности соответственно. Если предел достигнут, то следующим сгенерированным числом будет мин_значение или макс_значение соответственно.

NO CYCLE

Если указано NO CYCLE, то после того как последовательность достигнет своего максимального значения, при каждом вызове функции nextval будет возвращаться ошибка. Если указания CYCLE и NO CYCLE отсутствуют, сохраняется предыдущее поведение зацикливания.

OWNED BY имя_таблицы.имя_столбца
OWNED BY NONE

Указание OWNED BY связывает последовательность с определенным столбцом таблицы, чтобы при удалении этого столбца (или всей таблицы) последовательность также автоматически удалялась. Это указание заменяет любую ранее установленную связь данной последовательности. Указанная таблица должна иметь того же владельца и находиться в той же схеме, что и последовательность. Указание OWNED BY NONE удаляет все существующие связи, делая последовательность «независимой».

новый_владелец

Имя пользователя нового владельца последовательности.

новое_имя

Новое имя последовательности.

новая_схема

Новая схема последовательности.

Примечания

Команда ALTER SEQUENCE не сразу повлияет на результаты, возвращаемые nextval в обслуживающих процессах, отличных от текущего, которые могли предварительно сгенерировать (кэшировать) значения последовательности. Эти процессы заметят изменение параметров генерации последовательности только после того, как израсходуют все кэшированные значения. Текущий обслуживающий процесс отреагирует на изменения немедленно.

ALTER SEQUENCE не влияет на статус currval для последовательности.

ALTER SEQUENCE блокирует параллельные вызовы nextval, currval, lastval и setval.

По историческим причинам ALTER TABLE также может работать с последовательностями, но все варианты ALTER TABLE, которым разрешено управлять последовательностями, равнозначны вышеперечисленным формам.

Примеры

Перезапуск последовательности serial с числа 105:

ALTER SEQUENCE serial RESTART WITH 105;

Совместимость

Команда ALTER SEQUENCE соответствует стандарту SQL, за исключением предложений AS, START WITH, OWNED BY, OWNER TO, RENAME TO и SET SCHEMA, которые являются расширениями QHB.

См. также

CREATE SEQUENCE, DROP SEQUENCE

ALTER SERVER

ALTER SERVER — изменить определение стороннего сервера

Синтаксис

ALTER SERVER имя [ VERSION 'новая_версия' ]
    [ OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ] ) ]
ALTER SERVER имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER SERVER имя RENAME TO новое_имя

Описание

Команда ALTER SERVER изменяет определение стороннего сервера. Первая форма изменяет строку версии сервера или его общие параметры (требуется по крайней мере одно предложение). Вторая форма меняет владельца сервера.

Чтобы изменить сервер, нужно быть владельцем этого сервера. Кроме того, для смены владельца нужно также быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право USAGE для обертки сторонних данных сервера. (Обратите внимание, что суперпользователи удовлетворяют всем этим критериям автоматически.)

Параметры

имя

Имя существующего сервера.

новая_версия

Новая версия сервера.

OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ] )

Формы, изменяющие параметры сервера. ADD, SET и DROP указывают действие, которое необходимо выполнить. Если явно не указана ни одна операция, то по умолчанию предполагается AD. Имена параметров должны быть уникальными; имена и значения проверяются с помощью библиотеки обертки сторонних данных сервера.

новый_владелец

Имя пользователя, который станет новым владельцем стороннего сервера.

новое_имя

Новое имя для стороннего сервера.

Примеры

Изменение свойств сервера foo, добавление параметров подключения:

ALTER SERVER foo OPTIONS (host 'foo', dbname 'foodb');

Изменение свойств сервера foo, смена версии, изменение параметра host:

ALTER SERVER foo VERSION '8.4' OPTIONS (SET host 'baz');

Совместимость

Команда ALTER SERVER соответствует стандарту ISO/IEC 9075-9 (SQL/MED). Формы OWNER TO и RENAME являются расширениями QHB.

См. также

CREATE SERVER, DROP SERVER

ALTER STATISTICS

ALTER STATISTICS — изменить определение объекта расширенной статистики

Синтаксис

ALTER STATISTICS имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER STATISTICS имя RENAME TO новое_имя
ALTER STATISTICS имя SET SCHEMA новая_схема
ALTER STATISTICS имя SET STATISTICS новый_целевой_показатель

Описание

Команда ALTER STATISTICS изменяет параметры существующего объекта расширенной статистики. Все параметры, не заданные в команде ALTER STATISTICS, сохраняют свои предыдущие значения.

Чтобы использовать команду ALTER STATISTICS, нужно быть владельцем соответствующего объекта статистики. Чтобы изменить схему объекта статистики, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме объекта статистики. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания объекта статистики. Однако суперпользователь все равно может сменить владельца любого объекта статистики.)

Параметры

имя

Имя изменяемого объекта статистики (может быть дополнено схемой).

новый_владелец

Имя пользователя нового владельца объекта статистики.

новое_имя

Новое имя объекта статистики.

новая_схема

Новая схема объекта статистики.

новый_целевой_показатель

Целевой показатель статистики для этого объекта статистики для последующих операций ANALYZE. Значение целевого показателя можно задать в диапазоне от 0 до 10000; также его можно установить равным -1 для возврата к использованию максимального целевого показателя статистики ссылочных столбцов (если таковые заданы) или к системному целевому показателю статистики по умолчанию (default_statistics_target). Более подробную информацию об использовании статистики планировщиком запросов QHB см. в разделе Статистика, используемая планировщиком.

Совместимость

В стандарте SQL нет команды ALTER STATISTICS.

См. также

CREATE STATISTICS, DROP STATISTICS

ALTER SUBSCRIPTION

ALTER SUBSCRIPTION — изменить определение подписки

Синтаксис

ALTER SUBSCRIPTION имя CONNECTION 'строка_подключения'
ALTER SUBSCRIPTION имя SET PUBLICATION имя_публикации [, ...] [ WITH ( параметр_публикации [= значение] [, ... ] ) ]
ALTER SUBSCRIPTION имя ADD PUBLICATION имя_публикации [, ...] [ WITH ( параметр_публикации [= значение] [, ... ] ) ]
ALTER SUBSCRIPTION имя DROP PUBLICATION имя_публикации [, ...] [ WITH ( параметр_публикации [= значение] [, ... ] ) ]
ALTER SUBSCRIPTION имя REFRESH PUBLICATION [ WITH ( параметр_обновления [= значение] [, ... ] ) ]
ALTER SUBSCRIPTION имя ENABLE
ALTER SUBSCRIPTION имя DISABLE
ALTER SUBSCRIPTION имя SET ( параметр_подписки [= значение] [, ... ] )
ALTER SUBSCRIPTION имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER SUBSCRIPTION имя RENAME TO новое_имя

Описание

Команда ALTER SUBSCRIPTION может менять большинство свойств подписки, которые можно указать в команде CREATE SUBSCRIPTION.

Чтобы использовать команду ALTER SUBSCRIPTION, нужно быть владельцем соответствующей подписки. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца. Новый владелец должен быть суперпользователем. (В настоящее время все владельцы подписок должны быть суперпользователями, поэтому на практике проверка владельца будет пропущена. Но в будущем это может измениться.)

При обновлении публикации мы удаляем отношения, которые больше не являются частью этой публикации, а также слоты синхронизации этих таблиц, если таковые имеются. Эти слоты нужно удалить, чтобы освободить ресурсы, выделенные для данной подписки на удаленном хосте. Если из-за сбоя в сети или какой-либо другой ошибки QHB не может удалить эти слоты, будет выдано сообщение об ошибке (ERROR). Чтобы выйти из такой ситуации, пользователь должен либо попробовать повторить эту операцию, либо отсоединить слот от подписки и удалить подписку, как описано в DROP SUBSCRIPTION.

Команды ALTER SUBSCRIPTION ... REFRESH PUBLICATION и ALTER SUBSCRIPTION ... {SET|ADD|DROP} PUBLICATION ... с параметром refresh, равным true, нельзя выполнить в блоке транзакции.

Параметры

имя

Имя подписки, свойства которой подлежат изменению.

CONNECTION 'строка_подключения'

Это предложение изменяет свойство соединения, первоначально заданное командой CREATE SUBSCRIPTION. Дополнительную информацию см. на справочной странице этой команды.

SET PUBLICATION имя_публикации
ADD PUBLICATION имя_публикации
DROP PUBLICATION имя_публикации

Изменяет список публикаций, на которые оформлена подписка. SET заменяет весь список публикаций на новый, ADD добавляет в него новые публикации, а DROP удаляет из него публикации. Дополнительную информацию см. на справочной странице команды CREATE SUBSCRIPTION. По умолчанию эта команда также будет действовать как REFRESH PUBLICATION.

В указании параметр_публикации задаются дополнительные свойства этой операции. Поддерживаемые параметры:

refresh (boolean)
При значении false команда не будет пытаться обновить информацию о таблицах. После этого REFRESH PUBLICATION следует выполнять отдельно. Значение по умолчанию равно true.

Кроме того, здесь можно задавать параметры обновления, перечисленные в описании REFRESH PUBLICATION, чтобы управлять неявной операцией обновления.

REFRESH PUBLICATION

Считывает недостающую информацию о таблицах с публикующего сервера. Это приводит к запуску репликации таблиц, добавленных в публикации, на которые оформлена подписка, после последнего вызова REFRESH PUBLICATION или CREATE SUBSCRIPTION.

В указании параметр_обновления задаются дополнительные свойства операции обновления. Поддерживаемые параметры:

copy_data (boolean)
Указывает, следует ли копировать существующие данные в публикациях, на которые оформляется подписка, после запуска репликации. Значение по умолчанию равно true. (Таблицы, на которые уже оформлена подписка, не копируются.)

ENABLE

Включает ранее выключенную подписку, запуская процесс логической репликации в конце транзакции.

DISABLE

Выключает запущенную подписку, останавливая процесс логической репликации в конце транзакции.

SET ( параметр_подписки [= значение] [, ... ] )

Это предложение изменяет параметры, первоначально заданные с помощью команды CREATE SUBSCRIPTION. Допустимые параметры: slot_name, synchronous_commit, binary и streaming.

новый_владелец

Имя пользователя нового владельца подписки.

новое_имя

Новое имя подписки.

Примеры

Изменение подписки путем подписывания на публикацию insert_only:

ALTER SUBSCRIPTION mysub SET PUBLICATION insert_only;

Выключение (остановка) подписки:

ALTER SUBSCRIPTION mysub DISABLE;

Совместимость

Команда ALTER SUBSCRIPTION является расширением QHB.

См. также

CREATE SUBSCRIPTION, DROP SUBSCRIPTION, CREATE PUBLICATION, ALTER PUBLICATION

ALTER SYSTEM

ALTER SYSTEM — изменить параметр конфигурации сервера

Синтаксис

ALTER SYSTEM SET параметр_конфигурации { TO | = } { значение | 'значение' | DEFAULT }

ALTER SYSTEM RESET параметр_конфигурации
ALTER SYSTEM RESET ALL

Описание

Команда ALTER SYSTEM используется для изменения параметров конфигурации сервера во всем кластере баз данных. Это может быть удобней, чем традиционный метод редактирования файла qhb.conf вручную. ALTER SYSTEM записывает заданное значение параметра в файл qhb.auto.conf, который считывается в дополнение к qhb.conf. При установке параметра в значение DEFAULT или применении варианта RESET соответствующая запись конфигурации удаляется из файла qhb.auto.conf. Указание RESET ALL удаляет все установленные таким способом параметры.

Значения, установленные с помощью ALTER SYSTEM, начнут действовать после следующей перезагрузки конфигурации сервера или после перезагрузки сервера (в случае, если параметры можно изменить только при запуске сервера). Дать команду на перезагрузку конфигурации сервера можно, вызвав функцию SQL pg_reload_conf(), выполнив qhb_ctl reload или отправив главному серверному процессу сигнал SIGHUP.

Использовать команду ALTER SYSTEM могут только суперпользователи. Кроме того, поскольку эта команда действует непосредственно на файловую систему и не может быть отменена, ее нельзя помещать в блок транзакции или в функцию.

Параметры

параметр_конфигурации

Имя настраиваемого параметра конфигурации. Доступные параметры описаны в главе Конфигурация сервера.

значение

Новое значение параметра. Значения могут быть указаны в виде строковых констант, идентификаторов, чисел или разделенных запятыми списков таковых, в зависимости от конкретного параметра. Чтобы удалить параметр и его значение из qhb.auto.conf, можно написать DEFAULT.

Примечания

Эту команду нельзя использовать для установки каталога данных (data_directory) или параметров, которые не допустимы в qhb.conf (например, Предустановленные параметры).

Другие способы установки параметров см. в разделе Настройка параметров.

Примеры

Установка уровня ведения журнала транзакций (wal_level):

ALTER SYSTEM SET wal_level = replica;

Отмена этого изменения, восстановление значения, заданного в qhb.conf:

ALTER SYSTEM RESET wal_level;

Совместимость

Команда ALTER SYSTEM является расширением QHB.

См. также

SET, SHOW

ALTER TABLE

ALTER TABLE — изменить определение таблицы

Синтаксис

ALTER TABLE [ IF EXISTS ] [ ONLY ] имя [ * ]
    действие [, ... ]
ALTER TABLE [ IF EXISTS ] [ ONLY ] имя [ * ]
    RENAME [ COLUMN ] имя_столбца TO новое_имя_столбца
ALTER TABLE [ IF EXISTS ] [ ONLY ] имя [ * ]
    RENAME CONSTRAINT имя_ограничения TO имя_нового_ограничения
ALTER TABLE [ IF EXISTS ] имя
    RENAME TO новое_имя
ALTER TABLE [ IF EXISTS ] имя
    SET SCHEMA новая_схема
ALTER TABLE ALL IN TABLESPACE имя [ OWNED BY имя_роли [, ... ] ]
    SET TABLESPACE новое_табличное_пространство [ NOWAIT ]
ALTER TABLE [ IF EXISTS ] имя
    ATTACH PARTITION имя_партиции { FOR VALUES указание_границ_партиции | DEFAULT }
ALTER TABLE [ IF EXISTS ] имя
    DETACH PARTITION имя_партиции [ CONCURRENTLY | FINALIZE ]

где действием может быть:

    ADD [ COLUMN ] [ IF NOT EXISTS ] имя_столбца тип_данных [ COLLATE правило_сортировки ] [ ограничение_столбца [ ... ] ]
    DROP [ COLUMN ] [ IF EXISTS ] имя_столбца [ RESTRICT | CASCADE ]
    ALTER [ COLUMN ] имя_столбца [ SET DATA ] TYPE тип_данных [ COLLATE правило_сортировки ] [ USING выражение ]
    ALTER [ COLUMN ] имя_столбца SET DEFAULT выражение
    ALTER [ COLUMN ] имя_столбца DROP DEFAULT
    ALTER [ COLUMN ] имя_столбца { SET | DROP } NOT NULL
    ALTER [ COLUMN ] имя_столбца DROP EXPRESSION [ IF EXISTS ]
    ALTER [ COLUMN ] имя_столбца ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( параметры_последовательности ) ]
    ALTER [ COLUMN ] имя_столбца { SET GENERATED { ALWAYS | BY DEFAULT } | SET параметр_последовательности | RESTART [ [ WITH ] перезапуск ] } [...]
    ALTER [ COLUMN ] имя_столбца DROP IDENTITY [ IF EXISTS ]
    ALTER [ COLUMN ] имя_столбца SET STATISTICS целое_число
    ALTER [ COLUMN ] имя_столбца SET ( атрибут = значение [, ... ] )
    ALTER [ COLUMN ] имя_столбца RESET ( атрибут [, ... ] )
    ALTER [ COLUMN ] имя_столбца SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
    ALTER [ COLUMN ] имя_столбца SET COMPRESSION метод_сжатия
    ADD ограничение_таблицы [ NOT VALID ]
    ADD ограничение_таблицы_по_индексу
    ALTER CONSTRAINT имя_ограничения [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
    VALIDATE CONSTRAINT имя_ограничения
    DROP CONSTRAINT [ IF EXISTS ]  имя_ограничения [ RESTRICT | CASCADE ]
    DISABLE TRIGGER [ имя_триггера | ALL | USER ]
    ENABLE TRIGGER [ имя_триггера | ALL | USER ]
    ENABLE REPLICA TRIGGER имя_триггера
    ENABLE ALWAYS TRIGGER имя_триггера
    DISABLE RULE имя_правила_перезаписи
    ENABLE RULE имя_правила_перезаписи
    ENABLE REPLICA RULE имя_правила_перезаписи
    ENABLE ALWAYS RULE имя_правила_перезаписи
    DISABLE ROW LEVEL SECURITY
    ENABLE ROW LEVEL SECURITY
    FORCE ROW LEVEL SECURITY
    NO FORCE ROW LEVEL SECURITY
    CLUSTER ON имя_индекса
    SET WITHOUT CLUSTER
    SET WITHOUT OIDS
    SET TABLESPACE новое_табличное_пространство
    SET { LOGGED | UNLOGGED }
    SET ( параметр_хранения [= значение] [, ... ] )
    RESET ( параметр_хранения [, ... ] )
    INHERIT родительская_таблица
    NO INHERIT родительская_таблица
    OF имя_типа
    NOT OF
    OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
    REPLICA IDENTITY { DEFAULT | USING INDEX имя_индекса | FULL | NOTHING }

и указанием_границ_партиции может быть:

IN ( выражение_границ_партиции [, ...] ) |
FROM ( { выражение_границ_партиции | MINVALUE | MAXVALUE } [, ...] )
  TO ( { выражение_границ_партиции | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS числовая_константа, REMAINDER числовая_константа )

и ограничением_столбца может быть:

[ CONSTRAINT имя_ограничения ]
{ NOT NULL |
  NULL |
  CHECK ( выражение ) [ NO INHERIT ] |
  DEFAULT выражение_по_умолчанию |
  GENERATED ALWAYS AS ( генерирующее_выражение ) STORED |
  GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( параметры_последовательности ) ] |
  UNIQUE параметры_индекса |
  PRIMARY KEY параметры_индекса |
  REFERENCES ссылочная_таблица [ ( ссылочный_столбец ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
    [ ON DELETE ссылочное_действие ] [ ON UPDATE ссылочное_действие ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]

и ограничением_таблицы может быть:

[ CONSTRAINT имя_ограничения ]
{ CHECK ( выражение ) [ NO INHERIT ] |
  UNIQUE ( имя_столбца [, ... ] ) параметры_индекса |
  PRIMARY KEY ( имя_столбца [, ... ] ) параметры_индекса |
  EXCLUDE [ USING индексный_метод ] ( элемент_исключения WITH оператор [, ... ] ) параметры_индекса [ WHERE ( предикат ) ] |
  FOREIGN KEY ( имя_столбца [, ... ] ) REFERENCES ссылочная_таблица [ ( ссылочный_столбец [, ... ] ) ]
    [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE ссылочное_действие ] [ ON UPDATE ссылочное_действие ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]

и ограничением_таблицы_по_индексу может быть:

    [ CONSTRAINT имя_ограничения ]
    { UNIQUE | PRIMARY KEY } USING INDEX имя_индекса
    [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]

параметрами_индекса в ограничениях UNIQUE, PRIMARY KEY и EXCLUDE могут быть:

[ INCLUDE ( имя_столбца [, ... ] ) ]
[ WITH ( параметр_хранения [= значение] [, ... ] ) ]
[ USING INDEX TABLESPACE имя_табличного_пространства ]

элементом_исключения в ограничении EXCLUDE может быть:

{ имя_столбца | ( выражение ) } [ класс_операторов ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]

Описание

Команда ALTER TABLE изменяет определение существующей таблицы. Ниже описано несколько форм команды. Обратите внимание, что требуемый уровень блокировки у каждой формы может быть свой. Если явно не отмечено другое, требуется блокировка ACCESS EXCLUSIVE. При указании нескольких подкоманд будет запрашиваться самая сильная блокировка из требуемых ими.

ADD COLUMN [ IF NOT EXISTS ]

Эта форма добавляет в таблицу новый столбец, используя тот же синтаксис, что и команда CREATE TABLE. Если указано IF NOT EXISTS и столбец с таким именем уже существует, это не будет ошибкой.

DROP COLUMN [ IF EXISTS ]

Эта форма удаляет столбец из таблицы. Связанные со столбцом индексы и ограничения таблицы также будут автоматически удалены. Кроме того, будет удалена обращающаяся к удаляемому столбцу многовариантная статистика, если после его удаления в этой статистике останутся данные только одного столбца. Чтобы удалить какие-либо зависящие от этого столбца объекты вне таблицы, например ссылки на внешние ключи или представления, необходимо добавить в команду указание CASCADE. Если в команде указано IF EXISTS и удаляемый столбец не существует, это не считается ошибкой, вместо этого просто выдается замечание.

SET DATA TYPE

Эта форма изменяет тип столбца таблицы. Включающие данный столбец индексы и простые табличные ограничения будут автоматически преобразованы для использования нового типа столбца путем повторного синтаксического анализа изначально определяющего их выражения. Необязательное предложение OLLATE задает правило сортировки для нового столбца; если оно опущено, для нового типа столбца устанавливается правило сортировки по умолчанию. Необязательное предложение USING определяет, как вычислить новое значение столбца из старого; если оно опущено, по умолчанию преобразование выполняется как присваивание значения старого типа данных новому. Предложение USING становится обязательным, если отсутствует неявное приведение или присвоение от старого к новому типу.

SET/DROP DEFAULT

Эти формы устанавливают или удаляют для столбца значение по умолчанию (где удаление равнозначно указанию NULL в качестве значения по умолчанию). Новое значение по умолчанию применяется только в последующих командах INSERT или UPDATE, не вызывая изменений уже существующих в таблице строк.

SET/DROP NOT NULL

Эти формы определяют, будет ли столбец принимать значения NULL или нет.

Указание SET NOT NULL можно применять к столбцу, только если ни одна из записей в таблице не содержит значение NULL. Как правило, это проверяется во время выполнения команды ALTER TABLE и требует сканирования всей таблицы, однако в случае наличия у столбца ограничения CHECK, которое гарантирует, что значения NULL отсутствуют, сканирование таблицы пропускается.

Если таблица является партицией, нельзя выполнить DROP NOT NULL, если столбец помечен NOT NULL в родительской таблице. Для снятия ограничения NOT NULL со всех партиций нужно выполнить DROP NOT NULL у родительской таблицы. При этом, если у родительской таблицы отсутствует ограничение NOT NULL, при желании его все же можно добавить к отдельным партициям; то есть потомки могут запретить значения NULL, даже если родитель разрешает их, но не наоборот.

DROP EXPRESSION [ IF EXISTS ]

Эта форма превращает хранимый генерируемый столбец в обычный. Имеющиеся в столбцах данные сохраняются, но будущие изменения больше не будут вноситься генерирующим выражением.

Если указано DROP EXPRESSION IF EXISTS и столбец не является хранимым генерируемым столбцом, ошибка не возникает. В этом случае выдается только замечание.

ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY
SET GENERATED { ALWAYS | BY DEFAULT }
DROP IDENTITY [ IF EXISTS ]

Эти формы определяют, является ли столбец столбцом идентификации, или меняют атрибут генерирования уже существующего столбца идентификации. Дополнительную информацию см. на справочной странице команды CREATE TABLE. Как и SET DEFAULT, эти формы меняют поведение только последующих команд INSERT или UPDATE и не вызывают изменений уже имеющихся в таблице строк.

Если указывается DROP IDENTITY IF EXISTS и столбец не является столбцом идентификации, ошибка не возникает. В этом случае выдается только замечание.

SET параметр_последовательности
RESTART

Эти формы меняют последовательность, лежащую в основе уже существующего столбца идентификации. Свойство параметр_последовательности поддерживается командой ALTER SEQUENCE, например INCREMENT BY.

SET STATISTICS

Эта форма задает целевой показатель статистики по столбцу для последующих операций ANALYZE. Значение целевого показателя можно задать в диапазоне от 0 до 10000; также его можно установить равным -1 для возврата к использованию системного целевого показателя статистики по умолчанию (default_statistics_target). Более подробную информацию об использовании статистики планировщиком запросов QHB см. в разделе Статистика, используемая планировщиком.

SET STATISTICS запрашивает блокировку SHARE UPDATE EXCLUSIVE.

SET ( атрибут = значение [, ... ] )
RESET ( атрибут [, ... ] )

Эта форма устанавливает или сбрасывает параметры для каждого атрибута. В настоящее время единственными параметрами атрибутов являются n_distinct и n_distinct_inherited, которые переопределяют оценки количества уникальных значений, производимые последующими операциями ANALYZE. n_distinct влияет на статистику для самой таблицы, тогда как n_distinct_inherited влияет на статистику, собранную и для таблицы, и для ее потомков. Если установлено положительное значение, ANALYZE будет считать, что столбец содержит точно указанное количество уникальных значений, отличных от NULL. Если установлено отрицательное значение, которое должно быть больше или равно -1, ANALYZE будет считать, что количество уникальных значений, отличных от NULL, в столбце линейно зависит от размера таблицы; точное число будет вычислено путем умножения расчетного размера таблицы на абсолютное значение данного числа. Например, значение -1 подразумевает, что все значения в столбце уникальны, а значение -0,5 — что каждое значение повторяется в среднем дважды. Это может быть полезно, когда размер таблицы изменяется с течением времени, так как умножение на количество строк в таблице выполняется только во время планирования запроса. При указании значения 0 количество уникальных значений оценивается как обычно. Дополнительную информацию об использовании статистики планировщиком запросов QHB см. в разделе Статистика, используемая планировщиком.

Для изменения параметров атрибутов запрашивается блокировка SHARE UPDATE EXCLUSIVE.

SET STORAGE

Эта форма задает режим хранения для столбца. Этот параметр определяет, будет ли этот столбец храниться внутри таблицы или в отдельной таблице TOAST, а также следует ли сжимать данные. Режим PLAIN необходимо использовать для значений фиксированной длины, таких как integer; это вариант хранения внутри, без сжатия. Режим MAIN предназначен для хранения внутри, но сжимаемых данных. Режим EXTERNAL предназначен для внешних несжатых данных, а EXTENDED — для внешних сжатых данных. EXTENDED используется по умолчанию для большинства типов данных, которые поддерживают хранение в режиме, отличном от PLAIN. Применение EXTERNAL позволяет ускорить операции с подстроками на очень больших значениях text и bytea за счет проигрыша в объеме хранилища. Обратите внимание, что сам по себе SET STORAGE ничего не меняет в таблице, а лишь задает стратегию, которая будет реализована при последующих изменениях таблицы. Дополнительную информацию см. в разделе TOAST.

SET COMPRESSION метод_сжатия

Эта форма устанавливает метод сжатия для столбца, определяя, как будут сжиматься добавляемые в будущем значения (если режим хранения вообще допускает сжатие). Это не вызывает перезапись таблицы, поэтому существующие данные могут оставаться сжатыми другими методами. Если таблица восстанавливается утилитой pg_restore, то все значения перезаписываются с применением сконфигурированного метода сжатия. Однако когда данные добавляются из другого отношения (например командой INSERT ... SELECT), значения из исходной таблицы не обязательно будут распакованы, поэтому все ранее сжатые данные могут сохраниться с уже существующим методом сжатия, вместо того чтобы пересжиматься методом сжатия, заданным для целевого столбца. Поддерживаемые методы сжатия: pglz и lz4. (Метод lz4 доступен, только если при сборке QHB был использован ключ --with-lz4.) Кроме того, метод_сжатия может принимать значение default, который выбирает поведение по умолчанию, заключающееся в определении применяемого метода сжатия путем проверки значения параметра default_toast_compression во время добавления данных.

ADD ограничение_таблицы [ NOT VALID ]

Эта форма добавляет новое ограничение к таблице, используя тот же синтаксис, что и CREATE TABLE, плюс параметр NOT VALID, который в настоящее время разрешен только для ограничений внешнего ключа и проверочных ограничений.

Обычно эта форма вызывает сканирование таблицы с целью проверки, что все существующие строки в таблице удовлетворяют новому ограничению. Но если используется параметр NOT VALID, то это потенциально длительное сканирование пропускается. Ограничение по-прежнему будет применяться к последующим добавлениям или изменениям (то есть они завершатся ошибкой, если при наличии внешнего ключа в ссылочной таблице нет соответствующей строки или если новая строка не соответствует указанному условию проверки). Но база данных не будет считать, что ограничение выполняется для всех строк в таблице, пока оно не будет проверено с помощью VALIDATE CONSTRAIN. Дополнительную информацию об использовании указания NOT VALID см. ниже, в Примечаниях.

Хотя большинству форм ADD ограничение_таблицы нужна блокировка ACCESS EXCLUSIVE, вариант ADD FOREIGN KEY требует только блокировку SHARE ROW EXCLUSIVE. Обратите внимание, что ADD FOREIGN KEY запрашивает эту блокировку как в целевой таблице, так и в таблице, на которую данное ограничение ссылается.

С ограничениями уникальности и первичного ключа, которые добавляются в партиционированные таблицы, связаны дополнительные требования; см. CREATE TABLE. Кроме того, в настоящее время ограничения внешнего ключа для партиционированных таблиц не могут объявляться как непроверенные (NOT VALID).

ADD ограничение_таблицы_по_индексу

Эта форма добавляет в таблицу новое ограничение PRIMARY KEY или UNIQUE, основанное на существующем уникальном индексе. Все столбцы индекса будут включены в ограничение.

Индекс не может быть частичным и включать столбцы-выражения. Кроме того, это должен быть индекс В-дерево с порядком сортировки по умолчанию. Эти ограничения гарантируют, что данный индекс равнозначен тому, который был бы построен обычной командой ADD PRIMARY KEY или ADD UNIQUE.

Если указывается PRIMARY KEY и столбцы индекса еще не помечены как NOT NULL, то эта команда попытается выполнить ALTER COLUMN SET NOT NULL для каждого такого столбца. Это потребует полного сканирования таблицы с целью проверки, что столбцы не содержат NULL. Во всех остальных случаях это быстрая операция.

Если имя ограничения указано, то индекс будет переименован, чтобы соответствовать имени ограничения. В противном случае ограничение будет называться так же, как индекс.

После выполнения этой команды индекс становится «принадлежащим» ограничению, аналогично тому, как если бы этот индекс был построен обычной командой ADD PRIMARY KEY или ADD UNIQUE. В частности, удаление ограничения приведет к удалению индекса.

В настоящее время эта форма не поддерживается для партиционированных таблиц.

Примечание
Добавление ограничения с помощью существующего индекса может быть полезно в ситуациях, когда необходимо добавить новое ограничение, не блокируя изменения в таблице в течение длительного времени. Для этого можно создать индекс, используя CREATE INDEX CONCURRENTLY, а затем задействовать его как полноценное ограничение, используя эту запись. См. пример ниже.

ALTER CONSTRAINT

Эта форма меняет атрибуты созданного ранее ограничения. В настоящее время изменять можно только ограничения внешнего ключа.

VALIDATE CONSTRAINT

Эта форма проверяет ограничение внешнего ключа или проверочное ограничение, которое ранее было создано как NOT VALID, путем сканирования всей таблицы, чтобы убедиться в отсутствии строк, для которых ограничение не выполняется. Если ограничение уже помечено как проверенное, ничего не происходит. (См. параграф Примечания ниже, где объясняется полезность этой команды.)

DROP CONSTRAINT [ IF EXISTS ]

Эта форма удаляет указанное ограничение на таблицу вместе с лежащим в его основе индексом. Если указано IF EXISTS и ограничение не существует, это не считается ошибкой. В этом случае выдается только замечание.

DISABLE/ENABLE [ REPLICA | ALWAYS ] TRIGGER

Эти формы настраивают срабатывание принадлежащего таблице триггера (или триггеров). Выключенный триггер сохраняется в системе, но не выполняется, когда происходит его запускающее событие. Для отложенного триггера состояние включения проверяется при наступлении события, а не при фактическом выполнении триггерной функции. Можно выключить или включить один триггер, указанный по имени, или все триггеры в таблице, или только пользовательские триггеры (этот параметр исключает внутрисистемные триггеры ограничений, например используемые для реализации ограничений внешнего ключа или отложенных ограничений уникальности и исключения). Выключение или включение сгенерированных внутрисистемных триггеров ограничений требует прав суперпользователя; выключение следует делать с осторожностью, так как, разумеется, при невыполнении триггеров нельзя гарантировать целостность ограничения.

На механизм срабатывания триггера также влияет переменная конфигурации session_replication_role. Включенные без дополнительных указаний триггеры (по умолчанию) будут срабатывать, когда ролью репликации является «origin» (по умолчанию) или «local». Триггеры, настроенные с указанием ENABLE REPLICA, будут срабатывать только в том случае, если сеанс находится в режиме «replica», а триггеры, настроенные с указанием ENABLE ALWAYS, будут запущены независимо от текущего режима репликации.

Действие этого механизма заключается в том, что в конфигурации по умолчанию триггеры не срабатывают на репликах. Это полезно, так как если триггер используется в исходной базе для распространения данных между таблицами, то система репликации также будет реплицировать и распределенные данные, и триггер не должен срабатывать во второй раз на реплике, потому что это приведет к дублированию. Однако если триггер используется для другой цели, например для создания внешних оповещений, то его можно задать равным ENABLE ALWAYS, чтобы он срабатывал и в репликах.

Эта команда запрашивает блокировку SHARE ROW EXCLUSIVE.

DISABLE/ENABLE [ REPLICA | ALWAYS ] RULE

Эти формы настраивают запуск принадлежащих таблице правил перезаписи. Выключенное правило сохраняется в системе, но не применяется во время перезаписи запроса. Семантика является такой же, как для выключенных/включенных триггеров. Эта конфигурация игнорируется для правил ON SELECT, которые всегда применяются для поддержания работы представлений, даже в сеансах, исполняющих не основную роль репликации.

Механизм запуска правила также зависит от переменной конфигурации session_replication_role аналогичной вышеописанным триггерам.

DISABLE/ENABLE ROW LEVEL SECURITY

Эти формы управляют применением принадлежащих таблице политик защиты строк. Если защита включена и для таблицы не определены политики, то применяется политика по умолчанию — запретить доступ. Обратите внимание, что политики могут существовать для таблицы, даже если защита на уровне строк выключена — в этом случае политики не применяются, и их ограничения игнорируются. См. также CREATE POLICY.

NO FORCE/FORCE ROW LEVEL SECURITY

Эти формы управляют применением принадлежащих таблице политик защиты строк, когда пользователь является владельцем таблицы. Если этот параметр включен, политики защиты на уровне строк будут применяться, когда пользователь является владельцем таблицы. Если этот параметр выключен (по умолчанию), то защита на уровне строк не будет применяться, если пользователь является владельцем таблицы. См. также CREATE POLICY.

CLUSTER ON

Эта форма выбирает индекс по умолчанию для будущих операций кластера CLUSTER. На самом деле это не перекластеризует таблицу.

Для изменения параметров кластеризации запрашивается блокировка SHARE UPDATE EXCLUSIVE.

SET WITHOUT CLUSTER

Эта форма удаляет из таблицы самое последнее используемое указание индекса в CLUSTER. Это влияет на будущие операции кластера, для которых не будет задан индекс.

Для изменения параметров кластеризации запрашивается блокировка SHARE UPDATE EXCLUSIVE.

SET WITHOUT OIDS

Обеспечивающий обратную совместимость синтаксис удаления системного столбца oid. Так как добавить системные столбцы oid теперь невозможно, это указание не действует.

SET TABLESPACE

Эта форма изменяет табличное пространство таблицы на указанное табличное пространство и перемещает связанные с таблицей файлы данных в новое табличное пространство. Индексы таблицы, если таковые имеются, не перемещаются, но их можно перемещать отдельно дополнительными командами SET TABLESPACE. При применении к партиционированной таблице ничего не перемещается, но все партиции, созданные позднее с помощью CREATE TABLE PARTITION OF, будут использовать это табличное пространство, если только оно не будет переопределено предложением TABLESPACE.

Все таблицы в текущей базе данных в табличном пространстве можно переместить с помощью формы ALL IN TABLESPACE, которая заблокирует все таблицы, которые должны быть перемещены в первую очередь, а затем переместит каждую из них. Эта форма также поддерживает указание OWNED BY, с которым перемещаются только таблицы указанных владельцев. Если указан параметр NOWAIT, то команда завершится ошибкой, если не сможет сразу получить все необходимые блокировки. Обратите внимание, что системные каталоги этой командой не перемещаются; если требуется переместить их, следует использовать ALTER DATABASE или явные вызовы ALTER TABLE. Отношения information_schema не считаются частью системных каталогов и подлежат перемещению. См. также CREATE TABLESPACE.

SET { LOGGED | UNLOGGED }

Эта форма превращает таблицу из нежурналируемой в журналируемую и наоборот (см. UNLOGGED). Ее нельзя применить к временной таблице.

SET ( параметр_хранения = значение [, ... ] )

Эта форма изменяет один или несколько параметров хранения таблицы. Подробную информацию о доступных параметрах см. в параграфе Параметры хранения на справочной странице команды CREATE TABLE. Обратите внимание, что содержимое таблицы этой командой не будет изменено немедленно; в зависимости от параметра для получения желаемого эффекта может потребоваться переписать таблицы. Это можно сделать с помощью команд VACUUM FULL, CLUSTER или одной из форм ALTER TABLE, которая принудительно перезаписывает таблицу. Изменения параметров, связанных с планировщиком, вступят в силу при следующей блокировке таблицы, поэтому запросы, выполняемые в данный момент, затронуты не будут.

Данная форма запрашивает блокировку SHARE UPDATE EXCLUSIVE для изменения параметров хранения, связанных с коэффициентом заполнения, TOAST и автоочисткой, а также параметра планировщика parallel_workers.

RESET ( параметр_хранения [, ... ] )

Эта форма сбрасывает один или несколько параметров хранения до значений по умолчанию. Как и в случае с SET, для полного обновления таблицы может потребоваться ее перезапись.

INHERIT таблица_родитель

Эта форма добавляет целевую таблицу в качестве нового потомка указанной родительской таблицы. Впоследствии запросы к родительской таблице будут включать записи целевой таблицы. Чтобы стать потомком, целевая таблица уже должна содержать те же столбцы, что и родительская (она также может содержать и дополнительные столбцы). Данные столбцы должны иметь соответствующие типы данных, и если они имеют ограничения NOT NULL в родителе, то должны иметь их и в потомке.

Кроме того, в дочерней таблице должны присутствовать все ограничения CHECK родительской таблицы, за исключением помеченных как ненаследуемые (т. е. созданных с помощью команды ALTER TABLE ... ADD CONSTRAINT ... NO INHERIT), которые игнорируются; все соответствующие ограничения дочерней таблицы не должны быть помечены как ненаследуемые. В настоящий момент ограничения UNIQUE, PRIMARY KEY и FOREIGN KE не учитываются, но в будущем это может измениться.

NO INHERIT таблица_родитель

Эта форма удаляет целевую таблицу из списка потомков указанной родительской таблицы. Запросы к родительской таблице больше не будут включать записи, полученные из целевой таблицы.

OF имя_типа

Эта форма связывает таблицу с составным типом, как если бы таблица была сформирована командой CREATE TABLE OF. Список имен и типов столбцов таблицы должен точно соответствовать списку составного типа. Таблица не должна быть потомком другой таблицы. Эти ограничения гарантируют, что CREATE TABLE OF позволит создать таблицу с аналогичным определением.

NOT OF

Эта форма отделяет типизированную таблицу от ее типа.

OWNER TO

Эта форма меняет владельца таблицы, последовательности, представления, материализованного представления или сторонней таблицы на указанного пользователя.

REPLICA IDENTITY

Эта форма изменяет информацию, которая записывается в журнал упреждающей записи для идентификации изменяемых или удаляемых строк. В большинстве случаев старые значения всех столбцов протоколируются, только если они отличаются от новых значений; однако если старое значение хранится отдельно, оно протоколируется всегда, независимо от того, было ли оно изменено. Этот параметр действует только при использовании логической репликации.

DEFAULT
Записывает старые значения столбцов первичного ключа, если таковые имеются. Это режим по умолчанию для несистемных таблиц.

USING INDEX имя_индекса
Записывает старые значения столбцов, покрываемых именованным индексом, который должен быть уникальным, не частичным, не отложенным и должен включать только столбцы, помеченные как NOT NULL. При удалении этого индекса поведение будет аналогично режиму NOTHING.

FULL
Записывает старые значения всех столбцов в данной строке.

NOTHING
Не записывает никакой информации о старой строке. Это режим по умолчанию для системных таблиц.

RENAME

Формы RENAME изменяют имя таблицы (или индекса, последовательности, представления, материализованного представления или сторонней таблицы), имя отдельного столбца в таблице или имя ограничения таблицы. При переименовании ограничения, имеющего нижележащий индекс, этот индекс также переименовывается. При этом на хранимые данные это не влияет.

SET SCHEMA

Эта форма перемещает таблицу в другую схему. Связанные индексы, ограничения и последовательности, принадлежащие столбцам таблицы, также перемещаются.

ATTACH PARTITION имя_партиции { FOR VALUES указание_границ_партиции | DEFAULT }

Эта форма присоединяет существующую таблицу (которая сама может быть партиционирована) в качестве партиции к целевой таблице. Таблица может быть присоединена в качестве партиции для конкретных значений с помощью FOR VALUES или в качестве партиции по умолчанию с помощью DEFAULT. Для каждого индекса в целевой таблице будет создан соответствующий индекс в присоединенной таблице, или, если аналогичный индекс уже существует, он будет присоединен к индексу целевой таблицы, как при выполнении команды ALTER INDEX ATTACH PARTITION. Обратите внимание, что если существующая таблица является сторонней, то в настоящее время ее нельзя присоединить в качестве партиции целевой таблицы, если в целевой таблице есть индексы UNIQUE (уникальные). (См. также CREATE FOREIGN TABLE.) Для каждого пользовательского триггера уровня строк, существующего в целевой таблице, будет создан соответствующий триггер в присоединенной таблице.

Для партиции, добавляемой с FOR VALUES, используется то же синтаксис указание_границ_партиции, что и в CREATE TABLE. Указание границы партиции должно соответствовать стратегии партиционирования и ключу разбиения целевой таблицы. Присоединяемая таблица должна содержать те же столбцы, что и целевая, и никаких других; более того, типы столбцов тоже должны совпадать. Кроме того, присоединяемая таблица должна иметь все ограничения NOT NULL и CHECK целевой таблицы. В настоящий момент ограничения FOREIGN KEY не рассматриваются. Если ограничения UNIQUE и PRIMARY KEY еще не существуют в партиции, они будут созданы в ней из родительской таблицы. Если какое-либо из ограничений CHECK присоединяемой таблицы помечено как NO INHERIT, команда выдаст ошибку; такие ограничения должны быть пересозданы без предложения NO INHERIT.

Если новая партиция является обычной таблицей, выполняется ее полное сканирование, чтобы убедиться, что существующие в этой таблице строки не нарушают ограничение партиции. Этого сканирования можно избежать, перед выполнением этой команды добавив в таблицу допустимое ограничение CHECK, позволяющее использовать только строки, удовлетворяющие требуемому ограничению партиции. Ограничение CHECK будет использоваться для определения того, что таблицу не нужно сканировать с целью проверки ограничения партиции. Однако это не работает, если какой-либо из ключей разбиения является выражением и партиция не принимает значения NULL. При присоединении партиции, не принимающей значения NULL, по списку также добавьте ограничение NOT NULL в столбец ключа разбиения, если это не выражение.

Если новая партиция является сторонней таблицей, то проверка того, подчиняются ли все строки в сторонней таблице ограничению партиции, не выполняется. (См. описание ограничений сторонней таблицы на справочной странице команды CREATE FOREIGN TABLE.)

Если таблица имеет партицию по умолчанию, добавление новой партиции изменяет ограничение партиции по умолчанию. Партиция по умолчанию не может содержать строк, которые может понадобиться переместить в новую партицию, поэтому она будет просканирована и проверена на предмет их отсутствия. Этого сканирования, как и сканирования новой партиции, можно избежать, если имеется подходящее ограничение CHECK. Кроме того, как и сканирование новой партиции, оно всегда пропускается, когда партиция по умолчанию является сторонней таблицей.

Для присоединения партиции наряду с блокировками ACCESS EXCLUSIVE в присоединяемых таблицах и партиции по умолчанию (при наличии) запрашивается блокировка SHARE UPDATE EXCLUSIVE в родительской таблице.

Дополнительные блокировки должны также устанавливаться во всех вложенных партициях, если присоединяемая таблица либо партиция по умолчанию сама является партиционированной. Блокировки вложенных партиций можно избежать, добавив ограничение CHECK, как описано в подразделе Обслуживание партиций.

DETACH PARTITION имя_партиции [ CONCURRENTLY | FINALIZE ]

Эта форма отсоединяет указанную партицию целевой таблицы. Отсоединенная партиция продолжает существовать как отдельная таблица, но больше не имеет никаких связей с таблицей, от которой была отсоединена. Все индексы, присоединенные к индексам целевой таблицы, отсоединяются. Все триггеры, созданные как клоны триггеров целевой таблицы, удаляются. Все таблицы, ссылающиеся на эту партиционированную таблицу в ограничениях внешнего ключа, получают блокировку SHARE.

Если указано CONCURRENTLY, эта операция выполняется с пониженным уровнем блокировок во избежание блокирования других сеансов, которые могут обращаться к этой партиционированной таблице. В этом режиме внутренне используются две транзакции. Во время первой транзакции в родительской таблице и партиции устанавливается блокировка SHARE UPDATE EXCLUSIVE, а партиция помечается как отсоединяемая; в этот момент транзакция фиксируется, и все остальные транзакции, использующие партиционированную таблицу, ждут этого. Как только все эти транзакции завершаются, вторая транзакция получает блокировку SHARE UPDATE EXCLUSIVE для партиционированной таблицы и ACCESS EXCLUSIVE для партиции, после чего процесс отсоединения завершается. Режим CONCURRENTLY нельзя запускать в блоке транзакции, а кроме того, он не допускается, если партиционированная таблица содержит партицию по умолчанию.

Если указано FINALIZE, завершается предыдущий вызов DETACH CONCURRENTLY, который был отменен или прерван. Отсоединять от партиционированной таблицы можно максимум одну партицию за раз.
.

Все формы ALTER TABLE, которые действуют на одну таблицу, за исключением RENAME, SET SCHEMA, ATTACH PARTITION и DETACH PARTITION, можно объединить в список из нескольких изменений, которые будут применяться вместе. Например, можно добавить несколько столбцов и/или изменить тип нескольких столбцов в одной команде. Это особенно полезно с большими таблицами, поскольку вся таблица обрабатывается за один проход.

Чтобы использовать команду ALTER TABLE, нужно быть владельцем соответствующей таблицы. Чтобы изменить схему или табличное пространство таблицы, необходимо помимо этого иметь право CREATE в новой схеме или табличном пространстве. Чтобы добавить таблицу в качестве нового потомка родительской таблицы, нужно также быть владельцем родительской таблицы. Кроме того, чтобы присоединить таблицу в качестве новой партиции таблицы, нужно быть владельцем присоединяемой таблицы. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме таблицы. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания таблицы. Однако суперпользователь все равно может сменить владельца любой таблицы.) Чтобы добавить столбец, сменить тип столбца или применить предложение OF, необходимо помимо этого иметь право USAGE для соответствующего типа данных.

Параметры

IF EXISTS

Не считать ошибкой, если таблица не существует. В этом случае выдается только замечание.

имя

Имя изменяемой существующей таблицы (может быть дополнено схемой). Если перед именем таблицы указывается ONLY, изменяется только эта таблица. Если ONLY не указано, изменяются и сама таблица, и все ее потомки (если таковые имеются). После имени таблицы можно также добавить необязательное указание *, чтобы явно обозначить, что изменению подлежат все дочерние таблицы.

имя_столбца

Имя нового или существующего столбца.

новое_имя_столбца

Новое имя существующего столбца.

новое_имя

Новое имя таблицы.

тип_данных

Тип данных нового столбца или новый тип данных для существующего столбца.

ограничение_таблицы

Новое ограничение таблицы.

имя_ограничения

Имя нового или существующего ограничения.

CASCADE

Автоматически удалить объекты, зависящие от удаляемого столбца или ограничения (например представления, ссылающиеся на столбец), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Запретить удаление столбца или ограничения, если существуют зависящие от них объекты. Это поведение по умолчанию.

имя_триггера

Имя триггера, который нужно выключить или включить.

ALL

Выключить или включить все принадлежащие таблице триггеры. (Если в числе этих триггеров оказываются сгенерированные внутрисистемные триггеры исключений, например, используемые для реализации ограничений внешнего ключа или отложенных ограничений уникальности и исключения, то для этого потребуются права суперпользователя.)

USER

Выключить или включить все принадлежащие таблице триггеры, за исключением сгенерированных внутрисистемных триггеров исключений, например используемых для реализации ограничений внешнего ключа или отложенных ограничений уникальности и исключения.

имя_индекса

Имя существующего индекса.

параметр_хранения

Имя параметра хранения таблицы.

значение

Новое значение для параметра хранения таблицы. В зависимости от параметра, это может быть число или строка.

родительская_таблица

Родительская таблица для присоединения или отсоединения этой таблицы.

новый_владелец

Имя пользователя нового владельца этой таблицы.

новое_табличное_пространство

Имя табличного пространства, в которое будет перемещена эта таблица.

новая_схема

Имя схемы, в которую будет перемещена эта таблица.

имя_партиции

Имя таблицы, присоединяемой в качестве новой партиции или, наоборот, отсоединяемой от данной таблицы.

указание_границ_партиции

Указание границ для новой партиции. Более подробную информацию о синтаксисе этого указания см. на справочной странице команды CREATE TABLE.

Примечания

Ключевое слово COLUMN не несет смысловой нагрузки, и его можно опустить.

Когда столбец добавляется с помощью ADD COLUMN и задано неизменчивое выражение DEFAULT, значение по умолчанию вычисляется во время выполнения команды, и результат сохраняется в метаданных таблицы. Это значение будет использоваться для столбца во всех существующих строках. Если указание DEFAULT отсутствует, используется значение NULL. Ни в том, ни в другом случае переписывать таблицу не требуется.

Добавление столбца с изменчивым выражением DEFAULT или изменение типа существующего столбца потребует перезаписи всей таблицы и ее индексов. Исключение: если указание USING не изменяет содержимое столбца и старый тип двоично приводится к новому или является неограниченным доменом поверх нового типа, то при изменении типа существующего столбца перезапись таблицы не требуется, но все индексы на затронутых столбцах все равно должны быть перестроены. Перестройка большой таблицы и/или ее индекса может занять длительное время; при этом временно потребуется вдвое больше места на диске.

Добавление ограничений CHECK или NOT NULL требует сканирования таблицы, чтобы убедиться, что существующие строки соответствуют ограничению, но не требует перезаписи таблицы.

Аналогично при присоединении новой партиции та может быть просканирована, чтобы убедиться, что существующие строки соответствуют ограничению партиции.

Основная причина предоставления возможности указать несколько изменений в одной команде ALTER TABLE заключается в том, что это позволяет совместить требуемые этим операциям сканирования и перезаписи таблицы и выполнить их за один проход.

Сканирование большой таблицы для проверки нового внешнего ключа или проверочного ограничения может занять много времени, и другие изменения таблицы блокируются до тех пор, пока не будет зафиксирована команда ALTER TABLE ADD CONSTRAINT. Основное предназначение указания NOT VALID при добавлении ограничения состоит в уменьшении влияния этой операции на параллельные изменения данных. С указанием NOT VALID команда ADD CONSTRAINT не сканирует таблицу и может быть немедленно зафиксирована. После этого можно выполнить команду VALIDATE CONSTRAINT, чтобы проверить, что существующие строки удовлетворяют ограничению. Эта команда не будет препятствовать параллельным изменениям, так как ей известно, что в других транзакциях уже будет действовать ограничение для добавляемых или изменяемых строк; проверить нужно только уже существующие строки. Следовательно, проверка запрашивает на изменяемую таблицу только блокировку SHARE UPDATE EXCLUSIVE. (Если это ограничение внешнего ключа, то в ссылочной таблице данного ограничения также требуется блокировка ROW SHARE.) Помимо оптимизации параллелизма может быть полезно использовать указания NOT VALID и VALIDATE CONSTRAINT в тех случаях, когда заведомо известно, что таблица содержит строки, нарушающие ограничения. После создания ограничения добавить новые недопустимые строки будет невозможно, а все существующие проблемы могут разрешаться в свободное время, пока VALIDATE CONSTRAINT наконец не будет успешно завершена.

Форма DROP COLUMN не удаляет столбец физически, а просто делает его невидимым для операций SQL. Последующие операции добавления и обновления в таблице будут сохранять для столбца значение NULL. Как следствие, удаление столбца выполняется быстро, но не приводит к немедленному уменьшению размера таблицы на диске, поскольку пространство, занимаемое удаляемым столбцом, не восстанавливается. Пространство будет освобождаться с течением времени по мере изменения существующих строк.

Чтобы принудительно освободить место, занимаемое удаляемым столбцом, можно выполнить одну из форм ALTER TABLE, которая выполняет перезапись всей таблицы. Это приведет к воссозданию всех строк с заменой удаленного столбца значением NULL.

Перезаписывающие формы ALTER TABLE небезопасны с точки зрения MVCC. После перезаписи таблицы та будет казаться пустой для параллельных транзакций, если они используют снимок, сделанный до этого события. Более подробную информацию см. в разделе Ограничения применимости.

В параметре USING предложения SET DATA TYPE действительно можно указать любое выражение, включающее старые значения строки; то есть он может обращаться к другим столбцы, а не только к преобразуемому. Это позволяет с помощью синтаксиса SET DATA TYPE выполнять очень общие преобразования. Из-за этой гибкости выражение USING не применяется к значению столбца по умолчанию (если таковое имеется); результат может не быть константным выражением, как это требуется для значения по умолчанию. Это означает, что при отсутствии неявного приведения или присвоения от старого к новому типу SET DATA TYPE, возможно, не удастся преобразовать значение по умолчанию, даже если указано USING. В таких случаях нужно удалить значение по умолчанию с помощью DROP DEFAULT, выполнить ALTER TYPE, а затем использовать SET DEFAULT, чтобы добавить подходящее новое значение по умолчанию. Аналогичные рекомендации применимы к связанным со столбцом индексам и ограничениям.

Если таблица имеет дочерние таблицы, то не разрешается добавлять, переименовывать или изменять тип столбца в родительской таблице, не делая то же самое для потомков. Это гарантирует, что у потомков столбцы всегда соответствуют родительским. Точно так же нельзя переименовать ограничение CHECK в родительской таблице, не переименовывая его во всех дочерних таблицах, что тоже гарантирует соответствие всех ограничений CHECK между родителем и потомком. (Однако этот запрет не распространяется на ограничения, основанные на индексах.) Кроме того, поскольку выборка из родительской таблицы также отбирает из ее потомков, ограничение на родителе не может быть помечено как действующее, если оно не помечено как действующее для ее потомков. Во всех этих случаях команда ALTER TABLE ONLY выполнена не будет.

Рекурсивная операция DROP COLUMN удаляет столбец дочерней таблицы только в том случае, если та не унаследовала данный столбец от каких-то других родителей и никогда не имела независимое определение столбца. Нерекурсивная операция DROP COLUMN (т. е. ALTER TABLE ONLY ... DROP COLUMN) никогда не удаляет унаследованные столбцы, а вместо этого помечает их как независимо определенные, а не унаследованные. С партиционированной таблицей нерекурсивная команда DROP COLUMN выдаст ошибку, так как все партиции таблицы должны содержать те же столбцы, что и главная таблица.

Действия для столбцов идентификации (ADD GENERATED, SET и т. д., DROP IDENTITY), а также действия TRIGGER, CLUSTER, OWNER и TABLESPACE никогда не распространяются рекурсивно на дочерние таблицы; иначе говоря, они всегда действуют так, будто указано ONLY. Добавление ограничения выполняется рекурсивно только для ограничений CHECK, не помеченных как NO INHERIT.

Изменение любой части таблицы системного каталога не допускается.

Более подробное описание допустимых параметров см. на справочной странице команды CREATE TABLE. Дополнительная информация о наследовании содержится в главе Определение данных.

Примеры

Добавление в таблицу столбца типа varchar:

ALTER TABLE distributors ADD COLUMN address varchar(30);

При этом все существующие строки таблицы в новом столбце будут заполнены значениями NULL.

Добавление столбца со значением по умолчанию, отличным от NULL:

ALTER TABLE measurements
  ADD COLUMN mtime timestamp with time zone DEFAULT now();

В качестве значения нового столбца существующие строки будут заполнены значением текущего времени, а затем новые строки будут содержать время их добавления.

Добавление столбца и заполнение его значением, отличным от значения по умолчанию, которое будет использовано в дальнейшем:

ALTER TABLE transactions
  ADD COLUMN status varchar(30) DEFAULT 'old',
  ALTER COLUMN status SET default 'current';

Существующие строки будут заполнены значением old, но для последующих команд значением по умолчанию будет current. Результат будет таким же, как если бы эти две вложенные команды выполнялись в отдельных командах ALTER TABLE.

Удаление столбца из таблицы:

ALTER TABLE distributors DROP COLUMN address RESTRICT;

Изменение типов двух существующих столбцов в одной операции:

ALTER TABLE distributors
    ALTER COLUMN address TYPE varchar(80),
    ALTER COLUMN name TYPE varchar(100);

Смена типа целочисленного столбца, содержащего временную метку в стиле Unix, на тип timestamp with time zone с помощью предложения USING:

ALTER TABLE foo
    ALTER COLUMN foo_timestamp SET DATA TYPE timestamp with time zone
    USING
        timestamp with time zone 'epoch' + foo_timestamp * interval '1 second';

То же самое, но в случае, когда у столбца есть значение по умолчанию, которое нельзя автоматически привести к новому типу данных:

ALTER TABLE foo
    ALTER COLUMN foo_timestamp DROP DEFAULT,
    ALTER COLUMN foo_timestamp TYPE timestamp with time zone
    USING
        timestamp with time zone 'epoch' + foo_timestamp * interval '1 second',
    ALTER COLUMN foo_timestamp SET DEFAULT now();

Переименование существующего столбца:

ALTER TABLE distributors RENAME COLUMN address TO city;

Переименование существующей таблицы:

ALTER TABLE distributors RENAME TO suppliers;

Переименование существующего ограничения:

ALTER TABLE distributors RENAME CONSTRAINT zipchk TO zip_check;

Добавление в столбец ограничения NOT NULL:

ALTER TABLE distributors ALTER COLUMN street SET NOT NULL;

Удаление из столбца ограничения NOT NULL:

ALTER TABLE distributors ALTER COLUMN street DROP NOT NULL;

Добавление проверочного ограничения в таблицу и во всех ее потомков:

ALTER TABLE distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5);

Добавление проверочного ограничения только в таблицу, но не в ее потомков:

ALTER TABLE distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5)
	NO INHERIT;

(Будущими потомками данное проверочное ограничение тоже наследоваться не будет.)

Удаление проверочного ограничения из таблицы и из всех ее потомков:

ALTER TABLE distributors DROP CONSTRAINT zipchk;

Удаление проверочного ограничения только из самой таблицы:

ALTER TABLE ONLY distributors DROP CONSTRAINT zipchk;

(Проверочное ограничение остается во всех дочерних таблицах.)

Добавление в таблицу ограничения внешнего ключа:

ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address)
	REFERENCES addresses (address);

Добавление в таблицу ограничения внешнего ключа с наименьшим влиянием на работу других:

ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address)
	REFERENCES addresses (address) NOT VALID;
ALTER TABLE distributors VALIDATE CONSTRAINT distfk;

Добавление в таблицу ограничения уникальности (многостолбцового):

ALTER TABLE distributors ADD CONSTRAINT dist_id_zipcode_key UNIQUE (dist_id, zipcode);

Добавление в таблицу первичного ключа с автоматически задаваемым именем, учитывая, что в таблице может быть только один первичный ключ:

ALTER TABLE distributors ADD PRIMARY KEY (dist_id);

Перемещение таблицы в другое табличное пространство:

ALTER TABLE distributors SET TABLESPACE fasttablespace;

Перемещение таблицы в другую схему:

ALTER TABLE myschema.distributors SET SCHEMA yourschema;

Пересоздание ограничения первичного ключа без блокировки изменений в процессе перестроения индекса:

CREATE UNIQUE INDEX CONCURRENTLY dist_id_temp_idx ON distributors (dist_id);
ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
    ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;

Присоединение партиции к таблице, партиционированной по диапазонам:

ALTER TABLE measurement
    ATTACH PARTITION measurement_y2016m07 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');

Присоединение партиции к таблице, партиционированной по списку:

ALTER TABLE cities
    ATTACH PARTITION cities_ab FOR VALUES IN ('a', 'b');

Присоединение партиции к таблице, партиционированной по хешу:

ALTER TABLE orders
    ATTACH PARTITION orders_p4 FOR VALUES WITH (MODULUS 4, REMAINDER 3);

Присоединение партиции по умолчанию к партиционированной таблице:

ALTER TABLE cities
    ATTACH PARTITION cities_partdef DEFAULT;

Отсоединение партиции от партиционированной таблицы:

ALTER TABLE measurement
    DETACH PARTITION measurement_y2015m12;

Совместимость

Формы ADD (без USING INDEX), DROP [COLUMN], DROP IDENTITY, RESTART, SET DEFAULT, SET DATA TYPE (без USING), SET GENERATED и SET параметр_последовательности соответствуют стандарту SQL. Другие формы являются расширениями стандарта SQL, реализованными в QHB. Кроме того, возможность указать несколько операций изменения в одной команде ALTER TABLE тоже является расширением.

Команда ALTER TABLE DROP COLUMN позволяет удалить единственный столбец таблицы, оставляя ее без столбцов. Это расширение стандарта SQL, который не допускает таблицы без столбцов.

См. также

CREATE TABLE

ALTER TABLESPACE

ALTER TABLESPACE — изменить определение табличного пространства

Синтаксис

ALTER TABLESPACE имя RENAME TO новое_имя
ALTER TABLESPACE имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER TABLESPACE имя SET ( параметр_табличного_пространства = значение [, ... ] )
ALTER TABLESPACE имя RESET ( параметр_табличного_пространства [, ... ] )

Описание

Команда ALTER TABLESPACE изменяет определение табличного пространства.

Чтобы изменить определение табличного пространства, нужно быть владельцем этого табличного пространства. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца. (Обратите внимание, что суперпользователи имеют эти права автоматически.)

Параметры

имя

Имя существующего табличного пространства.

новое_имя

Новое имя табличного пространства. Новое имя не может начинаться с pg_, поскольку такие имена зарезервированы для системных табличных пространств.

новый_владелец

Новый владелец табличного пространства.

параметр_табличного_пространства

Параметр табличного пространства, который необходимо установить или сбросить. В настоящее время доступны только следующие параметры: seq_page_cost, random_page_cost, effective_io_concurrency и maintenance_io_concurrency. Установка любого значения для табличного пространства переопределит обычную оценку планировщиком стоимости чтения страниц из таблиц в этом пространстве, установленную параметрами конфигурации с тем же именем (см. seq_page_cost, random_page_cost, effective_io_concurrency, maintenance_io_concurrency). Это может быть полезно, если одно из табличных пространств расположено на диске, который быстрее или медленнее, чем остальная подсистема ввода/вывода.

Примеры

Переименование табличного пространства index_space в fast_raid:

ALTER TABLESPACE index_space RENAME TO fast_raid;

Смена владельца табличного пространства index_space:

ALTER TABLESPACE index_space OWNER TO mary;

Совместимость

В стандарте SQL нет команды ALTER TABLESPACE.

См. также

CREATE TABLESPACE, DROP TABLESPACE

ALTER TEXT SEARCH CONFIGURATION

ALTER TEXT SEARCH CONFIGURATION — изменить определение конфигурации текстового поиска

Синтаксис

ALTER TEXT SEARCH CONFIGURATION имя
    ADD MAPPING FOR тип_синтаксической_единицы [, ... ] WITH имя_словаря [, ... ]
ALTER TEXT SEARCH CONFIGURATION имя
    ALTER MAPPING FOR тип_синтаксической_единицы [, ... ] WITH имя_словаря [, ... ]
ALTER TEXT SEARCH CONFIGURATION имя
    ALTER MAPPING REPLACE старый_словарь WITH новый_словарь
ALTER TEXT SEARCH CONFIGURATION имя
    ALTER MAPPING FOR тип_синтаксической_единицы [, ... ] REPLACE старый_словарь WITH новый_словарь
ALTER TEXT SEARCH CONFIGURATION имя
    DROP MAPPING [ IF EXISTS ] FOR тип_синтаксической_единицы [, ... ]
ALTER TEXT SEARCH CONFIGURATION имя RENAME TO новое_имя
ALTER TEXT SEARCH CONFIGURATION имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER TEXT SEARCH CONFIGURATION имя SET SCHEMA новая_схема

Описание

Команда ALTER TEXT SEARCH CONFIGURATION изменяет определение конфигурации текстового поиска. Можно модифицировать его сопоставления типов синтаксических единиц со словарями или изменить имя конфигурации или владельца.

Чтобы использовать команду ALTER TEXT SEARCH CONFIGURATION, нужно быть владельцем соответствующей конфигурации.

Параметры

имя

Имя существующей конфигурации текстового поиска (может быть дополнено схемой).

тип_базового_элемента

Имя типа синтаксической единицы, которое выдается при синтаксическом анализе конфигурации.

имя_словаря

Имя словаря текстового поиска, в котором будет проводиться поиск указанных типов синтаксических единиц. Если перечислено несколько словарей, к ним будут обращаться в указанном порядке.

старый_словарь

Имя словаря текстового поиска, который будет заменен в сопоставлении.

новый_словарь

Имя словаря текстового поиска, заменяющего старый_словарь.

новое_имя

Новое имя конфигурации текстового поиска.

новый_владелец

Новый владелец конфигурации текстового поиска.

новая_схема

Новая схема конфигурации текстового поиска.

Форма ADD MAPPING FOR устанавливает список словарей, которые будут просматриваться в поиске указанных типов синтаксических единиц; если уже существует сопоставление для любого из типов синтаксических единиц, выдается ошибка. Форма ALTER MAPPING FOR делает то же самое, но сначала удаляет все существующие сопоставления для типов синтаксических единиц. Формы ALTER MAPPING REPLACE подставляют новый_словарь вместо старого_словаря везде, где имеется последний. При добавлении FOR это делается только для указанных типов синтаксических единиц, в противном случае — для всех сопоставлений конфигурации. Форма DROP MAPPING удаляет все словари для указанных типов синтаксических единиц, в результате чего синтаксические единицы этих типов игнорируются конфигурацией текстового поиска. Если сопоставлений для заданных типов синтаксических единиц нет и не добавлено указание IF EXISTS, возникает ошибка.

Примеры

В следующем примере словарь english заменяется на swedish везде, где в конфигурации my_config использовался english.

ALTER TEXT SEARCH CONFIGURATION my_config
  ALTER MAPPING REPLACE english WITH swedish;

Совместимость

В стандарте SQL нет команды ALTER TEXT SEARCH CONFIGURATION.

См. также

CREATE TEXT SEARCH CONFIGURATION, DROP TEXT SEARCH CONFIGURATION

ALTER TEXT SEARCH DICTIONARY

ALTER TEXT SEARCH DICTIONARY — изменить определение словаря текстового поиска

Синтаксис

ALTER TEXT SEARCH DICTIONARY имя (
    параметр [ = значение ] [, ... ]
)
ALTER TEXT SEARCH DICTIONARY имя RENAME TO новое_имя
ALTER TEXT SEARCH DICTIONARY имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER TEXT SEARCH DICTIONARY имя SET SCHEMA новая_схема

Описание

Команда ALTER TEXT SEARCH DICTIONARY изменяет определение словаря текстового поиска. Можно изменить параметры справочника, относящиеся к определенному шаблону, или изменить имя или владельца справочника.

Чтобы использовать команду ALTER TEXT SEARCH DICTIONARY, нужно быть владельцем соответствующего словаря.

Параметры

имя

Имя существующего словаря текстового поиска (может быть дополнено схемой).

параметр

Имя параметра шаблона, который будет установлен для этого словаря.

значение

Новое значение, используемое для параметра шаблона. Если знак равенства и значение опущены, то любое предыдущее значение параметра удаляется из справочника, позволяя использовать значение по умолчанию.

новое_имя

Новое имя словаря текстового поиска.

новый_владелец

Новый владелец словаря текстового поиска.

новая_схема

Новая схема словаря текстового поиска.

Параметры шаблона можно указывать в любом порядке.

Примеры

В следующем примере команда меняет список стоп-слов словаря на базе Snowball. Другие параметры остаются неизменными.

ALTER TEXT SEARCH DICTIONARY my_dict ( StopWords = newrussian );

В следующем примере команда меняет параметр, определяющий язык, на dutch и полностью удаляет параметр, задающий список стоп-слов.

ALTER TEXT SEARCH DICTIONARY my_dict ( language = dutch, StopWords );

Следующая команда «изменяет» определение словаря, на самом деле не меняя ничего.

ALTER TEXT SEARCH DICTIONARY my_dict ( dummy );

(Это работает потому, что код удаления параметра не считает ошибкой отсутствие такого параметра.) Этот способ полезен при изменении файлов конфигурации словаря; ALTER заставит все существующие сеансы перечитать файлы конфигурации, что в противном случае они бы не сделали, если уже прочитали эти файлы ранее.

Совместимость

В стандарте SQL нет команды ALTER TEXT SEARCH DICTIONARY.

См. также

CREATE TEXT SEARCH DICTIONARY, DROP TEXT SEARCH DICTIONARY

ALTER TEXT SEARCH PARSER

ALTER TEXT SEARCH PARSER — изменить определение синтаксического анализатора текстового поиска

Синтаксис

ALTER TEXT SEARCH PARSER имя RENAME TO новое_имя
ALTER TEXT SEARCH PARSER имя SET SCHEMA новая_схема

Описание

Команда ALTER TEXT SEARCH PARSER изменяет определение синтаксического анализатора текстового поиска. В настоящее время ее единственной поддерживаемой функциональностью является изменение имени синтаксического анализатора текстового поиска.

Чтобы использовать команду ALTER TEXT SEARCH PARSER, нужно быть суперпользователем.

Параметры

имя

Имя существующего синтаксического анализатора текстового поиска (может быть дополнено схемой).

новое_имя

Новое имя синтаксического анализатора текстового поиска.

новая_схема

Новая схема синтаксического анализатора текстового поиска.

Совместимость

В стандарте SQL нет команды ALTER TEXT SEARCH PARSER.

См. также

CREATE TEXT SEARCH PARSER, DROP TEXT SEARCH PARSER

ALTER TEXT SEARCH TEMPLATE

ALTER TEXT SEARCH TEMPLATE — изменить определение шаблона текстового поиска

Синтаксис

ALTER TEXT SEARCH TEMPLATE имя RENAME TO новое_имя
ALTER TEXT SEARCH TEMPLATE имя SET SCHEMA новая_схема

Описание

Команда ALTER TEXT SEARCH TEMPLATE изменяет определение шаблона текстового поиска. В настоящее время ее единственной поддерживаемой функциональностью является изменение имени шаблона.

Чтобы использовать команду ALTER TEXT SEARCH TEMPLATE, нужно быть суперпользователем.

Параметры

имя

Имя существующего шаблона текстового поиска (может быть дополнено схемой).

новое_имя

Новое имя шаблона текстового поиска.

новая_схема

Новая схема для шаблона текстового поиска.

Совместимость

В стандарте SQL нет команды ALTER TEXT SEARCH TEMPLATE.

См. также

CREATE TEXT SEARCH TEMPLATE, DROP TEXT SEARCH TEMPLATE

ALTER TRIGGER

ALTER TRIGGER — изменить определение триггера

Синтаксис

ALTER TRIGGER имя ON имя_таблицы RENAME TO новое_имя
ALTER TRIGGER имя ON имя_таблицы [ NO ] DEPENDS ON EXTENSION имя_расширения

Описание

Команда ALTER TRIGGER изменяет свойства существующего триггера. Предложение RENAME изменяет имя данного триггера без изменения его определения. Предложение DEPENDS ON EXTENSION помечает триггер как зависящий от расширения, из-за чего при удалении расширения триггер также будет автоматически удален.

Только владелец таблицы, на которую действует триггер, имеет возможность изменять его свойства.

Параметры

имя

Имя изменяемого существующего триггера.

имя_таблицы

Имя таблицы, на которую действует этот триггер.

новое_имя

Новое имя для триггера.

имя_расширения

Имя расширения, от которого будет зависеть триггер (или больше не будет зависеть, если указано NO). Триггер, помеченный как зависимый от расширения, автоматически удаляется при удалении этого расширения.

Примечания

Возможность временного включения или выключения триггера предоставляется командой ALTER TABLE, а не ALTER TRIGGER, поскольку в ALTER TRIGGER нет удобного способа выразить возможность включения или выключения всех триггеров таблицы одновременно.

Примеры

Переименование существующего триггера:

ALTER TRIGGER emp_stamp ON emp RENAME TO emp_track_chgs;

Обозначение триггера как зависимого от расширения:

ALTER TRIGGER emp_stamp ON emp DEPENDS ON EXTENSION emplib;

Совместимость

Команда ALTER TRIGGER, реализованная в QHB, является расширением стандарта SQL.

См. также

ALTER TABLE

ALTER TYPE

ALTER TYPE — изменить определение типа

Синтаксис

ALTER TYPE имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER TYPE имя RENAME TO новое_имя
ALTER TYPE имя SET SCHEMA новая_схема
ALTER TYPE имя RENAME ATTRIBUTE имя_атрибута TO новое_имя_атрибута [ CASCADE | RESTRICT ]
ALTER TYPE имя действие [, ... ]
ALTER TYPE имя ADD VALUE [ IF NOT EXISTS ] новое_перечислимое_значение [ { BEFORE | AFTER } соседнее_перечислимое_значение ]
ALTER TYPE имя RENAME VALUE существующее_перечислимое_значение TO новое_перечислимое_значение
ALTER TYPE имя SET ( свойство = значение [, ... ] )

Где действие может быть следующим:

    ADD ATTRIBUTE имя_атрибута тип_данных [ COLLATE правило_сортировки ] [ CASCADE | RESTRICT ]
    DROP ATTRIBUTE [ IF EXISTS ] имя_атрибута [ CASCADE | RESTRICT ]
    ALTER ATTRIBUTE имя_атрибута [ SET DATA ] TYPE тип_данных [ COLLATE правило_сортировки ] [ CASCADE | RESTRICT ]

Описание

Команда ALTER TYPE изменяет определение существующего типа. Существует несколько форм этой команды:

OWNER

Эта форма меняет владельца типа.

RENAME

Эта форма изменяет имя типа.

SET SCHEMA

Эта форма перемещает тип в другую схему.

RENAME ATTRIBUTE

Эта форма применима только к составным типам. Она изменяет имя отдельного атрибута составного типа.

ADD ATTRIBUTE

Эта форма добавляет новый атрибут к составному типу, используя тот же синтаксис, что и команда CREATE TYPE.

DROP ATTRIBUTE [ IF EXISTS ]

Эта форма удаляет атрибут из составного типа. Если указано IF EXISTS и атрибут не существует, это не считается ошибкой. В этом случае выдается только замечание.

ALTER ATTRIBUTE ... SET DATA TYPE

Эта форма изменяет тип атрибута составного типа.

ADD VALUE [ IF NOT EXISTS ] [ BEFORE | AFTER ]

Эта форма добавляет новое значение в перечислимый тип. Позицию нового значения в перечислении можно указать как BEFORE (до) или AFTER (после) с одним из существующих значений. Без этого указания новый элемент добавляется в конец списка значений.

С указанием IF NOT EXISTS, если тип уже содержит новое значение, ошибки не возникает: выдается замечание, и больше ничего не происходит. В противном случае, если новое значение уже имеется, возникнет ошибка.

RENAME VALUE

Эта форма переименовывает значение в перечислимом типе. Позиция значения в порядке перечисления не изменяется. Если указанное значение отсутствует или новое имя уже имеется, произойдет ошибка.

SET ( свойство = значение [, ... ] )

Эта форма применима только к базовым типам. Она позволяет настраивать подгруппу свойств типа, которую можно задать в команде CREATE TYPE. В частности, можно изменить следующие свойства:

RECEIVE позволяет задать имя функции двоичного ввода, а NONE удаляет ссылку на такую функцию. Для изменения этого свойства требуются права суперпользователя.

SEND позволяет задать имя функции двоичного вывода, а NONE удаляет ссылку на такую функцию. Для изменения этого свойства требуются права суперпользователя.

TYPMOD_IN позволяет задать имя функции ввода модификатора типа, а NONE удаляет ссылку на такую функцию. Для изменения этого свойства требуются права суперпользователя.

TYPMOD_OUT позволяет задать имя функции вывода модификатора типа, а NONE удаляет ссылку на такую функцию. Для изменения этого свойства требуются права суперпользователя.

ANALYZE позволяет задать имя функции сбора статистики типа, а NONE удаляет ссылку на такую функцию. Для изменения этого свойства требуются права суперпользователя.

STORAGE может принимать значения plain, extended, external или main (более подробную информацию о том, что они означают, см. в разделе TOAST). Однако для изменения значения с plain на все остальные требуются права суперпользователя (поскольку для этого изменения нужно, чтобы все функции, реализующие тип на С, поддерживали TOAST), а изменение других значений на plain не разрешено вовсе (поскольку этот тип может уже присутствовать в базе данных в виде значений TOAST). Обратите внимание, что изменение этого свойства само по себе не меняет никакие сохраненные данные; оно просто устанавливает стратегию TOAST по умолчанию, которая будет использоваться для столбцов, создаваемых в будущем. Изменение стратегии TOAST для существующих столбцов таблицы описано на справочной странице команды ALTER TABLE.

Более подробную информацию об этих свойствах типа см. на справочной странице команды CREATE TYPE. Обратите внимание, что везде, где применимо, изменение этих свойств у базового типа будет автоматически распространяться на основанные на этом типе домены.

Указания ADD ATTRIBUTE, DROP ATTRIBUTE и ALTER ATTRIBUTE можно объединить в список множественных изменений для одновременного выполнения. Например, можно добавить несколько атрибутов и/или изменить тип нескольких атрибутов в одной команде.

Чтобы использовать команду ALTER TYPE, нужно быть владельцем соответствующего типа. Чтобы изменить схему типа, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме типа. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания типа. Однако суперпользователь все равно может сменить владельца любого типа.) Чтобы добавить атрибут или изменить его тип, необходимо помимо этого иметь право USAGE для соответствующего типа данных.

Параметры

имя

Имя изменяемого существующего типа (может быть дополнено схемой).

новое_имя

Новое имя типа.

новый_владелец

Имя пользователя нового владельца типа.

новая_схема

Новая схема типа.

имя_атрибута

Имя атрибута, который нужно добавить, изменить или удалить.

новое_имя_атрибута

Новое имя переименовываемого атрибута.

тип_данных

Тип данных добавляемого атрибута либо новый тип данных изменяемого атрибута.

новое_перечислимое_значение

Новое значение, которое будет добавлено в список значений перечислимого типа, или новое имя, которое будет присвоено существующему значению. Как и все перечислимые элементы, оно должно быть заключено в кавычки.

соседнее_перечислимое_значение

Существующие значение в перечислении, непосредственно перед или после которого по порядку перечисления в перечислимом типе будет добавлено новое значение. Как и все перечислимые элементы, оно должно быть заключено в кавычки.

существующее_перечислимое_значение

Существующее значение в переименовываемом перечислении. Как и все перечислимые элементы, оно должно быть заключено в кавычки.

свойство

Имя изменяемого свойства базового типа; возможные значения см. выше.

CASCADE

Автоматически распространять операцию на типизированные таблицы изменяемого типа и их потомков.

RESTRICT

Запретить выполнение операции, если изменяемый тип является типом типизированной таблицы. Это поведение по умолчанию.

Примечания

Если ALTER TYPE ... ADD VALUE (форма, которая добавляет новое значение к перечислимому типу) выполняется внутри блока транзакции; новое значение нельзя использовать, пока транзакция не будет зафиксирована.

Сравнение, включающее добавленное перечислимое значение, иногда будет проходить медленнее, чем сравнение, включающее только исходные члены перечислимого типа. Это, как правило, происходит только в том случае, если BEFORE или AFTER устанавливает порядковую позицию нового элемента не в конце списка. Однако иногда это наблюдается даже тогда, когда новое значение добавляется в конец списка (это происходит, если счетчик OID «зациклился» с момента изначального создания перечислимого типа). Замедление обычно незначительно, но если это имеет значение, оптимальную производительность можно восстановить путем удаления и повторного создания перечислимого типа или путем выгрузки резервной копии и повторной загрузки базы данных.

Примеры

Переименование типа данных:

ALTER TYPE electronic_mail RENAME TO email;

Смена владельца типа email на joe:

ALTER TYPE email OWNER TO joe;

Смена схемы типа email на customers:

ALTER TYPE email SET SCHEMA customers;

Добавление в составной тип нового атрибута:

ALTER TYPE compfoo ADD ATTRIBUTE f3 int;

Добавление нового значения в перечислимый тип, в определенную порядковую позицию:

ALTER TYPE colors ADD VALUE 'orange' AFTER 'red';

Переименование перечислимого значения:

ALTER TYPE colors RENAME VALUE 'purple' TO 'mauve';

Создание функций двоичного ввода/вывода для существующего базового типа:

CREATE FUNCTION mytypesend(mytype) RETURNS bytea ...;
CREATE FUNCTION mytyperecv(internal, oid, integer) RETURNS mytype ...;
ALTER TYPE mytype SET (
    SEND = mytypesend,
    RECEIVE = mytyperecv
);

Совместимость

Варианты добавления и удаления атрибутов являются частью стандарта SQL; другие варианты являются расширениями QHB.

См. также

CREATE TYPE, DROP TYPE

ALTER USER

ALTER USER — изменить роль в базе данных

Синтаксис

ALTER USER описание_роли [ WITH ] параметр [ ... ]

Где параметром может быть:

      SUPERUSER | NOSUPERUSER
    | CREATEDB | NOCREATEDB
    | CREATEROLE | NOCREATEROLE
    | INHERIT | NOINHERIT
    | LOGIN | NOLOGIN
    | REPLICATION | NOREPLICATION
    | BYPASSRLS | NOBYPASSRLS
    | CONNECTION LIMIT предел_подключений
    | [ ENCRYPTED ] PASSWORD 'пароль' | PASSWORD NULL
    | VALID UNTIL 'метка_времени'

ALTER USER имя RENAME TO новое_имя

ALTER USER { описание_роли | ALL } [ IN DATABASE имя_базы_данных ] SET параметр_конфигурации { TO | = } { значение | DEFAULT }
ALTER USER { описание_роли | ALL } [ IN DATABASE имя_базы_данных ] SET параметр_конфигурации FROM CURRENT
ALTER USER { описание_роли | ALL } [ IN DATABASE имя_базы_данных ] RESET параметр_конфигурации
ALTER USER { описание_роли | ALL } [ IN DATABASE имя_базы_данных ] RESET ALL

где описанием_роли может быть:

    имя_роли
  | CURRENT_ROLE
  | CURRENT_USER
  | SESSION_USER

Описание

Команда ALTER USER теперь является псевдонимом команды ALTER ROLE.

Совместимость

Команда ALTER USER является расширением QHB. В стандарте SQL определение пользователей считается зависимым от реализации.

См. также

ALTER ROLE

ALTER USER MAPPING

ALTER USER MAPPING — изменить определение сопоставления пользователя

Синтаксис

ALTER USER MAPPING FOR { имя_пользователя | USER | CURRENT_ROLE | CURRENT_USER | SESSION_USER | PUBLIC }
    SERVER имя_сервера
    OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ] )

Описание

Команда ALTER USER MAPPING изменяет определение сопоставления пользователя.

Владелец стороннего сервера может изменять сопоставления любых пользователей для этого сервера. Кроме того, пользователь может изменить сопоставление для своего собственного имени пользователя, если у него есть право USAGE на этом сервере.

Параметры

имя_пользователя

Сопоставляемое имя пользователя. Значения CURRENT_ROLE, CURRENT_USER и USER соответствуют имени текущего пользователя. ЗначениеPUBLIC используется для сопоставления всех настоящих и будущих имен пользователей в данной системе.

имя_сервера

Имя сервера где происходит сопоставление пользователей.

OPTIONS ( [ ADD | SET | DROP ] параметр ['значение'] [, ... ] )

Изменить параметры сопоставления пользователей. Новые параметры переопределяют все ранее указанные параметры. ADD, SET и DROP задают выполняемое действие. Если операция не указана явно, предполагается ADD. Имена параметров должны быть уникальными; параметры также проверяются оберткой сторонних данных сервера.

Примеры

Изменение пароля в сопоставлении пользователя bob на сервере foo:

ALTER USER MAPPING FOR bob SERVER foo OPTIONS (SET password 'public');

Совместимость

Команда ALTER USER MAPPING соответствует стандарту ISO/IEC 9075-9 (SQL/MED), но существует небольшое синтаксическое различие: в стандарте ключевое слово FOR опускается. Так как и в CREATE USER MAPPING, и в DROP USER MAPPING слово FOR находится в аналогичных позициях, а IBM DB2 (еще одна популярная реализация SQL/MED) требует его наличия и для ALTER USER MAPPING, QHB в этом аспекте отклоняется от стандарта в целях согласованности и совместимости.

См. также

CREATE USER MAPPING, DROP USER MAPPING

ALTER VIEW

ALTER VIEW — изменить определение представления

Синтаксис

ALTER VIEW [ IF EXISTS ] имя ALTER [ COLUMN ] имя_столбца SET DEFAULT выражение
ALTER VIEW [ IF EXISTS ] имя ALTER [ COLUMN ] имя_столбца DROP DEFAULT
ALTER VIEW [ IF EXISTS ] имя OWNER TO { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER VIEW [ IF EXISTS ] имя RENAME [ COLUMN ] имя_столбца TO новое_имя_столбца
ALTER VIEW [ IF EXISTS ] имя RENAME TO новое_имя
ALTER VIEW [ IF EXISTS ] имя SET SCHEMA новая_схема
ALTER VIEW [ IF EXISTS ] имя SET ( имя_параметра_представления [= значение_параметра_представления] [, ... ] )
ALTER VIEW [ IF EXISTS ] имя RESET ( имя_параметра_представления [, ... ] )

Описание

Команда ALTER VIEW изменяет различные дополнительные свойства представления. (Для изменения запроса, определяющего представление, используйте команду CREATE OR REPLACE VIEW.)

Чтобы использовать команду ALTER VIEW, нужно быть владельцем соответствующего представления. Чтобы изменить схему представления, необходимо помимо этого иметь право CREATE в новой схеме. Для смены владельца текущий пользователь также должен быть непосредственным или опосредованным членом новой роли-владельца, и эта роль должна иметь право CREATE в схеме представления. (Эти ограничения направлены на то, чтобы при смене владельца не происходило ничего, что нельзя было бы сделать путем удаления и повторного создания представления. Однако суперпользователь все равно может сменить владельца любого представления.)

Параметры

имя

Имя существующего представления (может быть дополнено схемой).

имя_столбца

Имя существующего столбца.

новое_имя_столбца

Новое имя существующего столбца.

IF EXISTS

Не считать ошибкой, если представление не существует. В этом случае выдается только замечание.

SET/DROP DEFAULT

Эти формы устанавливают или удаляют значение по умолчанию для столбца. Значение по умолчанию столбца представления подставляется в команды INSERT или UPDATE, чьей целью является это представление, перед применением к нему любых правил или триггеров. Таким образом, значение по умолчанию представления будет иметь приоритет над любыми значениями по умолчанию в нижележащих отношениях.

новый_владелец

Имя пользователя нового владельца представления.

новое_имя

Новое имя представления.

новая_схема

Новая схема представления.

SET ( имя_параметра_представления [= значение_параметра_представления] [, ... ] )
RESET ( имя_параметра_представления [, ... ] )

Устанавливает или сбрасывает параметр представления. В настоящее время поддерживаются следующие параметры:

check_option (string)
Изменяет параметр проверки представления. Допустимые значения: local (локальная) или cascaded (каскадная)

security_barrier (boolean)
Изменяет свойство представления, включающее барьер безопасности. Значение должно быть логическим: true или false.

Примечания

По историческим причинам команду ALTER TABLE тоже можно использовать с представлениями, но формы команды ALTER TABLE, которые разрешены для работы с представлениями, равнозначны приведенным выше.

Примеры

Переименование представления foo в bar:

ALTER VIEW foo RENAME TO bar;

Добавление значения столбца по умолчанию в изменяемое представление:

CREATE TABLE base_table (id int, ts timestamptz);
CREATE VIEW a_view AS SELECT * FROM base_table;
ALTER VIEW a_view ALTER COLUMN ts SET DEFAULT now();
INSERT INTO base_table(id) VALUES(1);  -- ts получит значение NULL
INSERT INTO a_view(id) VALUES(2);  -- ts получит текущее время

Совместимость

Команда ALTER VIEW, реализованная в QHB, является расширением стандарта SQL.

См. также

CREATE VIEW, DROP VIEW

ANALYZE

ANALYZE — собрать статистику о базе данных

Синтаксис

ANALYZE [ ( параметр [, ...] ) ] [ таблица_и_столбцы [, ...] ]
ANALYZE [ VERBOSE ] [ таблица_и_столбцы [, ...] ]

где параметром может быть:

    VERBOSE [ логическое_значение ]
    SKIP_LOCKED [ логическое_значение ]

а таблица_и_столбцы это:

    имя_таблицы [ ( имя_столбца [, ...] ) ]

Описание

Команда ANALYZE собирает статистическую информацию о содержании таблиц в базе данных и сохраняет результаты в системном каталоге pg_statistic. Впоследствии планировщик запросов использует эти статистические данные для определения наиболее эффективных планов выполнения запросов.

Без списка таблица_и_столбцы команда ANALYZE обрабатывает все таблицы и материализованные представления в текущей базе данных, которые текущий пользователь имеет право анализировать. Со списком ANALYZE обрабатывает только те таблицы, которые в нем указаны. Кроме того, можно указать список имен столбцов таблицы; в этом случае собираются статистические данные только для этих столбцов.

Когда список заключается в скобки, параметры можно писать в любом порядке. Написание без скобок считается устаревшим.

Параметры

VERBOSE

Включает отображение сообщений о ходе выполнения.

SKIP_LOCKED

Указывает, что команда ANALYZE не должна ждать освобождения конфликтующих блокировок при начале работы над отношением: если отношение нельзя заблокировать сразу, без ожидания, оно пропускается. Обратите внимание, что даже при использовании этого параметра ANALYZE по-прежнему может блокироваться при открытии индексов отношения или при получении выборки строк из партиций, потомков таблиц и некоторых типов сторонних таблиц. Кроме того, хотя обычно ANALYZE обрабатывает все партиции указанных партиционированных таблиц, этот параметр заставит ANALYZE пропустить все партиции при наличии на партиционированной таблице конфликтующей блокировки.

логическое_значение

Указывает, должна ли выбранная функция быть включена или выключена. Для включения функции можно написать TRUE, ON или 1, а для выключения — FALSE, OFF или 0. Если этот параметр опущен, предполагается значение TRUE.

имя_таблицы

Имя конкретной таблицы для анализа (может быть дополнено схемой). Если этот параметр опущен, то анализируются все обычные таблицы, партиционированные таблицы и материализованные представления в текущей базе данных (но не сторонние таблицы). Если указанная таблица является партиционированной, то обновляется как статистика наследования партиционированной таблицы в целом, так и статистика отдельных партиций.

имя_столбца

Имя конкретного столбца для анализа. По умолчанию анализ проводится для всех столбцов.

Выводимая информация

При указании VERBOSE команда ANALYZE выдает сообщения о ходе выполнения, показывая, какая таблица обрабатывается в настоящее время. Также печатаются различные статистические данные о таблицах.

Примечания

Для анализа таблицы обычно необходимо быть владельцем таблицы или суперпользователем. Однако владельцам баз данных разрешается анализировать все таблицы в своих базах данных, за исключением разделяемых каталогов. (Ограничение для разделяемых каталогов означает, что по-настоящему глобальную команду ANALYZE может выполнить только суперпользователь.) ANALYZE пропустит все таблицы, для которых вызывающий пользователь не имеет разрешения на анализ.

Сторонние таблицы анализируются только при явном указании. Не все обертки сторонних данных поддерживают ANALYZE. Если обертка таблицы не поддерживает ANALYZE, команда выводит предупреждение и ничего не делает.

В конфигурации QHB по умолчанию процесс «Автовакуум» (см. подраздел Процесс «Автовакуум») запускает автоматический анализ таблиц, когда они впервые заполняются данными, а также по мере их изменения в ходе штатных операций. Если автоочистка выключена, рекомендуется запускать ANALYZE периодически или сразу после внесения существенных изменений в содержимое таблицы. Точная статистика поможет планировщику выбрать наиболее подходящий план запроса и тем самым повысить скорость обработки запроса. Общая стратегия для баз данных, где данные преимущественно читаются, заключается в запуске VACUUM и ANALYZE раз в день во время наименьшей активности. (Этого будет недостаточно, если данные обновляются активно.)

ANALYZE требуется только блокировка на чтение в целевой таблице, поэтому данная команда может выполняться параллельно с другими действиями в таблице.

Статистические данные, собранные ANALYZE, обычно включают список некоторых наиболее распространенных значений в каждом столбце и гистограмму, показывающую примерное распределение данных в каждом столбце. Один или оба этих элемента статистики могут быть опущены, если ANALYZE сочтет их неинтересными (например, в столбце уникального ключа нет общих значений) или если тип данных столбца не поддерживает соответствующие операторы. Более подробная информация о статистике приведена в главе Регулярные задачи обслуживания базы данных.

Для больших таблиц ANALYZE берет случайную выборку содержимого таблицы, а не изучает каждую строку. Это позволяет за короткое время проанализировать даже очень большие таблицы. Однако обратите внимание, что статистические данные окажутся всего лишь приблизительными и будут незначительно меняться при каждом запуске ANALYZE, даже если фактическое содержимое таблицы не изменилось. Это может привести к небольшим изменениям в расчетной стоимости запросов, показываемых командой EXPLAIN. В редких случаях этот недетерминизм приведет к смене выбора планов запросов у планировщика после выполнения ANALYZE. Чтобы избежать этого, нужно увеличить объем статистики, собранной методом ANALYZE, как описано ниже.

Количеством статистики можно управлять, настраивая конфигурационную переменную default_statistics_target или на уровне столбцов устанавливая целевой показатель статистики для каждого столбца командой ALTER TABLE ... ALTER COLUMN ... SET STATISTICS. Значение целевого показателя задает максимальное количество записей в списке наиболее распространенных значений и максимальное количество интервалов в гистограмме. Значение целевого показателя по умолчанию равно 100, но может быть увеличено или уменьшено, чтобы сбалансировать точность оценок планировщика относительно времени, затраченного на выполнение ANALYZE, и объема занимаемого пространства в pg_statistic. В частности, установка целевого показателя статистики в ноль выключает сбор статистических данных для этого столбца. Скорее всего, будет полезно сделать это для столбцов, которые никогда не используются в предложениях WHERE, GROUP BY или ORDER BY, так как планировщику не будет никакой пользы от статистики по таким столбцам.

Число строк таблицы, выбираемых для подготовки статистики, определяется наибольшим целевым показателем статистики из всех анализируемых столбцов этой таблицы. Увеличение целевого показателя вызывает пропорциональное увеличение времени и пространства, необходимого для выполнения ANALYZE.

Один из показателей, оцениваемых командой ANALYZE, — это число различных значений, встречающихся в каждом столбце. Поскольку рассматривается только подмножество строк, эта оценка иногда может быть довольно неточной, даже с максимально возможным целевым показателем статистики. Если эта неточность приводит к плохим планам запросов, можно вручную определить более точное значение, а затем задать его командой ALTER TABLE ... ALTER COLUMN ... SET (n_distinct = ...).

Если анализируемая таблица имеет потомков в иерархии наследования, ANALYZE будет собирать статистику дважды: один раз только по строкам родительской таблицы и второй раз по строкам родительской таблицы со всеми ее потомками. Этот второй набор статистических данных необходим при планировании запросов, проходящих через все дерево наследования. В этом случае сами дочерние таблицы по отдельности не анализируются Однако процесс «Автовакуум» при принятии решения о запуске автоматического анализа для этой таблицы будет рассматривать операции добавления или обновления данных только в самой родительской таблице. Если эта таблица изменяется редко, наследуемая статистика не будет обновляться до тех пор, пока команда ANALYZE не будет запущена вручную.

Для партиционированных таблиц ANALYZE собирает статистику путем выборки строк из всех партиций; кроме того, она будет выполнять рекурсивный обход всех партиций и обновлять их статистику. Каждая листовая партиция анализируется только один раз, даже при многоуровневом партиционировании. Статистика никогда не собирается только по родительской таблице (без данных из ее партиций), потому что партиционирование гарантирует, что эта таблица будет пустой.

Процесс «Автовакуум» не обрабатывает партиционированные таблицы и родительские таблицы в иерархии наследования, если изменяются только их потомки. Обычно для поддержания актуальности статистики в табличной иерархии необходимо периодически запускать ANALYZE вручную.

Если какая-либо из дочерних таблиц является сторонней таблицей, чьи обертки сторонних данных не поддерживают ANALYZE, такие таблицы при сборе статистики наследования игнорируются.

Если анализируемая таблица совершенно пуста, ANALYZE не будет записывать для этой таблицы новую статистику, но вся существующая статистика будет сохранена.

Каждый обслуживающий процесс, запускающий ANALYZE, будет записывать ход ее выполнения в представлении pg_stat_progress_analyze. Более подробную информацию см. в подразделе Отчет о ходе выполнения ANALYZE.

Совместимость

В стандарте SQL нет команды ANALYZE.

См. также

VACUUM, vacuumdb, Задержка очистки по стоимости, Процесс «Автовакуум», Отчет о ходе выполнения ANALYZE

BEGIN

BEGIN — начать блок транзакции

Синтаксис

BEGIN [ WORK | TRANSACTION ] [ режим_транзакции [, ...] ]

где режимом_транзакции может быть:

    ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
    READ WRITE | READ ONLY
    [ NOT ] DEFERRABLE

Описание

Команда BEGIN инициирует блок транзакции, то есть после ее выполнения все операторы будут выполняться в одной транзакции до тех пор, пока не будет дано явной команды COMMIT или ROLLBACK. По умолчанию (без BEGIN) QHB выполняет транзакции в режиме «автофиксации», то есть каждый оператор выполняется в своей собственной транзакции, а фиксация неявно выполняется в конце оператора (если выполнение было успешным; в противном случае выполняется откат).

В блоке транзакции операторы выполняются быстрее, потому что запуск/фиксация транзакции требует значительной активности процессора и диска. Также выполнение нескольких операторов внутри транзакции позволяет обеспечить согласованность при внесении нескольких связанных изменений: другие сеансы не смогут увидеть промежуточные состояния, в которых были сделаны не все связанные обновления.

Если указан уровень изоляции, режим чтения/записи или отложенный режим, новая транзакция имеет те же характеристики, что и после выполнения SET TRANSACTION.

Параметры

WORK
TRANSACTION

Необязательные ключевые слова. Они не имеют никакого эффекта.

Информацию о значении других параметров для данного оператора см. на справочной странице команды SET TRANSACTION.

Примечания

START TRANSACTION имеет ту же функциональность, что и BEGIN.

Для завершения блока транзакции воспользуйтесь командами COMMIT или ROLLBACK.

При попытке выполнить BEGIN внутри уже начатого блока транзакции будет выдано предупреждение, но на состояние транзакции это не повлияет. Для вложения субтранзакций внутри блока транзакции используйте точки сохранения (см. SAVEPOINT).

По соображениям обратной совместимости запятые между последовательными режимами_транзакции можно не ставить.

Примеры

Начало блока транзакции:

BEGIN;

Совместимость

Команда BEGIN является языковым расширением QHB. Она равнозначна стандартной команде SQL START TRANSACTION, справочная страница которой содержит дополнительную информацию о совместимости.

Значение DEFERRABLE параметра режим_транзакции является языковым расширением QHB.

По стечению обстоятельств во встраиваемом SQL ключевое слово BEGIN имеет другое значение. Поэтому при портировании приложений баз данных рекомендуется внимательно сверять семантику транзакций.

См. также

COMMIT, ROLLBACK, START TRANSACTION, SAVEPOINT

CALL

CALL — вызвать процедуру

Синтаксис

CALL имя ( [ аргумент ] [, ...] )

Описание

Команда CALL вызывает процедуру.

Если процедура имеет какие-либо выходные параметры, то будет возвращена результирующая строка, содержащая значения этих параметров.

Параметры

имя

Имя процедуры (может быть дополнено схемой).

аргумент

Входной аргумент для вызова процедуры.

Аргументы могут включать имена параметров, используя синтаксис имя => значение. Это работает так же, как при вызовах обычной функции; подробную информацию см. в разделе Вызов функции.

Аргументы должны быть предоставлены для всех параметров процедуры, для которых не определены значения по умолчанию, включая параметры OUT (выходные). Однако аргументы, соответствующие параметрам OUT, не вычисляются, поэтому обычно вместо них просто пишут NULL. (Написание в параметре OUT какого-то другого значения чревато проблемами совместимости с будущими версиями QHB.)

Примечания

Чтобы вызывать процедуру, необходимо иметь для нее право EXECUTE.

Чтобы вызвать функцию (а не процедуру), используйте команду SELECT.

Если CALL выполняется в блоке транзакции, то вызываемая процедура не может выполнять команды управления транзакцией. Эти команды допускаются только в том случае, если CALL выполняется в собственной транзакции.

PL/pgSQL обрабатывает выходные параметры в командах CALL по-другому; см. подраздел Вызов процедуры.

Примеры

CALL do_db_maintenance();

Совместимость

Команда CALL соответствует стандарту SQL, за исключением обращения с выходными параметрами. В стандарте говорится, что для получения значений выходных параметров пользователи должны задавать переменные.

См. также

CREATE PROCEDURE

CHECKPOINT

CHECKPOINT — выполнить контрольную точку в журнале упреждающей записи

Синтаксис

CHECKPOINT

Описание

Контрольная точка — это момент в последовательности событий в журнале упреждающей записи, когда все файлы данных приводятся в актуальное состояние, соответствующее информации в этом журнале. Все файлы данных сохраняются на диск. Более подробную информацию о том, что происходит во время контрольной точки, см. в разделе Конфигурация WAL.

Команда CHECKPOINT приводит к принудительному выполнению контрольной точки в момент ее вызова, не дожидаясь обычной контрольной точки, запланированной системой (управляется настройками, описанными в подразделе Контрольные точки). Команда CHECKPOINT не предназначена для использования в обычном режиме работы.

Если команда CHECKPOINT выполняется во время восстановления, то вместо записи новой контрольной точки будет принудительно выполнена точка перезапуска (см. раздел Конфигурация WAL).

Выполнять команду CHECKPOINT могут только суперпользователи.

Совместимость

Команда CHECKPOINT является языковым расширением QHB.

CLOSE

CLOSE — закрыть курсор

Синтаксис

CLOSE { имя | ALL }

Описание

Команда CLOSE освобождает ресурсы, связанные с открытым курсором. После закрытия курсора никакие последующие операции на нем не допускаются. Курсор нужно закрывать, когда он больше не нужен.

Каждый несвязанный открытый курсор неявно закрывается, когда транзакция завершается командами COMMIT или ROLLBACK. Связанный курсор неявно закрывается, если транзакция, которая его создала, прерывается командой ROLLBACK. Если создаваемая транзакция успешно фиксируется, связанный курсор остается открытым до выполнения команды CLOSE или отключения клиента.

Параметры

имя

Имя закрываемого открытого курсора.

ALL

Закрыть все открытые курсоры.

Примечания

QHB не имеет явной команды OPEN для курсора; курсор считается открытым после объявления. Для объявления курсора используйте команду DECLARE.

Все доступные курсоры можно просмотреть, запросив системное представление pg_cursors.

Если курсор закрывается после точки сохранения, которая позже откатывается назад, CLOSE не откатывается, то есть курсор остается закрытым.

Примеры

Закрытие курсора liahona:

CLOSE liahona;

Совместимость

Команда CLOSE полностью соответствует стандарту SQL. Команда CLOSE ALL является расширением QHB.

См. также

DECLARE, FETCH, MOVE

CLUSTER

CLUSTER — кластеризовать таблицу согласно индексу

Синтаксис

CLUSTER [VERBOSE] имя_таблицы [ USING имя_индекса ]
CLUSTER ( параметр [, ...] ) имя_таблицы [ USING имя_индекса ]
CLUSTER [VERBOSE]

где параметром может быть:

    VERBOSE [ логическое_значение ]

Описание

Команда CLUSTER дает указание QHB кластеризировать таблицу, заданную в параметре имя_таблицы, основываясь на индексе, заданном в параметре имя_индекса. Индекс уже должен быть определен в таблице имя_таблицы.

Когда таблица кластеризуется, она физически переупорядочивается на основе данных индекса. Кластеризация является одноразовой операцией: при последующих обновлениях таблицы изменения не кластеризуются. То есть не предпринимается никаких попыток сохранить новые или обновленные строки в соответствии с их порядком индексирования. (При желании можно периодически повторять кластеризацию, снова выполнив эту команду. Кроме того, установка табличного параметра хранения fillfactor в значение меньше 100% может помочь сохранить порядок кластеризации во время обновлений, так как обновленные строки останутся на той же странице, если там остается достаточно места.)

Когда таблица кластеризуется, QHB запоминает, по какому индексу она была кластеризована. Форма CLUSTER имя_таблицы повторно кластеризует таблицу, используя тот же индекс, что и раньше. Также команду CLUSTER или формы SET WITHOUT CLUSTER команды ALTER TABLE можно применить для установки индекса, который будет использоваться в будущих операциях кластеризации, или для очистки предыдущего значения.

Команда CLUSTER без параметров повторно кластеризует все ранее кластеризованные таблицы в текущей базе данных, принадлежащей вызывающему пользователю, или все такие таблицы, если они вызываются суперпользователем. Эту форму CLUSTER нельзя выполнить внутри блока транзакции.

Когда таблица кластеризуется, запрашивается блокировка ACCESS EXCLUSIVE. Это препятствует выполнению любых других операций базы данных (чтения и записи) над таблицей, пока CLUSTER не завершится.

Параметры

имя_таблицы

Имя таблицы (может быть дополнено схемой).

имя_индекса

Имя индекса.

VERBOSE

Выводит отчет о процессе кластеризации по мере обработки таблиц.

логическое_значение

Определяет, должен ли заданный параметр быть включен или выключен. Чтобы включить параметр, следует написать TRUE, ON или 1, а чтобы выключить его — FALSE, OFF или 0. Кроме того, логическое_значение можно опустить, и в этом случае подразумевается TRUE.

Примечания

В случаях, когда происходит обращение к случайным единичным строкам таблицы, фактический порядок данных в таблице не имеет значения. Однако если имеется тенденция обращения к одним данным чаще, чем к другим, и есть индекс, который группирует их вместе, использование команды CLUSTER дает преимущества. Если запросить из таблицы диапазон индексированных значений или одно индексированное значение, имеющее несколько соответствующих строк, CLUSTER поможет, так как страница таблицы, найденная по индексу для первой искомой строки, скорее всего, будет содержать и все остальные искомые строки. Таким образом, кластеризация помогает оптимизировать обращения к диску и ускорить запросы.

CLUSTER может повторно отсортировать таблицу, используя либо сканирование по указанному индексу, либо (если индекс является B-деревом) последовательное сканирование с последующей сортировкой. Она будет пытаться выбрать более быстрый метод на основе параметров затрат планировщика и доступной статистической информации.

При использовании сканирования индекса создается временная копия таблицы, содержащая данные таблицы в порядке следования индексов. Также создаются временные копии каждого индекса в таблице. Поэтому для данной операции потребуется свободное пространство на диске, по крайней мере равное сумме размера таблицы и размеров индексов.

Когда используется последовательное сканирование и сортировка, также создается временный файл сортировки, так что пиковое требование к временному пространству равно удвоенному размеру таблицы плюс размеры индексов. Этот метод часто работает быстрее, чем метод сканирования индекса, но если требование к дисковому пространству неприемлемо, этот вариант можно выключить, временно установив в параметре enable_sort значение off.

Перед кластеризацией рекомендуется установить значение maintenance_work_mem достаточно большим (но не больше, чем объем ОЗУ, который вы можете выделить для операции CLUSTER).

Поскольку планировщик записывает статистику о порядке таблиц, для новой кластеризованной таблицы рекомендуется выполнить ANALYZE. В противном случае планировщик может сделать неверный выбор планов запросов.

Поскольку CLUSTER запоминает, какие индексы кластеризуются, можно кластеризировать таблицы, которые требуется кластеризировать вручную в первый раз, а затем настроить скрипт периодического обслуживания, который будет выполнять CLUSTER без каких-либо параметров, так что нужные таблицы будут периодически повторно кластеризоваться.

Каждый обслуживающий процесс, запускающий CLUSTER, будет записывать ход ее выполнения в представлении pg_stat_progress_cluster. Более подробную информацию см. в подразделе Отчет о ходе выполнения CLUSTER.

Примеры

Кластеризация таблицы employees на основе ее индекса employees_ind:

CLUSTER employees USING employees_ind;

Кластеризация таблицы employees с помощью уже использовавшегося индекса:

CLUSTER employees;

Кластеризация всех кластеризованных ранее таблиц в базе данных:

CLUSTER;

Совместимость

В стандарте SQL нет команды CLUSTER.

См. также

clusterdb, Отчет о ходе выполнения CLUSTER

COMMENT

COMMENT — определить или изменить комментарий объекта

Синтаксис

COMMENT ON
{
  ACCESS METHOD имя_объекта |
  AGGREGATE имя_агрегата ( сигнатура_агрегата ) |
  CAST (исходный_тип AS целевой_тип) |
  COLLATION имя_объекта |
  COLUMN имя_отношения.имя_столбца |
  CONSTRAINT имя_ограничения ON имя_таблицы |
  CONSTRAINT имя_ограничения ON DOMAIN имя_домена |
  CONVERSION имя_объекта |
  DATABASE имя_объекта |
  DOMAIN имя_объекта |
  EXTENSION имя_объекта |
  EVENT TRIGGER имя_объекта |
  FOREIGN DATA WRAPPER имя_объекта |
  FOREIGN TABLE имя_объекта |
  FUNCTION имя_функции [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  INDEX имя_объекта |
  LARGE OBJECT oid_большого_объекта |
  MATERIALIZED VIEW имя_объекта |
  OPERATOR имя_оператора (тип_слева, тип_справа) |
  OPERATOR CLASS имя_объекта USING индексный_метод |
  OPERATOR FAMILY имя_объекта USING индексный_метод |
  POLICY имя_политики ON имя_таблицы |
  [ PROCEDURAL ] LANGUAGE имя_объекта |
  PROCEDURE имя_процедуры [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  PUBLICATION имя_объекта |
  ROLE имя_объекта |
  ROUTINE имя_подпрограммы [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  RULE имя_правила ON имя_таблицы |
  SCHEMA имя_объекта |
  SEQUENCE имя_объекта |
  SERVER имя_объекта |
  STATISTICS имя_объекта |
  SUBSCRIPTION имя_объекта |
  TABLE имя_объекта |
  TABLESPACE имя_объекта |
  TEXT SEARCH CONFIGURATION имя_объекта |
  TEXT SEARCH DICTIONARY имя_объекта |
  TEXT SEARCH PARSER имя_объекта |
  TEXT SEARCH TEMPLATE имя_объекта |
  TRANSFORM FOR имя_типа LANGUAGE имя_языка |
  TRIGGER имя_триггера ON имя_таблицы |
  TYPE имя_объекта |
  VIEW имя_объекта
} IS 'текст'

где сигнатура_агрегата:

* |
[ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] |
[ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] ] ORDER BY [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ]

Описание

Команда COMMENT сохраняет комментарий об объекте базы данных.

Для каждого объекта сохраняется только одна строка комментария, поэтому для изменения комментария следует ввести новую команду COMMENT для того же объекта. Чтобы удалить комментарий, вместо текстовой строки нужно написать NULL. Комментарии автоматически удаляются вместе с объектом.

Для большинства видов объектов установить комментарий может только владелец объекта. У ролей нет владельцев, поэтому команду COMMENT ON ROLE для ролей суперпользователей могут выполнять только суперпользователи, а для обычных ролей — те, кто имеет право CREATEROLE. Кроме того, методы доступа также не имеют владельцев; чтобы добавлять комментарий для метода доступа, нужно быть суперпользователем. Разумеется, суперпользователь может задавать комментарии любым объектам.

Комментарии можно просматривать с помощью семейства команд psql \d. Другие пользовательские интерфейсы для получения комментариев можно построить поверх тех же встроенных функций, которые использует psql, а именно obj_description, col_description и shobj_description (см. таблицу Функции для получения комментариев).

Параметры

имя_объекта
имя_отношения.имя_столбца
имя_агрегата
имя_ограничения
имя_функции
имя_оператора
имя_политики
имя_процедуры
имя_подпрограммы
имя_правила
имя_триггера

Имя объекта, для которого задается комментарий. Имена объектов, находящихся в схемах (таблиц, функций и т. п.) могут быть дополнены именем схемы. При комментировании столбца имя_отношения должно ссылаться на таблицу, представление, составной тип или стороннюю таблицу.

имя_таблицы
имя_домена

При создании комментария к ограничению, триггеру, правилу или политике эти параметры задают имя таблицы или домена, в котором этот объект определен.

исходный_тип

Имя исходного типа данных для приведения.

целевой_тип

Имя целевого типа данных для приведения.

режим_аргумента

Режим аргумента функции, процедуры или агрегатной функции: IN, OUT, INOUT или VARIADIC. Если этот параметр опущен, значение по умолчанию равно IN. Обратите внимание, что COMMENT на самом деле не обращает никакого внимания на аргументы OUT, так как для определения идентичности функции необходимы только входные аргументы. Поэтому достаточно перечислить аргументы IN, INOUT и VARIADIC.

имя_аргумента

Имя аргумента функции, процедуры или агрегата. Обратите внимание, что COMMENT фактически не обращает никакого внимания на имена аргументов, так как для определения идентичности функции необходимы только типы данных аргумента.

тип_аргумента

Тип данных аргумента функции, процедуры или агрегата.

oid_большого_объекта

Идентификатор большого объекта.

тип_слева
тип_справа

Типы данных аргументов оператора (могут быть дополнены именем схемы). В случае отсутствия аргумента оператора укажите вместо типа NONE.

PROCEDURAL

Пропускаемое слово.

имя_типа

Имя типа данных для трансформации.

имя_языка

Имя языка для трансформации.

текст

Новый комментарий, написанный в виде строкового литерала, или NULL для удаления комментария.

Примечания

В настоящее время механизм защиты для просмотра комментариев отсутствует: любой пользователь, подключенный к базе данных, может просматривать все комментарии для объектов в этой базе данных. Для разделяемых объектов, таких как базы данных, роли и табличные пространства, комментарии хранятся глобально, поэтому любой пользователь, подключенный к любой базе данных в кластере, может просматривать все комментарии для разделяемых объектов. Поэтому не следует помещать в комментарии критически важную для безопасности информацию.

Примеры

Добавление комментария для таблицы mytable:

COMMENT ON TABLE mytable IS 'Это моя таблица.';

Его удаление:

COMMENT ON TABLE mytable IS NULL;

Еще несколько примеров:

COMMENT ON ACCESS METHOD rtree IS 'Метод доступа R-Tree';
COMMENT ON AGGREGATE my_aggregate (double precision) IS 'Вычисляет дисперсию выборки';
COMMENT ON CAST (text AS int4) IS 'Выполняет приведение текста к int4';
COMMENT ON COLLATION "fr_CA" IS 'Канадский французский';
COMMENT ON COLUMN my_table.my_column IS 'Порядковый номер сотрудника';
COMMENT ON CONVERSION my_conv IS 'Перекодировка в UTF8';
COMMENT ON CONSTRAINT bar_col_cons ON bar IS 'Ограничение столбца col';
COMMENT ON CONSTRAINT dom_col_constr ON DOMAIN dom IS 'Ограничение col для домена';
COMMENT ON DATABASE my_database IS 'База данных разработчиков';
COMMENT ON DOMAIN my_domain IS 'Домен почтового адреса';
COMMENT ON EVENT TRIGGER abort_ddl IS 'Прерывает все команды DDL';
COMMENT ON EXTENSION hstore IS 'Реализует тип данных hstore';
COMMENT ON FOREIGN DATA WRAPPER mywrapper IS 'Моя обертка сторонних данных';
COMMENT ON FOREIGN TABLE my_foreign_table IS 'Информация о сотрудниках в другой БД';
COMMENT ON FUNCTION my_function (timestamp) IS 'Возвращает число римскими цифрами';
COMMENT ON INDEX my_index IS 'Обеспечивает уникальность по ID сотрудника';
COMMENT ON LANGUAGE plpython IS 'Поддержка Python для хранимых процедур';
COMMENT ON LARGE OBJECT 346344 IS 'Документ планирования';
COMMENT ON MATERIALIZED VIEW my_matview IS 'Сводка истории заказов';
COMMENT ON OPERATOR ^ (text, text) IS 'Вычисляет пересечение двух текстов';
COMMENT ON OPERATOR - (NONE, integer) IS 'Унарный минус';
COMMENT ON OPERATOR CLASS int4ops USING btree IS 'Операторы для четырехбайтовых целых для B-деревьев';
COMMENT ON OPERATOR FAMILY integer_ops USING btree IS 'Все целочисленные операторы для B-деревьев';
COMMENT ON POLICY my_policy ON mytable IS 'Фильтр строк по пользователям';
COMMENT ON PROCEDURE my_proc (integer, integer) IS 'Строит отчет';
COMMENT ON PUBLICATION alltables IS 'Публикует все операции во всех таблицах';
COMMENT ON ROLE my_role IS 'Административная группа для таблиц бухгалтерии';
COMMENT ON ROUTINE my_routine (integer, integer) IS 'Выполняет подпрограмму (функцию или процедуру)';
COMMENT ON RULE my_rule ON my_table IS 'Протоколирует изменения в записях сотрудников';
COMMENT ON SCHEMA my_schema IS 'Данные отдела';
COMMENT ON SEQUENCE my_sequence IS 'Предназначена для генерации первичных ключей';
COMMENT ON SERVER myserver IS 'Мой сторонний сервер';
COMMENT ON STATISTICS my_statistics IS 'Улучшает оценку числа строк для планировщика';
COMMENT ON SUBSCRIPTION alltables IS 'Подписка на все операции во всех таблицах';
COMMENT ON TABLE my_schema.my_table IS 'Данные сотрудников';
COMMENT ON TABLESPACE my_tablespace IS 'Табличное пространство для индексов';
COMMENT ON TEXT SEARCH CONFIGURATION my_config IS 'Фильтрация специальных слов';
COMMENT ON TEXT SEARCH DICTIONARY swedish IS 'Стеммер Snowball для шведского языка';
COMMENT ON TEXT SEARCH PARSER my_parser IS 'Разделяет текст на слова';
COMMENT ON TEXT SEARCH TEMPLATE snowball IS 'Стеммер Snowball';
COMMENT ON TRANSFORM FOR hstore LANGUAGE plpythonu IS 'Трансформирует данные из hstore в словарь языка Python';
COMMENT ON TRIGGER my_trigger ON my_table IS 'Обеспечивает ссылочную целостность';
COMMENT ON TYPE complex IS 'Тип данных комплексных чисел';
COMMENT ON VIEW my_view IS 'Представление расходов по отделам';

Совместимость

В стандарте SQL нет команды COMMENT.

COMMIT

COMMIT — зафиксировать текущую транзакцию

Синтаксис

COMMIT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]

Описание

Команда COMMIT фиксирует текущую транзакцию. Все внесенные транзакцией изменения становятся видимыми для других и гарантированно сохранятся, если произойдет сбой.

Параметры

WORK
TRANSACTION

Необязательные ключевые слова. Они не оказывают никакого влияния.

AND CHAIN

Если указывается AND CHAIN, то немедленно запускается новая транзакция с теми же характеристиками (см. справочную страницу команды SET TRANSACTION), что и только что завершившаяся. В противном случае новая транзакция не запускается.

Примечания

Для прерывания транзакции воспользуйтесь командой ROLLBACK.

Выполнение COMMIT вне транзакции не вызовет ошибку, но будет выдано предупреждающее сообщение. COMMIT AND CHAIN вне транзакции вызовет ошибку.

Примеры

Фиксация текущей транзакции и сохранение всех изменений:

COMMIT;

Совместимость

Команда COMMIT соответствует стандарту SQL. Форма COMMIT TRANSACTION является расширением QHB.

См. также

BEGIN, ROLLBACK

COMMIT PREPARED

COMMIT PREPARED — зафиксировать транзакцию, которая ранее была подготовлена для двухфазной фиксации

Синтаксис

COMMIT PREPARED id_транзакции

Описание

Команда COMMIT PREPARED фиксирует транзакцию, находящуюся в подготовленном состоянии.

Параметры

id_транзакции

Идентификатор фиксируемой транзакции.

Примечания

Чтобы зафиксировать подготовленную транзакцию, нужно быть либо пользователем, выполнявшим транзакцию изначально, либо суперпользователем. При этом необязательно работать в том же сеансе, где выполнялась транзакция.

Эта команда не может быть выполнена внутри блока транзакции. Подготовленная транзакция фиксируется немедленно.

Все доступные в настоящее время подготовленные транзакции перечислены в системном представлении pg_prepared_xacts.

Примеры

Фиксация транзакции с идентификатором foobar:

COMMIT PREPARED 'foobar';

Совместимость

Команда COMMIT PREPARED является расширением QHB. Она предназначена для использования внешними системами управления транзакциями, на некоторые из которых распространяются стандарты (например X/Open XA), но SQL-сторона этих систем не стандартизирована.

См. также

PREPARE TRANSACTION, ROLLBACK PREPARED

COPY

COPY — копировать данные между файлом и таблицей

Синтаксис

COPY имя_таблицы [ ( имя_столбца [, ...] ) ]
    FROM { 'имя_файла' | PROGRAM 'команда' | STDIN }
    [ [ WITH ] ( параметр [, ...] ) ]
    [ WHERE условие ]

COPY { имя_таблицы [ ( имя_столбца [, ...] ) ] | ( запрос ) }
    TO { 'имя_файла' | PROGRAM 'команда' | STDOUT }
    [ [ WITH ] ( параметр [, ...] ) ]

где параметром может быть:

    FORMAT имя_формата
    FREEZE [ логическое_значение ]
    DELIMITER 'символ_разделитель'
    NULL 'нулевая_строка'
    HEADER [ логическое_значение ]
    QUOTE 'символ_кавычки'
    ESCAPE 'управляющий_символ'
    FORCE_QUOTE { ( имя_столбца [, ...] ) | * }
    FORCE_NOT_NULL ( имя_столбца [, ...] )
    FORCE_NULL ( имя_столбца [, ...] )
    ENCODING 'имя_кодировки'

Описание

Команда COPY перемещает данные между таблицами QHB и стандартными файлами файловой системы. COPY TO копирует содержимое таблицы в файл, а COPY FROM копирует данные из файла в таблицу (добавляя данные к тем, что уже находятся в таблице). Кроме того, команда COPY TO может копировать результаты запроса SELECT.

Если указан список столбцов, COPY TO будет копировать в файл данные только из указанных столбцов. COPY FROM будет по порядку вставлять каждое поле из файла в указанные столбцы. В табличные столбцы, которых нет в списке COPY FROM, будут добавлены их значения по умолчанию.

COPY с именем файла указывает серверу QHB напрямую читать или записывать в конкретный файл. Файл должен быть доступен пользователю QHB (идентификатору пользователя, под которым работает сервер), а путь к нему должен быть указан с точки зрения сервера. Когда указывается PROGRAM, сервер выполняет заданную команду и считывает данные со стандартного вывода программы либо записывает их в стандартный вход программы. Команда должна быть определена с точки зрения сервера и быть выполнимой пользователем QHB. Когда указывается STDIN или STDOUT, данные передаются через соединение между клиентом и сервером.

Каждый обслуживающий процесс, запускающий COPY, будет записывать ход ее выполнения в представлении pg_stat_progress_copy. Более подробную информацию см. в подразделе Отчет о ходе выполнения COPY.

Параметры

имя_таблицы

Имя существующей таблицы (может быть дополнено схемой).

имя_столбца

Необязательный список столбцов для копирования. Если список столбцов не указан, то будут скопированы все столбцы таблицы, кроме генерируемых.

запрос

Команда SELECT, VALUES, INSERT, UPDATE или DELETE, результаты которой должны быть скопированы. Обратите внимание, что запрос заключается в круглые скобки.

Для запросов INSERT, UPDATE и DELETE должно задаваться предложение RETURNING, а целевое отношение не должно иметь ни условного правила, ни правила ALSO, ни правила INSTEAD, разворачивающегося в несколько операторов.

имя_файла

Путь к входному или выходному файлу. Путь входного файла может быть абсолютным или относительным путем, но путь выходного файла должен быть абсолютным путем.

PROGRAM

Выполняемая команда. COPY FROM читает стандартный вывод команды, а COPY TO записывает в ее стандартный ввод

Обратите внимание, что команда вызывается оболочкой, поэтому если вам нужно передать команде оболочки какие-либо аргументы, поступающие из ненадежного источника, обязательно следует удалить или экранировать любые специальные символы, которые могут иметь для оболочки особое значение. По соображениям безопасности лучше всего использовать фиксированную командную строку или, по крайней мере, избегать передачи в нее каких-либо входных данных от пользователя.

STDIN

Указывает, что входные данные поступают из клиентского приложения.

STDOUT

Указывает, что выходные данные передаются в клиентское приложение.

логическое_значение

Указывает, должен ли выбранный параметр быть включен или выключен. Для включения параметра можно написать TRUE, ON или 1, а для выключения — FALSE, OFF или 0. Также логическое_значение можно опустить; в этом случае подразумевается TRUE.

FORMAT

Выбор формата данных для чтения или записи: text (текстовый), csv (Comma Separated Values, значения, разделенные запятыми) или binary (двоичный). По умолчанию стоит формат text.

FREEZE

Запрашивает копирование данных с уже замороженными строками, как это было бы после выполнения команды VACUUM FREEZE. Это позволяет увеличить производительность для начальной загрузки данных. Строки будут заморожены только в том случае, если загружаемая таблица была создана или опустошена в текущей субтранзакции, нет открытых курсоров и нет удерживаемых этой транзакцией старых снимков. В настоящее время невозможно выполнить COPY FREEZE для партиционированной таблицы.

Обратите внимание, что все остальные сеансы сразу же смогут увидеть данные, как только те будут успешно загружены. Это нарушает обычные правила видимости MVCC, и пользователи, включающие данный режим, должны понимать, какие проблемы это может вызвать.

DELIMITER

Задает символ, который разделяет столбцы в каждой строке файла. По умолчанию в текстовом формате используется символ табуляции, а внутри формата CSV — запятая. Это должен быть один однобайтовый символ. Для формата binary данный параметр не допускается.

NULL

Задает строку, представляющую собой значение NULL. Значение по умолчанию: \N (обратный слэш и N) в текстовом формате и пустая строка без кавычек в формате CSV. Если нет желания различать значения NULL и пустые строки, можно использовать пустую строку даже в текстовом формате. Для формата binary данный параметр не допускается.

Примечание
При использовании COPY FROM любой элемент данных, соответствующий этой строке, будет сохранен как значение NULL, поэтому следует убедиться, что вы используете ту же строку, что и в COPY TO.

HEADER

Указывает, что файл содержит строку заголовка с именами всех столбцов в файле. На выходе первая строка содержит имена столбцов из таблицы, а на входе первая строка игнорируется. Данный параметр разрешен только при использовании формата CSV.

QUOTE

Задает символ кавычки, который будет использоваться при вводе значения данных в кавычках. По умолчанию используются обычные кавычки (""). Это должен быть один однобайтовый символ. Данный параметр разрешен только при использовании формата CSV.

ESCAPE

Указывает символ, который должен отображаться перед символом данных, соответствующих значению QUOTE. Значение по умолчанию совпадает с QUOTE (так что если символ кавычки появляется в данных, он удваивается). Это должен быть один однобайтовый символ. Данный параметр разрешен только при использовании формата CSV.

FORCE_QUOTE

Принудительно заключает в кавычки все значения, отличные от NULL, в указанных столбцах. Выводимое значение NULL никогда не заключается в кавычки. Если указано *, значения, отличные от NULL, будут заключены в кавычки во всех столбцах. Данный параметр разрешен только в COPY TO и только при использовании формата CSV.

FORCE_NOT_NULL

Не сопоставлять значения указанных столбцов с нулевой строкой. По умолчанию, когда нулевая строка пуста, это означает, что пустые значения будут считываться как строки нулевой длины, а не значения NULL, даже если они не заключены в кавычки. Данный параметр разрешен только в COPY FROM и только при использовании формата CSV.

FORCE_NULL

Сопоставлять значения указанных столбцов с нулевой строкой, даже если те были заключены в кавычки, и в случае совпадения устанавливать значение NULL. По умолчанию, когда нулевая строка пуста, это преобразует пустую строку в кавычках в значение NULL. Данный параметр разрешен только в COPY FROM и только при использовании формата CSV.

ENCODING

Указывает, что файл имеет кодировку имя_кодировки. Если этот параметр опущен, используется текущая клиентская кодировка. Более подробную информацию см. ниже в примечаниях.

WHERE

Необязательное предложение WHERE имеет общую форму

WHERE условие

где условие — это любое выражение, возвращающее результат типа boolean. Любая строка, которая не удовлетворяет этому условию, не будет добавлена в таблицу. Строка удовлетворяет условию, если она возвращает true при подстановке вместо ссылок на переменные фактических значений из этой строки.

В настоящее время выражения WHERE не могут включать подзапросы, а при вычислении выражений не видны изменения, которые вносит сама команда COPY (это имеет значение, когда в них вызываются функции с характеристикой VOLATILE).

Выводимая информация

После успешного завершения команда COPY возвращает метку команды в виде

COPY число

Где число — количество скопированных строк.

Примечание
psql будет выводить эту метку команды только в том случае, если выполняемой командой была не COPY ... TO STDOUT или ее аналог в psql, метакоманда \copy ... to stdout. Это сделано для того, чтобы не перепутать метку команды с выведенными перед ней данными.

Примечания

COPY TO можно использовать только с простыми таблицами, но не с представлениями, и эта команда не копирует строки из дочерних таблиц или партиций. Например, COPY таблица TO копирует те же строки, что и SELECT * FROM ONLY таблица. Чтобы выгрузить все строки представления, или таблицы с учетом иерархии наследования, или партиционированной таблицы, можно написать COPY (SELECT * FROM таблица) TO ...

COPY FROM можно использовать с простыми, сторонними или партиционированными таблицами или с представлениями, в которых установлены триггеры INSTEAD OF INSERT.

Нужно иметь право на выборку для таблицы, значения которой считываются COPY TO, и право на их добавление для таблицы, в которую значения добавляются COPY FROM. Однако если в команде перечисляются выбранные столбцы, достаточно иметь права только для них.

Если для таблицы включена защита на уровне строк, то соответствующие политики SELECT будут применяться и к операторам COPY таблица TO. В настоящее время COPY FROM не поддерживается для таблиц с защитой на уровне строк. Вместо этого используйте равнозначные операторы INSERT.

Файлы, заданные в команде COPY, считываются или записываются непосредственно сервером, а не клиентским приложением. Поэтому они должны располагаться на сервере базы данных или быть доступными для него, а не для клиента. Они должны быть доступны на чтение или запись пользователю QHB (идентификатору пользователя, под которым работает сервер), а не клиенту. Аналогично команда, указанная параметром PROGRAM, выполняется непосредственно сервером, а не клиентским приложением и должна быть доступна для выполнения пользователем QHB. Выполнять COPY с указанием файла или команды разрешено только суперпользователям базы данных или членам встроенных ролей pg_read_server_files, pg_write_server_files или pg_execute_server_program, так как они позволяют читать или записывать любые файлы и запускать любые программы, к которым имеет доступ сервер.

Не путайте COPY с командой psql \copy. Метакоманда \copy вызывает команду COPY FROM STDIN или COPY TO STDOUT, а затем извлекает/сохраняет данные в файле, доступном клиенту psql. Таким образом, при использовании \copy доступность файла и права доступа зависят от клиента, а не от сервера.

Рекомендуется, чтобы имя файла, используемое в COPY, всегда указывалось как абсолютный путь. Это обязательное условие для команды COPY TO, но для COPY FROM есть возможность чтения из файла, указанного относительным путем. Такой путь будет интерпретироваться относительно рабочего каталога серверного процесса (обычно это каталог данных кластера), а не рабочего каталога клиента.

Выполнение команды с помощью PROGRAM может быть ограничено механизмами контроля доступа операционной системы, такими как SELinux.

COPY FROM вызовет все триггеры и проверочные ограничения для целевой таблицы. Однако правила при загрузке данных не вызываются.

Для столбцов идентификации команда COPY FROM всегда будет записывать значения столбцов, указанные во входных данных, подобно параметру OVERRIDING SYSTEM VALUE команды INSERT.

При вводе и выводе данных COPY учитывается DateStyle. Чтобы обеспечить переносимость на другие установки QHB, которые могут использовать нестандартные настройки DateStyle, перед использованием COPY TO в DateStyle следует установить значение ISO. Также рекомендуется не выгружать данные при IntervalStyle равном sql_standard, так как сервер с другими значениями IntervalStyle может неправильно воспринимать значения отрицательных интервалов в таких данных.

Входные данные интерпретируются в соответствии с кодировкой, заданной параметром ENCODING, или текущей клиентской кодировкой, а выходные данные кодируются в кодировке ENCODING или текущую клиентской кодировке, даже если эти данные не проходят через клиент, а считываются или записываются в файл непосредственно сервером.

COPY останавливает работу при первой ошибке. Это не должно приводить к проблемам в случае COPY TO, но после COPY FROM в целевой таблице остаются ранее полученные строки. Эти строки не будут видны или доступны, но все равно будут занимать место на диске. Если сбой происходит при копировании большого объема данных, это может привести к значительным потерям дискового пространства. Восстановить потерянное пространство можно с помощью команды VACUUM.

FORCE_NULL и FORCE_NOT_NULL могут применяться одновременно к одному и тому же столбцу. В результате нулевые строки в кавычках будут преобразованы в значения NULL, а нулевые строки без кавычек — в пустые строки.

Форматы файлов

Текстовый формат

Когда используется формат text, данные читаются или записываются в виде текстового файла, строка в котором соответствует строке таблицы. Столбцы в строке разделяются символом-разделителем. Сами значения столбцов представляют собой текстовые строки, генерируемые функцией вывода либо принимаемые функцией ввода, соответствующей типу данных каждого атрибута. Вместо столбцов со значением NULL используется заданная нулевая строка. Если какая-либо строка входного файла содержит больше или меньше столбцов, чем ожидалось, COPY FROM выдает ошибку.

Конец данных может быть обозначен одной строкой, содержащей только обратный слэш и точку (\.). Маркер конца данных не является обязательным при чтении из файла, так как с его ролью вполне справляется конец файла; он необходим только при копировании данных в/из клиентских приложений, использующих клиентский протокол до версии 3.0.

Символы обратного слэша (\) в данных COPY позволяют экранировать символы данных, которые в противном случае могут быть приняты за разделители строк или столбцов. В частности, обратным слэшем должны предваряться следующие символы, если они оказываются частью значения столбца: сам обратный слэш, новая строка, возврат каретки и текущий символ разделителя.

Заданная нулевая строка передается командой COPY TO без добавления обратных слэшей; в то же время COPY FROM перед удалением обратных слэшей сопоставляет вводимые данные с нулевой строкой. Как следствие, нулевую строку, например \N, невозможно спутать с настоящим значением данных \N (которое должно быть представлено как \\N).

COPY FROM распознает следующие специальные последовательности с обратным слэшем:

Последовательность | Что представляет
\b | Возврат на символ (ASCII 8)
\f | Подача формы (ASCII 12))
\n | Новая строка (ASCII 10)
\r | Возврат каретки (ASCII 13)
\t | Табуляция (ASCII 9)
\v | Вертикальная табуляция (ASCII 11)
\цифры | Обратный слэш с последующими 1-3 восьмеричными цифрами задает байт с этим числовым кодом
\xцифры | Обратный слэш с последующим x и 1-2 шестнадцатеричными цифрами задает байт с этим числовым кодом

В настоящее время COPY TO никогда не выдает последовательности с обратным слэшем с восьмеричными или шестнадцатеричными кодами, однако эта команда использует для данных управляющих символов другие вышеперечисленные последовательности.

Любой другой символ после обратного слэша, который не упоминается в приведенной выше таблице, будет принят для представления самого себя. Однако остерегайтесь добавлять обратные слэши без необходимости, так как это может случайно привести к появлению строки, соответствующей маркеру конца данных (\.) или нулевой строке (по умолчанию \N). Эти строки будут восприняты раньше, чем обработаются любые последовательности с обратным слэшем.

Настоятельно рекомендуется, чтобы приложения, генерирующие данные для COPY, преобразовывали символы перевода строки и возврата каретки в в последовательности \n и \r соответственно. В настоящее время возврат каретки в данных можно представить сочетанием обратного слэша и возврата каретки, а новую строку — сочетанием обратного слэша и перевода строки. Однако эти представления могут не приниматься в будущих выпусках. Кроме того, они крайне уязвимы к повреждениям, если файл COPY переносится между разными системами (например, с Unix в Windows и наоборот).

Все последовательности с обратным слэшем интерпретируются после преобразования кодировки. Байты, заданные в этих последовательностях восьмеричными и шестнадцатеричными цифрами должны образовывать допустимые символы в кодировке базы данных.

COPY TO завершает каждую строку символом перевода строки в стиле Unix («\n»). Серверы, работающие на Microsoft Windows, вместо этого выводят символы возврат каретки/новая строка («\r\n»), но только при выводе COPY в файл на сервере; для обеспечения согласованности между платформами COPY TO STDOUT всегда передает «\n» независимо от платформы сервера. COPY FROM может обрабатывать строки, заканчивающиеся символами новая строка, возврат каретки или возврат каретки/новая строка. Чтобы уменьшить риск ошибки из-за отсутствия обратного слэша перед символами перевода строки и возврата каретки, которые должны были быть данными, COPY FROM сигнализирует о проблеме, если концы строк во входных данных различаются.

Формат CSV

Этот формат применяется для импорта и экспорта данных, разделенных запятой (Comma Separated Value, CSV), и используется многими другими программами, такими как электронные таблицы. Вместо правил экранирования, используемых стандартным текстовым форматом QHB, этот формат создает и распознает общий механизм экранирования CSV.

Значения в каждой записи разделяются символом DELIMITER. Если значение содержит символ разделителя, символ QUOTE, нулевую строку, символ возврата каретки или перевода строки, то все значение будет дополнено спереди и сзади символами QUOTE, а любое вхождение символа QUOTE или спецсимвола (ESCAPE) в данных предваряется управляющим символом. Также можно использовать FORCE_QUOTE, чтобы принудительно заключать в кавычки любые значения не NULL в указанных столбцах.

В формате CSV отсутствует стандартный способ отличить значение NULL от пустой строки. В QHB команда COPY решает это с помощью кавычек. Значение NULL выводится в виде строки, задаваемой параметром NULL, и не заключается в кавычки, тогда как значение не NULL, совпадающее со строкой, задаваемой параметром NULL, заключается. Например, с настройками по умолчанию NULL записывается в виде пустой строки без кавычек, в то время как пустое строковое значение данных записывается с кавычками (""). Чтение значений следует аналогичным правилам. Указание FORCE_NOT_NULL позволяет избежать сравнений с NULL во входных данных в заданных столбцах, а FORCE_NULL — преобразовывать в NULL даже заключенные в кавычки данные с нулевыми строками.

Так как обратный слэш не является специальным символом в формате CSV, маркер конца данных \. также может быть значением данных. Чтобы избежать любого неправильного толкования, значение данных \., отображаемое как одиночная запись в строке, автоматически заключается в кавычки при выводе, а при вводе этот маркер, заключенный в кавычки, не интерпретируется как маркер конца данных. Если вы загружаете файл, созданный другим приложением, который имеет один столбец без кавычек и может иметь значение \., потребуется дополнительно заключить это значение в кавычки во входном файле.

Примечание
В формате CSV все символы являются значимыми. Заключенное в кавычки значение, дополненное пробелами или любыми другими символами, кроме DELIMITER, будет включать в себя эти символы. Это может привести к ошибкам при импорте данных из системы, дополняющей строки CSV пробельными символами до некоторой фиксированной ширины. В случае возникновения такой ситуации перед импортом данных в QHB может потребоваться предварительная обработка файла CSV для удаления конечного пробела.

Примечание
Формат CSV будет воспринимать и генерировать файлы CSV со значениями в кавычках, которые могут содержать символы возврата каретки и перевода строки. Таким образом, число строк в этих файлах не строго равно числу строк в таблице, как в файлах текстового формата.

Примечание
Многие программы создают странные и иногда неприемлемые файлы CSV, поэтому данный формат файла используется скорее по соглашению, чем по стандарту. Как следствие, можно столкнуться с некоторыми файлами, которые нельзя импортировать с помощью этого механизма, и COPY тоже может создавать файлы, которые другие программы не могут обрабатывать.

Двоичный формат

При выборе формата binary все данные сохраняются/считываются в двоичном, а не текстовом формате. Этот формат может обрабатываться несколько быстрее, чем текстовый и CSV форматы, но файл двоичного формата менее переносим в разных машинных архитектурах и версиях QHB. Кроме того, двоичный формат очень зависит для типа данных; например, он не позволит вывести двоичные данные из столбца smallint а затем прочитать их в столбец integer, хотя с текстовым форматом это вполне возможно.

Формат binary состоит из заголовка файла, нуля или более кортежей, содержащих данные строк, и окончания файла. Для заголовков и данных принят сетевой порядок байтов.

Заголовок файла

Заголовок файла состоит из 15 байт фиксированных полей, за которыми следует область расширения заголовка переменной длины. Фиксированные поля:

Сигнатура

11-байтовая последовательность PGCOPY\n\377\r\n\0 — обратите внимание, что нулевой байт является обязательной частью сигнатуры. (Сигнатура предназначена для облегчения идентификации файлов, которые были испорчены при передаче, не сохраняющей все 8 бит данных. Она изменится при прохождении через фильтры, меняющие концы строк, удалении нулевых байтов или старших битов либо при изменениях четности.)

Поле флагов

32-разрядная целочисленная битовая маска для обозначения важных аспектов формата файла. Биты нумеруются от 0 (LSB) до 31 (MSB). Обратите внимание, что это поле хранится в сетевом порядке байтов (наиболее значащий байт первый), как и все целочисленные поля, используемые в данном формате. Биты 16-31 зарезервированы, чтобы обозначить критичные проблемы формата; встретив неожиданный набор битов в этом диапазоне, обработчик должен прервать чтение. Биты 0-15 зарезервированы, чтобы сигнализировать о проблемах формата обратной совместимости; обработчик может просто игнорировать любые неожиданные наборы битов в этом диапазоне. В настоящее время определен только один битовый флаг, остальные должны быть равны 0:

Бит 16
При 1 данные включаются в OID; при 0 — нет. В QHB больше не поддерживаются системные столбцы Oid, но формат все еще содержит этот индикатор.

Длина области расширения заголовка

32-битное целое число, длина в байтах остатка заголовка, не включая само это значение. В настоящее время содержит 0, и сразу за ним следует первая запись. Будущие изменения формата могут привести к появлению дополнительных данных в заголовке. Обработчик должен просто пропустить все данные расширения заголовка, с которыми он не знает, что делать.
.

Предполагается, что область расширения заголовка будет содержать последовательность самоидентифицирующихся фрагментов. Поле флагов не должно содержать указаний для обработчика о том, что содержится в области расширения. Точное содержимое области расширения может быть определено в будущих версиях.

Эта конструкция позволяет выполнять как обратно-совместимые добавления заголовков (добавлять блоки расширения заголовков или устанавливать младшие биты флагов), так и не обратно-совместимые изменения (устанавливать старшие биты флагов, чтобы сигнализировать о таких изменениях, и при необходимости добавлять вспомогательные данные в область расширения).

Кортежи

Каждый кортеж начинается с 16-битного целого числа, представляющего количество полей в записи. (В настоящее время все записи в таблице будут иметь одинаковое количество полей, но это может быть не всегда верно.) Затем для каждого поля в записи указывается 32-битное машинное слово, за которым следует это же количество байтов с данными поля. (Машинное слово не включает свой размер и может быть равно нулю.) В качестве особого варианта -1 обозначает, что в поле содержится значение NULL. В случае с NULL за длиной не следуют байты данных.

Между полями нет выравнивающих заполнителей или каких-либо других дополнительных данных.

В настоящее время предполагается, что все значения данных в файле двоичного формата содержатся в двоичном формате (формате под кодом один). Предполагается, что в будущем расширении в заголовок может быть добавлено поле, позволяющее задавать другие коды форматов для разных столбцов.

Чтобы определить подходящий двоичный формат для фактических данных, обратитесь к исходному коду QHB, в частности, к функциям send и recv для типов данных каждого столбца.

Если в файл включается идентификатор объекта (OID), поле OID следует сразу за числом, определяющим количество полей. Это обычное поле, за исключением того, что оно не учитывается в количестве полей. Обратите внимание, что системные столбцы oid не поддерживаются в текущих версиях QHB.

Окончание файла

Окончание файла состоит из 16-битного целого слова, содержащего -1. Это позволяет легко отличить его от счетчика полей в записи.

Обработчик, читающий файл, должен выдать ошибку, если число полей в записи не равно -1 или ожидаемому числу столбцов. Это обеспечивает дополнительную проверку на предмет нарушения синхронизации данных.

Примеры

В следующем примере таблица копируется в клиент с разделителем полей «вертикальная черта» (|):

COPY country TO STDOUT (DELIMITER '|');

Копирование данных из файла в таблицу country:

COPY country FROM '/usr1/proj/bray/sql/country_data';

Копирование в файл только данных стран, название которых начинается с «A»:

COPY (SELECT * FROM country WHERE country_name LIKE 'A%') TO '/usr1/proj/bray/sql/a_list_countries.copy';

Для копирования данных в сжатый файл можно направить вывод через внешнюю программу сжатия:

COPY country TO PROGRAM 'gzip > /usr1/proj/bray/sql/country_data.gz';

Пример данных, подходящих для копирования в таблицу из STDIN:

AF      AFGHANISTAN
AL      ALBANIA
DZ      ALGERIA
ZM      ZAMBIA
ZW      ZIMBABWE

Обратите внимание, что пробел в каждой строке фактически является символом табуляции.

Ниже приведены те же данные, но выведенные в двоичном формате. Данные показаны после обработки Unix-утилитой od -c. Таблица содержит три столбца; первый имеет тип char(2), второй — text, а третий — integer. Последний столбец во всех строках содержит значение NULL.

0000000   P   G   C   O   P   Y  \n 377  \r  \n  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0 003  \0  \0  \0 002   A   F  \0  \0  \0 013   A
0000040   F   G   H   A   N   I   S   T   A   N 377 377 377 377  \0 003
0000060  \0  \0  \0 002   A   L  \0  \0  \0 007   A   L   B   A   N   I
0000100   A 377 377 377 377  \0 003  \0  \0  \0 002   D   Z  \0  \0  \0
0000120 007   A   L   G   E   R   I   A 377 377 377 377  \0 003  \0  \0
0000140  \0 002   Z   M  \0  \0  \0 006   Z   A   M   B   I   A 377 377
0000160 377 377  \0 003  \0  \0  \0 002   Z   W  \0  \0  \0  \b   Z   I
0000200   M   B   A   B   W   E 377 377 377 377 377 377

Совместимость

В стандарте SQL нет команды COPY.

CREATE ACCESS METHOD

CREATE ACCESS METHOD — определить новый метод доступа

Синтаксис

CREATE ACCESS METHOD имя
    TYPE тип_метода_доступа
    HANDLER функция_обработки

Описание

Команда CREATE ACCESS METHOD создает новый метод доступа.

Имя метода доступа должно быть уникальным в базе данных.

Только суперпользователи могут определять новые методы доступа.

Параметры

имя

Имя создаваемого метода доступа.

тип_метода_доступа

В этом предложении указывается тип определяемого метода доступа. В настоящее время поддерживаются только TABLE и INDEX.

функция_обработки

функция_обработки — это имя ранее зарегистрированной функции, представляющей метод доступа (может быть дополнено схемой). Функция обработки должна объявляться как принимающая один аргумент типа internal, а тип результата зависит от типа метода доступа; для методов доступа типа TABLE он должен быть table_am_handler, а для INDEX — index_am_handler. API уровня C, который должна реализовывать эта функция обработки, меняется в зависимости от типа метода доступа. API табличных методов доступа описаны в главе Определение интерфейса табличных методов доступа, а индексных — в главе Определение интерфейса индексных методов доступа.

Примеры

Создание индексного метода доступа heptree с функцией обработки heptree_handler:

CREATE ACCESS METHOD heptree TYPE INDEX HANDLER heptree_handler;

Совместимость

Команда CREATE ACCESS METHOD является расширением QHB.

См. также

DROP ACCESS METHOD, CREATE OPERATOR CLASS, CREATE OPERATOR FAMILY

CREATE AGGREGATE

CREATE AGGREGATE — определить новую агрегатную функцию

Синтаксис

CREATE [ OR REPLACE ] AGGREGATE имя ( [ режим_аргумента ] [ имя_аргумента ] тип_данных_аргумента [ , ... ] ) (
    SFUNC = функция_состояния,
    STYPE = тип_данных_состояния
    [ , SSPACE = размер_данных_состояния ]
    [ , FINALFUNC = функция_завершения ]
    [ , FINALFUNC_EXTRA ]
    [ , FINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE } ]
    [ , COMBINEFUNC = комбинирующая_функция ]
    [ , SERIALFUNC = функция_сериализации ]
    [ , DESERIALFUNC = функция_десериализации ]
    [ , INITCOND = начальное_условие ]
    [ , MSFUNC = функция_состояния_движения ]
    [ , MINVFUNC = обратная_функция_движения ]
    [ , MSTYPE = тип_данных_состояния_движения ]
    [ , MSSPACE = размер_данных_состояния_движения ]
    [ , MFINALFUNC = функция_завершения_движения ]
    [ , MFINALFUNC_EXTRA ]
    [ , MFINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE } ]
    [ , MINITCOND = начальное_условие_движения ]
    [ , SORTOP = оператор_сортировки ]
    [ , PARALLEL = { SAFE | RESTRICTED | UNSAFE } ]
)

CREATE [ OR REPLACE ] AGGREGATE имя ( [ [ режим_аргумента ] [ имя_аргумента ] тип_данных_аргумента [ , ... ] ]
                        ORDER BY [ режим_аргумента ] [ имя_аргумента ] тип_данных_аргумента [ , ... ] ) (
    SFUNC = функция_состояния,
    STYPE = тип_данных_состояния
    [ , SSPACE = размер_данных_состояния ]
    [ , FINALFUNC = функция_завершения ]
    [ , FINALFUNC_EXTRA ]
    [ , FINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE } ]
    [ , INITCOND = начальное_условие ]
    [ , PARALLEL = { SAFE | RESTRICTED | UNSAFE } ]
    [ , HYPOTHETICAL ]
)

или старый синтаксис:

CREATE [ OR REPLACE ] AGGREGATE имя (
    BASETYPE = базовый_тип,
    SFUNC = функция_состояния,
    STYPE = тип_данных_состояния
    [ , SSPACE = размер_данных_состояния ]
    [ , FINALFUNC = функция_завершения ]
    [ , FINALFUNC_EXTRA ]
    [ , FINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE } ]
    [ , COMBINEFUNC = комбинирующая_функция ]
    [ , SERIALFUNC = функция_сериализации ]
    [ , DESERIALFUNC = функция_десериализации ]
    [ , INITCOND = начальное_условие ]
    [ , MSFUNC = функция_состояния_движения ]
    [ , MINVFUNC = обратная_функция_движения ]
    [ , MSTYPE = тип_данных_состояния_движения ]
    [ , MSSPACE = размер_данных_состояния_движения ]
    [ , MFINALFUNC = функция_завершения_движения ]
    [ , MFINALFUNC_EXTRA ]
    [ , MFINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE } ]
    [ , MINITCOND = начальное_условие_движения ]
    [ , SORTOP = оператор_сортировки ]
)

Описание

Команда CREATE AGGREGATE определяет новую агрегатную функцию. CREATE OR REPLACE AGGREGATE будет либо определять новую агрегатную функцию, либо заменять определение уже существующей. Некоторые основные и часто используемые агрегатные функции включены в дистрибутив; они описаны в разделе Агрегатные функции. При определении новых типов или возникновении потребности в агрегатной функции, которая еще не предоставлена, CREATE AGGREGATE можно использовать для получения требуемых объектов.

При замене существующего определения типы аргументов, тип результата и количество непосредственных аргументов изменить нельзя. Кроме того, новое определение должно быть того же вида (обычный, сортирующий или гипотезирующий агрегат), что и старое.

Если задано имя схемы (например CREATE AGGREGATE myschema.myagg ...), то агрегатная функция создается в указанной схеме. В противном случае — в текущей.

Агрегатная функция идентифицируется по имени, а также по типу (или типам) входных данных. Два агрегата в одной схеме могут иметь одно и то же имя, если они работают с разными типами входных данных. Имя и тип (или типы) входных данных агрегата также должны отличаться от имени и типа (или типов) входных данных простых функций в той же схеме. Это поведение идентично перегрузке обычных имен функций (см. CREATE FUNCTION).

Простая агрегатная функция состоит из одной или двух обычных функций: функции перехода состояния функция_состояния и необязательной функции окончательного вычисления функция_завершения. Они используются следующим образом:

функция_состояния( внутреннее-состояние, следующие-значения-данных ) ---> следующее-внутреннее-состояние
функция_завершения( внутреннее-состояние ) ---> агрегатное-значение

QHB создает временную переменную типа данных тип_данных_состояния, чтобы сохранить текущее внутреннее состояние агрегата. В каждой входной строке вычисляются значения агрегированных аргументов и вызывается функция перехода состояния с текущим значением состояния и значениями новых аргументов для вычисления нового значения внутреннего состояния. После обработки всех строк однократно вызывается завершающая функция для вычисления возвращаемого агрегатом значения. Если нет завершающей функции, то значение конечного состояния возвращается как есть.

Агрегатная функция может предоставить начальное условие, то есть начальное значение для значения внутреннего состояния. Это значение задается и сохраняется в базе данных в виде строки типа text, но оно должно быть допустимым внешним представлением константы типа данных значения состояния. Если оно не предоставляется, то значение состояния начинается с NULL.

Если функция перехода состояния объявлена как «strict» (строгая), то ее нельзя вызвать с аргументами NULL. С такой функцией перехода выполнение агрегата происходит следующим образом. Строки с входными значениями NULL игнорируются (функция не вызывается, и сохраняется предыдущее значение состояния). Если начальное значение состояния равно NULL, то в первой строке со всеми входными значениями, отличными от NULL, значение первого аргумента заменяется значением состояния, а функция перехода вызывается в каждой последующей строке со всеми входными значениями, отличными от NULL. Это удобно для реализации агрегатов вроде max. Обратите внимание, что это поведение возможно только тогда, когда тип_данных_состояния совпадает с первым типом_данных_аргумента. Если эти типы отличаются, необходимо указать начальное условие, отличное от NULL, или использовать нестрогую функцию перехода.

Если функция перехода состояния не является строгой, то она будет вызываться безусловно в каждой входной строке и должна сама обрабатывать вводимые значения и значения состояния, равные NULL. Это позволяет разработчику агрегата полностью управлять тем, как она воспринимает значения NULL.

Если функция завершения объявлена как «strict» (строгая), то она не будет вызываться, если значение конечного состояния равно NULL; вместо этого будет автоматически возвращаться результат NULL. (Разумеется, это всего лишь обычное поведение строгих функций.) В любом случае функция завершения имеет возможность возвращать результат NULL. Например, функция завершения для avg возвращает NULL, если определяет, что было обработано ноль входных строк

Иногда бывает полезно объявить функцию завершения как принимающую не только значение состояния, но и дополнительные параметры, соответствующие входным значениям агрегата. В основном это имеет смысл, если функция завершения является полиморфной и типа данных значения состояния было бы недостаточно, чтобы вывести тип результата. Эти дополнительные параметры всегда передаются как NULL (поэтому функция завершения не должна быть строгой, когда применяется параметр FINALFUNC_EXTRA), но в остальном это допустимые параметры. Функция завершения может, например, использовать get_fn_expr_argtype, чтобы определить фактический тип аргумента в текущем вызове.

Агрегатная функция может дополнительно поддерживать режим движущегося агрегата, как описано в подразделе Режим движущегося агрегата. Для этого необходимо указать параметры MSFUNC, MINVFUNC и MSTYPE, а также можно указать необязательные параметры MSSPACE, MFINALFUNC, MFINALFUNC_EXTRA, MFINALFUNC_MODIFY и MINITCOND. За исключением MINVFUNC, эти параметры работают как соответствующие параметры простого агрегата без начальной буквы M; они определяют отдельную реализацию агрегата, включающую функцию обратного перехода.

Если в список параметров добавлено указание ORDER BY, создается особый типа агрегата, называемый сортирующим агрегатом; с указанием HYPOTHETICAL создается гипотезирующий агрегат. Эти агрегаты работают над группами отсортированных значений в зависимости от порядка сортировки, так что указание порядка сортировки входных данных является неотъемлемой частью вызова. Кроме того, они могут иметь непосредственные аргументы, которые вычисляются единожды для всей процедуры агрегирования, а не для каждой входной строки. Гипотезирующие агрегаты — это подкласс сортирующих агрегатов, в котором часть непосредственных аргументов должна соответствовать столбцам агрегированных аргументов по количеству и типам данных. Это позволяет добавить значения этих непосредственных аргументов в набор агрегированных строк в качестве дополнительной «гипотетической» строки.

Агрегатная функция может дополнительно поддерживать частичное агрегирование, как описано в подразделе Частичная агрегация. Для этого необходимо задать параметр COMBINEFUNC. Если в качестве типа_данных_состояния выбран internal, обычно также указывают параметры SERIALFUNC и DESERIALFUN, чтобы было возможно параллельное агрегирование. Обратите внимание, что для возможности параллельного агрегирования агрегат также должен быть помечен как PARALLEL SAFE.

Агрегаты, ведущие себя как MIN и MAX, иногда можно оптимизировать, заменив сканирование каждой входной строки обращением по индексу. Если агрегат можно оптимизировать таким образом, обозначьте это, указав оператор сортировки. Основное требование состоит в том, что агрегат должен выдавать первый элемент по порядку сортировки, задаваемому этим оператором, другими словами:

SELECT agg(col) FROM tab;

должно быть равнозначно:

SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;

Дополнительно предполагается, что агрегат игнорирует аргументы NULL и возвращает результат NULL тогда и только тогда, когда строки со значениями не NULL отсутствуют. Обычно для MIN подходящим оператором сортировки является оператор <, а для MAX — >. Обратите внимание, что оптимизация ни на что не повлияет, если заданный оператор не является членом стратегии «меньше, чем» или «больше, чем» в классе операторов индекса B-дерева.

Чтобы иметь возможность создать агрегатную функцию, необходимо иметь право USAGE для типов аргументов, типа (или типов) состояния и типа результата, а также право EXECUTE для вспомогательных функций.

Параметры

имя

Имя создаваемой агрегатной функции (может быть дополнено схемой).

режим_аргумента

Режим аргумента: IN (входной) или VARIADIC (переменный). (Агрегатные функции не поддерживают аргументы OUT (выходные).) Если этот параметр опущен, значение по умолчанию равно IN. Только последний аргумент может быть помечен как VARIADIC.

имя_аргумента

Имя аргумента. В настоящее время используется только для целей документирования. Если опущено, соответствующий аргумент будет безымянным.

тип_данных_аргумента

Тип входных данных, с которым работает эта агрегатная функция. Чтобы создать агрегатную функцию без аргументов, вместо списка определений аргументов напишите *. (Примером такого агрегата является count(*).)

базовый_тип

В старом синтаксисе для CREATE AGGREGATE тип входных данных задается параметром базовый_тип, а не записывается после имени агрегата. Обратите внимание, что этот синтаксис допускает только один входной параметр. Чтобы с помощью этого синтаксиса определить агрегатную функцию без аргументов, в качестве значения базового_типа нужно указать ANY (не *). Создать сортирующий агрегат старый синтаксис не позволяет.

функция_состояния

Имя функции перехода состояния, вызываемой для каждой входной строки. Для обычных агрегатных функций с N аргументами функция_состояния должна принимать N+1 аргументов, первый должен иметь тип тип_данных_состояния, а остальные — соответствовать заявленным типам входных данных агрегата. Функция должна возвращать значение типа тип_данных_состояния. Эта функция принимает текущее значение состояния и текущее значение (или значения) входных данных, а возвращает следующее значение состояния.

В сортирующих (в том числе гипотезирующих) агрегатах функция перехода состояния получает только текущее значение состояния и агрегированные аргументы, а не непосредственные аргументы. В остальном это одно и то же.

тип_данных_состояния

Тип данных значения состояния агрегатной функции.

размер_данных_состояния

Приблизительный средний размер (в байтах) значения состояния агрегата. Если этот параметр опущен или равен нулю, используется оценка по умолчанию, определяемая на основе типа_данных_состояния. Планировщик использует это значение для оценки памяти, необходимой для агрегатного запроса с группировкой.

функция_завершения

Имя функции завершения, вызываемой для вычисления результата агрегатной функции после обработки всех входных строк. Для обычного агрегата эта функция должна принимать один аргумент типа тип_данных_состояния. Возвращаемый тип данных агрегата определяется как возвращаемый тип этой функции. Если функция_завершения не указана, то в качестве результата агрегирования используется значение конечного состояния, а типом возвращаемого значения является тип_данных_состояния.

В сортирующих (в том числе гипотезирующих) агрегатах функция завершения получает не только значение конечного состояния, но и значения всех непосредственных аргументов.

Если указано FINALFUNC_EXTRA, то в дополнение к значению конечного состояния и всем непосредственным аргументам конечная функция получает дополнительные значения NULL, соответствующие обычным (агрегированным) аргументам агрегата. Это полезно в основном для обеспечения правильного определения типа результата агрегата при определении полиморфной агрегатной функции.

FINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE }

Этот параметр указывает, является ли функция завершения чистой функцией, которая не изменяет свои аргументы. Это свойство функции передает значение READ_ONLY; другие два значения указывают, что она может изменить значение состояния перехода. Подробнее это рассматривается в Примечаниях ниже. По умолчанию значение равно READ_ONLY, за исключением сортирующих агрегатов (для них значение по умолчанию — READ_WRITE).

комбинирующая_функция

Можно дополнительно задать комбинирующую_функцию, чтобы разрешить агрегатной функции поддерживать частичное агрегирование. Если комбинирующая_функция предоставляется, она должна комбинировать два значения типа_данных_состояния, каждое из которых содержит результат агрегирования по некоторому подмножеству входных значений, чтобы получить новое значение типа_данных_состояния, представляющее результат агрегирования по обоим наборам данных. Эту функцию можно рассматривать как функцию_состояния, которая вместо обработки отдельной входной строки и добавления ее данных в текущее агрегатное состояние включает еще одно агрегатное состояние в текущее.

комбинирующая_функция должна быть объявлена как принимающая два аргумента с типом_данных_состояния и возвращающая значение с типом_данных_состояния. Эту функцию дополнительно можно объявить как «строгую». В этом случае функция не будет вызываться, когда одно из входных состояний равно NULL; в качестве корректного результата будет выдано другое состояние.

Для агрегатных функций, у которых тип_данных_состояния — internal, комбинирующая_функция не должна быть «строгой». В этом случае комбинирующая_функция должна позаботиться о том, чтобы состояния NULL обрабатывались правильно и чтобы возвращаемое состояние правильно хранилось в контексте агрегатной памяти.

функция_сериализации

Агрегатная функция, у которой тип_данных_состояния — internal, может участвовать в параллельном агрегировании только в том случае, если для нее задана функция_сериализации, которая должна сериализовать агрегатное состояние в значение bytea для передачи другому процессу. Эта функция должна принимать один аргумент типа internal и возвращать тип bytea. Также при этом нужно задать соответствующую функцию_десериализации.

функция_десериализации

Десериализует ранее сериализованное агрегатное состояние обратно в тип_данных_состояния. Эта функция должна принимать два аргумента типа bytea и internal и возвращать результат типа internal. (Примечание: второй аргумент internal не используется, но требуется из соображений безопасности для типа.)

начальное_условие

Начальное значение переменной состояния. Это должна быть строковая константа в форме, принятой для типа данных тип_данных_состояния. Если не указано, то значение состояния начинается с NULL.

функция_состояния_движения

Имя функции прямого перехода состояния, которая будет вызываться для каждой входной строки в режиме движущегося агрегата. Это такая же функция, как и обычная функция перехода, за исключением того, что ее первый аргумент и результат имеют тип тип_данных_состояния_движения, который может отличаться от типа_данных_состояния.

обратная_функция_движения

Имя функции обратного перехода состояния, которая будет использоваться в режиме движущегося агрегата. Эта функция имеет те же типы аргументов и результатов, что и функция_состояния_движения, но используется для удаления значения из текущего агрегатного состояния, а не для добавления к нему значения. Функция обратного перехода должна иметь ту же характеристику строгости, что и функция прямого перехода состояния.

тип_данных_состояния_движения

Тип данных для значения агрегатного состояния при использовании режима движущегося агрегата.

размер_данных_состояния_движения

Приблизительный средний размер (в байтах) значения состояния агрегата при использовании режима движущегося агрегата. Он работает так же, как размер_данных_состояния.

функция_завершения_движения

Имя функции завершения, вызываемой для вычисления результата агрегата после обработки всех входных строк при использовании режима движущегося агрегата. Она работает так же, как функция_завершения, за исключением того, что тип ее первого аргумента равен типу_данных_состояния_движения, а дополнительными пустыми аргументами управляет параметр MFINALFUNC_EXTRA. Тип результата, который определяет функция_завершения_движения или тип_данных_состояния_движения, должен совпадать с типом результата обычной реализации агрегата.

MFINALFUNC_MODIFY = { READ_ONLY | SHAREABLE | READ_WRITE }

Этот параметр подобен FINALFUNC_MODIFY, но описывает поведение функции завершения для движущегося агрегата.

начальное_условие_движения

Начальное значение переменной состояния в режиме движущегося агрегата. Оно работает так же, как начальное_условие.

оператор_сортировки

Связанный оператор сортировки для реализации агрегатов подобных MIN или MAX. Это просто имя оператора (может быть дополнено схемой). Предполагается, что этот оператор имеет те же типы входных данных, что и агрегат (который должен быть обычным и иметь один аргумент).

PARALLEL = { SAFE | RESTRICTED | UNSAFE }

Указания PARALLEL SAFE, PARALLEL RESTRICTED и PARALLEL UNSAFE имеют тот же смысл, что и в команде CREATE FUNCTION. Агрегатная функция не будет считаться распараллеливаемой, если она помечена как PARALLEL UNSAFE (что является значением по умолчанию!) или PARALLEL RESTRICTED. Обратите внимание, что планировщик не обращает внимание на допустимость распараллеливания вспомогательных функций агрегата, а учитывает только характеристику самого агрегата.

HYPOTHETICAL

Только для сортирующих агрегатов. Этот флаг указывает, что агрегатные аргументы должны обрабатываться в соответствии с требованиями для гипотезирующих агрегатов: то есть последние несколько непосредственных аргументов должны соответствовать типам данных агрегированных (WITHIN GROUP) аргументов. Флаг HYPOTHETICAL не влияет на поведение во время выполнения, а учитывается только при разрешении типов данных и правил сортировки для аргументов агрегата во время синтаксического анализа.

Параметры CREATE AGGREGATE можно записывать в любом порядке, а не только в показанном выше.

Примечания

В параметрах, определяющих имена вспомогательных функций, при необходимости можно записать имя схемы, например SFUNC = public.sum. Однако не записывайте там типы аргументов — типы аргументов вспомогательных функций выводятся из других параметров.

Обычно предполагается, что функции QHB являются настоящими функциями, которые не изменяют свои входные значения. Тем не менее функциям агрегатного перехода, используемым в контексте агрегата, разрешено действовать хитрее и изменять аргумент переходного состояния на ходу. Это может обеспечить существенные преимущества в производительности по сравнению с созданием каждый раз новой копии переходного состояния.

Подобным образом, хотя функция завершения агрегата обычно не должна изменять свои входные значения, иногда может быть полезно допустить изменение аргумента со значением переходного состояния. Такое поведение должно быть объявлено с помощью параметра FINALFUNC_MODIFY. Его значение READ_WRITE указывает, что функция завершения модифицирует переходное состояние неопределенным образом. Это значение предотвращает использование агрегата в качестве оконной функции, а также объединение переходных состояний при вызове агрегатов с одинаковыми входными данными и функциями перехода. Значение SHAREABLE указывает, что функцию перехода нельзя применять после функции завершения, но со значением конечного переходного состояния можно выполнить несколько вызовов функции завершения. Это значение предотвращает использование агрегата в качестве оконной функции, но позволяет объединять переходные состояния. (То есть оптимизация в данном случае заключается не в многократном применении одной и той же функции завершения, а в применении различных функции завершения к одному и тому же конечному переходному состоянию. Это разрешено, только если ни одна из функций завершения не помечена как READ_WRITE.)

Если агрегатная функция поддерживает режим движущегося агрегата, это повысит эффективность вычислений, когда агрегат будет использоваться в качестве оконной функции для окна с движущимся началом рамки (то есть при режиме начала рамки отличном от UNBOUNDED PRECEDING). По существу, функция прямого перехода добавляет входные значения к состоянию агрегата, когда они появляются в оконной рамке, а функция обратного перехода снова вычитает их, когда они покидают рамку. Поэтому вычитаются значения в том же порядке, в каком добавлялись. Всякий раз, когда вызывается обратная функция перехода, она, таким образом, получит самое раннее добавленное, но еще не удаленное значение (или значения) аргумента. Функция обратного перехода может предполагать, что по крайней мере одна строка останется в текущем состоянии после удаления самой старой строки. (В противном случае механизм функции окна просто запускает новую агрегацию, а не использует обратную функцию перехода.)

Функции прямого перехода движущегося агрегата нельзя возвращать NULL в качестве нового значения состояния. Если функция обратного перехода возвращает значение NULL, это показывает, что обратная функция не может произвести обратное вычисление состояния для этих конкретных входных данных, и поэтому вычисление агрегата следует выполнить заново от начальной позиции текущей рамки. Это соглашение позволяет использовать режим движущегося агрегата в ситуациях, в которых обратное вычисление состояния производить непрактично.

Даже без реализации движущегося агрегата агрегатную функцию можно использовать с движущимися рамками, но QHB будет заново агрегировать все данные при каждом перемещении начала рамки. Обратите внимание, что независимо от того, поддерживает ли агрегатная функция режим движущегося агрегата, QHB может обойтись без повторных вычислений при сдвиге конца рамки; это делается путем продолжения добавления новых значений в состояние агрегата. Именно поэтому использование агрегата в качестве оконной функции требует, чтобы функция завершения была только читающей: она не должна повредить значение состояния агрегата, чтобы агрегация могла продолжаться даже после получения значения результата агрегирования для одного набора границ рамки.

Синтаксис сортирующих агрегатных функций позволяет указать VARIADIC и в последнем непосредственном, и в последнем агрегатном (WITHIN GROUP) параметрах. Тем не менее текущая реализация ограничивает двоякое использование VARIADIC. Во-первых, в сортирующих агрегатных функциях можно использовать только VARIADIC "any", но не другие типы переменных массивов. Во-вторых, если последним непосредственным аргументом является VARIADIC "any", тогда может быть только один агрегированный параметр, и он также должен быть VARIADIC "any". (В представлении, используемом в системных каталогах, эти два параметра объединяются в один элемент VARIADIC "any", так как в pg_proc нельзя представить функцию с несколькими параметрами VARIADIC.) Если агрегат является гипотезирующим, то непосредственные аргументы, соответствующие параметру VARIADIC "any", будут гипотетическими; любые предыдущие параметры представляют дополнительные непосредственные аргументы, которые могут не соответствовать агрегированным.

В настоящее время сортирующие агрегатные функции не поддерживают режим движущегося агрегата, поскольку не могут использоваться в качестве оконных функций.

Частичное (в том числе параллельное) агрегирование в настоящее время не поддерживается для сортирующих агрегатных функций. Также оно никогда не будет применяться для агрегатных вызовов с предложениями DISTINCT или ORDER BY, так как они по природе своей не могут быть реализованы с частичным агрегированием.

Примеры

См. раздел Пользовательские агрегаты.

Совместимость

Команда CREATE AGGREGATE является расширением QHB. Стандарт SQL не предусматривает создание пользовательских агрегатных функций.

См. также

ALTER AGGREGATE, DROP AGGREGATE

CREATE CAST

CREATE CAST — определить новое приведение

Синтаксис

CREATE CAST (исходный_тип AS целевой_тип)
    WITH FUNCTION имя_функции [ (тип_аргумента [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

CREATE CAST (исходный_тип AS целевой_тип)
    WITHOUT FUNCTION
    [ AS ASSIGNMENT | AS IMPLICIT ]

CREATE CAST (исходный_тип AS целевой_тип)
    WITH INOUT
    [ AS ASSIGNMENT | AS IMPLICIT ]

Описание

Команда CREATE CAST определяет новое приведение. Приведение указывает, как выполнить преобразование из одного типа в другой. Например,

SELECT CAST(42 AS float8);

преобразует целочисленную константу 42 в тип float8 путем вызова ранее указанной функции, в данном случае float8(int4). (Если подходящее приведение не было определено, преобразование завершается ошибкой.)

Два типа могут быть двоично-сводимыми, то есть преобразование может быть выполнено «бесплатно» без вызова какой-либо функции. Для этого требуется, чтобы соответствующие значения имели одинаковое внутреннее представление. Например, типы text и varchar являются двоично-сводимыми в обе стороны. Отношение двоичной сводимости не обязательно симметрично. Например, приведение типа xml к типу text в настоящей реализации может быть выполнено бесплатно, но приведение в обратном направлении требует функции, которая выполняет, по крайней мере, проверку синтаксиса. (Два типа, двоично-сводимые в обе стороны, также называются двоично- совместимыми.)

Приведение можно определить как преобразование ввода/вывода, используя указание WITH INOUT. Преобразование ввода/вывода выполняется путем вызова выходной функции исходного типа данных и передачи результирующей строки во входную функцию целевого типа данных. Во многих распространенных случаях эта функция позволяет избежать необходимости писать отдельную функцию приведения для преобразования. Преобразование ввода/вывода действует так же, как и обычное приведение на основе функций; отличается только реализация.

По умолчанию приведение может быть вызвано только явным запросом приведения, то есть применением конструкции CAST(x AS имя_типа) или x::имя_типа.

Если приведение помечено как AS ASSIGNMENT, его можно вызывать неявно, присваивая значение столбцу с целевым типом данных. Например, если foo.f1 — столбец типа text, то команда:

INSERT INTO foo (f1) VALUES (42);

будет допустимой, если приведение типа integer к text помечено как AS ASSIGNMENT, и не будет в противном случае. (Для описания такого типа приведений обычно используют термин приведение присваивания.)

Если приведение помечено как AS IMPLICIT, то оно может быть вызван неявно в любом контексте, будь то присваивание или внутреннее преобразование в выражении. (Для описания такого типа приведений обычно используют термин неявное приведение.) Например, рассмотрим этот запрос:

SELECT 2 + 4.0;

Сначала анализатор помечает константы как относящиеся к типам integer и numeric соответственно. В системных каталогах нет оператора integer + numeric, но есть оператор numeric + numeric Поэтому запрос будет выполнен успешно, если доступно преобразование типа integer к типу numeric с пометкой AS IMPLICIT — и по факту так оно и есть. Синтаксический анализатор применит неявное приведение и обработает запрос, как если бы тот был записан в виде

SELECT CAST ( 2 AS numeric ) + 4.0;

Надо сказать, что системные каталоги также содержат приведение типа numeric к типу integer. Если бы это приведение тоже было помечено как AS IMPLICIT (на самом деле это не так), то анализатор столкнулся бы с выбором между приведенным выше вариантом и приведением константы numeric к типу integer с последующим применением оператора integer + integer. Не имея никаких знаний о том, какой выбор предпочесть, анализатор объявил бы запрос неоднозначным. Именно потому, что только одно из двух приведений неявно, анализатор приходит к пониманию, что предпочтительным является преобразование выражения numeric-и-integer в numeric; отдельного встроенного знания об этом нет.

Разумно проявлять консерватизм в отношении того, объявлять ли приведения неявными. Переизбыток неявных способов приведения может привести к тому, что QHB выберет неожиданные интерпретации команд или даже не сможет их выполнить из-за наличия нескольких возможных интерпретаций. Хорошее эмпирическое правило состоит в том, чтобы сделать приведение неявно вызываемым только для сохраняющих информацию преобразований между типами в одной и той же категории типов. Например, приведение int2 к int4 разумно сделать неявным, но приведение float8 к int4, возможно, лучше сделать только приведением присваивания. Приведения типов разных категорий, например text к int4, лучше делать только явными.

Примечание
Иногда по соображениям удобства использования или соответствия стандартам необходимо обеспечить несколько неявных приведений для набора типов, что приводит к неоднозначности, которую нельзя избежать способом, указанным выше. Чтобы в таких случаях синтаксический анализатор мог обеспечить желаемое поведение, он дополнительно принимает во внимание категории типов и предпочитаемые типы. Более подробную информацию см. на справочной странице команды CREATE TYPE.

Чтобы создать приведение, нужно быть владельцем исходного или целевого типа данных и иметь право USAGE для другого типа. Чтобы создать двоично-сводимое приведение, нужно быть суперпользователем. (Это ограничение введено потому, что преобразование данных с ошибочным двоичным сведением легко может вызвать сбой сервера.)

Параметры

исходный_тип

Имя исходного типа данных для приведения.

целевой_тип

Имя целевого типа данных для приведения.

имя_функции[(тип_аргумента [, ...])]

Функция, используемая для выполнения приведения. Имя функции может быть дополнено схемой. В ином случае для поиска функции просматривается путь поиска. Тип данных результата функции должен соответствовать целевому типу приведения. Аргументы функции рассматриваются ниже. Если список аргументов не указан, имя функции должно быть уникальным в своей схеме.

WITHOUT FUNCTION

Указывает, что исходный тип является двоично-сводимым относительно целевого типа, поэтому функция для приведения не требуется.

WITH INOUT

Указывает, что приведение является преобразованием ввода/вывода, выполняемым путем вызова выходной функции исходного типа данных и передачи результирующей строки во входную функцию целевого типа данных.

AS ASSIGNMENT

Указывает, что приведение может быть вызвано неявно в контекстах присвоения.

AS IMPLICIT

Указывает, что приведение может быть вызвано неявно в любом контексте.

Функции, реализующие приведение, могут иметь от одного до трех аргументов. Тип первого аргумента должен быть идентичным или двоично-сводимым из исходного типа приведения. Второй аргумент, если он присутствует, должен иметь тип integer; он получает модификатор типа, связанный с типом назначения, или -1, если их вообще нет. Третий аргумент, если он присутствует, должен иметь тип boolean; если приведение является явным, в нем передается true; иначе false. (Как ни странно, в некоторых случаях стандарт SQL требует различного поведения для явных и неявных приведений. Этот аргумент предназначен для функций, которые должны реализовывать такие приведения. Однако создавать собственные типы данных, для которых это имело бы значение, не рекомендуется.)

Возвращаемый тип функции приведения должен быть идентичным или двоично-сводимым к целевому типу приведения.

Обычно приведение должно иметь различные типы исходных и целевых данных. Однако разрешается объявлять приведение с одинаковыми исходными и целевыми типами, если функция, реализующая преобразование, имеет более одного аргумента. Это используется для представления в системных каталогах функций, сводящих разные длины типов. Реализующая такое приведение функция используется для сведения значения типа к значению модификатора типа, заданному его вторым аргументом.

Если приведение имеет различные исходные и целевые типы, а также функцию, принимающую более одного аргумента, то оно поддерживает преобразование из одного типа в другой и выполняет сведение к нужной длине за один шаг. Если же соответствующей записи не находится, приведение к типу с определенным модификатором включает два этапа приведения: один для преобразования между типами данных и второй для применения модификатора.

Приведение из типа домена или к типу домена в настоящее время не осуществляется. При попытке выполнить такое приведение, вместо этого выполняется приведение, связанное с его нижележащим типом.

Примечания

Для удаления приведений, созданных пользователем, применяется команда DROP CAST.

Помните, что если вы хотите иметь возможность конвертировать типы в обе стороны, вам нужно явно объявить приведение в обе стороны.

Обычно нет необходимости создавать приведения между пользовательскими типами и стандартными строковыми типами (text, varchar и char(n), а также пользовательскими типами, относящимися к категории строковых). QHB предоставляет для этого автоматические преобразования ввода/вывода. Автоматические приведения к строковым типам обрабатываются как приведения присваивания, тогда как автоматические приведения из строковых типов могут быть только явными. Это поведение можно переопределить, объявив свое собственное приведение на замену автоматическому, но обычно это нужно, только чтобы сделать вызов более удобным, чем стандартное присваивание или явное указание. Другая возможная причина заключается в желании, чтобы преобразование вело себя иначе, чем функция ввода/вывода типа; но это выглядит достаточно странно, поэтому стоит дважды подумать, хорошая ли это идея. (На самом деле у небольшого количества встроенных типов имеются приведения с разным поведением, в основном из-за требований стандарта SQL.)

Несмотря на то, что это необязательно, рекомендуется продолжать следовать старому соглашению именования функций, реализующих приведение, по целевому типу данных. Многие пользователи привыкли иметь возможность приводить типы данных, используя нотацию в стиле функций, т. е. имя_типа(x). Эта нотация на самом деле ни что иное, как вызов функции, реализующей приведение; такой вызов не воспринимается как именно приведение. Если не называть функции преобразования, следуя этому соглашению, это может удивить пользователей. Поскольку QHB позволяет перегружать одно и то же имя функции с различными типами аргументов, ничто не мешает иметь несколько функций преобразования из разных типов, названных по имени этого целевого типа.

Примечание
На самом деле в предыдущем абзаце содержится некоторое упрощение: есть два случая, в которых конструкция вызова функции будет рассматриваться как запрос на приведение без сопоставления его с фактической функцией. Если вызову функции имя(x) в точности не соответствует никакая существующая функция, но имеется тип данных имя и в pg_cast есть двоично-сводимое приведение типа x из этого типа, такой вызов будет воспринят как двоично-сводимое приведение. Это исключение введено, чтобы двоично-сводимое приведение можно было вызвать, используя синтаксис вызова функции, даже если никакой функции преобразования у него нет. Аналогично если запись приведения в pg_cast отсутствует, но приведение было бы к или из строкового типа, вызов будет воспринят как преобразование ввода/вывода. Это исключение позволяет вызывать преобразования ввода/вывода, используя синтаксис вызова функции.

Примечание
Существует также исключение и из этого исключения: преобразование ввода/вывода из составных типов в строковые нельзя вызвать, используя синтаксис вызова функции; его необходимо записать как явное приведение (используя CAST или нотацию ::) Это исключение было добавлено, так как после введения автоматически предоставляемых преобразований ввода/вывода обнаружилось, что такое приведение слишком легко вызвать случайно, намереваясь на самом деле дать ссылку на столбец или функцию.

Примеры

Создание приведения присваивания типа bigint к типу int4 с помощью функции int4(bigint):

CREATE CAST (bigint AS int4) WITH FUNCTION int4(bigint) AS ASSIGNMENT;

(Это приведение уже предопределено в системе.)

Совместимость

Команда CREATE CAST соответствует стандарту SQL, за исключением того, что в стандарте ничего не говорится о двоично-сводимых типах и дополнительных аргументах реализующих функций. Указание AS IMPLICIT тоже является расширением QHB.

См. также

CREATE FUNCTION, CREATE TYPE, DROP CAST

CREATE COLLATION

CREATE COLLATION — определить новое правило сортировки

Синтаксис

CREATE COLLATION [ IF NOT EXISTS ] имя (
    [ LOCALE = локаль, ]
    [ LC_COLLATE = категория_сортировки, ]
    [ LC_CTYPE = категория_типов_символов, ]
    [ PROVIDER = провайдер, ]
    [ DETERMINISTIC = логическое_значение, ]
    [ VERSION = версия ]
)
CREATE COLLATION [ IF NOT EXISTS ] имя FROM существующее_правило

Описание

Команда CREATE COLLATION определяет новое правило сортировки, используя указанные параметры локали операционной системы или копируя существующие правила сортировки.

Чтобы создать правило сортировки, необходимо иметь право CREATE в целевой схеме.

Параметры

IF NOT EXISTS

Не выдавать ошибку, если правило сортировки с тем же именем уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующее правило сортировки как-то соотносится с тем, которое было бы создано.

имя

Имя правила сортировки. Может быть дополнено схемой (если это не так, правило сортировки определяется в текущей схеме). Имя правила сортировки должно быть уникальным в этой схеме. (Системные каталоги могут содержать правила сортировки с тем же именем для других кодировок, но они игнорируются, если кодировка базы данных с ними не совпадает.)

локаль

Это ярлык для одновременной установки LC_COLLATE и LC_CTYPE. Если задать этот параметр, то нельзя будет указать ни один из этих параметров отдельно.

категория_сортировки

Использовать указанную локаль операционной системы в качестве категории локали LC_COLLATE.

категория_типов_символов

Использовать указанную локаль операционной системы в качестве категории локали LC_CTYPE.

провайдер

Задает провайдера, который будет использоваться для служб локали, связанных с данным правилом сортировки. Возможные значения: icu, libc. По умолчанию значение равно libc. Доступные варианты зависят от параметров операционной системы и сборки.

DETERMINISTIC

Определяет, должны ли правила сортировки использовать детерминированные сравнения. Значение по умолчанию равно true (использовать детерминированные сравнения). При детерминированном сравнении строки, которые не равны по байтам, считаются неравными, даже если они считаются логически равными при сравнении. QHB решает вопрос их равенства, используя побайтовое сравнение. Недетерминированное сравнение может сделать правило, к примеру, независимым от ударения или регистра символов. Для этого нужно выбрать подходящий вариант LC_COLLATE и сделать это правило сортировки недетерминированным.

Недетерминированные правила сортировки поддерживаются только с провайдером ICU.

версия

Задает строку версии, сохраняемую с правилом сортировки. Обычно ее следует опускать, что приведет к вычислению версии из фактической версии правила сортировки, предоставленного операционной системой. Этот параметр предназначен к использованию qhb_upgrade для копирования версии из существующей установки.

Что делать при несовпадении версий правил сортировки, описано на справочной странице команды ALTER COLLATION.

существующее_правило

Имя копируемого существующего правила сортировки. Новое правило сортировки будет иметь те же свойства, что и существующее, но будет независимым объектом.

Примечания

CREATE COLLATION устанавливает в системном каталоге pg_collation автоконфликтную блокировку SHARE ROW EXCLUSIVE, поэтому в один момент времени может выполняться только одна команда CREATE COLLATION.

Для удаления пользовательских правил сортировки используйте команду DROP COLLATION.

Дополнительную информацию о создании правил сортировки см. в подразделе Создание новых правил сортировки.

При использовании провайдера правил сортировки libc локаль должна быть применима к текущей кодировке базы данных. Конкретные правила см. на справочной странице команды CREATE DATABASE.

Примеры

Создание правила сортировки из локали операционной системы fr_FR.utf8 (предполагается, что кодировка текущей базы данных — UTF8):

CREATE COLLATION french (locale = 'fr_FR.utf8');

Создание правила сортировки с порядком, принятым в Германии для телефонных книг, с использованием провайдера ICU:

CREATE COLLATION german_phonebook (provider = icu, locale = 'de-u-co-phonebk');

Создание правила сортировки из уже существующего:

CREATE COLLATION german FROM "de_DE";

Иногда удобно использовать в приложениях имена правил сортировки, не зависящие от операционной системы.

Совместимость

В стандарте SQL есть команда CREATE COLLATION, но она ограничивается копированием существующих правил сортировки. Синтаксис для создания новых правил сортировки является расширением QHB.

См. также

ALTER COLLATION, DROP COLLATION

CREATE CONVERSION

CREATE CONVERSION — определить новую перекодировку

Синтаксис

CREATE [ DEFAULT ] CONVERSION имя
    FOR исходная_кодировка TO целевая_кодировка FROM имя_функции

Описание

Команда CREATE CONVERSION определяет новую перекодировку между двумя наборами символов.

Перекодировки, помеченные как DEFAULT, могут использоваться для автоматической перекодировки между клиентом и сервером. Для этого необходимо определить две перекодировки — из кодировки A в кодировку B и из кодировки B в кодировку A.

Чтобы создать перекодировку, необходимо иметь право EXECUTE для соответствующей функции и право CREATE в целевой схеме.

Параметры

DEFAULT

Предложение DEFAULT указывает, что эта перекодировка является перекодировкой по умолчанию для заданной исходной кодировки в целевую. В схеме для пары кодировок должна быть только одна перекодировка по умолчанию.

имя

Имя перекодировки (может быть дополнено схемой). Если схема не указана, перекодировка создается в текущей схеме. Имя перекодировки должно быть уникальным в пределах схемы.

исходная_кодировка

Имя исходной кодировки.

целевая_кодировка

Имя целевой кодировки.

имя_функции

Функция, используемая для выполнения перекодировки. Имя функции может быть дополнено схемой. Если это не так, функции разыскивается в пути поиска.

Функция должна иметь следующую сигнатуру:

conv_proc(
    integer,  -- идентификатор исходной кодировки
    integer,  -- идентификатор целевой кодировки
    cstring,  -- исходная строка (строка C, завершающаяся NULL)
    internal, -- целевая строка (заполняется строкой C, завершающейся NULL)
    integer   -- длина исходной строки
    boolean   -- если true, не выдавать ошибку при сбое перекодировки
) RETURNS integer;

Возвращаемое значение представляет собой количество исходных байтов, которые были успешно преобразованы. Если последний аргумент равен false, функция должна выдать ошибку при недопустимом входном значении. Кроме того, возвращаемое значение всегда равно длине исходной строки.

Примечания

Как исходная, так и целевая кодировки должны отличаться от SQL_ASCII, поскольку поведение сервера в случаях использования «кодировки» SQL_ASCII является аппаратным.

Для удаления пользовательских перекодировок используйте команду DROP CONVERSION.

Права доступа, необходимые для создания перекодировки, могут быть изменены в будущих версиях.

Примеры

Создание перекодировки из кодировки UTF8 в LATIN1 с использованием функции myfunc:

CREATE CONVERSION myconv FOR 'UTF8' TO 'LATIN1' FROM myfunc;

Совместимость

Команда CREATE CONVERSION является расширением QHB. В стандарте SQL нет команды CREATE CONVERSION, но есть команда CREATE TRANSLATION, которая очень похожа по назначению и синтаксису.

См. также

ALTER CONVERSION, CREATE FUNCTION, DROP CONVERSION

CREATE DATABASE

CREATE DATABASE — создать новую базу данных

Синтаксис

CREATE DATABASE имя
    [ [ WITH ] [ OWNER [=] имя_пользователя ]
           [ TEMPLATE [=] шаблон ]
           [ ENCODING [=] кодировка ]
           [ LOCALE [=] локаль ]
           [ LC_COLLATE [=] категория_сортировки ]
           [ LC_CTYPE [=] категория_типов_символов ]
           [ TABLESPACE [=] имя_табличного_пространства ]
           [ ALLOW_CONNECTIONS [=] разрешить_подключения ]
           [ CONNECTION LIMIT [=] предел_подключений ]
           [ IS_TEMPLATE [=] это_шаблон ] ]

Описание

Команда CREATE DATABASE создает новую базу данных QHB.

Чтобы создать базу данных, нужно быть суперпользователем или иметь специальное право CREATEDB. См. справочную страницу команды CREATE ROLE.

По умолчанию новая база данных будет создана путем клонирования стандартной системной базы данных template1. Другой шаблон можно задать, написав TEMPLATE имя. В частности, написав TEMPLATE template0, можно создать чистую базу данных (то есть такую, в которой отсутствуют пользовательские объекты и в которой не были изменены системные объекты), содержащую только стандартные объекты, предопределенные вашей версией QHB). Это полезно, если вы хотите избежать копирования любых локальных объектов установки, которые могли быть добавлены к template1.

Параметры

имя

Имя создаваемой базы данных.

имя_пользователя

Имя роли пользователя, которому будет принадлежать новая база данных, или DEFAULT, чтобы использовать значение по умолчанию (а именно, пользователя, выполняющего эту команду). Чтобы создать базу данных, которой владеет другая роль, необходимо быть прямым или опосредованным членом этой роли или суперпользователем.

шаблон

Имя шаблона, из которого будет создана новая база данных, или DEFAULT, чтобы использовать шаблон по умолчанию (template1).

кодировка

Кодировка набора символов для использования в новой базе данных. Укажите строковую константу (например 'SQL_ASCII'), или целочисленный номер кодировки, или DEFAULT, чтобы использовать кодировку по умолчанию (а именно, кодировку шаблона базы данных). Наборы символов, поддерживаемые сервером QHB, описаны в подразделе Управление правилами сортировки. Дополнительные ограничения см. ниже.

локаль

Это краткая форма установки двух параметров, LC_COLLATE и LC_TYPE, одновременно. При указании этой формы нельзя задать данные параметры по отдельности.

Совет
Другие параметры локали lc_messages, lc_monetary, lc_numeric и lc_time задаются не на уровне базы данных и не устанавливаются этой командой. Если хотите изменить их значения по умолчанию для конкретной базы данных, можно использовать ALTER DATABASE ... SET.

категория_сортировки

Порядок сортировки (LC_COLLATE), который будет использоваться в новой базе данных. Это влияет на порядок сортировки, применяемый к строкам, например, в запросах с ORDER BY, а также на порядок, используемый в индексах по текстовым столбцам. По умолчанию используется порядок сортировки шаблона базы данных. Дополнительные ограничения см. ниже.

категория_типов_символов

Классификация символов (LC_CTYPE), которая будет использоваться в новой базе данных. Это влияет на распределение символов по категориям, например строчные, заглавные и цифры. По умолчанию используется классификация символов шаблона базы данных. Дополнительные ограничения см. ниже.

имя_табличного_пространства

Имя табличного пространства, которое будет связано с новой базой данных, или DEFAULT, чтобы использовать табличное пространство шаблона базы данных. Это табличное пространство будет табличным пространством по умолчанию, используемым для объектов, созданных в этой базе данных. Дополнительную информацию см. на справочной странице команды CREATE TABLESPACE.

разрешить_подключение

Если установлено значение false, то никто не может подключиться к этой базе данных. По умолчанию установлено значение true, разрешающее подключения (за исключением случаев, ограниченных другими механизмами, такими как GRANT/REVOKE CONNECT).

предел_подключений

Предельное число одновременных подключений, которое может быть сделано к этой базе данных. -1 (по умолчанию) означает отсутствие ограничений.

это_шаблон

Если установлено значение true, то эта база данных может быть клонирована любым пользователем с правами CREATEDB; если стоит значение false (по умолчанию), то клонировать базу данных могут только суперпользователи или владелец этой базы.

Необязательные параметры можно записать в любом порядке, а не только в показанном выше.

Примечания

Команду CREATE DATABASE нельзя выполнить внутри блока транзакции.

Ошибки вида «не удалось инициализировать каталог базы данных», скорее всего, связаны с недостатком разрешений в каталоге данных, переполненным диском или другими проблемами в файловой системе.

Для удаления базы данных воспользуйтесь командой DROP DATABASE.

Программа createdb является программой-оболочкой этой команды, предоставляемой для удобства.

Параметры конфигурации уровня базы данных (устанавливаемые через ALTER DATABASE), а также разрешения уровня базы данных (устанавливаемые через GRANT) не копируются из шаблона базы данных.

Хотя можно скопировать не только template1, но и другую базу данных, указав ее имя в качестве шаблона, данная команда не предназначена (пока) для использования в качестве универсального средства вроде «COPY DATABASE». Основное ограничение заключается в том, что во время копирования никто не должен быть подключен к базе данных, указанной в качестве шаблона. CREATE DATABASE завершится ошибкой, если при запуске команды есть другие подключения к этой базе; в противном случае новые соединения с базой данных шаблона будут заблокированы до завершения процесса CREATE DATABASE. Дополнительную информацию см. в разделе Шаблоны баз данных.

Кодировка набора символов, заданная для новой базы данных, должна быть совместима с выбранными параметрами локали (LC_COLLATE и LC_CTYPE). Если выбрана локаль С (или равнозначная ей POSIX), то разрешены все кодировки, но для других параметров локали есть только одна кодировка, которая будет работать должным образом. CREATE DATABASE позволяет суперпользователям указывать кодировку SQL_ASCII независимо от настроек локали, но этот вариант устарел и может привести к неправильному использованию функций символьной строки, если в базе хранятся данные, несовместимые с кодировкой локали.

Параметры кодировки и локали должны соответствовать параметрам шаблона базы данных, за исключением случаев, когда в качестве шаблона используется template0. Это связано с тем, что другие базы данных могут содержать данные, не соответствующие указанной кодировке, или индексы, порядок сортировки которых зависит от LC_COLLATE и LC_CTYPE. Копирование таких данных может привести к повреждению базы данных в соответствии с новыми параметрами. Однако template0, как известно, не содержит никаких данных или индексов, которые могло бы затронуть.

Ограничение CONNECTION LIMIT применяется только приблизительно; если два новых сеанса начинаются примерно в то же время, когда для базы данных остается только один «слот» на подключение, не исключено, что оба подключения завершатся неудачей. Кроме того, это ограничение не применяется к суперпользователям или фоновым рабочим процессам.

Примеры

Создание новой базы данных:

CREATE DATABASE lusiadas;

Создание базы данных sales, принадлежащей пользователю salesapp, с табличным пространством по умолчанию salesspace:

CREATE DATABASE sales OWNER salesapp TABLESPACE salesspace;

Создание базы данных music с другой локалью:

CREATE DATABASE music
    LC_COLLATE 'sv_SE.utf8' LC_CTYPE 'sv_SE.utf8'
    TEMPLATE template0;

В этом примере предложение TEMPLATE template0 необходимо, только если указанная локаль отличается от локали в template1. (В противном случае явное указание локали является избыточным.)

Создание базы данных music2 с другой локалью и другой кодировкой набора символов:

CREATE DATABASE music2
    LC_COLLATE 'sv_SE.iso885915' LC_CTYPE 'sv_SE.iso885915'
    ENCODING LATIN9
    TEMPLATE template0;

Параметры кодировки должны соответствовать параметрам локали, иначе возникнет ошибка.

Обратите внимание, что имена локалей зависят от операционной системы, поэтому показанные выше команды могут не везде работать одинаково.

Совместимость

В стандарте SQL нет команды CREATE DATABASE. Базы данных равнозначны каталогам, создание которых определяется реализацией.

См. также

ALTER DATABASE, DROP DATABASE

CREATE DOMAIN

CREATE DOMAIN — определить новый домен

Синтаксис

CREATE DOMAIN имя [ AS ] тип_данных
    [ COLLATE правило_сортировки ]
    [ DEFAULT выражение ]
    [ ограничение [ ... ] ]

где ограничением может быть:

[ CONSTRAINT имя_ограничения ]
{ NOT NULL | NULL | CHECK (выражение) }

Описание

Команда CREATE DOMAIN создает новый домен. Домен по существу является типом данных с дополнительными ограничениями (ограничениями на допустимый набор значений). Пользователь, определяющий домен, становится его владельцем.

Если задано имя схемы (например CREATE DOMAIN myschema.mydomain ...), то домен создается в указанной схеме, в противном случае — в текущей. Имя домена должно быть уникальным среди имен типов и доменов, существующих в его схеме.

Домены полезны для реферирования общих характеристик разных полей в единое место для упрощения сопровождения. Например, несколько таблиц могут содержать столбцы адресов электронной почты, для проверки синтаксиса которых требуется одно и то же проверочное ограничение CHECK. Определите домен, вместо того чтобы устанавливать ограничение для каждой таблицы по отдельности.

Чтобы иметь возможность создать домен, необходимо иметь право USAGE для нижележащего типа.

Параметры

имя

Имя создаваемого домена (может быть дополнено схемой).

тип_данных

Нижележащий тип данных домена. Может включать определение массива с этим типом.

правило_сортировки

Необязательное правило сортировки для домена. Если правило сортировки не указано, используется правило сортировки по умолчанию для нижележащего типа данных. Указать COLLATE можно, только если нижележащий тип данных является сортируемым.

DEFAULT выражение

Предложение DEFAULT задает значение по умолчанию для столбцов, типом данных которых является этот домен. Значением является любое выражение без переменной (но подзапросы не разрешены). Тип данных выражения по умолчанию должен соответствовать типу данных домена. Если значение по умолчанию не указано, оно равно NULL.

Выражение по умолчанию будет использоваться в любой операции добавления, где не задано значение для столбца. Если для конкретного столбца определено значение по умолчанию, оно переопределяет все значения по умолчанию, связанные с доменом. В свою очередь, значение по умолчанию для домена переопределяет любое значение по умолчанию, связанное с нижележащим типом данных.

CONSTRAINT имя_ограничения

Необязательное имя ограничения. Если не указано, то имя генерирует система.

NOT NULL

Значения этого домена не могут быть равны NULL (но см. Примечания ниже).

NULL

Значения этого домена могут быть равны NULL. Это значение по умолчанию.

Это предложение предназначено только для обеспечения совместимости с нестандартными базами данных SQL. В новых приложениях его использование не рекомендуется.

CHECK (выражение)

Предложения CHECK определяют ограничения целостности или проверки, которым должны удовлетворять значения домена. Каждое ограничение должно быть выражением, дающим логический результат. Проверяемое значение в этом выражении должно обозначаться ключевым словом VALUE. Выражения, выдающие значение TRUE или UNKNOWN, успешно выполняются. Если выражение выдает FALSE, появляется сообщение об ошибке, и значение не может быть преобразовано в тип домена.

В настоящий момент выражения CHECK не могут содержать подзапросы и ссылаться на другие переменные, кроме VALUE.

Когда домен имеет несколько ограничений CHECK, они будут проверяться в алфавитном порядке по названию.

Примечания

Ограничения домена, в частности, NOT NULL, проверяются при преобразовании значения в тип домена. Бывает, что даже несмотря на наличие такого ограничения, столбец, который номинально относится к типу домена, все же можно прочитать как NULL. Например, это может произойти в запросе внешнего соединения, если столбец домена находится на стороне внешнего соединения, допускающей значения NULL. Более тонкий пример:

INSERT INTO tab (domcol) VALUES ((SELECT domcol FROM tab WHERE false));

Пустой скалярный подзапрос SELECT выдаст значение NULL, которое считается доменным типом, поэтому к нему не применяется дополнительная проверка ограничений, и добавление будет выполнено успешно.

Очень трудно избежать подобных проблем из-за общего предположения SQL, что значение NULL является допустимым значением для каждого типа данных. Поэтому наилучшим решением будет разработать ограничения домена таким образом, чтобы разрешить значение NULL, а затем применять ограничения NOT NULL для столбцов типа домена по мере необходимости, а не непосредственно для типа домена.

QHB предполагает, что условия ограничений CHECK неизменны, то есть они всегда будут давать один и тот же результат для одного и того же входного значения. Именно это предположение объясняет проверку ограничений CHECK только при первом преобразовании значения в тип домена, а не при каждом обращении к нему. (По сути, таким же образом обрабатываются ограничения CHECK для таблиц, как описано в подразделе Проверочные ограничения.)

Примером распространенного способа нарушения данного предположения является обращение в выражении CHECK к пользовательской функции, а затем изменение ее поведения. QHB не запрещает этого, но и не заметит, если есть сохраненные значения типа домена, которые теперь нарушают ограничение CHECK. Это может привести к сбою последующего дампа и перезагрузки базы данных. Рекомендуемый способ осуществления такого изменения состоит в том, чтобы удалить ограничение (используя ALTER DOMAIN), настроить определение функции и повторно добавить ограничение, тем самым перепроверив его относительно сохраненных данных.

Примеры

В этом примере создается тип данных us_postal_code (почтовый индекс США), который затем используется в определении таблицы. Для проверки значения на соответствие формату почтовых индексов США применяется проверка с регулярным выражением:

CREATE DOMAIN us_postal_code AS TEXT
CHECK(
   VALUE ~ '^\d{5}$'
OR VALUE ~ '^\d{5}-\d{4}$'
);

CREATE TABLE us_snail_addy (
  address_id SERIAL PRIMARY KEY,
  street1 TEXT NOT NULL,
  street2 TEXT,
  street3 TEXT,
  city TEXT NOT NULL,
  postal us_postal_code NOT NULL
);

Совместимость

Команда CREATE DOMAIN соответствует стандарту SQL.

См. также

ALTER DOMAIN, DROP DOMAIN

CREATE EVENT TRIGGER

CREATE EVENT TRIGGER — определить новый триггер события

Синтаксис

CREATE EVENT TRIGGER имя
    ON событие
    [ WHEN переменная_фильтра IN (значение_фильтра [, ... ]) [ AND ... ] ]
    EXECUTE { FUNCTION | PROCEDURE } имя_функции()

Описание

Команда CREATE EVENT TRIGGER создает новый триггер события. Всякий раз, когда происходит назначенное событие и удовлетворяется связанное с триггером условие WHEN (при наличии), функция триггера будет выполнена. Общие сведения о событийных триггерах см. в главе Триггеры событий. Пользователь, создающий триггер события, становится его владельцем.

Параметры

имя

Имя, назначаемое новому триггер. Это имя должно быть уникальным в пределах базы данных.

событие

Имя события, которое вызывает срабатывание триггера и вызов функции. Дополнительную информацию об именах событий см. в разделе Поведение триггера событий.

переменная_фильтра

Имя переменной, используемой для фильтрации событий. Это позволяет ограничить запуск триггера подмножеством случаев, в которых он поддерживается. В настоящее время единственным поддерживаемым значением параметра переменная_фильтра является TAG.

значение_фильтра

Список значений для связанного параметра переменная_фильтра, для которых должен срабатывать триггер. Для переменной TAG это подразумевает список меток команд (например 'DROP FUNCTION').

имя_функции

Предоставляемая пользователем функция, которая заявлена как не принимающая аргументов и возвращающая тип event_trigger.

В синтаксисе CREATE EVENT TRIGGER ключевые слова FUNCTION и PROCEDURE равнозначны, но указанная функция должна в любом случае быть функцией, а не процедурой. Использование здесь ключевого слова PROCEDURE поддерживается по историческим причинам и считается устаревшим.

Примечания

Только суперпользователи могут создавать триггеры событий.

В однопользовательском режиме триггеры событий выключены (см. описание qhb). Если ошибочный триггер события заблокировал базу данных настолько, что его даже невозможно удалить, перезагрузите сервер в однопользовательском режиме, и вы сможете это сделать.

Примеры

Запрет на выполнение любой команды DDL:

CREATE OR REPLACE FUNCTION abort_any_command()
  RETURNS event_trigger
 LANGUAGE plpgsql
  AS $$
BEGIN
  RAISE EXCEPTION 'command % is disabled', tg_tag;
END;
$$;

CREATE EVENT TRIGGER abort_ddl ON ddl_command_start
   EXECUTE FUNCTION abort_any_command();

Совместимость

В стандарте SQL нет команды CREATE EVENT TRIGGER.

См. также

ALTER EVENT TRIGGER, DROP EVENT TRIGGER, CREATE FUNCTION

CREATE EXTENSION

CREATE EXTENSION — установить расширение

Синтаксис

CREATE EXTENSION [ IF NOT EXISTS ] имя_расширения
    [ WITH ] [ SCHEMA имя_схемы ]
             [ VERSION версия ]
             [ CASCADE ]

Описание

Команда CREATE EXTENSION загружает в текущую базу данных новое расширение. Уже загруженного расширения с тем же именем в базе быть не должно.

Загрузка расширения по существу сводится к запуску файла скрипта расширения. Скрипт обычно создает новые объекты SQL, например, функции, типы данных, операторы и методы поддержки индексов. CREATE EXTENSION дополнительно записывает идентификаторы всех созданных объектов, так что впоследствии их можно снова удалить, выполнив команду DROP EXTENSION.

Пользователь, запускающий CREATE EXTENSION, становится владельцем расширения для последующих проверок его прав, а также, как правило, владельцем всех объектов, созданных скриптом расширения.

Для загрузки расширения требуются те же права доступа, что и для создания составляющих его объектов. Для большинства расширений это означает, что необходимы права суперпользователя или владельца базы данных. Однако если расширение помечено в своем управляющем файле как trusted (доверенное), его может установить любой пользователь с правом CREATE в текущей базе данных. В этом случае сам объект расширения будет принадлежать вызывающему пользователю, но владельцем содержащихся в нем объектов будет суперпользователь, выполнявший начальную загрузку (если только скрипт расширения явно не назначит их владельцем вызывающего пользователя). Эта конфигурация дает вызывающему пользователю право удалить расширение, но не модифицировать отдельные объекты внутри него.

Параметры

IF NOT EXISTS

Не выдавать ошибку, если расширение с тем же именем уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующее расширение как-то соотносится с тем, которое было бы создано из доступного в данный момент скрипта.

имя_расширения

Имя устанавливаемого расширения. QHB создаст расширение, используя сведения из файла SHAREDIR/extension/имя_расширения.control.

имя_схемы

Имя схемы, в которой будут устанавливаться объекты расширения (при условии, что расширение позволяет перемещать его содержимое). Названная схема уже должна существовать. Если этот параметр не указан, а в управляющем файле расширения схема тоже не указана, то используется текущая схема создания объекта по умолчанию.

Если в управляющем файле расширения задается параметр schema, то эту схему нельзя переопределить предложением SCHEMA. Обычно при указании предложения SCHEMA возникает ошибка, если эта схема конфликтует с параметром schema данного расширения. Однако если также задано предложение CASCADE, то в случае конфликта имя_схемы игнорируется. Заданное имя_схемы будет использоваться для установки любых необходимых расширений, если в их управляющем файле не указана schema.

Помните, что само расширение не считается частью какой-либо схемы: расширения имеют неполные имена, которые должны быть уникальными для всей базы данных. Но объекты, принадлежащие расширению, могут находиться внутри схем.

версия

Версия устанавливаемого расширения. Ее можно записать в виде идентификатора или строкового значения. Версия по умолчанию указана в управляющем файле расширения.

CASCADE

Автоматически устанавливать все расширения, от которых зависит это расширение и которые еще не установлены. Их зависимости также устанавливаются автоматически, рекурсивно. Предложение SCHEMA (если указано) применяется ко всем расширениями, которые устанавливаются таким образом. Другие параметры оператора к автоматически устанавливаемым расширениям не применяются; в частности, всегда выбираются их версии по умолчанию.

Примечания

Прежде чем использовать CREATE EXTENSION для загрузки расширения в базу данных, необходимо установить его вспомогательные файлы. Информацию об установке расширений, поставляемых с QHB, можно найти в разделе Расширения QHB.

Расширения, доступные для загрузки в настоящее время, можно найти в системных представлениях pg_available_extensions или pg_available_extension_versions.

ВНИМАНИЕ!
Установка расширение от имени суперпользователя требует уверенности в том, что автор расширения написал его установочный скрипт безопасным образом. Для злоумышленного пользователя не составит особого труда создать объекты типа троянского коня, которые впоследствии скомпрометируют выполнение небрежно написанного скрипта расширения, позволив этому пользователю получить права суперпользователя. Однако такие объекты опасны, только если во время выполнения скрипта находятся в пути search_path , то есть если они находятся в схеме, в которую устанавливается расширение, или в схеме, где располагается другое расширение, от которого зависит первое. Таким образом, имея дело с расширениями, скрипты которых не были тщательно проверены, рекомендуется устанавливать их только в те схемы, для которых недоверенные пользователи не имеют и не будут иметь права CREATE. То же самое касается других расширений, от которых они зависят.

Расширения, поставляемые в составе QHB, можно считать защищенными от подобного рода атак времени выполнения, за исключением некоторых, зависящих от других расширений. Как отмечается в документации таких расширений, их следует устанавливать в безопасные схемы и/или в те же схемы, в которые устанавливаются требующиеся им расширения.

Дополнительную информацию о создании новых расширений см. в разделе Упаковка связанных объектов в расширение.

Примеры

Установка расширения hstore в текущую базу данных с размещением его объектов в схеме addons:

CREATE EXTENSION hstore SCHEMA addons;

Еще один способ сделать вышеописанную операцию:

SET search_path = addons;
CREATE EXTENSION hstore;

Совместимость

Команда CREATE EXTENSION является расширением QHB.

См. также

ALTER EXTENSION, DROP EXTENSION

CREATE FOREIGN DATA WRAPPER

CREATE FOREIGN DATA WRAPPER — определить новую обертку сторонних данных

Синтаксис

CREATE FOREIGN DATA WRAPPER имя
    [ HANDLER функция_обработки | NO HANDLER ]
    [ VALIDATOR функция_проверки | NO VALIDATOR ]
    [ OPTIONS ( параметр 'значение' [, ... ] ) ]

Описание

Команда CREATE FOREIGN DATA WRAPPER создает новую обертку сторонних данных. Пользователь, определяющий обертку сторонних данных, становится ее владельцем.

Имя обертки сторонних данных должно быть уникальным в пределах базы данных.

Только суперпользователи могут создавать обертки сторонних данных.

Параметры

имя

Имя создаваемой обертки сторонних данных.

HANDLER функция_обработки

функция_обработки — это имя ранее зарегистрированной функции, которая будет вызвана для извлечения функций, реализующих обращение к сторонним таблицам. Функция обработки должна быть без аргументов, и ее возвращаемый тип должен быть fdw_handler.

Можно создать обертку сторонних данных без функции обработки, но сторонние таблицы, использующие такую обертку, могут быть только объявлены, но не доступны.

VALIDATOR функция_проверки

функция_проверки — это имя ранее зарегистрированной функции, которая будет вызвана для проверки общих параметров, передаваемых обертке сторонних данных, а также параметров для сторонних серверов, сопоставлений пользователей и сторонних таблиц, использующих эту обертку сторонних данных. Если функции проверки нет или указано NO VALIDATOR, то во время создания объектов параметры проверяться не будут. (Возможно, обертки сторонних данных будут игнорировать или отклонять недопустимые значения параметров во время выполнения, в зависимости от реализации.) Функция проверки должна принимать два аргумента: первый типа text[] (в нем содержится массив параметров, хранящихся в системном каталоге), а второй типа oid (в нем указывается OID системного каталога, содержащего эти параметры). Возвращаемое значение игнорируется; функция должна сообщить о недопустимых параметрах с помощью системной функции ereport(ERROR).

OPTIONS ( параметр 'значение' [, ... ] )

Это предложение задает параметры для новой обертки сторонних данных. Допустимые имена и значения параметров являются специфичными для каждой обертки сторонних данных и проверяются с помощью функции проверки. Имена параметров должны быть уникальными.

Примечания

Функциональность работы QHB со сторонними данными все еще находится в стадии активной разработки. Оптимизация запросов примитивна (и по большей части это тоже делает обертка). Таким образом, имеются широкие возможности для дальнейшего повышения производительности.

Примеры

Создание бесполезной обертки сторонних данных dummy:

CREATE FOREIGN DATA WRAPPER dummy;

Создание обертки сторонних данных file с функцией обработки file_fdw_handler:

CREATE FOREIGN DATA WRAPPER file HANDLER file_fdw_handler;

Создание обертки сторонних данных mywrapper с некими параметрами:

CREATE FOREIGN DATA WRAPPER mywrapper
    OPTIONS (debug 'true');

Совместимость

Команда CREATE FOREIGN DATA WRAPPER соответствует стандарту ISO/IEC 9075-9 (SQL/MED), за исключением того, что в QHB предложения HANDLER и VALIDATOR являются расширениями, а стандартные предложения LIBRARY и LANGUAGE не реализованы.

Обратите внимание, однако, что соответствие функциональности SQL/MED в комплексе пока не достигнуто.

См. также

ALTER FOREIGN DATA WRAPPER, DROP FOREIGN DATA WRAPPER, CREATE SERVER, CREATE USER MAPPING, CREATE FOREIGN TABLE

CREATE FOREIGN TABLE

CREATE FOREIGN TABLE — определить новую стороннюю таблицу

Синтаксис

CREATE FOREIGN TABLE [ IF NOT EXISTS ] имя_таблицы ( [
  { имя_столбца тип_данных [ OPTIONS ( параметр 'значение' [, ... ] ) ] [ COLLATE правило_сортировки ] [ ограничение_столбца [ ... ] ]
    | ограничение_таблицы }
    [, ... ]
] )
[ INHERITS ( таблица_родитель [, ... ] ) ]
  SERVER имя_сервера
[ OPTIONS ( параметр 'значение' [, ... ] ) ]

CREATE FOREIGN TABLE [ IF NOT EXISTS ] имя_таблицы
  PARTITION OF таблица_родитель [ (
  { имя_столбца [ WITH OPTIONS ] [ ограничение_столбца [ ... ] ]
    | ограничение_таблицы }
    [, ... ]
) ]
{ FOR VALUES указание_границ_партиции | DEFAULT }
  SERVER имя_сервера
[ OPTIONS ( параметр 'значение' [, ... ] ) ]

где ограничением_столбца может быть:

[ CONSTRAINT имя_ограничения ]
{ NOT NULL |
  NULL |
  CHECK ( выражение ) [ NO INHERIT ] |
  DEFAULT выражение_по_умолчанию |
  GENERATED ALWAYS AS ( генерирующее_выражение ) STORED }

и ограничение_таблицы:

[ CONSTRAINT имя_ограничения ]
CHECK ( выражение ) [ NO INHERIT ]

и указание_границ_партиции:

IN ( выражение_границ_партиции [, ...] ) |
FROM ( { выражение_границ_партиции | MINVALUE | MAXVALUE } [, ...] )
  TO ( { выражение_границ_партиции | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS числовой_литерал, REMAINDER числовой_литерал )

Описание

Команда CREATE FOREIGN TABLE создает новую стороннюю таблицу в текущей базе данных. Таблица будет принадлежать пользователю, выполнившему эту команду.

Если задано имя схемы (например CREATE FOREIGN TABLE myschema.mytable ...), то таблица создается в указанной схеме, в противном случае — в текущей. Имя сторонней таблицы должно отличаться от имени любой другой сторонней таблицы, а также обычной таблицы, последовательности, индекса, представления или материализованного представления в той же схеме.

Также CREATE FOREIGN TABLE автоматически создает тип данных, который представляет собой составной тип, соответствующий одной строке сторонней таблицы. По этой причине сторонние таблицы не могут иметь имя, совпадающее с именем любого существующего типа данных в той же схеме.

Если указано предложение PARTITION OF, таблица создается в виде партиции parent_table с заданными границами.

Чтобы создать стороннюю таблицу, необходимо иметь право USAGE для стороннего сервера, а также право USAGE для всех типов столбцов, содержащихся в таблице.

Параметры

IF NOT EXISTS

Не считать ошибкой, если отношение с таким именем уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующее отношение как-то соотносится с тем, которое было бы создано.

имя_таблицы

Имя создаваемой таблицы (может быть дополнено схемой).

имя_столбца

Имя столбца, который будет создан в новой таблице.

тип_данных

Тип данных столбца. Может включать спецификаторы массива. Более подробную информацию о типах данных, поддерживаемых QHB, см. в главе Типы данных.

COLLATE правило_сортировки

Предложение COLLATE задает правило сортировки для столбца (который должен иметь сортируемый тип данных). Если оно не указано, используются правила сортировки по умолчанию для типа данных столбца.

INHERITS ( таблица_родитель [, ... ] )

Необязательное предложение INHERITS задает список таблиц, из которых новая сторонняя таблица автоматически наследует все столбцы. Родительские таблицы могут быть простыми или сторонними таблицами. Более подробную информацию см. в аналогичной форме команды CREATE TABLE.

PARTITION OF таблица_родитель FOR VALUES указание_границ_партиции

Эту форму можно использовать для создания сторонней таблицы в виде партиции указанной родительской таблицы с заданными значениями границ. Более подробную информацию см. в аналогичной форме команды CREATE TABLE. Обратите внимание, что в настоящее время не разрешается создавать стороннюю таблицу в качестве партиции родительской таблицы, если в последней есть индексы UNIQUE (уникальные). (См. также ALTER TABLE ATTACH PARTITION.)

CONSTRAINT имя_ограничения

Необязательное имя ограничения столбца или таблицы. Если ограничение нарушается, имя ограничения присутствует в сообщениях об ошибках, поэтому имена ограничений, например столбец должен быть положительным, могут использоваться для передачи клиентским приложениям полезной информации об ограничениях. (Имена ограничений, содержащие пробелы, необходимо заключать в кавычки.) Если имя ограничения не указано, система генерирует его автоматически.

NOT NULL

Столбец не может содержать значения NULL.

NULL

Столбец может содержать значения NULL. Это значение по умолчанию.

Это предложение предусмотрено только для обеспечения совместимости с нестандартными базами данных SQL. Использовать его в новых приложениях не рекомендуется.

CHECK ( выражение ) [ NO INHERIT ]

Предложение CHECK задает выражение, выдающее логический результат, которому должна удовлетворять каждая строка в сторонней таблице; то есть выражение должно выдавать TRUE или UNKNOWN, но не FALSE для всех строк в сторонней таблице. Проверочное ограничение, указанное в качестве ограничения столбца, должно ссылаться только на значение этого столбца, тогда как выражение, указанное в ограничении таблицы, может ссылаться на несколько столбцов.

В настоящий момент выражения CHECK не могут содержать подзапросы и ссылаться на переменные, отличные от столбцов текущей строки. Также допустима ссылка на системный столбец tableoid, но не на другие системные столбцы.

Ограничение с пометкой NO INHERIT не будет наследоваться дочерними таблицами.

DEFAULT выражение_по_умолчанию

Предложение DEFAULT задает значение по умолчанию для столбца, в определении которого оно присутствует. Значением является любое выражение без переменных (подзапросы и перекрестные ссылки на другие столбцы текущей таблицы запрещены). Тип данных выражения по умолчанию должен соответствовать типу данных столбца.

Выражение по умолчанию будет использоваться в любой операции добавления, в которой не задано значение для столбца. Если значение по умолчанию для столбца не задано, то этим значением будет NULL.

GENERATED ALWAYS AS ( генерирующее_выражение ) STORED

Это предложение создает столбец как генерируемый. В такой столбец нельзя записать данные, а при его чтении возвращается результат указанного выражения

Ключевое слово STORED требуется для обозначения того, что столбец будет вычисляться при записи. (Вычисленное значение будет передаваться обертке сторонних данных для сохранения и должно быть возвращено оберткой при чтении.)

Генерирующее выражение может ссылаться на другие столбцы в таблице, но не на другие генерируемые столбцы. Все функции и операторы в нем должны быть постоянными. Ссылки на другие таблицы не допускаются.

имя_сервера

Имя существующего стороннего сервера, используемого для сторонней таблицы. Дополнительную информацию об определении сервера см. на справочной странице команды CREATE SERVER.

OPTIONS ( параметр 'значение' [, ...] )

Параметры, которые будут связаны с новой сторонней таблицей или одним из ее столбцов. Допустимые имена параметров и их значений являются специфичными для каждой обертки сторонних данных и проверяются с помощью функции проверки. Имена параметров не должны повторяться (хотя параметр таблицы и параметр столбца могут иметь одинаковые имена).

Примечания

Ограничения сторонних таблицы (такие как CHECK или NOT NULL) не контролируются ядром системы QHB, и большинство оберток сторонних данных также не пытаются их контролировать; то есть система просто предполагает, что ограничение выполняется. Подобный контроль был бы нецелесообразен, поскольку применялся бы только к строкам, добавленным или обновленным через стороннюю таблицу, а не к строкам, измененным другими способами, например, непосредственно на удаленном сервере. Вместо этого ограничение, связанное со сторонней таблицей, должно представлять собой ограничение, контролируемое удаленным сервером.

Некоторые специальные обертки сторонних данных могут быть единственным механизмом доступа к данным, к которым они обращаются, и в этом случае может быть уместно выполнение контроля ограничений самой оберткой сторонних данных. Но не следует полагать, что какая-либо обертка ведет себя так, если об этом не сказано явно в ее документации.

Хотя QHB не пытается контролировать ограничения для сторонних таблиц, она предполагает, что они выполняются для целей оптимизации запросов. Если есть строки, видимые в сторонней таблице, которые не удовлетворяют объявленному ограничению, запросы к таблице могут выдавать некорректные результаты. Ответственность за то, чтобы условия ограничения выполнялись, несет пользователь.

ВНИМАНИЕ!
Когда сторонняя таблица используется в качестве партиции партиционированной таблицы, существует неявное ограничение, что ее содержимое должно удовлетворять правилу партиционирования. Опять же, обеспечивать соблюдение этого ограничения — задача пользователя, и лучше всего это можно сделать, установив соответствующее ограничение на стороннем сервере.

В партиционированной таблице, содержащей в роли партиций сторонние таблицы, команда UPDATE, меняющая значение ключа разбиения, может вызвать перемещение строки из локальной партиции в партицию-стороннюю таблицу при условии, что обертка сторонних данных поддерживает перераспределение кортежей. Однако перемещение строки из партиции-сторонней таблицы в другую партиции в настоящее время невозможно. UPDATE, которая потребует сделать это, завершится ошибкой вследствие ограничения партиционирования, если оно должным образом установлено на удаленном сервере.

Подобные соображения распространяются и на генерируемые столбцы. Сохраненные генерируемые столбцы вычисляются при добавлении или обновлении на локальном сервере QHB и передаются обертке сторонних данных для записи в стороннее хранилище данных. При этом сервер не требует, чтобы запросы к сторонней таблице возвращали значения сохраненных генерируемых столбцов, соответствующие генерирующему выражению. Опять же, это может привести к неверным результатам запроса.

Примеры

Создание сторонней таблицы films, которая будет доступна через сервер film_server:

CREATE FOREIGN TABLE films (
    code        char(5) NOT NULL,
    title       varchar(40) NOT NULL,
    did         integer NOT NULL,
    date_prod   date,
    kind        varchar(10),
    len         interval hour to minute
)
SERVER film_server;

Создание сторонней таблицы measurement_y2016m07, которая будет доступна через сервер server_07, в виде партиции таблицы measurement, партиционированной по диапазонам:

CREATE FOREIGN TABLE measurement_y2016m07
    PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')
    SERVER server_07;

Совместимость

Команда CREATE FOREIGN TABLE в значительной степени соответствует стандарту SQL, однако, так же, как и CREATE TABLE, допускает ограничения NULL и сторонние таблицы без столбцов. Возможность указать значения столбца по умолчанию также является расширением QHB. Наследование таблиц в форме, определенной QHB, не соответствует стандарту.

См. также

ALTER FOREIGN TABLE, DROP FOREIGN TABLE, CREATE TABLE, CREATE SERVER, IMPORT FOREIGN SCHEMA

CREATE FUNCTION

CREATE FUNCTION — определить новую функцию

Синтаксис

CREATE [ OR REPLACE ] FUNCTION
    имя ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ { DEFAULT | = } выражение_по_умолчанию ] [, ...] ] )
    [ RETURNS возвращаемый_тип
      | RETURNS TABLE ( имя_столбца тип_столбца [, ...] ) ]
  { LANGUAGE имя_языка
    | TRANSFORM { FOR TYPE имя_типа } [, ... ]
    | WINDOW
    | { IMMUTABLE | STABLE | VOLATILE }
    | [ NOT ] LEAKPROOF
    | { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT }
    | { [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER }
    | PARALLEL { UNSAFE | RESTRICTED | SAFE }
    | COST стоимость_выполнения
    | ROWS строк_в_результате
    | SUPPORT вспомогательная_функция
    | SET параметр_конфигурации { TO значение | = значение | FROM CURRENT }
    | AS 'определение'
    | AS 'объектный_файл', 'ссылочный_символ'
    | тело_sql
  } ...

Описание

Команда CREATE FUNCTION определяет новую функцию. CREATE OR REPLACE FUNCTION будет либо создавать новую функцию, либо заменять определение существующей. Чтобы определить функцию, пользователь должен иметь право USAGE для соответствующего языка.

Если указано имя схемы, то функция создается в указанной схеме, в противном случае — в текущей. Имя новой функции не должно совпадать ни с одной существующей функцией или процедурой с теми же типами входных аргументов в этой же схеме. Однако функции и процедуры с различными типами аргументов могут иметь одно имя (это называется перегрузкой).

Чтобы заменить текущее определение существующей функции, используйте CREATE OR REPLACE FUNCTION. Однако таким способом невозможно изменить имя или типы аргументов функции (если вы попытаетесь, то фактически создадите новую, независимую функцию). Кроме того, CREATE OR REPLACE FUNCTION не позволит изменить тип возвращаемого значения существующей функции. Для этого необходимо удалить и заново создать функцию. (В случае использования параметров OUT (выходных) это означает, что изменить типы параметров OUT можно, только удалив функцию.)

Когда CREATE OR REPLACE FUNCTION используется для замены существующей функции, владелец и права доступа функции не изменяются. Всем остальным свойствам функции присваиваются значения, указанные в команде, или значения по умолчанию. Чтобы заменить функцию, необходимо быть ее владельцем (или быть членом роли-владельца).

Если вы удалите и снова создадите функцию, новая функция будет другой сущностью; вам придется удалить существующие правила, представления, триггеры и т. д. — всё, что относится к старой функции. Поэтому чтобы изменить определение функции, не ломая ссылающиеся на нее объекты, используйте команду CREATE OR REPLACE FUNCTION. Кроме того, многие дополнительные свойства существующей функции можно изменить с помощью команды ALTER FUNCTION.

Пользователь, создавший функцию, становится ее владельцем.

Чтобы создать функцию, необходимо иметь право USAGE для типов ее аргументов и возвращаемого типа.

Более подробную информацию о написании функций см. в разделе Пользовательские функции.

Параметры

имя

Имя создаваемой функции (может быть дополнено схемой).

режим_аргумента

Режим аргумента: IN (входной), OUT (выходной), INOUT (входной и выходной) или VARIADIC (переменный). Если этот параметр опущен, значение по умолчанию равно IN. За аргументом VARIADIC могут следовать только аргументы OUT. Также аргументы OUT и INOUT нельзя использовать с предложением RETURNS TABLE.

имя_аргумента

Имя аргумента. Некоторые языки (включая SQL и PL/pgSQL) позволяют использовать это имя в теле функции. Для других языков имя входного аргумента является просто дополнительным описанием, если дело касается самой функции; но имена входных аргументов при вызове функции можно использовать для улучшения читаемости (см. раздел Вызов функции). В любом случае имя выходного аргумента является значимым, поскольку оно определяет имя столбца в типе строки результата. (Если вы опустите имя выходного аргумента, система выберет имя столбца по умолчанию.)

тип_аргумента

Тип(ы) данных аргументов функции (может быть дополнен схемой), если аргументы имеются. Типы аргументов могут быть базовыми, составными или доменными типами, а также могут ссылаться на тип столбца таблицы.

В зависимости от языка реализации также может быть разрешено указывать «псевдотипы», например cstring. Псевдотипы свидетельствуют о том, что фактический тип аргумента либо указан не полностью, либо находится вне набора обычных типов данных SQL.

Ссылка на тип столбца записывается в виде имя_таблицы.имя_столбца%TYPE. Использование такой записи иногда может помочь сделать функцию независимой от изменений в определении таблицы.

выражение_по_умолчанию

Выражение, которое будет использоваться в качестве значения по умолчанию, если параметр не указан. Результат выражения должен сводиться к типу соответствующего параметра. Только входные параметры (включая INOUT) могут иметь значение по умолчанию. Все входные параметры, следующие за параметром со значением по умолчанию, также должны иметь значения по умолчанию.

тип_результата

Возвращаемый тип данных (может быть дополнен схемой). Тип возвращаемого значения может быть базовым, составным или доменным, а также может ссылаться на тип столбца таблицы. В зависимости от языка реализации также может быть разрешено указывать «псевдотипы», например cstring. Если функция не должна возвращать значение, в качестве возвращаемого типа укажите void.

В случае наличия параметров OUT или INOUT предложение RETURNS можно опустить. Если оно присутствует, то должно быть согласовано с типом результата, подразумеваемым выходными параметрами: возвращается либо RECORD, если выходных параметров несколько, либо тот же тип, что и у единственного выходного параметра.

Указание SETOF показывает, что функция возвращает множество, а не единственный элемент.

Ссылка на тип столбца записывается в виде имя_таблицы.имя_столбца%TYPE.

имя_столбца

Имя выходного столбца в записи RETURNS TABLE. Это фактически еще один способ объявления именованного параметра OUT, но RETURNS TABLE также подразумевает и RETURNS SETOF.

тип_столбца

Тип данных выходного столбца в записи RETURNS TABLE.

имя_языка

Имя языка, на котором реализована функция. Это может быть sql, c, internal, или имя пользовательского процедурного языка, например plpgsql. Если указано тело_sql, языком по умолчанию является sql. Заключение имени в апострофы считается устаревшим вариантом и требует совпадения регистра.

TRANSFORM { FOR TYPE имя_типа } [, ... ] }

Устанавливает список трансформаций, которые должны применяться при вызове функции. Трансформации выполняют преобразования между типами SQL и языковыми типами данных; см. CREATE TRANSFORM. Преобразования встроенных типов обычно жестко предопределены в реализациях процедурных языков, так что их здесь указывать не нужно. Если реализация процедурного языка не знает, как обрабатывать тип, и трансформация не предоставляется, будет выполнено преобразование типов данных по умолчанию, но это зависит от реализации.

WINDOW

WINDOW указывает, что функция является не простой, а оконной функцией. В настоящее время актуален только для функций, написанных на языке C/RUST. Атрибут WINDOW нельзя изменить при изменении определения существующей функции.

IMMUTABLE
STABLE
VOLATILE

Эти атрибуты информируют оптимизатор запросов о поведении функции. Одновременно можно указать не более одного атрибута. Если ни один из них не указан, по умолчанию предполагается VOLATILE.

IMMUTABLE (постоянная) означает, что функция не может изменять базу данных и всегда возвращает один и тот же результат при одинаковых заданных значениях аргумента; т. е. она не выполняет поиск в базе данных и не использует иным образом информацию, не представленную непосредственно в ее списке аргументов. Если задан этот параметр, то любой вызов функции со всеми постоянными аргументами может быть немедленно заменен значением функции.

STABLE (стабильная) означает, что функция не может изменять базу данных и что в рамках одного сканирования таблицы она будет последовательно возвращать один и тот же результат для одних и тех же значений аргументов, но при этом ее результат может изменяться в разных инструкциях SQL. Это подходящий выбор для функций, результаты которых зависят от содержимого базы данных, настраиваемых параметров (таких как текущий часовой пояс) и т. д. (Но этот вариант не подходит для триггеров AFTER, желающих сделать запрос на строки, измененные текущей командой.) Также обратите внимание, что функции семейства current_timestamp считаются стабильными, так как их результаты не изменяются в рамках транзакции.

VOLATILE (изменчивая) означает, что значение функции может изменяться даже в пределах одного сканирования таблицы, поэтому ее вызовы нельзя оптимизировать. Относительно немногие функции базы данных являются изменчивыми в этом смысле, например: random(), currval() и timeofday(). Но обратите внимание, что любая функция, которая имеет побочные эффекты, должна быть классифицирована как VOLATILE, даже если ее результат вполне предсказуем, чтобы предотвратить переоптимизацию ее вызовов; примером является setval().

Дополнительную информацию см. в разделе Категории изменчивости функций.

LEAKPROOF

Указание LEAKPROOF (герметичная) означает, что функция не имеет никаких побочных эффектов. Она не раскрывает никакой информации о своих аргументах, кроме своего возвращаемого значения. Например, функция, которая выдает сообщение об ошибке только для некоторых значений аргументов или которая включает значения аргументов в каждое сообщение об ошибке, не является герметичной. Это влияет на то, как система выполняет запросы к представлениям, созданным с параметром security_barrier (барьер безопасности), или таблицам с включенной защитой на уровне строк. Система будет принудительно применять условия из политик защиты и представлений с барьерами безопасности перед любыми условиями, которые пользователь задает в запросе и в которых задействуются негерметичные функции, чтобы предотвратить непреднамеренное раскрытие данных. Функции и операторы, помеченные как герметичные, считаются надежными и могут выполняться до выполнения условий из политик защиты и представлений с барьерами безопасности. Кроме того, функции, которые не имеют аргументов или которым не передаются никакие аргументы из представления с барьером безопасности или таблицы, не требуется помечать как герметичные, чтобы они выполнялись до условий, связанных с безопасностью. См. справочную страницу команды CREATE VIEW. Этот параметр может установить только суперпользователь.

CALLED ON NULL INPUT
RETURNS NULL ON NULL INPUT
STRICT

CALLED ON NULL INPUT (по умолчанию) означает, что функция будет вызываться как обычно, когда некоторые из ее аргументов равны NULL. В этом случае ответственность за проверку (при необходимости) на значения NULL и соответствующую их обработку несет автор функции.

RETURNS NULL ON NULL INPUT или STRICT означает, что функция всегда возвращает значение NULL, когда любой из ее аргументов равен NULL. Если этот параметр указан, функция не выполняется при наличии аргументов NULL; вместо этого автоматически предполагается результат NULL.

[EXTERNAL] SECURITY INVOKER
[EXTERNAL] SECURITY DEFINER

Характеристика SECURITY INVOKER (безопасность вызывающего) указывает, что функция должна выполняться с правами вызывающего ее пользователя. Это значение по умолчанию. Вариант SECURITY DEFINER (безопасность определившего) указывает, что функция должна выполняться с правами пользователя, который ей владеет.

Ключевое слово EXTERNAL (внешняя) допускается для соответствия стандарту SQL, но является необязательным, так как, в отличие от SQL, эта характеристика распространяется на все функции, а не только на внешние.

PARALLEL

Указание PARALLEL UNSAFE означает, что функция не может быть выполнена в параллельном режиме, и наличие такой функции в инструкции SQL приводит к последовательному плану выполнения. Это значение по умолчанию. Указание PARALLEL RESTRICTED означает, что функция может выполняться в параллельном режиме, но только в ведущем процессе параллельной группы. PARALLEL SAFE означает, что функция безопасна для работы в параллельном режиме без ограничений.

Функции должны быть помечены как небезопасные для параллельного выполнения (PARALLEL UNSAFE), если они изменяют состояние базы данных, или вносят изменения в транзакцию, например используя субтранзакции, или обращаются к последовательностям, или пытаются сохранять настройки параметров (например, используя setval). Функции должны быть помечены как ограниченно параллельные (PARALLEL RESTRICTED), если они обращаются к временным таблицам, состоянию клиентского подключения, курсорам, подготовленным операторам или разнообразному состоянию обслуживающего процесса, которые система не может синхронизировать в параллельном режиме (например, setseed может выполнять только ведущий процесс группы, так как изменения, внесенные другим процессом, не передаются ведущему). В целом, если функция помечена как безопасная (PARALLEL SAFE), являясь на самом деле ограниченной или небезопасной, либо помечена как ограниченно безопасная, не являясь безопасной, она может выдавать ошибки или давать неправильные результаты при использовании в параллельном запросе. Функции на языке C/RUST теоретически могут демонстрировать совершенно неопределенное поведение при неправильной маркировке, поскольку система не может защитить себя от произвольного кода на C/RUST, но в большинстве случаев результат будет не хуже, чем для любой другой функции. При возникновении сомнений функции следует помечать как UNSAFE, что является значением по умолчанию.

COST стоимость_выполнения

Положительное число, задающее примерную стоимость выполнения функции в единицах cpu_operator_cost. Если функция возвращает множество, то это стоимость за возвращенную строку. Если стоимость не указана, то для функций на языке C/RUST и внутренних функций она считается равной 1 единице, а для функций на всех других языках — 100 единицам. При более высоких значениях планировщик постарается не вычислять эту функцию чаще необходимого.

ROWS строк_в_результате

Положительное число, задающее примерное количество строк, которое планировщик должен ожидать от функции на выходе. Это указание допустимо только в том случае, если функция объявлена возвращающей множество. Предполагаемое по умолчанию значение — 1000 строк.

SUPPORT вспомогательная_функция

Имя вспомогательной функции для планировщика, используемое для этой функции (может быть дополнено схемой). Дополнительную информацию см. в разделе Информация по оптимизации функций. Чтобы использовать эту опцию, нужно быть суперпользователем.

параметр_конфигурации
значение

Предложение SET определяет, что при вызове функции указанный параметр конфигурации должен принять заданное значение, а затем, по завершении этой функции, восстановить свое предыдущее значение. Предложение SET FROM CURRENT сохраняет значение параметра, действующее при выполнении CREATE FUNCTION, в качестве значения, которое будет применено при вызове функции.

Если в определение функции добавлено предложение SET, то действие команды SET LOCAL, выполняемой внутри функции для той же переменной, ограничивается этой функцией: предыдущее значение параметра конфигурации восстанавливается по завершении функции. Однако обычная команда SET (без LOCAL) переопределяет предложение SET, как сделало бы и для предыдущей команды SET LOCAL: эффекты такой команды сохранятся после завершения функции, если не случится откат текущей транзакции.

Дополнительную информацию о допустимых именах и значениях параметров см. на справочной странице команды SET и в главе Конфигурация сервера.

определение

Строковая константа, определяющая функцию; значение зависит от языка. Это может быть имя внутренней функции, путь к объектному файлу, команда SQL или текст на процедурном языке.

Часто бывает полезно заключать определение функции в знаки доллара (см. подраздел Строковые константы с экранированием знаками доллара), а не в традиционные апострофы. Если не использовать знаки доллара, то любые апострофы или обратные слэши в определении функции должны быть экранированы путем удвоения.

объектный_файл, ссылочный_символ

Эта форма предложения AS используется для динамически загружаемых функций на языке C/RUST, когда имя функции в исходном коде языка C/RUST не совпадает с именем функции на SQL. Строка объектный_файл является именем файла разделяемой библиотеки, содержащего скомпилированную функцию C/RUST, и интерпретируется как параметр команды LOAD. Строка ссылочный_символ задает ссылочный символ функции, то есть имя функции в исходном коде языка C/RUST. Если ссылочный символ опущен, предполагается, что он совпадает с именем определяемой функции SQL. В языке C/RUST имена всех функций должны быть разными, поэтому следует давать перегруженным функциям C/RUST разные имена (например, включать в имена C/RUST обозначения типов аргументов).

При повторных вызовах CREATE FUNCTION ссылается на один и тот же объектный файл, который загружается только один раз за сеанс. Чтобы выгрузить и перезагрузить файл (скажем, во время разработки), запустите новый сеанс.

тело_sql

Тело функции в стиле LANGUAGE SQL. Это может быть один оператор

RETURN выражение

или блок операторов

BEGIN ATOMIC
  оператор;
  оператор;
  ...
  оператор;
END

Это схоже с написанием текста тела функции в виде строковой константы (см. определение выше), но есть некоторые различия: эта форма работает только для LANGUAGE SQL, а форма строковой константы работает для всех языков. Эта форма анализируется во время определения функции, а форма строковой константы анализируется во время выполнения; таким образом, эта форма не может поддерживать полиморфные типы аргументов и другие конструкции, которые нельзя разрешить во время определения функции. Эта форма отслеживает зависимости между функцией и объектами, используемыми в ее теле, так что команда DROP ... CASCADE будет работать корректно, тогда как форма, использующая строковые литералы, может оставить недействительные функции. Наконец, данная форма более совместима со стандартом SQL и другими реализациями SQL.

Перегрузка

QHB допускает перегрузку функций; то есть одно и то же имя может использоваться для нескольких различных функций, если они имеют различные типы входных аргументов. Независимо от того, используете вы эту возможность или нет, она требует предосторожности при вызове функций в базах данных, где одни пользователи не доверяют другим пользователям (см. раздел Перегрузка функций).

Две функции считаются одинаковыми, если они имеют одинаковые имена и типы входных аргументов, игнорируя параметры OUT. Так, например, эти объявления конфликтуют:

CREATE FUNCTION foo(int) ...
CREATE FUNCTION foo(int, out text) ...

Функции, которые имеют разные списки типов аргументов, не будут считаться конфликтующими во время создания, но предоставленные для них значения по умолчанию могут вызвать конфликт в момент использования. Например, рассмотрим

CREATE FUNCTION foo(int) ...
CREATE FUNCTION foo(int, int default 42) ...

Вызов foo(10) завершится ошибкой из-за неоднозначности в выборе вызываемой функции.

Примечания

В объявлении аргументов функции и возвращаемого значения допускается полный синтаксис описания типа SQL. Однако заключенные в скобки модификаторы типа (например поле точности для типа numeric) команда CREATE FUNCTION не учитывает. Так, например, CREATE FUNCTION foo (varchar(10)) ... — это ровно то же самое, что и CREATE FUNCTION foo (varchar) ....

При замене существующей функции с помощью CREATE OR REPLACE FUNCTION существуют ограничения на изменение имен параметров. Нельзя изменить имя, уже присвоенное какому-либо входному параметру (хотя можно добавить имена к параметрам, которые ранее не имели имени). Если существует несколько выходных параметров, нельзя изменить их имена, поскольку это приведет к изменению имен столбцов анонимного составного типа, описывающего результат функции. Эти ограничения применяются для того, чтобы существующие вызовы функции гарантированно не прекращали работу после ее замены.

Если функция объявлена как STRICT с аргументом VARIADIC, то при оценке строгости проверяется, чтобы весь переменный массив в целом не был равен NULL. Если же массив содержит элементы NULL, функция будет по-прежнему вызываться.

Примеры

Добавление двух целых чисел с помощью функции SQL:

CREATE FUNCTION add(integer, integer) RETURNS integer
    AS 'select $1 + $2;'
    LANGUAGE SQL
    IMMUTABLE
    RETURNS NULL ON NULL INPUT;

Та же функция, написанная в стиле, более соответствующей стандарту SQL, с использованием имен аргументов и телом без кавычек:

CREATE FUNCTION add(a integer, b integer) RETURNS integer
    LANGUAGE SQL
    IMMUTABLE
    RETURNS NULL ON NULL INPUT
    RETURN a + b;

Функция увеличения целого числа, использующая именованный аргумент, на языке PL/pgSQL:

CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
        BEGIN
                RETURN i + 1;
        END;
$$ LANGUAGE plpgsql;

Функция, возвращающая запись с несколькими выходными параметрами:

CREATE FUNCTION dup(in int, out f1 int, out f2 text)
    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
    LANGUAGE SQL;

SELECT * FROM dup(42);

То же самое можно сделать более развернуто, явно объявив составной тип:

CREATE TYPE dup_result AS (f1 int, f2 text);

CREATE FUNCTION dup(int) RETURNS dup_result
    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
    LANGUAGE SQL;

SELECT * FROM dup(42);

Еще один способ вернуть несколько столбцов — применить функцию TABLE:

CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text)
    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
    LANGUAGE SQL;

SELECT * FROM dup(42);

Однако пример с TABLE отличается от предыдущих, так как в нем функция на самом деле возвращает не одну, а набор записей.

Разработка защищенных функций SECURITY DEFINER

Так как функция SECURITY DEFINER выполняется с правами владеющего ей пользователя, необходимо позаботиться о том, чтобы ее невозможно было использовать не по назначению. В целях обеспечения безопасности следует установить search_path, чтобы исключить любые схемы, доступные для записи ненадежным пользователям. Это предотвращает создание злоумышленниками объектов (например таблиц, функций и операторов), маскирующих объекты, предназначенные для использования функцией. Особенно важной в этом отношении является схема временной таблицы, которая по умолчанию ищется первой и обычно доступна для записи любым пользователям. Соответствующую защиту можно организовать, поместив временную схему в конец списка поиска. Для этого нужно сделать pg_temp последней записью в search_path. Эта функция иллюстрирует безопасное использование:

CREATE FUNCTION check_password(uname TEXT, pass TEXT)
RETURNS BOOLEAN AS $$
DECLARE passed BOOLEAN;
BEGIN
        SELECT  (pwd = $2) INTO passed
        FROM    pwds
        WHERE   username = $1;

        RETURN passed;
END;
$$  LANGUAGE plpgsql
    SECURITY DEFINER
    -- Установить безопасный путь поиска: сначала доверенная схема (или схемы), затем 'pg_temp'.
    SET search_path = admin, pg_temp;

Эта функция должна обращаться к таблице admin.pwds, но без предложения SET или с предложением SET, включающим только admin; ее можно «обмануть», создав временную таблицу с именем pwds.

Еще один момент, который следует иметь в виду, — это то, что по умолчанию право выполнения для создаваемых функций имеет роль PUBLIC (дополнительную информацию см. в разделе Права). Однако зачастую требуется разрешить доступ к функции, работающей в контексте безопасности определившего, только некоторым пользователям. Для этого необходимо отозвать стандартные права PUBLIC, а затем предоставить права выполнения выборочно. Чтобы избежать появления окна, в котором новая функция доступна для всех, создайте ее и установите права доступа в рамках одной транзакции. Например:

BEGIN;
CREATE FUNCTION check_password(uname TEXT, pass TEXT) ... SECURITY DEFINER;
REVOKE ALL ON FUNCTION check_password(uname TEXT, pass TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION check_password(uname TEXT, pass TEXT) TO admins;
COMMIT;

Совместимость

Команда CREATE FUNCTION определена в стандарте SQL. Ее реализацию в QHB можно использовать совместимым образом, но у нее есть множество расширений. И наоборот, в стандарте SQL указан ряд дополнительных возможностей, не реализованных в QHB.

У совместимости существуют следующие важные ограничения:

OR REPLACE является расширением QHB.

Для обеспечения совместимости с некоторыми другими системами баз данных режим_аргумента можно записать как после имени_аргумента, так и перед ним. Но только первый способ соответствует стандарту.

Для определения значений параметров по умолчанию стандарт SQL поддерживает только синтаксис с ключевым словом DEFAULT. Синтаксис со знаком = используется в T-SQL и Firebird.

Модификатор SETOF является расширением QHB.

В стандарте определен только язык SQL.

Все атрибуты, кроме CALLED ON NULL INPUT и RETURNS NULL ON NULL INPUT, в стандарте не определены.

Для тела функции в стиле LANGUAGE SQL в стандарте SQL указана только форма тело_sql.

Простые функции в стиле LANGUAGE SQL можно написать так, чтобы они одновременно соответствовали стандарту и были переносимыми на другие реализации. Более сложные функции, использующие расширенные возможности, атрибуты оптимизации или другие языки, обязательно будут в значительной мере специфичными для QHB.

См. также

ALTER FUNCTION, DROP FUNCTION, GRANT, LOAD, REVOKE

CREATE GROUP

CREATE GROUP — определить новую роль в базе данных

Синтаксис

CREATE GROUP имя [ [ WITH ] параметр [ ... ] ]

где параметром может быть:

      SUPERUSER | NOSUPERUSER
    | CREATEDB | NOCREATEDB
    | CREATEROLE | NOCREATEROLE
    | INHERIT | NOINHERIT
    | LOGIN | NOLOGIN
    | REPLICATION | NOREPLICATION
    | BYPASSRLS | NOBYPASSRLS
    | CONNECTION LIMIT предел_подключений
    | [ ENCRYPTED ] PASSWORD 'пароль' | PASSWORD NULL
    | VALID UNTIL 'дата_время'
    | IN ROLE имя_роли [, ...]
    | IN GROUP имя_роли [, ...]
    | ROLE имя_роли [, ...]
    | ADMIN имя_роли [, ...]
    | USER имя_роли [, ...]
    | SYSID uid

Описание

Команда CREATE GROUP теперь является псевдонимом команды CREATE ROLE.

Совместимость

В стандарте SQL нет команды CREATE GROUP.

См. также

CREATE ROLE

CREATE INDEX

CREATE INDEX — определить новый индекс

Синтаксис

CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] имя ] ON [ ONLY ] имя_таблицы [ USING метод ]
    ( { имя_столбца | ( выражение ) } [ COLLATE правило_сортировки ] [ класс_операторов  [ ( параметр_класса_операторов = значение [, ... ] ) ] ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
    [ INCLUDE ( имя_столбца [, ...] ) ]
    [ WITH ( параметр_хранения [= значение] [, ... ] ) ]
    [ TABLESPACE имя_табличного_пространства ]
    [ WHERE предикат ]

Описание

Команда CREATE INDEX строит индекс по указанному столбцу (или столбцам) заданного отношения, которое может быть таблицей или материализованным представлением. Индексы в основном используются для повышения производительности базы данных (хотя их неправильное использование может привести к ее снижению).

Ключевое поле для индекса указывается в виде имени столбца или же в виде выражения, заключенного в круглые скобки. Если индексный метод поддерживает составные индексы, можно указать несколько полей.

Поле индекса может быть выражением, вычисленным из значений одного или нескольких столбцов строки таблицы. Эту особенность можно использовать для быстрого доступа к данным на основе некоторого преобразования исходных данных. Например, индекс, построенный по выражению upper(col), позволит использовать поиск по индексу в предложении WHERE upper(col) = 'JIM'.

QHB предоставляет следующие индексные методы: B-дерево, хеш, GiST, SP-GiST, GIN и BRIN. Кроме того, пользователи могут определять свои собственные индексные методы, но это довольно сложно.

Когда в команде присутствует предложение WHERE, создается частичный индекс. Частичный индекс — это индекс, содержащий записи только для части таблицы, обычно более полезной для индексации, чем остальная таблица. Например, если у вас есть таблица, содержащая выставленные и неоплаченные счета за заказы, и неоплаченные заказы занимают малую часть общей таблицы, но запрашиваются чаще, можно улучшить производительность, создав индекс только для этой части. Еще одним возможным применением является указание WHERE вместе с UNIQUE для обеспечения уникальности по подмножеству таблицы. Дополнительную информацию см. в разделе Частичные индексы.

Выражение, используемое в предложении WHERE, может ссылаться только на столбцы нижележащей таблицы, но при этом использовать все столбцы, а не только индексируемые. В настоящее время подзапросы и агрегатные выражения в WHERE тоже запрещены. Такие же ограничения применяются к выражениями в полях индексов.

Все функции и операторы, используемые в определении индекса, должны быть «постоянными», то есть их результаты должны зависеть только от их аргументов, а не от какого-либо внешнего влияния (например содержимого другой таблицы или текущего времени). Это ограничение гарантирует, что поведение индекса четко определено. Чтобы использовать пользовательскую функцию в выражении индекса или предложении WHERE, не забудьте при создании отметить функцию как постоянную.

Параметры

UNIQUE

Указывает, что система должна проверять наличие повторяющихся значений в таблице при создании индекса (если данные уже существуют) и при каждом добавлении данных. Попытки добавить или обновить данные, которые вызовут дублирование записей, приведут к ошибке.

Дополнительные ограничения действуют при применении уникальных индексов к партиционированным таблицам; см. справочную страницу команды CREATE TABLE.

CONCURRENTLY

Если используется этот параметр, QHB будет строить индекс без установления каких-либо блокировок, предотвращающих параллельные добавления, изменения или удаление записей в таблице, тогда как стандартная операция построения индекса блокирует запись (но не чтение) в таблице до своего завершения. Существует несколько особенностей, которые следует учитывать при использовании этого параметра — см. параграф Неблокирующее построение индексов ниже.

IF NOT EXISTS

Не считать ошибкой, если индекс с таким именем уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующий индекс как-то соотносится с тем, который был бы создан. Если указано IF NOT EXISTS, то имя индекса является обязательным.

INCLUDE

Необязательное предложение INCLUDE указывает список столбцов, которые будут включены в индекс в качестве неключевых столбцов. Неключевой столбец не будет использоваться в условии поиска для сканирования по индексу; также он игнорируется при анализе ограничения уникальности или исключения, применяемого индексом. Однако при сканировании только индекса может возвращаться содержимое неключевых столбцов без необходимости обращения к таблице индекса, поскольку они доступны непосредственно из записи индекса. Таким образом, добавление неключевых столбцов позволяет использовать сканирование только индекса в тех запросах, где иначе оно было бы неприменимо.

В отношении добавления неключевых столбцов в индекс разумно проявлять консерватизм, особенно для широких столбцов. Если кортеж индекса превысит максимальный размер, разрешенный для типа индекса, добавление данных завершится ошибкой. В любом случае, неключевые столбцы дублируют данные из таблицы и раздувают размер индекса, что потенциально замедляет поиск. Более того, в индексе B-дереве, содержащем неключевой столбец, не будет работать дедупликация.

Столбцы, перечисленные в предложении INCLUDE, не нуждаются в соответствующих классах операторов; предложение может включать столбцы, типы данных которых не имеют классов операторов, определенных для данного метода доступа.

Выражения во включаемых столбцах не поддерживаются, так как они не могут использоваться при сканировании только индекса.

В настоящее время эту возможность поддерживают только методы доступа по индексам B-деревьям и GiST. В таких индексах значения столбцов, перечисленных в предложении INCLUDE, включаются в кортежи на уровне листьев, которые соответствуют кортежам кучи, но не включаются в записи индекса верхнего уровня, используемые для навигации по деревьям.

имя

Имя создаваемого индекса. Имя схемы сюда включить нельзя; индекс всегда создается в той же схеме, что и его родительская таблица. Если имя опущено, QHB выбирает подходящее имя на основе имени родительской таблицы и имен индексированных столбцов.

ONLY

Указывает, что индексы не должны рекурсивно создаваться на партициях, если таблица партиционирована. По умолчанию создание происходит рекурсивно.

имя_таблицы

Имя индексируемой таблицы (может быть дополнено схемой).

метод

Имя используемого индексного метода. Возможные варианты: btree, hash, gist, spgist, gin, brin или методы доступа, установленные пользователем, вроде bloom. По умолчанию используется метод btree.

имя_столбца

Имя столбца таблицы.

выражение

Выражение, основанное на одном или нескольких столбцах таблицы. Обычно выражение должно быть записано в скобках, как показано в синтаксисе. Однако если выражение записано в виде вызова функции, круглые скобки можно опустить.

правило_сортировки

Имя правила сортировки, используемого для индекса. По умолчанию индекс использует правило сортировки, объявленное для подлежащего индексированию столбца, или результирующее правило подлежащего индексированию выражения. Индексы с нестандартными правилами сортировки могут быть полезны для запросов, содержащих выражения, использующие такие правила.

класс_операторов

Имя класса операторов. Дополнительную информацию см. ниже.

параметр_класса_операторов

Имя параметра класса операторов. Дополнительную информацию см. ниже.

ASC

Задает порядок сортировки по возрастанию (который является порядком сортировки по умолчанию).

DESC

Задает порядок сортировки по убыванию.

NULLS FIRST

Указывает, что значения NULL после сортировки оказываются перед остальными. Это значение по умолчанию, когда задано DESC.

NULLS LAST

Указывает, что значения NULL после сортировки оказываются после остальных. Это значение по умолчанию, когда задано ASC.

параметр_хранения

Имя параметра хранения, зависящего от индексного метода. Дополнительную информацию см. в разделе Параметры хранения индекса.

имя_табличного_пространства

Табличное пространство, в котором будет создан индекс. Если этот параметр не задан, выполняется обращение к default_tablespace или, для индексов по временным таблицам, к temp_tablespaces.

предикат

Выражение ограничения для частичного индекса.

Параметры хранения индекса

Необязательное предложение WITH задает параметры хранения для индекса. Каждый индексный метод имеет свой собственный набор допустимых параметров хранения. Индексные методы B-дерево, хеш, GiST и SP-GiST принимают этот параметр:

fillfactor (integer)

Коэффициент заполнения для индекса выражается в процентах и определяет, насколько плотно индексный метод будет пытаться заполнить страницы индекса. Для B-деревьев страницы уровня листьев заполняются до этого процента во время первоначального построения индекса, а также при расширении индекса вправо (добавление новых наибольших значений ключей). Если впоследствии страницы будут полностью заполнены, они будут разделены, что приведет к фрагментации структуры индекса на диске. По умолчанию B-деревья используют коэффициент заполнения 90, но можно выбрать любое целочисленное значение от 10 до 100.

Индексы B-деревья по таблицам, где ожидается много добавлений и/или изменений, могут извлечь пользу из меньших значений коэффициента заполнения во время выполнения CREATE INDEX (на фоне массовой загрузки данных в таблицу). Значения в диапазоне 50 — 90 могут эффективно «сгладить» интенсивность разделений страниц на начальной стадии существования индекса B-дерева (такое снижение коэффициента заполнения может даже уменьшить абсолютное количество разделений страниц, хотя этот эффект сильно зависит от рабочей нагрузки). Методика восходящего удаления индексов B-деревьев, описанная в подразделе Восходящее удаление индексных кортежей, зависит от наличия некоего «лишнего» места на страницах для сохранения «лишних» версий кортежей, и поэтому на нее может повлиять коэффициент заполнения (хотя обычно его эффект незначителен).

В других особых случаях может быть полезно увеличить коэффициент заполнения до 100 перед проведением CREATE INDEX, чтобы использовать пространство максимально эффективно. Этот вариант следует рассматривать, только когда вы полностью уверены, что таблица является статичной (т. е. что на нее никогда не повлияют добавления или изменения). В противном случае коэффициент заполнения, равный 100, грозит повредить производительности: даже единичные добавления или изменения вызовут резкий поток разделений страниц.

Другие индексные методы используют коэффициент заполнения другими, но примерно схожими способами; значение по умолчанию коэффициента заполнения варьирует от метода к методу.

Индексы B-деревья дополнительно принимают этот параметр:

deduplicate_items (boolean)

Контролирует использование методики исключения дубликатов, описанной в подразделе Дедупликация. Устанавливается на значения ON или OFF для включения или выключения соответственно. (Допустимые варианты написания ON и OFF описаны в разделе Настройка параметров.) Значение по умолчанию равно ON.

Примечание
После выключения deduplicate_items во время выполнения команды ALTER INDEX в будущем при операциях добавления элементов автоматическая дедупликация срабатывать не будет, но представление существующих кортежей в виде списка на стандартное не поменяется.

Индексы GiST дополнительно принимают этот параметр:

buffering (enum)

Определяет, используется ли для построения индекса метод буферизации, описанный в разделе Реализация главы «Индексы GiST». Со значением OFF он выключен, с ON — включен, а с AUTO — вначале выключен, но включается на ходу, как только размер индекса достигает effective_cache_size. Значение по умолчанию равно AUTO. Обратите внимание, что если возможно построение индекса с сортировкой, именно оно будет использоваться вместо построения с буферизацией, если только не указано buffering=ON.

Индексы GIN принимают другие параметры:

fastupdate (boolean)

Этот параметр управляет использованием механизма быстрого обновления, описанного в разделе Быстрое обновление GIN. Это логический параметр: ON включает быстрое обновление, OFF выключает его. Значение по умолчанию равно ON.

Примечание
Выключение fastupdate в ALTER INDEX предотвращает помещение добавляемых в дальнейшем строк в список записей, ожидающих индексации, но записи, добавленные в этот список ранее, в нем остаются. Чтобы очистить очередь операций, надо затем выполнить для этой таблицы VACUUM или вызвать функцию gin_clean_pending_list.

gin_pending_list_limit (integer)

Пользовательский параметр gin_pending_list_limit. Это значение указывается в килобайтах.

Индексы BRIN принимают другие параметры:

pages_per_range (integer)

Определяет количество блоков таблицы, которые составляют один диапазон блоков для каждой записи индекса BRIN (более подробную информацию см. в разделе Введение главы «Индексы BRIN»). Значение по умолчанию равно 128.

autosummarize (boolean)

Определяет, нужно ли вычислять сводное значение для диапазона предыдущей страницы, когда происходит добавление на следующей странице. Более подробную информацию см. в подразделе Обслуживание индекса. Значение по умолчанию равно off (выключен).

Неблокирующее построение индексов

Создание индекса может помешать обычной работе базы данных. Как правило, QHB блокирует для записи предназначенную к индексированию таблицу и осуществляет полное построение индекса за одно сканирование таблицы. Другие транзакции по-прежнему могут читать из этой таблицы, но при попытке добавления, изменения или удаления строки в таблице они будут блокироваться до завершения построения индекса. Это может иметь серьезные последствия, если система является производственной базой данных в реальном времени. Индексация очень больших таблиц может занять много часов, и даже у таблиц меньшего размера построение индекса может блокировать записи на время, неприемлемое для производственной системы.

QHB поддерживает построение индексов без блокировки записи. Этот метод активизируется путем указания CONCURRENTLY команды CREATE INDEX. При использовании этого параметра QHB должна выполнить два сканирования таблицы и помимо этого дождаться завершения всех существующих транзакций, которые потенциально могут изменить или использовать индекс. Таким образом, данный метод требует проделать суммарно больше действий, чем стандартное построение индекса, и занимает значительно больше времени. Однако поскольку он позволяет продолжать обычную работу во время построения индекса, этот метод удобен для добавления новых индексов в производственной среде. Разумеется, дополнительная нагрузка на процессор и подсистему ввода/вывода, вызванная созданием индекса, может замедлить другие операции.

При неблокирующем построении индекса он действительно попадает в системный каталог в одной транзакции, а затем в еще двух транзакциях происходят два сканирования таблицы. Перед каждым сканированием операция построения индекса должна ожидать завершения существующих транзакций, изменяющих таблицу. После второго сканирования этой операции также необходимо дождаться завершения всех транзакций, которые получили снимок таблицы (см. главу Параллельный контроль) перед вторым сканированием, включая транзакции, используемые на любом этапе неблокирующего построения индексов по другим таблицам, если затрагиваемые индексы являются частичными или имеют столбцы, отличные от обычных ссылок на столбцы. Затем индекс наконец может быть помечен как «рабочий» и готовый к использованию, и команда CREATE INDEX завершается. Однако даже в этом случае индекс может не сразу стать пригодным для запросов: в худшем случае его нельзя применять до тех пор, пока существуют транзакции, запущенные до начала построения индекса.

Если при сканировании таблицы возникает проблема, например, взаимоблокировка или нарушение уникальности в уникальном индексе, команда CREATE INDEX завершится ошибкой, но оставит «нерабочий» индекс. Этот индекс будет игнорироваться при запросах, поскольку он может быть неполным; однако он все равно будет потреблять ресурсы при обновлениях. Команда psql \d сообщит о таком индексе, пометив его как INVALID:

qhb=# \d tab
       Table "public.tab"
 Column |  Type   | Collation | Nullable | Default
--------+---------+-----------+----------+---------
 col    | integer |           |          |
Indexes:
    "idx" btree (col) INVALID

Рекомендуемый метод восстановления в таких случаях заключается в том, чтобы удалить индекс и попытаться снова выполнить CREATE INDEX CONCURRENTLY. (Другая возможность состоит в том, чтобы перестроить индекс с помощью команды REINDEX INDEX CONCURRENTLY.)

Еще одна сложность, с которой можно столкнуться при неблокирующем построении уникального индекса, в том, что когда начинается второе сканирование таблицы, ограничение уникальности уже применяется к другим транзакциям. Это означает, что нарушения ограничений могут быть зарегистрированы в других запросах раньше, чем индекс станет доступным для использования, или даже в тех случаях, когда построение индекса в конечном итоге завершится неудачей. Кроме того, если во время второго сканирования происходит сбой, то «нерабочий» индекс все равно продолжает после этого применять свое ограничение уникальности.

Метод неблокирующего построения поддерживает также индексы выражений и частичные индексы. Ошибки, возникающие при вычислении этих выражений, могут привести к поведению, описанному выше для случаев с нарушением ограничений уникальности.

Обычное построение индекса допускает одновременное построение других индексов для таблицы обычным методом, но неблокирующее построение для конкретной таблицы в один момент времени допускается только одно. В любом случае никакие другие изменения схемы таблицы во время построения индекса не допускаются. Другое отличие состоит в том, что обычную команда CREATE INDEX можно выполнить в блоке транзакции, а CREATE INDEX CONCURRENTLY — нет.

Метод неблокирующего построения для индексов в партиционированных таблицах в настоящее время не поддерживаются. Однако можно использовать этот метод для каждой партиции отдельно, а затем создать индекс партиции в обычном режиме, чтобы сократить время блокировки записи в партиционированную таблицу. В этом случае построение индекса партиции будет заключаться только в изменении метаданных.

Примечания

Информацию о том, когда можно использовать индексы, когда они не используются и в каких конкретных ситуациях они могут быть полезны, см. в главе Индексы.

В настоящее время только методы B-дерево, GiST, GIN и BRIN поддерживают индексы по нескольким ключевым столбцам. Возможность построения индекса по нескольким ключевым столбцам не зависит от того, можно ли добавить в индекс неключевые столбцы (INCLUDE). Индексы могут иметь до 32 столбцов, включая неключевые. (Это ограничение можно изменить при пересборке QHB.) В настоящее время только B-дерево поддерживает уникальные индексы.

Для каждого столбца индекса можно указать класс операторов с дополнительными параметрами. Класс операторов определяет операторов, которые будут использоваться индексом для этого столбца. Например, индекс B-дерево для четырехбайтовых целых чисел будет использовать класс int4_ops; этот класс операторов включает функции сравнения для четырехбайтовых целых чисел. На практике обычно достаточно класса операторов по умолчанию для типа данных столбца. Основной смысл наличия классов операторов состоит в том, что для некоторых типов данных может существовать больше одного осмысленного порядка сортировки. Например, мы можем захотеть отсортировать комплексные числа как по абсолютному значению, так и по вещественной части. Мы могли бы сделать это, определив два класса операторов для типа данных и затем при создании индекса выбрав подходящий. Более подробную информацию о классах операторов см. в разделах Классы операторов и семейства операторов и Интерфейсные расширения для индексов.

Когда команда CREATE INDEX вызывается для партиционированной таблицы, поведение по умолчанию — это рекурсивно распространить ее действие на все партиции, чтобы все они гарантированно имели соответствующие индексы. Сначала каждая партиция проверяется на наличие равнозначного индекса, и если таковой имеется, он будет присоединен в качестве индекса партиции к создаваемому индексу, который станет его родительским индексом. Если соответствующий индекс не существует, он будет создан и автоматически присоединен; имя индекса для каждой партиции выбирается так же, как и при выполнении этой команды без имени индекса. С указанием ONLY рекурсия не производится, и индекс помечается как нерабочий. (Команда ALTER INDEX ... ATTACH PARTITION пометит его как рабочий, когда все партиции получат соответствующие индексы.) Однако обратите внимание, что для любой партиции, которая будет создана в будущем с помощью CREATE TABLE ... PARTITION OF, соответствующий индекс будет создан автоматически, независимо от того, указано ли ONLY.

Для индексных методов, поддерживающих сканирование по порядку (в настоящее время это только В-дерево), можно изменить порядок сортировки индекса, указав необязательные предложения AS, DESC, NULLS FIRST или NULLS LAST. Поскольку упорядоченный индекс можно сканировать как вперед, так и назад, обычно не рекомендуется создавать индекс по убыванию (DESC) для одного столбца — этот порядок сортировки уже доступен с обычным индексом. Ценность этих параметров состоит в возможности создавать многостолбцовые индексы, которые будут соответствовать порядку сортировки, указанному в запросе со смешанным порядком сортировки, например SELECT ... ORDER BY x ASC, y DESC. Параметры NULLS полезны, когда в запросах, зависящих от индексов, требуется вместо стандартного «NULL вверху» реализовать поведение «NULL внизу», чтобы избежать этапов сортировки.

Система регулярно собирает статистику со всех столбцов таблицы. Новые индексы, созданные без применения выражений, могут сразу использовать эту статистику, чтобы определить полезность индекса. Для тех же новых индексов, что были построены с применением выражений, нужно выполнить команду ANALYZE или подождать, пока процесс «Автовакуум» не проанализирует таблицу и не сформирует статистику для этих индексов.

Для большинства методов индексов скорость создания индекса зависит от параметра maintenance_work_mem. Более высокие значения этого параметра сократят время, необходимое для создания индекса, если только заданное значение не превышает объем фактически доступной памяти, что влечет за собой использование подкачки.

QHB может создавать индексы, используя несколько процессоров, чтобы быстрее обрабатывать строки таблицы. Эта функция называется параллельным построением индекса. Для индексных методов, поддерживающих параллельное построение индексов (в настоящее время только В-дерево), maintenance_work_mem задает максимальный объем памяти, который может использовать каждая операция построения индекса в целом, независимо от количества запущенных рабочих процессов. Целесообразность использования рабочих процессов и их оптимальное количество обычно автоматически определяется моделью стоимости.

Параллельное построение индексов может выиграть от увеличения maintenance_work_mem там, где для аналогичного последовательного построения индексов выигрыша не будет или он будет минимальным. Обратите внимание, что maintenance_work_mem может влиять на количество запрашиваемых рабочих процессов, так как параллельные рабочие процессы должны иметь не менее 32 Мб из общего бюджета maintenance_work_mem. Кроме того, еще 32 Мб должно остаться для основного процесса. Увеличение max_parallel_maintenance_workers может позволить использовать больше рабочих процессов, что сократит время, необходимое для создания индекса (если только построение индекса уже не упирается в скорость ввода/вывода). Конечно, для этого должно быть достаточно процессорных ресурсов, которые иначе бы простаивали.

Если в команде ALTER TABLE задается значение для parallel_workers, это напрямую определяет, сколько параллельных рабочих процессов будет запрашивать для таблицы команда CREATE INDEX. При этом модель стоимости полностью игнорируется, и maintenance_work_mem не влияет на определение количества параллельных рабочих процессов. Если в ALTER TABLE параметр parallel_workers установлен равным 0, это полностью выключает параллельное построение индексов для этой таблицы.

Совет
Имеет смысл сбросить настройки parallel_workers после выполнения построения индекса. Это позволит избежать нежелательных изменений планов запросов, так как parallel_workers влияет на все параллельные сканирования таблицы.

Хотя CREATE INDEX с указанием CONCURRENTLY поддерживает параллельное построение без специальных ограничений, только первое сканирование таблицы на самом деле выполняется параллельно.

Для удаления индекса воспользуйтесь командой DROP INDEX.

Как и любая другая длительная транзакция, операция CREATE INDEX с таблицей может повлиять на то, какие кортежи может удалить параллельная операция VACUUM с какой-либо другой таблицей

Предыдущие выпуски QHB также поддерживали индексный метод R-дерево. Этот метод был удален, поскольку не имел существенных преимуществ по сравнению с методом GiST. Указание USING rtree в CREATE INDEX будет интерпретироваться как USING GiST, чтобы упростить перевод старых баз данных на GiST.

Каждый обслуживающий процесс, запускающий CREATE INDEX, будет записывать ход ее выполнения в представлении pg_stat_progress_create_index. Более подробную информацию см. в подразделе Отчет о ходе выполнения CREATE INDEX.

Примеры

Создание уникального индекса B-дерева по столбцу title в таблице films:

CREATE UNIQUE INDEX title_idx ON films (title);

Создание уникального индекса B-дерева по столбцу title, а также включенным столбцам director и rating в таблице films:

CREATE UNIQUE INDEX title_idx ON films (title) INCLUDE (director, rating);

Создание индекса B-дерева с выключенным механизмом дедупликации:

CREATE INDEX title_idx ON films (title) WITH (deduplicate_items = off);

Создание индекса по выражению lower(title), позволяющего эффективно выполнять поиск без учета регистра:

CREATE INDEX ON films ((lower(title)));

(В этом примере мы решили опустить имя индекса, чтобы его выбрала система; обычно это films_lower_idx.)

Создание индекса с нестандартным правилом сортировки:

CREATE INDEX title_idx_german ON films (title COLLATE "de_DE");

Создание индекса с нестандартным порядком сортировки значений NULL:

CREATE INDEX title_idx_nulls_low ON films (title NULLS FIRST);

Создание индекса с нестандартным коэффициентом заполнения:

CREATE UNIQUE INDEX title_idx ON films (title) WITH (fillfactor = 70);

Создание индекса GIN с выключенным механизмом быстрого обновления:

CREATE INDEX gin_idx ON documents_table USING GIN (locations) WITH (fastupdate = off);

Создание индекса по столбцу code в таблице films и размещение его в табличном пространстве indexspace:

CREATE INDEX code_idx ON films (code) TABLESPACE indexspace;

Создание индекса GiST по координатам точек, позволяющего эффективно использовать операторы для прямоугольников с результатом функции преобразования:

CREATE INDEX pointloc
    ON points USING gist (box(location,location));
SELECT * FROM points
    WHERE box(location,location) && '(0,0),(1,1)'::box;

Создание индекса без блокировки записи в таблицу:

CREATE INDEX CONCURRENTLY sales_quantity_index ON sales_table (quantity);

Совместимость

Команда CREATE INDEX является языковым расширением QHB. В стандарте SQL нет положений для индексов.

См. также

ALTER INDEX, DROP INDEX, REINDEX, Отчет о ходе выполнения CREATE INDEX

CREATE LANGUAGE

CREATE LANGUAGE — определить новый процедурный язык

Синтаксис

CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE имя
    HANDLER обработчик_вызова [ INLINE обработчик_внедренного_кода ] [ VALIDATOR функция_проверки ]
CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE имя

Описание

Команда CREATE LANGUAGE регистрирует в базе данных QHB новый процедурный язык. Впоследствии на этом новом языке могут быть определены функции и процедуры.

В сущности, CREATE LANGUAGE связывает имя языка с функциями обработки, которые отвечают за выполнение функций, написанных на этом языке. Дополнительную информацию о языковых обработчиках см. в главе Процедурные языки.

Команда CREATE OR REPLACE LANGUAGE либо создаст новый язык, либо заменит существующее определение. Если язык уже существует, его параметры изменятся в соответствии со значениями, указанными в команде, но при этом владелец языка и права доступа не меняются, а все существующие функции, написанные на этом языке, считаются по-прежнему действительными.

Чтобы зарегистрировать новый язык или изменить параметры существующего языка, нужно иметь права суперпользователя QHB. Однако после создания языка его владельцем можно назначить и обычного пользователя, который затем может его удалить или переименовать, изменить права доступа к нему или назначить нового владельца. (Однако нельзя назначить обычного пользователя владельцем нижележащих функций на С, поскольку это откроет перед ним возможность повысить свои права.)

Форма CREATE LANGUAGE, которая не задает никакую функцию обработки, является устаревшей. Для обратной совместимости со старыми файлами дампа она интерпретируется как CREATE EXTENTION. Это сработает, если язык был упакован в расширение с тем же именем — как обычно и устанавливаются процедурные языки.

Параметры

TRUSTED

TRUSTED (доверенный) указывает, что язык не предоставляет доступ к данным, к которым пользователь не имел бы доступа без него. Если при регистрации языка это ключевое слово опущено, только пользователи с правами суперпользователя QHB смогут использовать этот язык для создания новых функций.

PROCEDURAL

Пропускаемое слово.

имя

Имя нового процедурного языка. Оно должно быть уникальным среди всех языков в этой базе данных.

HANDLER обработчик_вызова

обработчик_вызова — это имя ранее зарегистрированной функции, которая будет вызываться для выполнения функций на этом процедурном языке. Обработчик вызова для процедурного языка должен быть написан на компилируемом языке, например C с соглашениями о вызовах версии 1, и зарегистрирован в QHB как функция, не принимающая аргументы и возвращающая фиктивный тип language_handler, который просто используется для идентификации функции в качестве обработчика вызова.

INLINE обработчик_внедренного_кода

обработчик_внедренного_кода — это имя ранее зарегистрированной функции, которая будет вызываться для выполнения анонимного блока кода (команды DO) на этом языке. Если функция обработчик_внедренного_кода не указана, язык не будет поддерживать анонимные блоки кода. Функция обработки должна принимать один аргумент типа internal, который будет внутренним представлением команды DO, и обычно возвращает тип void. Возвращаемое значение обработчика игнорируется.

VALIDATOR функция_проверки

функция_проверки — это имя ранее зарегистрированной функции, которая будет вызываться при создании новой функции на языке, чтобы ее проверить. Если функция проверки не указана, то новая функция при создании проверяться не будет. Функция проверки должна принимать один аргумент типа oid, который станет идентификатором создаваемой функции, и обычно возвращает void.

Функция проверки обычно проверяет синтаксическую корректность тела функции, но также может анализировать и другие свойства функции, например поддержку определенных типов аргументов этим языком. Чтобы сигнализировать об ошибке, функция проверки должна использовать функцию ereport(). Возвращаемое значение этой функции игнорируется.

Примечания

Для удаления процедурных языков воспользуйтесь командой DROP LANGUAGE.

Информация о текущих установленных языках содержится в системном каталоге pg_language. Кроме того, команда psql \dL показывает список установленных языков.

Чтобы создавать функции на процедурном языке, необходимо иметь право USAGE для языка. По умолчанию для доверенных языков право USAGE предоставляется роли PUBLIC (т. е. для всех), но при желании это можно отменить.

Процедурные языки являются локальными для отдельных баз данных. Тем не менее язык можно установить в базу данных template1, вследствие чего он будет автоматически доступен во всех создаваемых после этого базах данных.

Примеры

Минимальная запись для создания нового процедурного языка:

CREATE FUNCTION plsample_call_handler() RETURNS language_handler
    AS '$libdir/plsample'
    LANGUAGE C;
CREATE LANGUAGE plsample
    HANDLER plsample_call_handler;

Обычно эти команды записываются в скрипте установки расширения, и пользователи устанавливают его так:

CREATE EXTENSION plsample;

Совместимость

Команда CREATE LANGUAGE является расширением QHB.

См. также

ALTER LANGUAGE, CREATE FUNCTION, DROP LANGUAGE, GRANT, REVOKE

CREATE MATERIALIZED VIEW

CREATE MATERIALIZED VIEW — определить новое материализованное представление

Синтаксис

CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] имя_таблицы
    [ (имя_столбца [, ...] ) ]
    [ USING метод ]
    [ WITH ( параметр_хранения [= значение] [, ... ] ) ]
    [ TABLESPACE имя_табличного_пространства ]
    AS запрос
    [ WITH [ NO ] DATA ]

Описание

Команда CREATE MATERIALIZED VIEW определяет материализованное представление запроса. Запрос выполняется и используется для заполнения представления во время выполнения команды (если только не указано WITH NO DATA). Обновить представление позже можно, выполнив REFRESH MATERIALIZED VIEW.

Команда CREATE MATERIALIZED VIEW подобна CREATE TABLE AS, за исключением того, что она еще и запоминает запрос, используемый для инициализации представления, чтобы позже его при желании можно было обновить. Материализованное представление по многим свойствам сходно с таблицей, но в нем не поддерживаются временные материализованные представления.

Команде CREATE MATERIALIZED VIEW требуется право CREATE в схеме, используемой для создания материализованного представления.

Параметры

IF NOT EXISTS

Не выдавать ошибку, если материализованное представление с таким же именем уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующее материализованное представление как-то соотносится с тем, которое было бы создано.

имя_таблицы

Имя создаваемого материализованного представления (может быть дополнено схемой).

имя_столбца

Имя столбца в новом материализованном представлении. Если имена столбцов не указаны, они берутся из имен столбцов результата запроса.

USING метод

Это необязательное предложение определяет табличный метод доступа, используемый для хранения содержимого нового материализованного представления; этот метод должен быть типа TABLE. Дополнительную информацию см. в главе Определение интерфейса табличных методов доступа. Если этот параметр не указан, то для нового материализованного представления выбирается табличный метод доступа по умолчанию. Дополнительную информацию см. в описании параметра default_table_access_method.

WITH ( параметр_хранения [= значение] [, ... ] )

В этом предложении указываются дополнительные параметры хранения для нового материализованного представления; дополнительную информацию см. в подразделе Параметры хранения описания команды CREATE TABLE. Все параметры, которые поддерживает CREATE TABLE, поддерживает и CREATE MATERIALIZED VIEW. Дополнительную информацию см. на справочной странице команды CREATE TABLE.

TABLESPACE имя_табличного_пространства

имя_табличного_пространства — это имя табличного пространства, в котором будет создано новое материализованное представление. Если не указано, то выбирается значение из default_tablespace.

запрос

Команда SELECT, TABLE или VALUES. Этот запрос будет выполняться в рамках операции с ограничениями безопасности; в частности, вызовы функций, которые сами создают временные таблицы, завершатся ошибкой.

WITH [ NO ] DATA

Это предложение указывает, следует ли заполнять материализованное представление во время его создания. Если материализованное представление не наполняется, оно помечается как нечитаемое, так что к нему нельзя будет обращаться до выполнения REFRESH MATERIALIZED VIEW.

Совместимость

Команда CREATE MATERIALIZED VIEW является расширением QHB.

См. также

ALTER MATERIALIZED VIEW, CREATE TABLE AS, CREATE VIEW, DROP MATERIALIZED VIEW, REFRESH MATERIALIZED VIEW

CREATE OPERATOR

CREATE OPERATOR — определить новый оператор

Синтаксис

CREATE OPERATOR имя (
    {FUNCTION|PROCEDURE} = имя_функции
    [, LEFTARG = тип_слева ] [, RIGHTARG = тип_справа ]
    [, COMMUTATOR = коммут_оператор ] [, NEGATOR = обратный_оператор ]
    [, RESTRICT = процедура_ограничения ] [, JOIN = процедура_соединения ]
    [, HASHES ] [, MERGES ]
)

Описание

Команда CREATE OPERATOR определяет новый оператор, имя. Пользователь, создавший оператор, становится его владельцем. Если задано имя схемы, то оператор создается в указанной схеме, если нет — в текущей.

Имя оператора представляет собой последовательность из нескольких символов (не более чем NAMEDATALEN-1, по умолчанию 63) из следующего списка:

+ - * / < > = ~ ! @ # % ^ & | ` ?

Есть несколько ограничений на выбор имени:

-- и /* в имени оператора присутствовать не могут, так как они приняты в качестве начала комментария.

Многосимвольное имя оператора не может заканчиваться на + или -, если только имя также не содержит хотя бы один из этих символов:

~ ! @ \# % ^ &  ‘ ?

Например, @- является допустимым именем оператора, а *- — не является. Это ограничение позволяет QHB анализировать SQL-совместимые команды, не требуя пробелов между синтаксическими единицами.

Использование => в качестве имени оператора считается устаревшим и может быть окончательно запрещено в будущих выпусках.

Оператор != при вводе отображается в <>, так что эти два имени всегда равнозначны.

Для двоичных операторов обязательно необходимо определить и LEFTARG, и RIGHTARG. Для префиксных операторов должен быть определен только параметр RIGHTARG. Функция имя_функции должна быть заранее определена с помощью CREATE FUNCTION и таким образом, чтобы принимать правильное количество аргументов (один или два) указанных типов.

В синтаксисе CREATE OPERATOR ключевые слова FUNCTION и PROCEDURE равнозначны, но указанная функция должна в любом случае быть функцией, а не процедурой. Использование ключевого слова PROCEDURE здесь поддерживается по историческим причинам и считается устаревшим.

В других предложениях указываются необязательные параметры оптимизации оператора. Их значение подробно описано в разделе Информация по оптимизации операторов.

Чтобы создать оператор, необходимо иметь право USAGE для типов аргументов и результата, а также право EXECUTE для нижележащей функции. Если указывается коммутативный или обратный оператор, нужно быть его владельцем.

Параметры

имя

Имя определяемого оператора. Допустимые символы см. выше. Имя может быть дополнено схемой, например, так: CREATE OPERATOR myschema.+ (...). Если схема не указана, то оператор создается в текущей схеме. Если два оператора в одной схеме работают с разными типами данных, они могут иметь одинаковое имя. Это называется перегрузкой.

имя_функции

Функция, используемая для реализации этого оператора.

тип_слева

Тип данных левого операнда оператора, если таковой имеется. Этот параметр будет опущен для префиксного оператора.

тип_справа

Тип данных правого операнда оператора.

коммут_оператор

Оператор, коммутирующий для данного.

обратный_оператор

Оператор, обратный для данного.

процедура_ограничения

Функция оценки избирательности ограничения для этого оператора.

процедура_соединения

Функция оценки избирательности соединения для этого оператора.

HASHES

Указывает, что этот оператор может поддерживать соединение по хешу.

MERGES

Указывает, что этот оператор может поддерживать соединение слиянием.

Чтобы задать имя оператора с указанием схемы в коммут_оператор или другом дополнительном аргументе, применяется синтаксис OPERATOR(), например:

COMMUTATOR = OPERATOR(myschema.===) ,

Примечания

Дополнительную информацию см. в разделе Пользовательские операторы.

Указать лексический приоритет оператора в команде CREATE OPERATOR невозможно, поскольку приоритетное поведение синтаксического анализатора реализовано аппаратно. Подробнее приоритеты описаны в подразделе Приоритеты операторов.

Устаревшие параметры SORT1, SORT2, LTCMP и GTCMP ранее использовались для указания имен операторов сортировки, связанных с оператором, применяемым при соединении слиянием. В этом больше нет необходимости, так как сведения о связанных операторах можно найти, обратившись вместо этого к семействам операторов B-деревьев. Если в команде отсутствует явное указание MERGES, все эти параметры игнорируются.

Для удаления из базы данных пользовательских операторов воспользуйтесь командой DROP OPERATOR, а для изменения их свойств — ALTER OPERATOR.

Примеры

Следующая команда определяет новый оператор, проверяющий равенство площадей, для типа данных box:

CREATE OPERATOR === (
    LEFTARG = box,
    RIGHTARG = box,
    FUNCTION = area_equal_function,
    COMMUTATOR = ===,
    NEGATOR = !==,
    RESTRICT = area_restriction_function,
    JOIN = area_join_function,
    HASHES, MERGES
);

Совместимость

Команда CREATE OPERATOR является расширением QHB. В стандарте SQL нет определения для пользовательских операторов.

См. также

ALTER OPERATOR, CREATE OPERATOR CLASS, DROP OPERATOR

CREATE OPERATOR CLASS

CREATE OPERATOR CLASS — определить новый класс операторов

Синтаксис

CREATE OPERATOR CLASS имя [ DEFAULT ] FOR TYPE тип_данных
  USING индексный_метод [ FAMILY имя_семейства ] AS
  {  OPERATOR номер_стратегии имя_оператора [ ( тип_операнда, тип_операнда ) ] [ FOR SEARCH | FOR ORDER BY имя_семейства_сортировки ]
   | FUNCTION номер_вспомогательной_функции [ ( тип_операнда [ , тип_операнда ] ) ] имя_функции ( тип_аргумента [, ...] )
   | STORAGE тип_хранения
  } [, ... ]

Описание

Команда CREATE OPERATOR CLASS создает новый класс операторов. Класс операторов определяет, как конкретный тип данных может использоваться с индексом. Класс операторов указывает, что некоторые операторы будут исполнять для этого типа данных и этого индексного метода определенные роли или «стратегии». Также класс операторов определяет вспомогательные функции, которые будут использоваться индексным методом при выборе класса операторов для столбца индекса. Все операторы и функции, используемые классом операторов, должны быть определены еще до его создания.

Если задано имя схемы, то класс операторов создается в указанной схеме, в противном случае — в текущей схеме. Два класса операторов в одной схеме могут иметь одинаковое имя только в том случае, если они предназначены для разных индексных методов.

Пользователь, определяющий класс операторов, становится его владельцем. В настоящее время создавать классы операторов могут только суперпользователи. (Причина этого ограничения в том, что ошибочное определение класса операторов может вызвать нарушения или даже сбой в работе сервера.)

В настоящее время CREATE OPERATOR CLASS не проверяет, включает ли определение класса оператора все операторы и функции, требуемые индексным методом, и образуют ли операторы и функции целостный набор. Ответственность за определение корректного класса операторов лежит на пользователе.

Связанные классы операторов можно сгруппировать в семейства операторов. Чтобы добавить новый класс операторов в существующее семейство, укажите в команде CREATE OPERATOR CLASS параметр FAMILY. Без этого параметра новый класс помещается в семейство с тем же именем, что и класс (если такое семейство не существует, оно создается).

Дополнительную информацию см. в разделе Интерфейсные расширения для индексов.

Параметры

имя

Имя создаваемого класса операторов (может быть дополнено схемой).

DEFAULT

При наличии этого указания класс операторов становится классом по умолчанию для своего типа данных. У конкретного типа данных и индексного метода не может быть более одного класса операторов по умолчанию.

тип_данных

Тип данных столбца, для которого предназначен этот класс операторов.

индексный_метод

Имя индексного метода, для которого предназначен этот класс операторов.

имя_семейства

Имя существующего семейства операторов, в которое будет добавлен этот класс. Если не указано, используется семейство с тем же именем, что и у класса оператора (если такое семейство не существует, оно создается).

номер_стратегии

Номер стратегии индексного метода для оператора, связанного с этим классом операторов.

имя_оператора

Имя оператора, связанного с этим классом операторов (может быть дополнено схемой).

тип_операнда

В предложении OPERATOR это тип данных операнда оператора либо ключевое слово NONE, характеризующее префиксный оператор. В нормальных условиях типы данных операнда можно опустить, когда они совпадают с типом данных класса оператора.

В предложении FUNCTION это тип данных операнда, для поддержки которого предназначена эта функция, если тот отличается от типа входных данных функции (для функций сравнения в B-деревьях и хеш-функций) или типа данных класса (для вспомогательных функций сортировки и функций равенства образов B-деревьев и всех функций в классах операторов GiST, SP-GiST, GIN и BRIN). Обычно предполагаемые по умолчанию типы оказываются верными, так что тип_операнда указывать в FUNCTION не нужно (исключение составляют функции сортировки B-деревьев, предназначенные для сравнений разных типов данных).

имя_семейства_сортировки

Имя существующего семейства операторов btree, описывающего порядок сортировки, связанный с оператором сортировки (может быть дополнено схемой).

Если не указано ни FOR SEARCH (для поиска), ни FOR ORDER BY (для сортировки), по умолчанию подразумевается FOR SEARCH.

номер_вспомогательной_функции

Номер вспомогательной функции индексного метода для функции, связанной с этим классом операторов.

имя_функции

Имя функции, которая является вспомогательной функцией индексного метода для этого класса операторов (может быть дополнено схемой).

тип_аргумента

Тип(ы) данных параметра функции.

тип_хранения

Тип данных, фактически сохраненных в индексе. Обычно это то же самое, что и тип данных столбца, но некоторые индексные методы (в настоящее время GiST, GIN и BRIN) могут работать с отличающимся типом. Предложение STORAGE можно указывать, только если индексный метод позволяет использовать другой тип данных. Если тип_данных столбца задан как anyarray, тип_хранения может быть объявлен как anyelement, чтобы показать, что записи в индексе являются членами типа элемента, принадлежащего к фактическому типу массива, для которого создается конкретный индекс.

Предложения OPERATOR, FUNCTION и STORAGE могут появляться в любом порядке.

Примечания

Поскольку механизм индексирования не проверяет права доступа к функциям перед их использованием, включение функции или оператора в класс операторов равносильно предоставлению всем права на их выполнение. Обычно это не проблема для разновидностей функций, которые бывают полезны в классе операторов.

Операторы не должны определяться функциями SQL. Функция SQL, скорее всего, будет встроена в вызывающий запрос, что помешает оптимизатору распознать, что запрос соответствует индексу.

Примеры

Команда в следующем примере определяет класс операторов индекса GiST для типа данных _int4 (массива из int4). Полный пример приведен в модуле intarray.

CREATE OPERATOR CLASS gist__int_ops
    DEFAULT FOR TYPE _int4 USING gist AS
        OPERATOR        3       &&,
        OPERATOR        6       = (anyarray, anyarray),
        OPERATOR        7       @>,
        OPERATOR        8       <@,
        OPERATOR        20      @@ (_int4, query_int),
        FUNCTION        1       g_int_consistent (internal, _int4, smallint, oid, internal),
        FUNCTION        2       g_int_union (internal, internal),
        FUNCTION        3       g_int_compress (internal),
        FUNCTION        4       g_int_decompress (internal),
        FUNCTION        5       g_int_penalty (internal, internal, internal),
        FUNCTION        6       g_int_picksplit (internal, internal),
        FUNCTION        7       g_int_same (_int4, _int4, internal);

Совместимость

Команда CREATE OPERATOR CLASS является расширением QHB. В стандарте SQL такой команды нет.

См. также

ALTER OPERATOR CLASS, DROP OPERATOR CLASS, CREATE OPERATOR FAMILY, ALTER OPERATOR FAMILY

CREATE OPERATOR FAMILY

CREATE OPERATOR FAMILY — определить новое семейство операторов

Синтаксис

CREATE OPERATOR FAMILY имя USING индексный_метод

Описание

Команда CREATE OPERATOR FAMILY создает новое семейство операторов. Семейство операторов определяет набор связанных классов операторов и, возможно, некоторые дополнительные операторы и вспомогательные функции, совместимые с этими классами операторов, но несущественные для функционирования любого отдельного индекса. (Операторы и функции, которые важны для индексов, следует сгруппировать в пределах соответствующего класса операторов, а не «слабо связывать» в семействе операторов. Как правило, операторы с одним типом данных привязаны к классам операторов, тогда как операторы с перекрестными типами данных могут быть слабо связаны в семействе операторов, содержащем классы операторов для обоих типов данных.)

Новое семейство операторов изначально пустое. Его нужно заполнить путем последующего выполнения команд CREATE OPERATOR CLASS, которые добавят в него классы операторов, и, возможно, команд ALTER OPERATOR FAMILY, которые добавят «слабосвязанные» операторы и соответствующие вспомогательные функции.

Если задано имя схемы, то семейство операторов создается в указанной схеме, в противном случае — в текущей схеме. Два семейства операторов в одной схеме могут иметь одинаковое имя только в том случае, если они предназначены для разных индексных методов.

Пользователь, определяющий семейство операторов, становится его владельцем. В настоящее время для создания семейства операторов нужно быть суперпользователем. (Причина этого ограничения в том, что ошибочное определение семейства операторов может вызвать нарушения или даже сбой в работе сервера.)

Дополнительную информацию см. в разделе Интерфейсные расширения для индексов.

Параметры

имя

Имя создаваемого семейства операторов (может быть дополнено схемой).

индексный_метод

Имя индексного метода, для которого предназначено это семейство операторов.

Совместимость

Команда CREATE OPERATOR FAMILY является расширением QHB. В стандарте SQL такой команды нет.

См. также

ALTER OPERATOR FAMILY, DROP OPERATOR FAMILY, CREATE OPERATOR CLASS, ALTER OPERATOR CLASS, DROP OPERATOR CLASS

CREATE POLICY

CREATE POLICY — определить для таблицы новую политику защиты на уровне строк

Синтаксис

CREATE POLICY имя ON имя_таблицы
    [ AS { PERMISSIVE | RESTRICTIVE } ]
    [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
    [ TO { имя_роли | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ]
    [ USING ( выражение_USING ) ]
    [ WITH CHECK ( выражение_CHECK ) ]

Описание

Команда CREATE POLICY определяет для таблицы новую политику защиты на уровне строк. Обратите внимание, что для применения созданных политик нужно включить для таблицы защиту на уровне строк (используя ALTER TABLE ... ENABLE ROW LEVEL SECURITY).

Политика предоставляет разрешение на выборку, добавление, обновление или удаление строк, удовлетворяющих соответствующему выражению политики. Существующие строки таблицы сравниваются с выражением, указанным в в USING, в то время как новые строки, которые будут созданы с помощью INSERT или UPDATE, проверяются на соответствие выражению, указанному в WITH CHECK. Когда выражение USING возвращает для данной строки true, эта строка видна пользователю, а если возвращается false или NULL, то не видна. Когда выражение WITH CHECK возвращает для строки значение true, эта строка добавляется или обновляется, а если возвращается значение false или NULL, то возникает ошибка.

Для операторов INSERT и UPDATE выражения WITH CHECK применяются после того, как срабатывают триггеры BEFORE, но до того, как будут сделаны какие-либо фактические изменения данных. Таким образом, триггер BEFORE ROW может изменять добавляемые данные, влияя на результат проверки политики защиты. Выражения WITH CHECK применяются перед любыми другими ограничениями.

Имена политик указаны на уровне таблицы. Поэтому одно имя политики может использоваться для нескольких разных таблиц и в каждой дать отдельное определение политики, подходящее этой конкретной таблице.

Политики могут применяться для определенных команд или для определенных ролей. По умолчанию вновь созданные политики применяются для всех команд и ролей, если не указано иное. К одной команде может применяться несколько политик; дополнительную информацию см. ниже. В таблице Политики, применяемые по типу команды показано, как различные типы политик применяются к определенным командам.

Для политик, которые могут иметь и выражения USING, и выражения WITH CHECK (ALL и UPDATE), в случае отсутствия выражения WITH CHECK выражение USING будет использоваться и для определения того, какие строки будут видимыми (обычное назначение USING), и для определения того, какие новые строки будет разрешено добавить (назначение WITH CHECK)

Если для таблицы включена защита на уровне строк, но применимые политики отсутствуют, то предполагается политика «по умолчанию запретить», чтобы никакие строки не были видны или доступны для обновления.

Параметры

имя

Имя создаваемой политики. Должно отличаться от имени любой другой политики для таблицы.

имя_таблицы

Имя таблицы, к которой применяется политика (может быть дополнено схемой).

PERMISSIVE

Указывает, что политика должна быть создана как разрешительная. Все разрешительные политики, применимые к данному запросу, будут объединены с помощью логического оператора «ИЛИ». Создавая разрешительные политики, администраторы могут расширить множество записей, к которым можно обращаться. По умолчанию политики являются разрешительными.

RESTRICTIVE

Указывает, что политика должна быть создана как ограничительная. Все ограничительные политики, применимые к данному запросу, будут объединены с помощью логического оператора «И». Создавая ограничительные политики, администраторы могут сократить множество записей, к которым можно обращаться, так как для каждой записи должны удовлетворяться все ограничительные политики.

Обратите внимание, что для предоставления доступа к записям необходима хотя бы одна разрешительная политика, и только после этого возможно успешное использование ограничительных политик для сокращения этого доступа. Если существуют только ограничительные политики, то никакие записи не будут доступны. При наличии сочетания разрешительных и ограничительных политик запись доступна только в том случае, если кроме всех ограничительных политик удовлетворяется как минимум одна из разрешительных.

команда

Команда, к которой применяется политика. Допустимые варианты: ALL, SELECT, INSERT, UPDATE и DELETE. По умолчанию подразумевается ALL (все). Особенности их применения описаны ниже.

имя_роли

Роль (или роли), к которой будет применяться политика. Значение по умолчанию равно PUBLIC, то есть политика применяется ко всем ролям.

выражение_USING

Произвольное условное выражение SQL (возвращающее boolean). Условное выражение не может содержать никаких агрегатных или оконных функций. Это выражение будет добавлено в запросы, обращающиеся к данной таблице, если включена защита на уровне строк. Строки, для которых выражение возвращает true, будут видны. Любые строки, для которых выражение возвращает значение false или NULL, не будут видны пользователю (в запросе SELECT) и не будут доступны для изменения (в запросе UPDATE или DELETE). Такая строка просто пропускается; ошибка при этом не выдается.

выражение_CHECK

Произвольное условное выражение SQL (возвращающее boolean). Условное выражение не может содержать никаких агрегатных или оконных функций. Это выражение будет использоваться в запросах INSERT и UPDATE к таблице, если включена защита на уровне строк, так что в них принимаются только те строки, для которых оно выдает true. Если это выражение выдает false или NULL для любой из добавляемых записей или записей, получаемых при изменении, выдается ошибка. Обратите внимание, что выражение_CHECK вычисляется для предлагаемого нового содержимого строки, а не для существующих данных.

Политики по командам

ALL

Указание ALL для политики означает, что она будет применяться ко всем командам, независимо от типа последних. Если существует политика ALL и другие более детализированные политики, то будут применяться и политика ALL, и более детализированная политика (или политики). Вдобавок политики ALL будут применяться как к стороне выборки, так и к стороне изменения данных в запросе, используя в обоих случаях выражение USING, если было определено только оно.

Например, когда выполняется UPDATE, политика ALL будет фильтровать и строки, которые UPDATE сможет выбрать для изменения (применяя выражение USING), и итоговые измененные строки, проверяя, разрешено ли записать их в таблицу (применяя выражение WITH CHECK, если оно определено, или, в противном случае, USING). Если команда INSERT или UPDATE пытается добавить в таблицу строки, не удовлетворяющие выражению WITH CHECK политики ALL, вся команда будет прервана.

SELECT

Указание SELECT для политики означает, что она будет применяться к запросам SELECT всякий раз, когда при обращении к отношению, для которого определена политика, требуется право SELECT. Результатом является то, что запрос SELECT выдаст только те записи из отношения, которые удовлетворят политике SELECT, и запросы, использующие право SELECT, например запрос UPDATE, увидят только записи, разрешенные политикой SELECT. Для политики SELECT не может задаваться выражение WITH CHECK, так как оно действует, только когда записи читаются из отношения.

INSERT

Указание INSERT для политики означает, что она будет применяться к командам INSERT. Попытка добавления строк, которые не удовлетворят этой политике, приведет к ошибке нарушения политики, и вся команда INSERT будет прервана. Для политики INSERT не может задаваться выражение USING, так как она действует, только когда в отношение добавляются записи.

Обратите внимание, что INSERT с указанием ON CONFLICT DO UPDATE проверяет выражения WITH CHECK политик INSERT только для строк, добавляемых в отношение по пути INSERT.

UPDATE

Указание UPDATE для политики означает, что она будет применяться к командам UPDATE, SELECT FOR UPDATE и SELECT FOR SHARE, а также к дополнительным предложениям ON CONFLICT DO UPDATE команд INSERT. Так как UPDATE подразумевает извлечение существующей записи и замену ее новой измененной записью, политики UPDATE принимают оба выражения: и USING, и WITH CHECK. Выражение USING определяет, какие записи команда UPDATE будет видеть для работы с ними, в то время как выражение WITH CHECK определяет, какие измененные строки могут быть сохранены в отношении.

Любые строки, измененные значения которых не будут удовлетворять выражению WITH CHECK, вызовут ошибку, и вся команда будет прервана. Если указывается только предложение USING, оно будет применяться и в качестве, собственно, USING, и в качестве выражения WITH CHECK.

Как правило, команда UPDATE также должна считывать данные из столбцов подлежащего изменению отношения (например, в предложении WHERE или RETURNING либо в выражении в правой части предложения SET). В этом случае также требуется иметь права SELECT в изменяемом отношении, а в дополнение к политикам UPDATE будут применяться соответствующие политики SELECT или ALL. Таким образом, пользователь должен иметь доступ к изменяемой строке (или строкам) через политику UPDATE или ALL, а также разрешение на изменение этой строки (или строк) от политики SELECT или ALL.

Если команда INSERT указана со вспомогательным предложением ON CONFLICT DO UPDATE, то при выборе пути UPDATE сначала подлежащая изменению строка проверяется по выражениям USING всех политик UPDATE, а затем новая обновленная строка проверяется по выражениям WITH CHECK. Обратите внимание, однако, что, в отличие от отдельной команды UPDATE, если существующая строка не удовлетворяет выражениям USING, будет выдаваться ошибка (путь UPDATE никогда не пропускается неявно).

DELETE

Указание DELETE для политики означает, что она будет применяться к командам DELETE. Команде DELETE будут видны только те строки, которые разрешит эта команда. При этом среди них могут быть строки, которые видны через SELECT, но если они не удовлетворяют выражению USING политики DELETE, то удалить их будет нельзя.

В большинстве случаев команде DELETE также требуется считывать данные из столбцов в отношении, из которого осуществляется удаление (например, в предложении WHERE или RETURNING). В этом случае также требуются права SELECT для отношения, и в дополнение к политикам DELETE будут применяться соответствующие политики SELECT или ALL. Таким образом, пользователь должен иметь доступ к удаляемой строке (или строкам) через политику SELECT или ALL, а также разрешение на удаление этой строки (или строк) от политики DELETE или ALL.

Для политики DELETE не может задаваться выражение WITH CHECK, так как она применяется только тогда, когда записи удаляются из отношения, а в этом случае новые строки, подлежащие проверке, отсутствуют.

Таблица. Политики, применяемые по типу команды

Команда
Политика SELECT/ALL
Выражение USING
Политика INSERT/ALL
Выражение WITH CHECK
Политика UPDATE/ALL
Выражение USING
Политика UPDATE/ALL
Выражение WITH CHECK
Политика DELETE/ALL
Выражение USING

SELECT
Существующая строка
—
—
—
—

SELECT FOR UPDATE/SHARE
Существующая строка
—
Существующая строка
—
—

INSERT
—
Новая строка
—
—
—

INSERT ... RETURNING
Новая строка ¹
Новая строка
—
—
—

UPDATE
Существующие и новые строки ¹
—
Существующая строка
Новая строка
—

DELETE
Существующая строка ¹
—
—
—
Существующая строка

ON CONFLICT DO UPDATE
Существующие и новые строки
—
Существующая строка
Новая строка
—

¹

Если для существующей или новой строки требуется доступ на чтение (например, предложения WHERE или RETURNING, обращающиеся к столбцам отношения).

Применение нескольких политик

Когда к одной команде применяются несколько политик различных типов команд (например, политики SELECT и UPDATE применяются к команде UPDATE), пользователь должен иметь оба типа разрешений (например разрешение на выборку строк из отношения и разрешение на их изменение). Таким образом, выражения для одного типа политики комбинируются с выражениями для другого типа политики с помощью оператора И.

Если к одной команде применяется несколько политик одного и того же типа команд, то должна быть хотя бы одна политика PERMISSIVE (разрешительная), предоставляющая доступ к отношению, а также должны удовлетворяться все политики RESTRICTIVE (ограничительные). Таким образом, выражения всех политик PERMISSIVE объединяются с помощью ИЛИ, выражения всех политик RESTRICTIVE объединяются с помощью И и полученные результаты также объединяются при помощи И. Если политики PERMISSIVE отсутствуют, доступ запрещается.

Обратите внимание, что в целях объединения нескольких политик политики ALL рассматриваются как политики любого применимого в данном случае типа.

Например, в команде UPDATE, требующей разрешения и для SELECT, и для UPDATE, в случае существования нескольких применимых политик каждого типа они будут объединяться следующим образом:

выражение from RESTRICTIVE SELECT/ALL policy 1
AND
выражение from RESTRICTIVE SELECT/ALL policy 2
AND
...
AND
(
  выражение from PERMISSIVE SELECT/ALL policy 1
  OR
  выражение from PERMISSIVE SELECT/ALL policy 2
  OR
  ...
)
AND
выражение from RESTRICTIVE UPDATE/ALL policy 1
AND
выражение from RESTRICTIVE UPDATE/ALL policy 2
AND
...
AND
(
  выражение from PERMISSIVE UPDATE/ALL policy 1
  OR
  выражение from PERMISSIVE UPDATE/ALL policy 2
  OR
  ...
)

Примечания

Чтобы создавать или изменять политики для таблицы, нужно быть ее владельцем.

Хотя политики будут применяться для явных запросов к таблицам в базе данных, они не применяются, когда система выполняет внутренние проверки целостности ссылок или проверяет ограничения. Это означает, что существуют косвенные способы определить, что данное значение существует. Примером этого является попытка добавить повторяющееся значение в столбец, образующий первичный ключ или имеющий ограничение уникальности. Если добавление завершается неудачно, то пользователь может сделать вывод, что значение уже существует. (В этом примере предполагается, что пользователю разрешено политикой добавлять записи, которые он не может видеть.) Другой пример — это когда пользователю разрешено добавлять записи в таблицу, которая ссылается на другую, иным образом не видимую. Существование значения может быть определено пользователем, который добавляет значения в подчиненную таблицу, где успешный результат операции будет признаком того, что значение существует в главной таблице. Эти проблемы можно решить путем тщательной разработки политик, чтобы полностью запретить пользователям добавлять, удалять или изменять записи, которые могут указывать на не видимое иным образом значение, либо путем использования генерируемых значений (например суррогатных ключей) вместо ключей с внешними значениями.

Как правило, система будет применять условия фильтрации, введенные с помощью политик безопасности, до условий в запросах пользователя, чтобы предотвратить нежелательную утечку защищаемых данных через пользовательские функции, которые могут не быть доверенными. Однако функции и операторы, помеченные системой (или системным администратором) как LEAKPROOF, могут вычисляться до условий политики, так как они считаются доверенными.

Поскольку выражения политики добавляются непосредственно в запрос пользователя, они будут выполняться с правами пользователя, выполняющего запрос. Таким образом, пользователи, на которых распространяется заданная политика, должны иметь доступ ко всем таблицам или функциям, на которые ссылается выражение, иначе при попытке обращения к таблице, у которой включена защита на уровне строк, они просто получат ошибку «отказано в доступе». Однако это не меняет способ работы представлений. Как и с обычными запросами и представлениями, проверки разрешений и политики для таблиц, на которые ссылается представление, будут использовать права владельца представления и все политики, распространяющиеся на этого владельца.

Дополнительное описание и практические примеры можно найти в разделе Политики безопасности строк.

Совместимость

Команда CREATE POLICY является расширением QHB.

См. также

ALTER POLICY, DROP POLICY, ALTER TABLE

CREATE PROCEDURE

CREATE PROCEDURE — определить новую процедуру

Синтаксис

CREATE [ OR REPLACE ] PROCEDURE
    имя ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ { DEFAULT | = } выражение_по_умолчанию ] [, ...] ] )
  { LANGUAGE имя_языка
    | TRANSFORM { FOR TYPE имя_типа } [, ... ]
    | [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
    | SET параметр_конфигурации { TO значение | = значение | FROM CURRENT }
    | AS 'определение'
    | AS 'объектный_файл', 'ссылочный_символ'
    | тело_sql
  } ...

Описание

Команда CREATE PROCEDURE определяет новую процедуру. CREATE OR REPLACE PROCEDURE либо создаст новую процедуру, либо заменит существующее определение. Чтобы иметь возможность определить процедуру, необходимо иметь право USAGE для соответствующего языка.

Если указано имя схемы, то процедура создается в указанной схеме, в противном случае — в текущей. Имя новой процедуры не должно совпадать ни с одной существующей процедурой или функцией с теми же типами входных аргументов в той же схеме. Однако процедуры и функции с различными типами аргументов могут иметь одинаковое имя (это называется перегрузкой).

Чтобы заменить текущее определение существующей процедуры, используйте CREATE OR REPLACE PROCEDURE. Таким способом невозможно изменить имя или типы аргументов процедуры (если вы попытаетесь, то фактически создадите новую, отдельную процедуру).

Если CREATE OR REPLACE PROCEDURE используется для замены существующей процедуры, владелец и права доступа к этой процедуре не меняются. Всем остальным свойствам процедуры присваиваются значения, указанные в команде явно или подразумеваемые по умолчанию. Чтобы заменить процедуру, нужно быть ее владельцем (или быть членом роли-владельца).

Пользователь, создающий процедуру, становится ее владельцем.

Чтобы создать процедуру, необходимо иметь право USAGE для типов ее аргументов.

Более подробную информацию о написании процедур см. в разделе Пользовательские процедуры.

Параметры

имя

Имя создаваемой процедуры (может быть дополнено схемой).

режим_аргумента

Режим аргумента: IN (входной), OUT (выходной), INOUT (входной и выходной) или VARIADIC (переменный). Если этот параметр опущен, значение по умолчанию равно IN.

имя_аргумента

Имя аргумента.

тип_аргумента

Тип данных аргументов процедуры (при необходимости дополненный схемой), если аргументы имеются. Типы аргументов могут быть базовыми, составными или доменными типами, а также могут ссылаться на тип столбца таблицы.

В зависимости от языка реализации также может быть разрешено указывать «псевдотипы», такие как cstring. Псевдотипы показывают, что фактический тип аргумента либо указан не полностью, либо находится вне набора обычных типов данных SQL.

Ссылка на тип столбца записывается в виде имя_таблицы.имя_столбца%TYPE. Использование этой возможности иногда может помочь сделать процедуру независимой от изменений в определении таблицы.

выражение_по_умолчанию

Выражение, которое будет использоваться в качестве значения по умолчанию, если параметр не указан. Выражение должно быть приведено к типу аргумента параметра. Все входные параметры, следующие за параметром со значением по умолчанию, также должны иметь значения по умолчанию.

имя_языка

Имя языка, на котором реализована процедура. Это может быть sql, c, internal или имя пользовательского процедурного языка, например plpgsql. Если указано тело_sql, языком по умолчанию является sql. Заключение имени в апострофы считается устаревшим вариантом и требует совпадения регистра.

TRANSFORM { FOR TYPE имя_типа } [, ... ] }

Устанавливает список трансформаций, которые должны применяться при вызове процедуры. Трансформации выполняют преобразования между типами SQL и типами данных, специфичными для языков; см. справочную страницу команды CREATE TRANSFORM. Преобразования встроенных типов обычно жестко предопределены в реализациях процедурных языков, так что их здесь указывать не нужно. Если реализация процедурного языка не знает, как обрабатывать тип, и трансформация не предоставляется, будет выполнено преобразование типов данных по умолчанию, но это зависит от реализации.

[EXTERNAL] SECURITY INVOKER
[EXTERNAL] SECURITY DEFINER

Характеристика SECURITY INVOKER (безопасность вызывающего) указывает, что процедура должна выполняться с правами вызывающего ее пользователя. Это значение по умолчанию. Вариант SECURITY DEFINER (безопасность определившего) указывает, что процедура должна выполняться с правами пользователя, который ей владеет.

Ключевое слово EXTERNAL (внешняя) допускается для соответствия стандарту SQL, но является необязательным, так как, в отличие от SQL, эта характеристика распространяется на все процедуры, а не только на внешние.

В процедуре с характеристикой SECURITY DEFINER не могут выполняться операторы управления транзакциями (например COMMIT и ROLLBACK в некоторых языках).

параметр_конфигурации
значение

Предложение SET определяет, что при вызове процедуры указанный параметр конфигурации должен принять заданное значение, а затем, по завершении процедуры, восстановить свое предыдущее значение. Предложение SET FROM CURRENT сохраняет значение параметра, действующее при выполнении CREATE PROCEDURE, в качестве значения, которое будет применено при входе в процедуру.

Если в определение процедуры добавлено предложение SET, то действие команды SET LOCAL, выполняемой внутри процедуры для той же переменной, ограничивается процедурой: предыдущее значение параметра конфигурации по завершении процедуры все равно восстанавливается. Однако обычная команда SET (без LOCAL) переопределяет предложение SET, как и предыдущую команду SET LOCAL: эффекты команды сохраняются после завершения процедуры, если не случится откат текущей транзакции.

Если к определению процедуры добавлено предложение SET, то в этой процедуре не смогут выполняться операторы управления транзакциями (например COMMIT и ROLLBACK в некоторых языках).

Дополнительную информацию о допустимых именах и значениях параметров см. на справочной странице команды SET и в главе Конфигурация сервера.

определение

Строковая константа, определяющая процедуру; значение зависит от языка. Это может быть имя внутренней процедуры, путь к объектному файлу, команда SQL или код на процедурном языке.

Часто бывает полезно заключать определение процедуры в знаки доллара (см. подраздел Строковые константы с экранированием знаками доллара), а не в традиционные апострофы. Если не использовать знаки доллара, то любые апострофы или обратные слэши в определении процедуры должны быть экранированы путем удвоения.

объектный_файл, ссылочный_символ

Это форма предложения AS используется для динамически загружаемых процедур на языке C/RUST, когда имя процедуры в исходном коде на языке C/RUST не совпадает с именем процедуры SQL. Строка объектный_файл является именем файла разделяемой библиотеки, содержащего скомпилированную процедуру на C/RUST, и интерпретируется как параметр команды LOAD. Строка ссылочный_символ задает ссылочный символ процедуры, то есть имя процедуры в исходном коде на языке C/RUST. Если ссылочный символ опущен, предполагается, что он совпадает с именем определяемой процедуры SQL.

При повторных вызовах CREATE PROCEDURE ссылается на один и тот же объектный файл, который загружается только один раз за сеанс. Чтобы выгрузить и перезагрузить файл (возможно, во время разработки), запустите новый сеанс.

тело_sql

Тело функции в стиле LANGUAGE SQL. Это должен быть блок операторов

BEGIN ATOMIC
  оператор;
  оператор;
  ...
  оператор;
END

Это схоже с написанием текста тела процедуры в виде строковой константы (см. определение выше), но есть некоторые различия: эта форма работает только для LANGUAGE SQL, а форма строковой константы работает для всех языков. Эта форма анализируется во время определения процедуры, а форма строковой константы анализируется во время выполнения; таким образом, эта форма не может поддерживать полиморфные типы аргументов и другие конструкции, которые нельзя разрешить во время определения процедуры. Эта форма отслеживает зависимости между процедурой и объектами, используемыми в ее теле, так что команда DROP ... CASCADE будет работать корректно, тогда как форма, использующая строковые литералы, может оставить недействительные процедуры. Наконец, данная форма более совместима со стандартом SQL и другими реализациями SQL.

Примечания

Дополнительную информацию о создании функций, которая также применима к процедурам, см. на справочной странице команды CREATE FUNCTION.

Чтобы выполнить процедуру, воспользуйтесь командой CALL.

Примеры

CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
AS $$
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;

или

CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
  INSERT INTO tbl VALUES (a);
  INSERT INTO tbl VALUES (b);
END;

и вызов вроде такого:

CALL insert_data(1, 2);

Совместимость

Команда CREATE PROCEDURE определена в стандарте SQL. Ее реализацию в QHB можно использовать совместимым образом, но у нее есть множество расширений. Дополнительную информацию см. на справочной странице команды CREATE FUNCTION.

См. также

ALTER PROCEDURE, DROP PROCEDURE, CALL, CREATE FUNCTION

CREATE PUBLICATION

CREATE PUBLICATION — определить новую публикацию

Синтаксис

CREATE PUBLICATION имя
    [ FOR TABLE [ ONLY ] имя_таблицы [ * ] [, ...]
      | FOR ALL TABLES ]
    [ WITH ( параметр_публикации [= значение] [, ... ] ) ]

Описание

Команда CREATE PUBLICATION добавляет новую публикацию в текущую базу данных. Имя публикации должно отличаться от имени других существующих публикаций в текущей базе данных.

Публикация — это, по существу, группа таблиц, изменения данных которых подлежат логической репликации. Дополнительную информацию о том, как публикации вписываются в параметры логической репликации, см. в разделе Публикация.

Параметры

имя

Имя новой публикации.

FOR TABLE

Указывает список таблиц для добавления в публикацию. Если перед именем таблицы указано ONLY, в публикацию добавляется только заданная таблица. Без ONLY добавляется и заданная таблица, и все ее потомки (если таковые имеются). После имени таблицы можно добавить необязательное указание *, чтобы явно обозначить, что включены дочерние таблицы. Однако это не распространяется на партиционированные таблицы. Партиции партиционированных таблиц всегда неявно считаются частью публикации, поэтому никогда не добавляются в нее явно.

Только постоянные базовые таблицы могут быть частью публикации. Временные, нежурналируемые и сторонние таблицы, а также материализованные и обычные представления не могут быть частью публикации.

Когда в публикацию добавляется партиционированная таблица, все ее существующие и будущие партиции неявно считаются частью этой публикации. Поэтому даже операции, выполняемые непосредственно с партицией, тоже публикуются через публикации, в которые входит их родительская таблица.

FOR ALL TABLES

Отмечает публикацию как реплицирующую изменения для всех таблиц в базе данных, включая таблицы, созданные в будущем.

WITH ( параметр_публикации [= значение] [, ... ] )

В этом предложении указываются необязательные параметры для публикации. Поддерживаются следующие параметры:

publish (string)
Этот параметр определяет, какие операции DML будут опубликованы новой публикацией для подписчиков. В качестве его значения через запятую задается список операций. Разрешены следующие операции: insert, update, delete и truncate. По умолчанию публикуются все действия, поэтому значение по умолчанию для этого параметра равно 'insert, update, delete, truncate'.

publish_via_partition_root (boolean)
Этот параметр определяет, будут ли изменения во включенной в публикацию партиционированной таблице (или в ее партициях) опубликованы с именем и схемой этой партиционированной таблицы, а не ее отдельных партиций, где эти изменения на самом деле произошли (второе — поведение по умолчанию). Включение этого параметра позволяет реплицировать изменения в непартиционированную таблицу или в партиционированную таблицу, состоящую из другого набора партиций.

Если этот параметр включен, операции TRUNCATE, выполняемые непосредственно с партициями, не реплицируются.

Примечания

Если не задано ни FOR TABLE, ни FOR ALL TABLES, публикация создается с пустым набором таблиц. Это удобно, если таблицы будут добавлены позже.

Создание публикации не запускает репликацию. Она лишь определяет логику группировки и фильтрации для будущих подписчиков.

Чтобы создать публикацию, необходимо иметь право CREATE в текущей базе данных. (Разумеется, суперпользователи эту проверку проходят.)

Чтобы добавить таблицу в публикацию, нужно быть владельцем таблицы. Предложение FOR ALL TABLES требует, чтобы вызывающий его пользователь был суперпользователем.

Таблицы, добавленные в публикацию, которая публикует операции UPDATE и/или DELETE, должны иметь свойство REPLICA IDENTITY. В противном случае данные операции для этих таблиц будут запрещены.

Для команды INSERT ... ON CONFLICT публикация выдает операцию, которая фактически является результатом выполнения этой команды. Таким образом, в зависимости от результата, она может быть опубликована как INSERT или UPDATE или не быть опубликована вовсе.

Присоединение (ATTACH) таблицы к дереву партиционирования, корень которого публикуется через публикацию со значением параметра publish_via_partition_root, равным true, не приводит к репликации существующего содержимого этой таблицы.

Команды COPY ... FROM публикуются в виде операций INSERT.

Операции DDL не публикуются.

Примеры

Создание публикации, охватывающей изменения в двух таблицах:

CREATE PUBLICATION mypublication FOR TABLE users, departments;

Создание публикации, охватывающей все изменения во всех таблицах:

CREATE PUBLICATION alltables FOR ALL TABLES;

Создание публикации, охватывающей только операции INSERT в одной таблице:

CREATE PUBLICATION insert_only FOR TABLE mydata
    WITH (publish = 'insert');

Совместимость

Команда CREATE PUBLICATION является расширением QHB.

См. также

ALTER PUBLICATION, DROP PUBLICATION, CREATE SUBSCRIPTION, ALTER SUBSCRIPTION

CREATE ROLE

CREATE ROLE — определить новую роль в базе данных

Синтаксис

CREATE ROLE имя [ [ WITH ] параметр [ ... ] ]

где параметром может быть:

      SUPERUSER | NOSUPERUSER
    | CREATEDB | NOCREATEDB
    | CREATEROLE | NOCREATEROLE
    | INHERIT | NOINHERIT
    | LOGIN | NOLOGIN
    | REPLICATION | NOREPLICATION
    | BYPASSRLS | NOBYPASSRLS
    | CONNECTION LIMIT предел_подключений
    | [ ENCRYPTED ] PASSWORD 'пароль' | PASSWORD NULL
    | VALID UNTIL 'метка_времени'
    | IN ROLE имя_роли [, ...]
    | IN GROUP имя_роли [, ...]
    | ROLE имя_роли [, ...]
    | ADMIN имя_роли [, ...]
    | USER имя_роли [, ...]
    | SYSID uid

Описание

Команда CREATE ROLE добавляет новую роль в кластер баз данных QHB. Роль — это сущность, которая может владеть объектами базы данных и иметь некоторые права в базе данных; роль может рассматриваться как «пользователь», «группа» или и то, и другое, в зависимости от того, как она используется. Информацию об управлении пользователями и аутентификации см. в главах Роли в базе данных и Аутентификация клиентского приложения. Чтобы выполнить эту команду, нужно быть суперпользователем базы данных или иметь право CREATEROLE.

Обратите внимание, что роли определяются на уровне кластера баз данных и поэтому действительны во всех базах данных в кластере.

Параметры

имя

Имя новой роли.

SUPERUSER
NOSUPERUSER

Эти предложения определяют, является ли новая роль «суперпользователем», который может переопределить все ограничения доступа в базе данных. Статус суперпользователя опасен и должен использоваться только тогда, когда это действительно необходимо. Чтобы создать нового суперпользователя нужно самому быть суперпользователем. Если ничего не указано, значение по умолчанию равно NOSUPERUSER.

CREATEDB
NOCREATEDB

Эти предложения определяют способность роли создавать базы данных. Если указывается CREATEDB, то определяемой роли будет разрешено создавать новые базы данных. NOCREATEDB лишит роль возможности создавать базы данных. Если ничего не указано, значение по умолчанию равно NOCREATEDB.

CREATEROLE
NOCREATEROLE

Эти предложения определяют, будет ли роли разрешено создавать новые роли (то есть выполнять CREATE ROLE). Роль с правом CREATEROLE может также изменять и удалять другие роли. Если ничего не указано, значение по умолчанию равно NOCREATEROLE.

INHERIT
NOINHERIT

Эти предложения определяют, «наследует» ли роль права ролей, членом которых является. Роль с атрибутом INHERIT может автоматически использовать любые права в базе данных, предоставленные всем ролям, в которые она непосредственно или опосредованно входит. Без INHERIT членство в другой роли позволяет только выполнить SET ROLE и переключиться на эту роль; права, назначенные другой роли, будут доступны только после этого. Значение по умолчанию равно INHERIT.

LOGIN
NOLOGIN

Эти предложения определяют, разрешен ли роли вход на сервер; то есть может ли роль быть задана в качестве начального авторизованного имени сеанса при подключении клиента. Роль с атрибутом LOGIN может рассматриваться как пользователь. Роли без этого атрибута полезны для управления правами доступа к базе данных, но не являются пользователями в обычном смысле этого слова. Если ничего не указано, значение по умолчанию равно NOLOGIN, за исключением случаев, когда CREATE ROLE вызывается через альтернативное написание CREATE USER.

REPLICATION
NOREPLICATION

Эти предложения определяют, является ли роль ролью репликации. Роль должна иметь этот атрибут (или быть суперпользователем), чтобы иметь возможность подключаться к серверу в режиме репликации (физической или логической), а также создавать или удалять слоты репликации. Роль с атрибутом REPLICATION обладает очень большими правами и должна использоваться только в ролях, фактически используемых для репликации. Если ничего не указано, значение по умолчанию равно NOREPLICATION. Чтобы создать новую роль с атрибутом REPLICATION, нужно быть суперпользователем.

BYPASSRLS
NOBYPASSRLS

Эти предложения определяют, будут ли для роли игнорироваться все политики защиты на уровне строк (row-level security, RLS). Значение по умолчанию равно NOBYPASSRLS. Чтобы создать новую роль с атрибутом BYPASSRLS, нужно быть суперпользователем.

Обратите внимание, что qhb_dump по умолчанию установит для row_security значение OFF для гарантии, что все содержимое таблицы выгружается. Если пользователь, выполняющий qhb_dump, не имеет соответствующих прав, будет возвращена ошибка. Суперпользователь и владелец выгружаемой таблицы всегда обходят RLS.

CONNECTION LIMIT предел_подключений

Если роли разрешен вход, этот параметр указывает, сколько одновременных подключений она может выполнить. Значение -1 (по умолчанию) означает отсутствие ограничений. Обратите внимание, что под это ограничение подпадают только обычные соединения. Ни подготовленные транзакции, ни подключения фоновых рабочих процессов не учитываются.

[ ENCRYPTED ] PASSWORD 'пароль'
PASSWORD NULL

Задает пароль роли. (Пароль используется только для ролей с атрибутом LOGIN, но, тем не менее его можно определить и для ролей без этого атрибута.) Если вы не планируете использовать аутентификацию по паролю, можете опустить этот параметр. Если пароль не указан, он будет установлен в значение NULL, и аутентификация по паролю для этого пользователя всегда будет завершаться ошибкой. При желании пароль NULL можно установить явно, указав PASSWORD NULL.

Примечание
При указании пустой строки пароль также будет иметь значение NULL. Чтобы избежать двусмысленности, следует избегать указания пустой строки.

Пароль всегда хранится зашифрованным в системных каталогах. Ключевое слово ENCRYPTED не имеет эффекта, но принимается для обратной совместимости. Способ шифрования определяется параметром конфигурации password_encryption. Если представленная строка пароля уже зашифрована в формате MD5 или SCRAM, она сохраняется как есть независимо от password_encryption (поскольку система не может расшифровать указанную зашифрованную строку пароля, чтобы зашифровать ее в другом формате). Это позволяет перезагружать зашифрованные пароли во время выгрузки/восстановления.

VALID UNTIL 'метка_времени'

Предложение VALID UNTIL устанавливает дату и время, после которых пароль роли становится недействительным. Если это предложение опущено пароль будет действителен в течение всего времени.

IN ROLE имя_роли

В предложении IN ROLE перечисляются одна или несколько существующих ролей, к которым новая роль будет немедленно добавлена в качестве нового члена. (Обратите внимание, что нет возможности добавить новую роль в качестве администратора; для этого используйте отдельную команду GRANT).

IN GROUP имя_роли

IN GROUP — это устаревшее написание предложения IN ROLE.

ROLE имя_роли

В предложении ROLE перечислены одна или несколько существующих ролей, которые автоматически добавляются в качестве членов новой роли. (Это фактически делает новую роль «группой».)

ADMIN имя_роли

Предложение ADMIN подобно ROLE, но перечисленные роли добавляются в новую роль с атрибутом WITH ADMIN OPTION, давая им право предоставлять членство в этой роли другим ролям.

USER имя_роли

Предложение USER является устаревшим написанием предложения ROLE.

SYSID uid

Предложение SYSID игнорируется, но принимается для обратной совместимости.

Примечания

Используйте команду ALTER ROLE, чтобы изменить атрибуты роли, и DROP ROLE, чтобы ее удалить. Все атрибуты, указанные в CREATE ROLE, могут быть позднее изменены командами ALTER ROLE.

Предпочтительным способом добавления и удаления членов ролей, используемых в качестве групп, является использование команд GRANT и REVOKE.

Предложение VALID UNTIL определяет срок действия только для пароля, а не для роли как таковой. В частности, при входе в систему с помощью метода аутентификации без пароля ограничение срока действия пароля не действует.

Атрибут INHERIT управляет наследованием предоставленных прав (т. е. прав доступа для объектов базы данных и членства в ролях). Он не применяется к специальным атрибутам роли, заданным с помощью CREATE ROLE и ALTER ROLE. Например, членства в роли с правом CREATEDB недостаточно для получения права создавать базы данных, даже если установлен атрибут INHERIT; перед тем как создать базу данных, необходимо будет переключиться на эту роль, выполнив SET ROLE.

Хотя по умолчанию установлено INHERIT, но значение NOINHERIT больше подходит по смыслу к тому, что описано в стандарте SQL.

Будьте осторожны с правом CREATEROLE, т. к. на роли с этим правом не распространяется концепция наследования прав. Это означает, что даже если у роли нет определенных прав, но ей все равно разрешено создавать другие роли, она может легко создать другую роль с правами, отличными от тех, что есть у нее самой (за исключением создания ролей с правами суперпользователя). Например, если роль «user» имеет право CREATEROLE, но не CREATEDB, она, тем не менее, может создать новую роль с правом CREATEDB. Поэтому роль с правом CREATEROLE следует воспринимать почти как роль суперпользователя.

QHB включает в себя программу createuser, которая имеет ту же функциональность, что и команда CREATE ROLE (на самом деле она вызывает эту команду), но может запускаться в командной оболочке.

Ограничение CONNECTION LIMIT действует только приблизительно; если одновременно запускаются два сеанса, в то время как для этой роли остается только один «слот», может так случиться, что будут отклонены оба подключения. Кроме того, это ограничение не распространяется на суперпользователей.

При указании с помощью этой команды незашифрованного пароля следует соблюдать осторожность. Пароль будет передан на сервер открытым текстом, и он также может быть зарегистрирован в журнале команд клиента или в журнале сервера. Однако команда createuser передает зашифрованный пароль. Кроме того, psql содержит команду \password, которую можно использовать для последующего безопасного изменения пароля.

Примеры

Создание роли, для которой разрешен вход, но не задан пароль:

CREATE ROLE jonathan LOGIN;

Создание роли с паролем:

CREATE USER davide WITH PASSWORD 'jw8s0F4';

(CREATE USER действует так же, как CREATE ROLE, но подразумевает еще и атрибут LOGIN.)

Создание роли с паролем, действующим до конца 2004 г., то есть пароль перестает действовать в первую же секунду 2005 г.

CREATE ROLE miriam WITH LOGIN PASSWORD 'jw8s0F4' VALID UNTIL '2005-01-01';

Создание роли, которая может создавать базы данных и управлять ролями:

CREATE ROLE admin WITH CREATEDB CREATEROLE;

Совместимость

Команда CREATE ROLE описана в стандарте SQL, но стандарт требует поддержки только следующего синтаксиса:

CREATE ROLE имя [ WITH ADMIN имя_роли ]

Возможность создавать множество начальных администраторов и все другие параметры CREATE ROLE относятся к расширениям QHB.

Стандарт SQL определяет концепции пользователей и ролей, но рассматривает их как отдельные понятия и оставляет на усмотрение каждой реализации СУБД самостоятельно указывать все команды, определяющие пользователей. В QHB мы решили объединить пользователей и роли в единую сущность. Таким образом, роли имеют гораздо больше дополнительных атрибутов, чем в стандарте.

Поведение, заданное стандартом SQL, с наибольшей достоверностью можно получить, если создавать пользователей с атрибутом NOINHERIT, а роли — с атрибутом INHERIT.

См. также

SET ROLE, ALTER ROLE, DROP ROLE, GRANT, REVOKE, createuser

CREATE RULE

CREATE RULE — определить новое правило перезаписи

Синтаксис

CREATE [ OR REPLACE ] RULE имя AS ON событие
    TO имя_таблицы [ WHERE условие ]
    DO [ ALSO | INSTEAD ] { NOTHING | команда | ( команда ; команда ... ) }

где событием может быть:

    SELECT | INSERT | UPDATE | DELETE

Описание

Команда CREATE RULE определяет новое правило, применяемое к указанной таблице или представлению. CREATE OR REPLACE RULE либо создаст новое правило, либо заменит существующее правило с тем же именем для той же таблицы.

Система правил QHB позволяет определить альтернативное действие, которое будет выполняться при добавлении, изменении или удалении в таблицах базы данных. Грубо говоря, когда выполняется данная команда на данной таблице, правило вызывает выполнение дополнительных команд. В качестве альтернативы правило INSTEAD может заменить данную команду другой или привести к тому, что команда не будет выполняться вообще. Правила также используются для реализации представлений SQL. Важно понимать, что правило — это действительно механизм преобразования команд, или командный макрос. Преобразование происходит до начала выполнения команды. Если вам действительно нужна операция, которая срабатывает независимо для каждой физической строки, вероятно, лучше использовать триггер, а не правило.

На данный момент правила ON SELECT должны быть безусловными, с указанием INSTEAD, и их действия должны состоять из единственной команды SELECT. Таким образом, правило ON SELECT по сути превращает таблицу в представление, видимым содержимым которого являются строки, возвращаемые заданной в правиле командой SELECT, а не данные, хранящиеся в таблице (если они есть). Считается все же, что для этой цели лучше пользоваться командой CREATE VIEW, а не создавать реальную таблицу и затем определять для нее правило ON SELECT.

Чтобы заменить операции изменения в представлении соответствующими изменениями в других таблицах, можно создать иллюзию изменяемого представления путем определения правил ON INSERT, ON UPDATE и ON DELETE (или любого подмножество правил, достаточного для ваших целей). Если желаете поддержать операторы вроде INSERT RETURNING и пр., то не забудьте поместить в каждое из этих правил подходящее предложение RETURNING.

При попытке использовать условные правила для изменения сложных представлений возникает одна загвоздка: для каждого действия, которое вы хотите разрешить для представления, необходимо определить безусловное правило INSTEAD. Если правило условное или не типа INSTEAD, система отвергнет попытки выполнить изменения, предполагая, что в некоторых случаях они могут свестись к попыткам провести операцию с фиктивной таблицей представления. Если вы хотите обрабатывать все полезные случаи изменений в условных правилах, добавьте безусловное правило DO INSTEAD NOTHING, гарантирующее, что система поймет, что ей никогда не придется изменять фиктивную таблицу. Затем создайте условные правила без свойства INSTEAD; в тех случаях, когда они будут применяться, их действия будут добавлены к действию по умолчанию INSTEAD NOTHING. (Однако в настоящее время этот способ не подходит для реализации запросов RETURNING.)

Примечание
Представление достаточно простое, чтобы изменяться автоматически (см. CREATE VIEW), не требует для этого созданного пользователем правила. Хотя все равно можно создать явное правило, но автоматическое преобразование изменения в общем и целом будет работать лучше.

Еще одна альтернатива, которую стоит рассмотреть, — это использование вместо правил триггеров INSTEAD OF (см. справочную страницу команды CREATE TRIGGER).

Параметры

имя

Имя создаваемого правила. Должно отличаться от имени любого другого правила для той же таблицы. Несколько правил для одной и той же таблицы и одного и того же типа событий применяются в алфавитном порядке имен.

событие

Тип события: SELECT, INSERT, UPDATE или DELETE. Обратите внимание, что команду INSERT с предложением ON CONFLICT нельзя использовать в таблицах, для которых определено правило INSERT или UPDATE. Вместо этого рассмотрите возможность использования изменяемого представления.

имя_таблицы

Имя таблицы или представления, к которым будет применяться правило (может быть дополнено схемой).

условие

Любое выражение условия SQL (возвращающее boolean). Выражение условия не может ссылаться ни на какие таблицы, кроме NEW и OLD, и не может содержать агрегатных функций.

INSTEAD

INSTEAD показывает, что команды должны выполняться вместо исходной команды.

ALSO

ALSO показывает, что команды должны выполняться в дополнение к исходной команде.

Если ни INSTEAD, ни ALSO не заданы, по умолчанию применяется ALSO.

команда

Команда или команды, составляющие действие правила. Допустимыми командами являются SELECT, INSERT, UPDATE, DELETE и NOTIFY.

В параметрах условие и команда для обращения к значениям в соответствующей таблице могут использоваться имена специальных таблиц NEW и OLD. NEW действительна в правилах ON INSERT и ON UPDATE для ссылки на новую добавляемую или изменяемую строку. OLD действительна в правилах ON UPDATE и ON DELETE для ссылки на существующую строку, которая изменяется или удаляется.

Примечания

Чтобы создавать или изменять правила для таблицы, нужно быть ее владельцем.

В правила для INSERT, UPDATE или DELETE для представления можно добавить предложение RETURNING, которое выдает столбцы представления. Это предложение будет использоваться для вычисления выходных данных, если правило инициируется командой INSERT RETURNING, UPDATE RETURNING или DELETE RETURNING. Когда правило запускается командой без RETURNING, его собственное предложение RETURNING будет проигнорировано. Текущая реализация позволяет содержать RETURNING только безусловным правилам INSTEAD; более того, среди всех правил для одного события может быть не более одного предложения RETURNING. (Это гарантирует, что существует только одно подходящее предложение RETURNING, которое и будет использоваться для вычисления результатов.) Запросы с RETURNING к представлению будут отклонены, если в имеющихся у него правилах нет ни одного предложения RETURNING.

Очень важно позаботиться о том, чтобы правила не зацикливались. Например, хотя каждое из следующих двух определений правил принимается QHB, команда SELECT приведет к тому, что QHB сообщит об ошибке из-за рекурсивного расширения правила:

CREATE RULE "_RETURN" AS
    ON SELECT TO t1
    DO INSTEAD
        SELECT * FROM t2;

CREATE RULE "_RETURN" AS
    ON SELECT TO t2
    DO INSTEAD
        SELECT * FROM t1;

SELECT * FROM t1;

В настоящее время если действие правила содержит команду NOTIFY, та будет выполняться безоговорочно, то есть уведомление будет выдано, даже если нет строк, к которым должно применяться правило. Например, в:

CREATE RULE notify_me AS ON UPDATE TO mytable DO ALSO NOTIFY mytable;

UPDATE mytable SET name = 'foo' WHERE id = 42;

событие NOTIFY будет отправлено во время выполнения UPDATE независимо от того, есть ли строки, соответствующие условию id = 42. Это ограничение реализации, которое может быть исправлено в будущих выпусках.

Совместимость

Команда CREATE RULE является языковым расширением QHB, как и вся система перезаписи запросов.

См. также

ALTER RULE, DROP RULE

CREATE SCHEMA

CREATE SCHEMA — определить новую схему

Синтаксис

CREATE SCHEMA имя_схемы [ AUTHORIZATION описание_роли ] [ элемент_схемы [ ... ] ]
CREATE SCHEMA AUTHORIZATION описание_роли [ элемент_схемы [ ... ] ]
CREATE SCHEMA IF NOT EXISTS имя_схемы [ AUTHORIZATION описание_роли ]
CREATE SCHEMA IF NOT EXISTS AUTHORIZATION описание_роли

где описанием_роли может быть:

    имя_пользователя
  | CURRENT_ROLE
  | CURRENT_USER
  | SESSION_USER

Описание

Команда CREATE SCHEMA вносит новую схему в текущую базу данных. Имя схемы должно отличаться от имени любой существующей схемы в текущей базе данных.

Схема по существу является пространством имен: она содержит именованные объекты (таблицы, типы данных, функции и операторы), имена которых могут дублировать имена других объектов, существующих в других схемах. Доступ к именованным объектам осуществляется либо путем «дополнения» их имен именем схемы в качестве префикса, либо путем задания пути поиска, включающего требуемую схему (или схемы). Команда CREATE, в которой указывается неполное имя объекта, создает объект в текущей схеме (той, что стоит в пути поиска первой; определить ее позволяет функция current_schema).

При необходимости CREATE SCHEMA может включать вложенные команды для создания объектов в новой схеме. В целом, вложенные команды обрабатываются так же, как и отдельные команды, выполняемые после создания схемы, за исключением того, что при использовании предложения AUTHORIZATION все созданные объекты будут принадлежать указанному пользователю.

Параметры

имя_схемы

Имя создаваемой схемы. Если этот параметр опущен, то в качестве имени схемы используется имя_пользователя. Имя не может начинаться с pg_, поскольку такие имена зарезервированы для системных схем.

имя_пользователя

Имя роли пользователя, которому будет принадлежать новая схема. Если этот параметр опущен, по умолчанию владельцем становится пользователь, выполняющий команду. Чтобы создать схему, принадлежащую другой роли, необходимо быть непосредственным или опосредованным членом этой роли или суперпользователем.

элемент_схемы

Оператор SQL, определяющий объект, который должен быть создан в схеме. В настоящее время в качестве предложений CREATE SCHEMA принимаются только команды CREATE TABLE, CREATE VIEW, CREATE INDEX, CREATE SEQUENCE, CREATE TRIGGER и GRANT. Другие виды объектов должны быть созданы в отдельных командах после создания схемы.

IF NOT EXISTS

Не делать ничего (только выдать замечание), если схема с тем же именем уже существует. Когда используется это указание, такая команда не может содержать вложенные команды элемент_схемы.

Примечания

Чтобы создать схему, необходимо иметь право CREATE в текущей базе данных. (Разумеется, на суперпользователей эта проверка не распространяется.)

Примеры

Создание схемы:

CREATE SCHEMA myschema;

Создание схемы для пользователя joe; схема тоже получит имя joe:

CREATE SCHEMA AUTHORIZATION joe;

Создание схемы с именем test, владельцем которой будет пользователь joe, если только схема test еще не существует. (Является ли пользователь joe владельцем существующей схемы, значения не имеет.)

CREATE SCHEMA IF NOT EXISTS test AUTHORIZATION joe;

Создание схемы, в которой сразу создаются таблица и представление:

CREATE SCHEMA hollywood
    CREATE TABLE films (title text, release date, awards text[])
    CREATE VIEW winners AS
        SELECT title, release FROM films WHERE awards IS NOT NULL;

Обратите внимание, что отдельные вложенные команды не завершаются точкой с запятой.

Следующие команды приводят к тому же результату другим способом:

CREATE SCHEMA hollywood;
CREATE TABLE hollywood.films (title text, release date, awards text[]);
CREATE VIEW hollywood.winners AS
    SELECT title, release FROM hollywood.films WHERE awards IS NOT NULL;

Совместимость

Стандарт SQL допускает в команде CREATE SCHEMA предложение DEFAULT CHARACTER SET и дополнительные типы подкоманд, которые QHB в настоящее время не принимает.

Стандарт SQL указывает, что вложенные команды CREATE SCHEMA можно указывать в любом порядке. Настоящая реализация QHB не обрабатывает все случаи порядка указания вложенных команд; иногда может потребоваться изменить их порядок, чтобы избежать ссылок вперед.

Согласно стандарту SQL, владелец схемы всегда владеет всеми объектами внутри нее. QHB позволяет схемам содержать объекты, принадлежащие пользователям, отличным от владельца схемы. Это может произойти, только если владелец схемы предоставляет право CREATE в своей схеме кому-то другому, либо объекты в ней будет создавать суперпользователь.

Указание IF NOT EXISTS является расширением QHB.

См. также

ALTER SCHEMA, DROP SCHEMA

CREATE SEQUENCE

CREATE SEQUENCE — определить новый генератор последовательности

Синтаксис

CREATE [ TEMPORARY | TEMP ] SEQUENCE [ IF NOT EXISTS ] имя
    [ AS тип_данных ]
    [ INCREMENT [ BY ] шаг ]
    [ MINVALUE мин_значение | NO MINVALUE ] [ MAXVALUE макс_значение | NO MAXVALUE ]
    [ START [ WITH ] начало ] [ CACHE кэш ] [ [ NO ] CYCLE ]
    [ OWNED BY { имя_таблицы.имя_столбца | NONE } ]

Описание

Команда CREATE SEQUENCE создает новый генератор последовательных чисел. Это включает в себя создание и инициализацию новой специальной однострочной таблицы с именем имя. Генератор будет принадлежать пользователю, выполнившему эту команду.

Если задано имя схемы, то последовательность создается в указанной схеме, в противном случае — в текущей. Временные последовательности существуют в специальной схеме, поэтому при создании временной последовательности имя схемы не может быть задано. Имя последовательности должно отличаться от имени любой другой последовательности, таблицы, индекса, представления или сторонней таблицы в той же схеме.

После того как последовательность создана, с ней можно работать, используя функции nextval, currval и setval. Эти функции описаны в разделе Функции для управления последовательностями.

Хотя обновить последовательность напрямую нельзя, можно использовать запрос наподобие этого:

SELECT * FROM имя;

чтобы изучить ее параметры и текущее состояние. В частности, поле last_value последовательности показывает последнее значение, выделенное для какого-либо сеанса. (Разумеется, если другие сеансы активно вызывают nextval, то к моменту выведения это значение может устареть.)

Параметры

TEMPORARY или TEMP

Если этот параметр указан, объект последовательности создается только для этого сеанса и автоматически удаляется при выходе из него. Существующие постоянные последовательности с одинаковым именем не видны (в этом сеансе), пока существует временная последовательность, если только к ним не обращаются по именам, дополненным схемой.

IF NOT EXISTS

Не считать ошибкой, если отношение с тем же именем уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующее отношение как-то соотносится с последовательностью, которая была бы создана — оно может даже не быть последовательностью.

имя

Имя создаваемой последовательности (может быть дополнено схемой).

тип_данных

Необязательное предложение AS тип_данных задает тип данных последовательности. Допустимые типы: smallint, integer и bigint. Значение по умолчанию равно bigint. Тип данных определяет минимальное и максимальное значения последовательности по умолчанию.

шаг

Необязательное предложение INCREMENT BY шаг указывает, какое значение добавляется к текущему значению последовательности для создания нового значения. Положительное значение сделает восходящую последовательность, отрицательное — нисходящую. Значение по умолчанию равно 1.

мин_значение
NO MINVALUE

Необязательное предложение MINVALUE мин_значение определяет минимальное значение, которое может генерировать последовательность. Если это предложение не указано или не указано значение NO MINVALUE, будут использоваться значения по умолчанию. Значение по умолчанию для восходящей последовательности равно 1. Значение по умолчанию для нисходящей последовательности равно минимальному значению типа данных.

макс_значение
NO MAXVALUE

Необязательное предложение MAXVALUE макс_значение определяет максимальное значение для последовательности. Если это предложение не указано или указано значение NO MAXVALUE, будут использоваться значения по умолчанию. Значение по умолчанию для восходящей последовательности равно максимальному значению типа данных. Значение по умолчанию для нисходящей последовательности равно -1.

начало

Необязательное предложение START WITH начало позволяет последовательности начинаться в любом месте. Начальное значение по умолчанию равно мин_значению для восходящих последовательностей и макс_значению для нисходящих.

кэш

Необязательное предложение CACHE кэш указывает, сколько чисел последовательности должно быть предварительно выделено и сохранено в памяти для быстрого доступа. Минимальное значение равно 1 (за один раз может быть сгенерировано только одно значение, т. е. кэша нет), и оно же является значением по умолчанию.

CYCLE
NO CYCLE

Параметр CYCLE позволяет последовательности зацикливаться, когда восходящей или нисходящей последовательностью было достигнуто макс_значение или мин_значение соответственно. Если предел достигнут, то следующим сгенерированным номером будет мин_значение или макс_значение соответственно.

Если указано NO CYCLE, любые вызовы nextval после достижения последовательностью максимального значения будут возвращать ошибку. Если не указано ни CYCLE, ни NO CYCLE, значение по умолчанию равно NO CYCLE.

OWNED BY имя_таблицы.имя_столбца
OWNED BY NONE

Параметр OWNED BY заставляет последовательность связываться с определенным столбцом таблицы таким образом, что если этот столбец (или вся его таблица) удаляется, последовательность будет также автоматически удалена. Указанная таблица должна иметь того же владельца и находиться в той же схеме, что и последовательность. Значение по умолчанию OWNED BY NONE указывает, что такой связи не существует.

Примечания

Чтобы удалить последовательность, воспользуйтесь командой DROP SEQUENCE.

Последовательности основаны на арифметике bigint, поэтому диапазон не может превышать диапазон восьмибайтного целого числа (от -9223372036854775808 до 9223372036854775807).

Поскольку вызовы nextval и setval никогда не откатываются, объекты последовательности нельзя использовать, если требуется «беззазорное» присвоение порядковых номеров. Такое присвоение можно устроить с помощью эксклюзивной блокировки таблицы, содержащей счетчик, но это решение гораздо дороже, чем объекты последовательности, особенно если сразу много транзакций одновременно затребует числа последовательности.

Если для объекта последовательности, который будет использоваться одновременно несколькими сеансами, значение кэша установлено больше единицы, это может привести к неожиданным результатам. Каждый сеанс будет выделять и кэшировать следующие друг за другом значения последовательности во время одного обращения к объекту последовательности и, соответственно, увеличивать его last_value (последнее значение). Затем следующие кэш-1 вызовы nextval в этом сеансе будут просто возвращать предварительно выделенные значения, не касаясь объекта последовательности. В результате все числа, выделенные, но не используемые в сеансе, по его окончании будут потеряны, что приведет к «прорехам» в последовательности.

Более того, хотя несколько сеансов гарантированно выделяют отдельные значения последовательности, эти значения могут быть сгенерированы из последовательности, когда рассматриваются все сеансы. Например, когда значение кэша равно 10, сеанс A может резервировать значения от 1 до 10 и возвращать nextval=1, затем сеанс B может зарезервировать значения от 11 до 20 и возвращать nextval=11 до того, как сеанс А сгенерирует nextval=2. Таким образом, при значении кэша, равном единице, можно с уверенностью предположить, что значения nextval генерируются последовательно, но при значении кэша больше единицы следует предполагать лишь, что все значения nextval различны, а не исключительную последовательность их генерации. Кроме того, last_value будет отражать самое последнее значение, зарезервированное любым сеансом, независимо от того, было ли оно уже возвращено nextval.

Также следует учесть, что действие функции setval, выполненное в такой последовательности, не будет замечено другими сеансами, пока те не используют все предварительно выделенные значения, которые они кэшировали.

Примеры

Создание восходящей последовательности с именем serial с начальным значением 101:

CREATE SEQUENCE serial START 101;

Выборка следующего числа из этой последовательности:

SELECT nextval('serial');

 nextval
---------
     101

Выборка следующего числа из этой последовательности:

SELECT nextval('serial');

 nextval
---------
     102

Использование этой последовательности в команде INSERT:

INSERT INTO distributors VALUES (nextval('serial'), 'nothing');

Изменение значения последовательности после COPY FROM:

BEGIN;
COPY distributors FROM 'input_file';
SELECT setval('serial', max(id)) FROM distributors;
END;

Совместимость

Команда CREATE SEQUENCE соответствует стандарту SQL со следующими исключениями:

Получение следующего значения производится с помощью функции nextval() вместо стандартного выражения NEXT VALUE FOR.

Предложение OWNED BY является расширением QHB.

См. также

ALTER SEQUENCE, DROP SEQUENCE

CREATE SERVER

CREATE SERVER — определить новый сторонний сервер

Синтаксис

CREATE SERVER [ IF NOT EXISTS ] имя_сервера [ TYPE 'тип_сервера' ] [ VERSION 'версия_сервера' ]
    FOREIGN DATA WRAPPER имя_обертки_сторонних_данных
    [ OPTIONS ( параметр 'значение' [, ... ] ) ]

Описание

Команда CREATE SERVER определяет новый сторонний сервер. Пользователь, определивший этот сервер, становится его владельцем.

Определение стороннего сервера обычно включает информацию о подключении, которую обертка сторонних данных использует для доступа к внешнему источнику данных. Дополнительную информацию о подключении, связанную с пользователями, можно задать с помощью сопоставлений пользователей.

Имя сервера должно быть уникальным в пределах базы данных.

Для создания сервера требуется право USAGE на используемую обертку сторонних данных.

Параметры

IF NOT EXISTS

Не выдавать ошибку, если сервер с тем же именем уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующий сервер как-то соотносится с тем, который был бы создан.

имя_сервера

Имя создаваемого стороннего сервера.

тип_сервера

Необязательный тип сервера, потенциально полезный для оберток сторонних данных.

версия_сервера

Необязательная версия сервера, потенциально полезная для оберток сторонних данных.

имя_обертки_сторонних_данных

Имя обертки сторонних данных, управляющей сервером.

OPTIONS ( параметр 'значение' [, ... ] )

Это предложение задает параметры для сервера. Параметры обычно определяют характеристики подключения к серверу, но фактические имена и значения зависят от обертки сторонних данных сервера.

Примечания

При использовании модуля dblink имя стороннего сервера можно использовать в качестве аргумента функции dblink_connect для указания параметров подключения. Чтобы иметь возможность использовать сторонний сервер таким образом, необходимо иметь для него право USAGE.

Примеры

Создание сервера myserver, который использует обертку сторонних данных postgres_fdw:

CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432');

Дополнительную информацию см. в описании модуля postgres_fdw.

Совместимость

Команда CREATE SERVER соответствует стандарту ISO/IEC 9075-9 (SQL/MED).

См. также

ALTER SERVER, DROP SERVER, CREATE FOREIGN DATA WRAPPER, CREATE FOREIGN TABLE, CREATE USER MAPPING

CREATE STATISTICS

CREATE STATISTICS — определить расширенную статистику

Синтаксис

CREATE STATISTICS [ IF NOT EXISTS ] имя_статистики
    ON ( выражение )
    FROM имя_таблицы

CREATE STATISTICS [ IF NOT EXISTS ] имя_статистики
    [ ( вид_статистики [, ... ] ) ]
    ON { имя_столбца | ( выражение ) }, { имя_столбца | ( выражение ) } [, ...]
    FROM имя_таблицы

Описание

Команда CREATE STATISTICS создаст новый расширенный объект статистики, отслеживающий данные о заданной таблице, сторонней таблице или материализованном представлении. Объект статистики будет создан в текущей базе данных и будет принадлежать пользователю, выполнившему команду.

Команда CREATE STATISTICS имеет две основные формы. Первая форма позволяет собрать одномерную статистику по одному выражению, предоставляя преимущества, схожие с индексом по выражению, но без затрат на обслуживание индекса. Эта форма не позволяет указывать вид статистики, поскольку различные виды относятся только к многомерным статистикам. Вторая форма этой команды позволяет собрать многомерные статистики по нескольким столбцам и/или выражениям, при необходимости указав, какой вид статистики нужно включить. Кроме того, эта форма автоматически запускает сбор одномерных статистик по всем выражениям в списке.

Если задано имя схемы (например CREATE STATISTICS myschema.mystat ...), то объект статистики создается в указанной схеме, в противном случае — в текущей. Имя объекта статистики должно отличаться от имени любого другого объекта статистики в той же схеме.

Параметры

IF NOT EXISTS

Не выдавать ошибку, если объект статистики с тем же именем уже существует. В этом случае выдается только замечание. Обратите внимание, что здесь рассматривается только имя объекта статистики, а не подробности его определения.

имя_статистики

Имя создаваемого объекта статистики (может быть дополнено схемой).

вид_статистики

Вид многомерной статистики, который будет вычисляться в этом объекте статистики. В настоящее время поддерживаются следующие виды: ndistinct, который активирует статистику количества уникальных значений, dependencies, который активирует статистику функциональных зависимостей, и mcv, который активирует списки наиболее распространенных значений. Если это предложение опущено, в объект статистики включаются все поддерживаемые виды статистики. Одномерная статистика по выражениям собирается автоматически, если определение статистики включает любые сложные выражения, а не просто ссылки на столбцы. Дополнительную информацию см. в подразделе Расширенная статистика и разделе Примеры многомерной статистики.

имя_столбца

Имя столбца таблицы, который будет покрыт вычисляемой статистикой. Это указание допустимо только при построении многомерных статистик. Должно быть задано не менее двух имен столбцов или выражений; их порядок значения не имеет.

выражение

Выражение, которое будет покрыто вычисляемой статистикой. Это можно использовать для построения одномерной статистики по одному выражению или как часть списка из нескольких имен столбцов и/или выражений для построения многомерных статистик. В последнем случае для каждого выражения в списке автоматически строятся отдельные одномерные статистики.

имя_таблицы

Имя таблицы (может быть дополнено схемой), содержащей столбец (или столбцы), по которому вычисляются статистические данные. Объяснение особенностей обработки иерархии наследования и партиций см. на справочной странице команды ANALYZE.

Примечания

Чтобы создать считывающий таблицу объект статистики, нужно быть владельцем этой таблицы. Однако после создания объекта статистики его владелец определяется независимо от нижележащих таблиц.

Статистики выражений рассчитываются по отдельным выражениям, и этим схожи с созданием индекса по выражению за исключением того, что они не требуют расходов на обслуживание индекса. Статистики выражений строятся автоматически для каждого выражения в определении объекта статистики.

Примеры

Создание таблицы t1 с двумя функционально зависимыми столбцами, т. е. знания значения в первом столбце достаточно для определения значения в другом столбце. Затем по этим столбцам строится статистика функциональной зависимости:

CREATE TABLE t1 (
    a   int,
    b   int
);

INSERT INTO t1 SELECT i/100, i/500
                 FROM generate_series(1,1000000) s(i);

ANALYZE t1;

-- количество совпадающих строк будет существенно занижено:
EXPLAIN ANALYZE SELECT * FROM t1 WHERE (a = 1) AND (b = 0);

CREATE STATISTICS s1 (dependencies) ON a, b FROM t1;

ANALYZE t1;

-- теперь оценка количества строк стала точнее:
EXPLAIN ANALYZE SELECT * FROM t1 WHERE (a = 1) AND (b = 0);

Без статистики функциональной зависимости планировщик предположил бы, что эти два условия WHERE независимы, и перемножил бы их избирательности вместе, получив при этом слишком маленькую оценку количества строк. С такой статистикой планировщик признает, что условия WHERE являются избыточными, и не занижает количество строк.

Создание таблицы t2 с двумя идеально коррелирующими столбцами (содержащими идентичные данные) и списком MCV по этим столбцам:

CREATE TABLE t2 (
    a   int,
    b   int
);

INSERT INTO t2 SELECT mod(i,100), mod(i,100)
                 FROM generate_series(1,1000000) s(i);

CREATE STATISTICS s2 (mcv) ON a, b FROM t2;

ANALYZE t2;

-- рабочая комбинация (входит в MCV)
EXPLAIN ANALYZE SELECT * FROM t2 WHERE (a = 1) AND (b = 1);

-- нерабочая комбинация (не входит в MCV)
EXPLAIN ANALYZE SELECT * FROM t2 WHERE (a = 1) AND (b = 2);

Список MCV предоставляет планировщику более подробную информацию о конкретных значениях, которые обычно отображаются в таблице, а также верхнюю границу избирательности комбинаций значений, которые не отображаются в таблице, что позволяет ему генерировать лучшие оценки в обоих случаях.

Создание таблицы t3 с единственным столбцом метки времени и выполнение запросов с использованием выражений по этому столбцу. Без расширенной статистики планировщик не имеет информации о распределении данных для этих выражений и использует оценки по умолчанию. Также планировщик не понимает, что значение даты, усеченной до месяцев, полностью определяется значением даты, усеченной до дней. Затем по этим двум выражениям строится статистика выражений и ndistinct:

CREATE TABLE t3 (
    a   timestamp
);

INSERT INTO t3 SELECT i FROM generate_series('2020-01-01'::timestamp,
                                             '2020-12-31'::timestamp,
                                             '1 minute'::interval) s(i);

ANALYZE t3;

-- количество совпадающих строк будет существенно занижено:
EXPLAIN ANALYZE SELECT * FROM t3
  WHERE date_trunc('month', a) = '2020-01-01'::timestamp;

EXPLAIN ANALYZE SELECT * FROM t3
  WHERE date_trunc('day', a) BETWEEN '2020-01-01'::timestamp
                                 AND '2020-06-30'::timestamp;

EXPLAIN ANALYZE SELECT date_trunc('month', a), date_trunc('day', a)
   FROM t3 GROUP BY 1, 2;

-- построить статистику ndistinct по этой паре выражений (статистика по
-- отдельным выражениям строится автоматически)
CREATE STATISTICS s3 (ndistinct) ON date_trunc('month', a), date_trunc('day', a) FROM t3;

ANALYZE t3;

-- теперь оценка количества строк стала точнее:
EXPLAIN ANALYZE SELECT * FROM t3
  WHERE date_trunc('month', a) = '2020-01-01'::timestamp;

EXPLAIN ANALYZE SELECT * FROM t3
  WHERE date_trunc('day', a) BETWEEN '2020-01-01'::timestamp
                                 AND '2020-06-30'::timestamp;

EXPLAIN ANALYZE SELECT date_trunc('month', a), date_trunc('day', a)
   FROM t3 GROUP BY 1, 2;

Без статистики выражений и ndistinct планировщик не имеет информации о количестве уникальных значений для этих выражений и должен полагаться на оценки по умолчанию. Предполагается, что условия равенства и вхождения в диапазон имеют избирательность 0,5%, а количество уникальных значений в выражении такое же, как и в столбце (т. е. предполагаются уникальные значения). Это приводит к значительному занижению оценки количества строк в первых двух запросах. Более того, у планировщика нет информации об отношении между выражениями, поэтому он предполагает, что два условия WHERE и GROUP BY независимы, и перемножает их избирательности, получая сильно завышенную оценку количества групп в запросе с агрегатом. Это еще больше усугубляется отсутствием точной статистики по выражениям, вынуждающей планировщик использовать для выражения оценку ndistinct по умолчанию, полученную из статистики ndistinct для столбца. Но при наличии такой статистики планировщик понимает, что условия коррелируют, и выдает гораздо более точные оценки.

Совместимость

В стандарте SQL нет команды CREATE STATISTICS.

См. также

ALTER STATISTICS, DROP STATISTICS

CREATE SUBSCRIPTION

CREATE SUBSCRIPTION — определить новую подписку

Синтаксис

CREATE SUBSCRIPTION имя_подписки
    CONNECTION 'строка_подключения'
    PUBLICATION имя_публикации [, ...]
    [ WITH ( параметр_подписки [= значение] [, ... ] ) ]

Описание

Команда CREATE SUBSCRIPTION добавляет новую подписку для текущей базы данных. Имя подписки должно отличаться от имени любой существующей подписки в базе данных.

Подписка представляет собой подключение репликации к публикующему серверу. Таким образом, эта команда не только добавляет определения в локальные каталоги, но и создает слот репликации на публикующем сервере.

При фиксации транзакции, в которой выполняется эта команда, будет запущен рабочий процесс логической репликации, который будет реплицировать данные для новой подписки.

Дополнительную информацию о подписках и логической репликации в целом см. в разделе Подписка и главе Логическая репликация соответственно.

Параметры

имя_подписки

Имя новой подписки.

CONNECTION 'строка_подключения'

Строка подключения к публикующему серверу. Дополнительную информацию см. в подразделе Строки подключения.

PUBLICATION имя_публикации

Имена публикаций на публикующем сервере, на которые производится подписка.

WITH ( параметр_подписки [= значение] [, ... ] )

В этом предложении указываются необязательные параметры для подписки. Поддерживаются следующие параметры:

copy_data (boolean)
Определяет, должны ли копироваться существующие данные в публикациях, на которые выполняется подписка, сразу после запуска репликации. Значение по умолчанию равно true.

create_slot (boolean)
Определяет, должна ли команда создать слот репликации на публикующем сервере. Значение по умолчанию равно true.

enabled (boolean)
Определяет, следует ли активировать репликацию подписки или ее нужно только установить, но пока не запускать. Значение по умолчанию равно true.

slot_name (string)
Имя используемого слота репликации. Поведение по умолчанию заключается в использовании имени подписки в качестве имени слота.

Когда значение slot_name задается как NONE, с подпиской не будет связан никакой слот репликации. Это можно использовать, если слот репликации будет создан позже вручную. В таких подписках также должны быть установлены значения false для параметров enabled и create_slot.

synchronous_commit (enum)
Значение этого параметра переопределяет значение параметра synchronous_commit для рабочих процессов, применяющих изменения данной подписки. Значение по умолчанию равно off (выключен).

Использование значения off безопасно для логической репликации: если подписчик потеряет транзакции из-за отсутствия синхронизации, данные будут повторно отправлены с публикующего сервера.

При выполнении синхронной логической репликации может потребоваться другое значение. Рабочие процессы логической репликации передают на публикующий сервер позиции записанных и сброшенных на диск данных, и при использовании синхронной репликации тот будет ожидать фактического сохранения. Следовательно, значение off параметра synchronous_commit для подписчика при использовании подписки для синхронной репликации может увеличить задержку выполнения COMMIT на публикующем сервере. При таком сценарии может быть выгодно установить для synchronous_commit значение local или выше.

binary (boolean)
Определяет, будет ли данная подписка запрашивать у публикующего сервера передачу данных в двоичном (а не текстовом) формате. Значение по умолчанию равно false. Даже когда этот параметр включен, в двоичном формате передаются только те типы данных, у которых имеются функции двоичного получения и отправки.

При репликации между серверами разных версий может получиться так, что у публикующего сервера есть функция двоичной отправки для некоторого типа данных, но у подписчика нет функции двоичного получения для этого типа. В таком случае передача данных будет невозможна, и параметр binary использовать нельзя.

connect (boolean)
Определяет, должна ли CREATE SUBSCRIPTION вообще подключаться к публикующему серверу. При установке для этого параметра значения false значения по умолчанию параметров enabled, create_slot и copy_data также сменятся на false.

Не допускается совмещение значения false для параметра connect и значения true для параметров enabled,create_slot или copy_data.

Так как при значении false для этого параметра соединение не устанавливается, подписка на таблицы не производится, и поэтому после включения подписки ничего реплицироваться не будет. Чтобы запустить подписку на таблицы, нужно выполнить команду ALTER SUBSCRIPTION ... REFRESH PUBLICATION.

streaming (boolean)
Определяет, должна ли быть включена для этой подписки потоковая передача изменений еще выполняющихся транзакций. По умолчанию все транзакции целиком декодируются на публикующем сервере и только потом передаются подписчику как единое целое.

Примечания

Подробную информацию о настройке управления доступом подписчиков к публикующему серверу см. в разделе Безопасность.

При создании слота репликации (поведение по умолчанию) команду CREATE SUBSCRIPTION нельзя выполнить внутри блока транзакции.

Создание подписки, которая подключается к одному и тому же кластеру баз данных (например, для репликации между базами данных в одном и том же кластере или для репликации в одной и той же базе данных), будет успешным только в том случае, если слот репликации не создается в рамках той же команды. В противном случае вызов CREATE SUBSCRIPTION будет зависать. Чтобы выполнить эту работу, создайте слот репликации отдельно (с помощью функции pg_create_logical_replication_slot с именем плагина pgoutput) и создайте подписку с помощью параметра create_slot = false. Это ограничение реализации, которое может быть снято в будущих выпусках.

Примеры

Создание подписки на удаленный сервер, который реплицирует таблицы в публикациях mypublication и insert_only с немедленным запуском репликации при фиксации транзакции:

CREATE SUBSCRIPTION mysub
         CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb'
        PUBLICATION mypublication, insert_only;

Создание подписки на удаленный сервер, который реплицирует таблицы в публикацию insert_only с отложенным запуском репликации.

CREATE SUBSCRIPTION mysub
         CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb'
        PUBLICATION insert_only
               WITH (enabled = false);

Совместимость

Команда CREATE SUBSCRIPTION является расширением QHB.

См. также

ALTER SUBSCRIPTION, DROP SUBSCRIPTION, CREATE PUBLICATION, ALTER PUBLICATION

CREATE TABLE

CREATE TABLE — определить новую таблицу

Синтаксис

CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] имя_таблицы ( [
  { имя_столбца тип_данных [ COMPRESSION метод_сжатия ] [ COLLATE правило_сортировки ] [ ограничение_столбца [ ... ] ]
    | ограничение_таблицы
    | LIKE исходная_таблица [ вариант_копирования ... ] }
    [, ... ]
] )
[ INHERITS ( таблица_родитель [, ... ] ) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { имя_столбца | ( выражение ) } [ COLLATE правило_сортировки ] [ класс_операторов ] [, ... ] ) ]
[ USING метод ]
[ WITH ( параметр_хранения [= значение] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE имя_табличного_пространства ]

CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] имя_таблицы
    OF имя_типа [ (
  { имя_столбца [ WITH OPTIONS ] [ ограничение_столбца [ ... ] ]
    | ограничение_таблицы }
    [, ... ]
) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { имя_столбца | ( выражение ) } [ COLLATE правило_сортировки ] [ класс_операторов ] [, ... ] ) ]
[ USING метод ]
[ WITH ( параметр_хранения [= значение] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE имя_табличного_пространства ]

CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] имя_таблицы
    PARTITION OF таблица_родитель [ (
  { имя_столбца [ WITH OPTIONS ] [ ограничение_столбца [ ... ] ]
    | ограничение_таблицы }
    [, ... ]
) ] { FOR VALUES указание_границ_партиции | DEFAULT }
[ PARTITION BY { RANGE | LIST | HASH } ( { имя_столбца | ( выражение ) } [ COLLATE правило_сортировки ] [ класс_операторов ] [, ... ] ) ]
[ USING метод ]
[ WITH ( параметр_хранения [= значение] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE имя_табличного_пространства ]

где ограничением_столбца может быть:

[ CONSTRAINT имя_ограничения ]
{ NOT NULL |
  NULL |
  CHECK ( выражение ) [ NO INHERIT ] |
  DEFAULT выражение_по_умолчанию |
  GENERATED ALWAYS AS ( генерирующее_выражение ) STORED |
  GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( параметры_последовательности ) ] |
  UNIQUE параметры_индекса |
  PRIMARY KEY параметры_индекса |
  REFERENCES ссылочная_таблица [ ( ссылочный_столбец ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
    [ ON DELETE ссылочное_действие ] [ ON UPDATE ссылочное_действие ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]

и ограничением_таблицы может быть:

[ CONSTRAINT имя_ограничения ]
{ CHECK ( выражение ) [ NO INHERIT ] |
  UNIQUE ( имя_столбца [, ... ] ) параметры_индекса |
  PRIMARY KEY ( имя_столбца [, ... ] ) параметры_индекса |
  EXCLUDE [ USING индексный_метод ] ( элемент_исключения WITH оператор [, ... ] ) параметры_индекса [ WHERE ( предикат ) ] |
  FOREIGN KEY ( имя_столбца [, ... ] ) REFERENCES целевая_таблица [ ( целевой_столбец [, ... ] ) ]
    [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE ссылочное_действие ] [ ON UPDATE ссылочное_действие ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]

и вариантом_копирования может быть:

{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }

и указанием_границ_партиции может быть:

IN ( выражение_границ_партиции [, ...] ) |
FROM ( { выражение_границ_партиции | MINVALUE | MAXVALUE } [, ...] )
  TO ( { выражение_границ_партиции | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS числовая_константа, REMAINDER числовая_константа )

параметры_индекса в ограничениях UNIQUE, PRIMARY KEY и EXCLUDE:

[ INCLUDE ( имя_столбца [, ... ] ) ]
[ WITH ( параметр_хранения [= значение] [, ... ] ) ]
[ USING INDEX TABLESPACE имя_табличного_пространства ]

элемент_исключения в ограничении EXCLUDE:

{ имя_столбца | ( выражение ) } [ класс_операторов ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]

Описание

Команда CREATE TABLE создаст новую, изначально пустую таблицу в текущей базе данных. Таблица будет принадлежать пользователю, выполнившему эту команду.

Если задано имя схемы (например CREATE TABLE myschema.mytable ...), то таблица создается в указанной схеме, в противном случае — в текущей. Временные таблицы существуют в специальной схеме, поэтому при их создании не может быть задано имя схемы. Имя таблицы должно отличаться от имени любой другой таблицы, последовательности, индекса, представления или сторонней таблицы в той же схеме.

Также команда CREATE TABLE автоматически создает тип данных, представляющий составной тип, соответствующий одной строке таблицы. Поэтому таблицы не могут иметь то же имя, что и любой существующий тип данных в той же схеме.

Необязательные предложения ограничений определяют ограничения (тесты), которым новые или измененные строки должны удовлетворять для успешного выполнения операции добавления или изменения. Ограничение — это объект SQL, который помогает различными способами определить набор допустимых значений в таблице.

Существует два способа определения ограничений: ограничения таблицы и ограничения столбца. Ограничение столбца определяется как часть определения столбца. Определение ограничения таблицы не привязано к определенному столбцу и может охватывать несколько столбцов. Каждое ограничение столбца может быть также записано как ограничение таблицы; ограничение столбца введено только для удобства нотации, когда ограничение затрагивает только один столбец.

Чтобы иметь возможность создать таблицу, необходимо иметь право USAGE на все типы столбцов или тип в предложении OF соответственно.

Параметры

TEMPORARY или TEMP

Если указан этот параметр, таблица создается как временная таблица. Временные таблицы автоматически удаляются в конце сеанса или, при необходимости, в конце текущей транзакции (см. предложение ON COMMIT ниже). В путь поиска по умолчанию первой включается временная схема, поэтому существующие постоянные таблицы с теми же именами не будут выбираться для новых планов, пока существуют временные таблицы, если только к ним не обращаются по именам, дополненным схемой. Все индексы, созданные во временной таблице, также автоматически становятся временными.

Процесс «Автовакуум» не может получить доступ и, следовательно, не может очищать или анализировать временные таблицы. По этой причине соответствующие операции очистки и анализа должны выполняться с помощью команд SQL сеанса. Например, если временная таблица будет использоваться в сложных запросах, целесообразно после ее заполнения запустить для нее ANALYZE.

При желании перед TEMPORARY или TEMP можно написать GLOBAL или LOCAL. В настоящее время в QHB это не имеет никакого значения и считается устаревшим; см. параграф Совместимость ниже.

UNLOGGED

Если указан этот параметр, таблица создается как нерегистрируемая таблица. Данные, записанные в нерегистрируемые таблицы, не записываются в журнал упреждающей записи (см. главу Надежность и журнал упреждающей записи), что делает их значительно быстрее, чем обычные таблицы. Однако они не защищены от сбоев: нерегистрируемая таблица автоматически усекается после сбоя или небезопасного завершения работы. Кроме того, содержимое нерегистрируемой таблицы не реплицируется на резервные серверы. Все индексы, созданные в незарегистрированной таблице, тоже автоматически становятся нерегистрируемыми.

IF NOT EXISTS

Не считать ошибкой, если отношение с тем же именем уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующее отношение как-то соотносится с тем, которое было бы создано.

имя_таблицы

Имя создаваемой таблицы (может быть дополнено схемой).

OF имя_типа

Создает типизированную таблицу, которая берет свою структуру из указанного составного типа (имя может быть дополнено схемой). Типизированная таблица привязывается к своему типу: например, при удалении типа (с помощью DROP TYPE ... CASCADE) таблица тоже будет удалена.

При создании типизированной таблицы типы данных столбцов определяются нижележащим составным типом и не задаются командой CREATE TABLE. Но CREATE TABLE может добавлять в таблицу значения по умолчанию и ограничения, а также указать параметры хранения.

имя_столбца

Имя столбца, который будет создан в новой таблице.

тип_данных

Тип данных столбца. Может включать в себя спецификаторы массива. Подробную информацию о типах данных, поддерживаемых QHB, см. в главе Типы данных.

COLLATE правило_сортировки

Предложение COLLATE назначает правило сортировки для столбца (который должен иметь сопоставимый тип данных). Если этот параметр не указан, используется правило сортировки по умолчанию для типа данных столбца.

COMPRESSION метод_сжатия

Предложение COMPRESSION устанавливает метод сжатия для столбца. Сжатие поддерживается только для типов данных переменной длины и используется, только когда столбец имеет режим хранения main или extended. (Информацию о режимах хранения столбцов см. на справочной странице команды ALTER TABLE.) Установка этого свойства для партиционированной таблицы не имеет прямого эффекта, поскольку такие таблицы сами по себе не имеют хранимых данных, но сконфигурированное значение будет унаследовано их вновь создаваемыми партициями. Поддерживаемые методы сжатия: pglz и lz4. (lz4 доступен, только если при сборке QHB был использован ключ --with-lz4.) Кроме того, метод_сжатия может иметь значение default, явно задающее поведение по умолчанию — когда используемый метод определяется значением параметра default_toast_compression во время добавления данных.

INHERITS ( таблица_родитель [, ... ] )

Необязательное предложение INHERITS задает список таблиц, из которых новая таблица автоматически наследует все столбцы. Родительские таблицы могут быть простыми или сторонними таблицами.

Использование INHERITS создает постоянную связь между новой дочерней таблицей и ее родительской таблицей (или таблицами). Изменения схемы в родительской таблице (или таблицах) обычно распространяются также и на дочерние, и по умолчанию данные дочерней таблицы включаются в сканы родительской.

Если одно и то же имя столбца существует в более чем одной родительской таблице, то появляется сообщение об ошибке, если только типы данных столбцов не совпадают в каждой из родительских таблиц. В этом случае повторяющиеся столбцы объединяются, чтобы сформировать один столбец в новой таблице. Если список имен столбцов новой таблицы содержит имя столбца, которое тоже наследуется, тип данных аналогичным образом должен соответствовать унаследованному столбцу (или столбцам), и определения столбцов объединяются в одно. Если новая таблица явно указывает значение по умолчанию для столбца, оно переопределяет любые значения по умолчанию из унаследованных объявлений этого столбца. В противном случае все родительские таблицы, указывающие значения по умолчанию для столбца, должны указывать одно и то же значение по умолчанию, иначе будет выдана ошибка.

Ограничения CHECK объединяются, по сути, так же, как и столбцы: если несколько родительских таблиц и/или новое определение таблицы содержат ограничения CHECK с идентичными именами, то все эти ограничения должны иметь одно и то же проверочное выражение, иначе появится сообщение об ошибке. Ограничения, имеющие одинаковое имя и выражение, будут объединены в одну копию. Ограничение, помеченное в родительской таблице как NO INHERIT, рассматриваться не будет. Обратите внимание, что безымянное ограничение CHECK в новой таблице никогда не будет объединено, так как для него всегда будет выбираться уникальное имя.

Параметры STORAGE для столбца тоже копируются из родительских таблиц.

Если столбец в родительской таблице является столбцом идентификации, это свойство не наследуется. При желании столбец в дочерней таблице можно объявить столбцом идентификации.

PARTITION BY { RANGE | LIST | HASH } ( { имя_столбца | ( выражение ) } [ класс_операторов ] [, ...] )

Необязательное предложение PARTITION BY задает стратегию разбиения таблицы. Созданная таким образом таблица называется партиционированной таблицей. Заключенный в скобки список столбцов или выражений формирует ключ разбиения для этой таблицы. При использовании партиционирования по диапазонам или по хешу ключ разбиения может включать несколько столбцов или выражений (до 32, но это ограничение можно изменить при сборке QHB), но для партиционирования по списку ключ разбиения должен состоять из одного столбца или выражения.

Для партиционирования по диапазонам и по списку требуется класс операторов В-дерева, а для партиционирования по хешу — класс операторов хеширования. Если класс операторов не указан явно, будет использоваться класс операторов по умолчанию соответствующего типа; если класс операторов по умолчанию отсутствует, возникнет ошибка. При использовании хеш-партиционирования применяемый класс операторов должен реализовывать вспомогательную функцию 2 (более подробную информацию см. в подразделе Процедуры поддержки индексного метода).

Партиционированная таблица делится на подтаблицы (называемые партициями), которые создаются с помощью отдельных команд CREATE TABLE. Партиционированная таблица сама по себе пуста. Добавленная в таблицу строка данных направляется в партицию на основании значения столбцов или выражений в ключе разбиения. Если ни одна существующая партиция не соответствует значениям в новой строке, будет сообщено об ошибке.

Партиционированные таблицы не поддерживают ограничение EXCLUDE, однако эти ограничения можно определить для отдельных партиций.

Дополнительную информацию о партиционировании таблиц см. в разделе Партиционирование таблиц.

PARTITION OF таблица_родитель { FOR VALUES указание_границ_партиции | DEFAULT }

Создает таблицу как партицию указанной родительской таблицы. Таблица может быть создана либо как партиция для конкретных значений (с помощью FOR VALUES), либо в качестве партиции по умолчанию (с помощью DEFAULT). Все индексы, ограничения и пользовательские триггеры уровня строк, которые имеются в родительской таблице, клонируются в новую партицию.

указание_границ_партиции должно соответствовать методу партиционирования и ключу разбиения родительской таблицы и не должно перекрываться ни с какой существующей партицией этого родителя. Форма с IN используется для партиционирования по списку, форма с FROM и TO — для партиционирования по диапазонам, а форма с WITH — для партиционирования по хешу.

выражение_границ_партиции — это любое выражение без переменных (подзапросы, оконные функции, агрегатные функции и функции, возвращающие множества, не допускаются). Его тип данных должен совпадать с типом данных соответствующего столбца ключа разбиения. Выражение вычисляется один раз во время создания таблицы, поэтому может даже содержать изменчивые выражения, например CURRENT_TIMESTAMP.

При создании списочной партиции можно указать в этом списке NULL, чтобы обозначить, что столбец ключа разбиения может содержать NULL. Однако у отдельно взятой родительской таблицы не может быть более одной такой списочной партиции. Для диапазонных партиций указывать NULL нельзя.

При создании диапазонной партиции нижняя граница, заданная с помощью FROM, является включающей границей, тогда как верхняя граница, заданная с помощью TO, является исключающей границей. То есть значения, указанные в списке FROM, — это допустимые значения соответствующих столбцов ключа разбиения для данной партиции, тогда как значения в списке TO таковыми не являются. Обратите внимание, что это утверждение следует понимать в соответствии с правилами сравнения строк (см. подраздел Сравнение конструкторов строк). Например, с разбиением PARTITION BY RANGE (x,y) партиция с границами FROM (1, 2) TO (3, 4) примет x=1 с любым значением y>=2, x=2 — с любым значением y, отличным от NULL, и x=3 — с любым y<4.

При создании диапазонной партиции можно использовать особые значения MINVALUE и MAXVALUE, чтобы указать, что нижняя или верхняя граница значения столбца отсутствует. Например, партиция, определенная с помощью FROM (MINVALUE) TO (10) принимает любые значения меньше 10, а партиция, определенная с помощью FROM (10) TO (MAXVALUE), принимает любые значения, равные или превышающие 10.

При создании диапазонной партиции, включающей более одного столбца, также может иметь смысл использовать MAXVALUE как часть нижней границы и MINVALUE как часть верхней границы. Например, партиция, определенная с помощью FROM (0, MAXVALUE) TO (10, MAXVALUE), принимает любые строки, в которых первый столбец ключа разбиения больше 0 и меньше или равен 10. Аналогично партиция, определенная с помощью FROM (’a’, MINVALUE) TO (’b’, MINVALUE), принимает любые строки, в которых первый столбец ключа разбиения начинается с «a».

Обратите внимание, что если MINVALUE или MAXVALUE используется для одного столбца границы партиции, то это же значение должно использоваться для всех последующих столбцов. Например, (10, MINVALUE, 0) не является допустимой границей; следует написать (10, MINVALUE, MINVALUE).

Также обратите внимание, что у некоторых типов элементов, таких как timestamp (отметка времени), есть понятие «infinity» (бесконечность), которое является просто еще одним сохраняемым значением. Оно отличается от MINVALUE и MAXVALUE, являющимися не реальными значениями, которые могут быть сохранены, а скорее способами сказать, что значение безгранично. MAXVALUE можно рассматривать как значение, превышающее любое другое, включая «бесконечность», а MINVALUE — как значение меньше любого другого, включая «минус бесконечность». Таким образом, диапазон FROM ('infinity') TO (MAXVALUE) — это не пустой диапазон; он позволяет хранить ровно одно значение — «infinity».

Если указывается DEFAULT, то таблица будет создана как партиция по умолчанию родительской таблицы. Этот параметр недоступен для таблиц, партиционированных по хешу. Значение ключа разбиения, не вписывающееся в любую другую партицию данного родительского объекта, будет перенаправлено в партицию по умолчанию.

Когда таблица уже имеет партицию по умолчанию (DEFAULT) и к ней добавляется новая партиция, нужно просканировать партицию по умолчанию, чтобы убедиться, что она не содержит строк, которые точно подходят для новой партиции. Если партиция по умолчанию содержит большое количество строк, этот процесс может быть медленным. Сканирование будет пропущено, если партиция по умолчанию является сторонней таблицей или имеет ограничение, доказывающее, что она не может содержать строки, которые должны быть размещены в новой партиции.

При создании хеш-партиции необходимо указать модуль и остаток. Модуль должен быть положительным целым числом, а остаток — неотрицательным целым числом меньше модуля. Как правило, при первоначальной настройке хеш-партиционированной таблицы следует выбрать модуль, равный числу партиций, и назначить каждой таблице этот модуль и разные остатки (см. примеры ниже). Однако партициям необязательно иметь одинаковый модуль, достаточно, чтобы каждый модуль, который встречается среди партиций хеш-партиционированной таблицы, был коэффициентом для следующего большего модуля. Это позволяет постепенно увеличивать число партиций без необходимости перемещать все данные одновременно. Например, предположим, что у вас есть хеш-партиционированная таблица с 8 партициями, каждая из которых имеет модуль 8, но необходимо увеличить число партиций до 16. Можно отсоединить одну из партиций по модулю 8, создать две новые партиции по модулю 16, охватывающие ту же часть пространства ключей (одну с остатком, равным остатку отсоединенной партиции, а другую с остатком, равным этому значению плюс 8), и повторно заполнить их данными. Затем этот процесс можно повторить — возможно, позднее — для каждой партиции по модулю 8, пока ни одной из них не останется. Хотя такой способ по-прежнему может включать в себя большой объем перемещения данных на каждом этапе, это все же лучше, чем создавать целую новую таблицу и перемещать все данные сразу.

Партиция должна содержать те же имена и типы столбцов, что и партиционированная таблица, к которой она принадлежит. Изменения имен или типов столбцов партиционированной таблицы будут автоматически распространяться на все партиции. Ограничения CHECK будут наследоваться автоматически каждой партицией, но для отдельных партиций можно указать дополнительные ограничения CHECK; дополнительные ограничения с теми же именами и условиями, что и в родительском элементе, будут объединены с родительским ограничением. Значения по умолчанию можно указать отдельно для каждой партиции. Однако обратите внимание, что значение по умолчанию, заданное для партиции, не будет применяться при добавлении кортежа через партиционированную таблицу.

Строки, добавленные в партиционированную таблицу, будут автоматически перенаправлены в нужную партицию. Если подходящей партиции не существует, произойдет ошибка.

Такие операции, как TRUNCATE, которые обычно влияют на таблицу и все ее дочерние элементы, будут каскадно распространяться на все партиции, но также могут выполняться на отдельной партиции. Обратите внимание, что удаление партиции с помощью DROP TABLE требует установки блокировки ACCESS EXCLUSIVE на родительской таблице.

LIKE исходная_таблица [ вариант_копирования ... ]

Предложение LIKE задает таблицу, из которой новая таблица автоматически копирует все имена столбцов, их типы данных и их ограничения NOT NULL.

В отличие от INHERITS, новая и исходная таблицы полностью разъединяются после завершения создания. Изменения в исходной таблицы не будут применены к новой таблице, и нет возможности включить данные новой таблицы в сканы исходной таблицы.

Также, в отличие от INHERITS, столбцы и ограничения, скопированные при помощи LIKE, не объединяются с одноименными столбцами и ограничениями. Если одно и то же имя указано явно или содержится в другом предложении LIKE, будет выдана ошибка.

Необязательные предложения вариант_копирования указывают, какие дополнительные свойства исходной таблицы следует скопировать. Указание INCLUDING копирует свойство, указание EXCLUDING опускает свойство. EXCLUDING — это значение по умолчанию. Если для одного и того же типа объекта создается несколько спецификаций, то используется последняя из них. Доступные варианты:

INCLUDING COMMENTS
Будут скопированы комментарии для скопированных столбцов, ограничений и индексов. Поведение по умолчанию заключается в исключении комментариев, в результате чего скопированные столбцы и ограничения в новой таблице их не имеют.

INCLUDING COMPRESSION
Будет скопирован метод сжатия для столбцов. Поведение по умолчанию — исключить методы сжатия, в результате чего у новых столбцов будет метод сжатия по умолчанию.

INCLUDING CONSTRAINTS
Будут скопированы ограничения CHECK. Между ограничениями столбцов и таблиц никакого различия не делается. Ограничения NOT NULL всегда копируются в новую таблицу.

INCLUDING DEFAULTS
Будут скопированы выражения по умолчанию для скопированных определений столбцов. В противном случае выражения по умолчанию не копируются, в результате чего скопированные столбцы в новой таблице имеют значения по умолчанию, равные NULL. Обратите внимание, что копирование значений по умолчанию, которые вызывают функции изменения базы данных, например nextval, может создать функциональную связь между исходной и новой таблицами.

INCLUDING GENERATED
Будут скопированы все генерирующие выражения скопированных определений столбцов. По умолчанию новые столбцы будут обычными базовыми столбцами.

INCLUDING IDENTITY
Будут скопированы все спецификации идентификаторов скопированных определений столбцов. Новая последовательность создается для каждого столбца идентификации новой таблицы, отдельно от последовательностей, связанных со старой таблицей.

INCLUDING INDEXES
Индексы и ограничения PRIMARY KEY, UNIQUE и EXCLUDE для исходной таблицы будут созданы для новой таблицы. Имена для новых индексов и ограничений выбираются в соответствии с правилами по умолчанию, независимо от того, как были названы оригиналы. (Это поведение позволяет избежать возможных ошибок, связанных с повторяющимися именами для новых индексов.)

INCLUDING STATISTICS
В новую таблицу копируется расширенная статистика.

INCLUDING STORAGE
Будут скопированы параметры STORAGE для скопированных определений столбцов. Поведением по умолчанию является исключение параметров STORAGE, в результате чего скопированные столбцы в новой таблице имеют типоспецифические параметры по умолчанию. Дополнительную информацию о параметрах STORAGE см. в разделе TOAST.

INCLUDING ALL
INCLUDING ALL — это сокращенная форма выбора всех доступных отдельных параметров. (Может быть полезно после применения INCLUDING ALL написать отдельные предложения EXCLUDING, чтобы выбрать все, кроме некоторых конкретных параметров.)

Кроме того, предложение LIKE можно использовать для копирования определений столбцов из представлений, сторонних таблиц или составных типов. Неприменимые настройки (например INCLUDING INDEXES из представления) игнорируются.

CONSTRAINT имя_ограничения

Необязательное имя для ограничения столбца или таблицы. Если ограничение нарушается, имя ограничения присутствует в сообщениях об ошибках, поэтому имена ограничений, например столбец должен быть положительным, можно использовать для передачи полезной информации об ограничениях клиентским приложениям. (Для указания имен ограничений, содержащих пробелы, необходимы кавычки.) Если имя ограничения не указано, система генерирует его автоматически.

NOT NULL

Столбец не может содержать значения NULL.

NULL

Столбец может содержать значения NULL. Это значение по умолчанию.

Это предложение предусмотрено только для обеспечения совместимости с нестандартными базами данных SQL. Использовать его в новых приложениях не рекомендуется.

CHECK ( выражение ) [ NO INHERIT ]

Предложение CHECK задает выражение, создающее логический результат, которому должны удовлетворять новые или измененные строки для успешного выполнения операции добавления или изменения. Выражения, результат которых равен TRUE или UNKNOWN, успешно выполняются. Если какая-либо строка операции добавления или изменения приводит к результату FALSE, возникает ошибка, и эта операция не изменяет базу данных. Проверочное ограничение, указанное как ограничение столбца, должно ссылаться только на значение этого столбца, тогда как выражение, находящееся в ограничении таблицы, может ссылаться на несколько столбцов.

В настоящий момент выражения CHECK не могут содержать подзапросы и ссылаться на переменные, отличные от столбцов текущей строки (см. подраздел Проверочные ограничения). Допустима ссылка на системный столбец tableoid, но не на другие системные столбцы.

Ограничение с пометкой NO INHERIT не будет распространяться на дочерние таблицы.

Когда таблица имеет несколько ограничений CHECK; они будут проверяться для каждой строки в алфавитном порядке по имени после проверки ограничений NOT NULL.

DEFAULT выражение_по_умолчанию

Предложение DEFAULT присваивает значение данных по умолчанию для столбца, в определении которого оно отображается. Значением является любое выражение без переменной (в частности, перекрестные ссылки на другие столбцы в текущей таблице не допускаются). Подзапросы тоже не допускаются. Тип данных выражения по умолчанию должен соответствовать типу данных столбца.

Выражение по умолчанию будет использоваться в любой операции добавления, где не задано значение для столбца. Если для столбца нет значения по умолчанию, то оно будет равно NULL.

GENERATED ALWAYS AS ( генерирующее_выражение ) STORED

Это предложение создает столбец как генерируемый столбец. В такой столбец нельзя записать данные, и при его чтении будет возвращен результат заданного выражения.

Ключевое слово STORED требуется для обозначения того, что столбец будет вычислен при записи и сохранен на диске.

Генерирующее выражение может ссылаться на другие столбцы в таблице, но не на другие генерируемые столбцы. Любые используемые функции и операторы должны быть неизменяемыми. Ссылки на другие таблицы не допускаются.

GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( параметры_последовательности ) ]

Это предложение создает столбец как столбец идентификации. К нему будет прикреплена неявная последовательность, и в новых строках такой столбец будет автоматически получать значения из назначенной ему последовательности. Такой столбец неявно имеет свойство NOT NULL.

Предложения ALWAYS и BY DEFAULT определяют, насколько явно заданные пользователем значения обрабатываются в командах INSERT и UPDATE.

В команде INSERT при выборе ALWAYS пользовательское значение принимается только в том случае, если в операторе INSERT указано VERRIDING SYSTEM VALUE. При выборе BY DEFAULT пользовательское значение имеет приоритет. Дополнительную информацию см. на справочной странице команды INSERT. (В команде COPY всегда используются пользовательские значения, независимо от этого параметра.)

В команде UPDATE при выборе ALWAYS любая попытка заменить содержимое столбца на любое значение, отличное от DEFAULT, будет отвергнута. При выборе BY DEFAULT столбец можно изменить как обычно. (Предложение OVERRIDING для команды UPDATE отсутствует.)

Необязательное предложение параметры_последовательности можно использовать для переопределения параметров последовательности. Дополнительную информацию см. на справочной странице команды CREATE SEQUENCE.

UNIQUE (ограничение столбца)
UNIQUE ( имя_столбца [, ... ] ) [ INCLUDE ( имя_столбца [, ...]) ] (ограничение таблицы)

Ограничение UNIQUE указывает, что группа из одного или нескольких столбцов таблицы может содержать только уникальные значения. Поведение ограничения уникальности для таблицы такое же, как и для столбцов, с дополнительной возможностью охватывать несколько столбцов. Тем самым это ограничение гарантирует, что любые две строки должны отличаться хотя бы в одном из этих столбцов.

При проверке ограничения уникальности значения NULL не считаются равными.

Каждое ограничение уникальности для таблицы должно содержать имя набора столбцов, отличного от набора столбцов, названных любым другим ограничением уникального или первичного ключа, определенным для таблицы. (В противном случае это будет просто одно и то же ограничение, указанное дважды.)

При установлении ограничения уникальности для многоуровневой иерархии партиций в определение ограничения должны быть включены все столбцы в ключе разбиения целевой партиционированной таблицы, а также столбцы всех ее дочерних партиционированных таблиц.

Добавление ограничения уникальности автоматически создаст уникальный индекс В-дерево для столбца или группы столбцов, используемых в этом ограничении.

Необязательное предложение INCLUDE добавляет к этому индексу один или несколько столбцов, которые являются просто «полезной нагрузкой»: для них не требуется уникальность, и значения в этих столбцах по этому индексу искать нельзя. Однако их можно извлечь при сканировании только индекса. Обратите внимание, что хотя это ограничение не применяется к включенным столбцам, оно все равно зависит от них. Следовательно, некоторые операции над этими столбцами (например, DROP COLUMN) могут вызвать каскадное ограничение и удаление индекса.

PRIMARY KEY (ограничение столбца)
PRIMARY KEY ( имя_столбца [, ... ] ) [ INCLUDE ( имя_столбца [, ...]) ] (ограничение таблицы)

Ограничение PRIMARY KEY указывает, что столбец или столбцы таблицы могут содержать только уникальные (не повторяющиеся) значения, отличные от NULL. Для таблицы можно указать только один первичный ключ, будь то ограничение столбца или ограничение таблицы.

Ограничение первичного ключа должно содержать имя набора столбцов, отличного от набора столбцов, названных любым ограничением уникальности, определенным для той же таблицы. (В противном случае ограничение уникальности является избыточным и будет отброшено.)

PRIMARY KEY принудительно применяет те же ограничения данных, что и комбинация UNIQUE и NOT NULL, но указание набора столбцов в качестве первичного ключа также предоставляет метаданные о конструкции схемы, поскольку первичный ключ подразумевает, что другие таблицы могут полагаться на этот набор столбцов в качестве уникального идентификатора для строк.

При размещении в партиционированных таблицах ограничения PRIMARY KEY имеют те же условия, которые ранее описаны для ограничений UNIQUE.

Добавление ограничения PRIMARY KEY автоматически создаст уникальный индекс B-дерево для столбца или группы столбцов, используемых в этом ограничении.

Необязательное предложение INCLUDE к этому индексу один или несколько столбцов, которые являются просто «полезной нагрузкой»: для них не требуется уникальность, и значения в этих столбцах по этому индексу искать нельзя. Однако их можно извлечь при сканировании только индекса. Обратите внимание, что хотя это ограничение не применяется к включенным столбцам, оно все равно зависит от них. Следовательно, некоторые операции над этими столбцами (например, DROP COLUMN) могут вызвать каскадное ограничение и удаление индекса.

EXCLUDE [ USING индексный_метод ] ( элемент_исключения WITH оператор [, ... ] ) параметры_индекса [ WHERE ( предикат ) ]

Предложение EXCLUDE определяет ограничение исключения, которое гарантирует, что если любые две строки сравниваются в указанном столбце (столбцах) или выражении (выражениях) с использованием указанного оператора (или операторов), не все эти сравнения вернут значение TRUE. Если все указанные операторы проверяются на равенство, это эквивалентно ограничению UNIQUE, хотя обычное ограничение уникальности будет быстрее. Однако ограничения исключения могут указывать на ограничения более общие, чем простое равенство. Например, можно указать ограничение, что никакие две строки в таблице не содержат перекрывающихся кругов (см. раздел Геометрические типы), используя оператор &&.

Ограничения исключения реализуются с помощью индекса, поэтому каждый указанный оператор должен быть связан с соответствующим классом операторов (см. раздел Классы операторов и семейства операторов) для индексного метода доступа индексный_метод. Операторы должны быть коммутативными. В каждом элементе_исключения можно дополнительно указать класс оператора и/или параметры сортировки; они полностью описаны на справочной странице команды CREATE INDEX.

Метод доступа должен поддерживать amgettuple (см. главу Определение интерфейса индексных методов доступа); в настоящее время это означает, что GIN использовать нельзя. Хотя это и разрешено, нет особого смысла в использовании с ограничением исключения B-дерева или хеш-индексов, потому что это ничуть не лучше, чем обычное ограничение уникальности. Поэтому на практике метод доступа всегда будет GiST или SP-GiST.

Параметр предикат позволяет задать ограничение исключения для подмножества таблицы; внутри он создает частичный индекс. Обратите внимание, что вокруг предиката требуются круглые скобки.

REFERENCES ссылочная_таблица [ ( ссылочный_столбец ) ] [ MATCH тип_совпадения ] [ ON DELETE ссылочное_действие ] [ ON UPDATE ссылочное_действие ] (ограничение столбца)
FOREIGN KEY ( имя_столбца [, ... ] ) REFERENCES ссылочная_таблица [ ( ссылочный_столбец [, ... ] ) ] [ MATCH тип_совпадения ] [ ON DELETE ссылочное_действие ] [ ON UPDATE ссылочное_действие ] (ограничение таблицы)

Эти предложения задают ограничение внешнего ключа, которое требует, чтобы группа из одного или нескольких столбцов новой таблицы содержала только значения, соответствующие значениям в ссылочном столбце (или столбцах) некоторой строки ссылочной таблицы. Если список ссылочных_столбцов опущен, в качестве него будет использован первичный ключ ссылочной_таблицы. Ссылочные столбцы должны быть столбцами неоткладываемого ограничения уникальности или первичного ключа в ссылочной таблице. Необходимо иметь разрешение REFERENCES на эту ссылочную таблицу (либо на всю таблицу, либо на конкретные ссылочные столбцы). Добавление ограничения внешнего ключа требует блокировки SHARE ROW EXCLUSIVE соответствующей ссылочной таблицы. Обратите внимание, что ограничения внешнего ключа нельзя определить между временными и постоянными таблицами.

Значение, добавленное в ссылающийся столбец (или столбцы), сопоставляется со значениями ссылочной таблицы и ссылочных столбцов с использованием заданного типа соответствия. Существует три типа соответствия: MATCH FULL, MATCH PARTIAL и MATCH SIMPLE (что является значением по умолчанию). MATCH FULL не допускает, чтобы один столбец многостолбцового внешнего ключа содержал NULL, если только все столбцы внешнего ключа не содержат NULL; если все они равны NULL, то строка не обязана иметь соответствие в ссылочной таблице. MATCH SIMPLE позволяет любому из столбцов внешнего ключа иметь значение NULL; если какой-либо из них равен NULL, строка не обязана иметь соответствие в ссылочной таблице. Тип MATCH PARTIAL пока не реализован. (Разумеется, для предотвращения возникновения подобных случаев к ссылочному столбцу (или столбцам) можно применить ограничения NOT NULL.)

Кроме того, при изменении данных в ссылочных столбцах выполняются определенные действия с данными в столбцах этой таблицы. Предложение ON DELETE задает действие, которое необходимо выполнить при удалении ссылочной строки в ссылочной таблице. Аналогичным образом предложение ON UPDATE задает действие, которое необходимо выполнить, когда ссылочный столбец в ссылочной таблице обновляется до нового значения. Если строка обновляется, но ссылочный столбец фактически не изменяется, никакие действия не производятся. Ссылочные действия, отличные от проверки NO ACTION, нельзя отложить, даже если ограничение объявлено откладываемым. Для каждого предложения возможны следующие действия:

NO ACTION
Выдать ошибку, показывающую, что удаление или изменение приведет к нарушению ограничения внешнего ключа. Если ограничение отложено, эта ошибка произойдет во время проверки ограничения, если все еще существуют какие-либо ссылающиеся строки. Это действие по умолчанию.

RESTRICT
Выдать ошибку, показывающую, что удаление или изменение приведет к нарушению ограничения внешнего ключа. Это то же самое, что и NO ACTION, за исключением того, что проверка не откладывается.

CASCADE
Удалить все строки, ссылающиеся на удаленную строку, или обновить значения ссылающегося столбца (или столбцов) до новых значений ссылочных столбцов соответственно.

SET NULL
Установить для ссылающихся столбцов значение NULL.

SET DEFAULT
Установить для ссылающегося столбца (или столбцов) значения по умолчанию. (В ссылочной таблице должна быть строка, соответствующая этим значениям по умолчанию, если их значение не равно NULL, иначе операция завершится ошибкой.)

Если ссылочные столбцы часто изменяются, возможно, целесообразно будет добавить к ссылающимся столбцам индекс, чтобы ссылочные действия, связанные с ограничением внешнего ключа, могли выполняться более эффективно.

DEFERRABLE
NOT DEFERRABLE

Это предложение определяет, может ли ограничение быть отложено. Неоткладываемое ограничение будет проверяться сразу же после выполнения каждой команды. Проверка откладываемых ограничений может быть отсрочена до окончания транзакции (с помощью команды SET CONSTRAINTS). NOT DEFERRABLE является значением по умолчанию. В настоящее время это предложение принимают только ограничения UNIQU, PRIMARY KEY, EXCLUDE и REFERENCES (внешний ключ). Ограничения NOT NULL и CHECK не являются откладываемыми. Обратите внимание, что откладываемые ограничения нельзя использовать в качестве арбитраторов конфликтов в операторе INSERT, включающим в себя предложение ON CONFLICT DO UPDATE.

INITIALLY IMMEDIATE
INITIALLY DEFERRED

Если ограничение является откладываемым, это предложение указывает время по умолчанию для проверки этого ограничения. Ограничение с характеристикой INITIALLY IMMEDIATE проверяется после каждого оператора. Это поведение по умолчанию. Ограничение с характеристикой INITIALLY DEFERRED проверяется только в конце транзакции. Время проверки ограничений можно изменить с помощью команды SET CONSTRAINTS.

USING метод

Это необязательное предложение определяет табличный метод доступа, который будет использоваться для хранения содержимого для новой таблицы; этот метод должен быть типа TABLE. Дополнительную информацию см. в главе Определение интерфейса табличных методов доступа. Если этот параметр не указан, то для новой таблицы выбирается табличный метод доступа по умолчанию. Дополнительную информацию см. в описании параметра default_table_access_method.

В QHB имеется встроенный табличный метод доступа qss, при указании которого данные таблицы, ее индексов и журнала будут записываться на диск в зашифрованном виде. Дополнительную информацию см. в главе Модуль безопасного хранения QSS.

Также в QHB реализован встроенный метод доступа, реализующий хранилище append_only, для которого характерны следующие свойства:

Запрещена модификация данных (изменение и удаление).

Осуществляется максимально быстрая вставка данных.

Отсутствует необходимость в автоочистке.

Поддерживаются все типы индексов.

Для удаления старых данных можно использовать партиционирование таблицы и удалять данные партициями либо использовать команду TRUNCATE.

Не поддерживается механизм TOAST.

Дополнительную информацию см. в главе Таблицы APPEND_ONLY.

WITH ( параметр_хранения [= значение] [, ... ] )

В этом предложении задаются необязательные параметры хранения для таблицы или индекса; дополнительную информацию см. в параграфе Параметры хранения ниже. В целях обратной совместимости предложение WITH для таблицы также может включать в себя условие OIDS=FALSE, указывающее, что строки новой таблицы не должны содержать OID (идентификаторы объектов); OIDS=TRUE больше не поддерживается.

WITHOUT OIDS

Это синтаксис обратной совместимости для объявления таблицы WITHOUT OIDS; создание таблицы WITH OIDS больше не поддерживается.

ON COMMIT

Поведение временных таблиц в конце блока транзакции можно контролировать с помощью предложения ON COMMIT. Есть три параметра:

PRESERVE ROWS
Никаких специальных действий по завершению транзакций не предпринимается. Это поведение по умолчанию.

DELETE ROWS
Все строки во временной таблице будут удалены в конце каждого блока транзакции. По существу, при каждой фиксации автоматически выполняется TRUNCATE. При использовании в партиционированной таблице это не распространяется на ее партиции.

DROP
Временная таблица будет удалена в конце текущего блока транзакции. При использовании в партиционированной таблице это действие удаляет ее партиции, а при использовании в таблицах с потомками в иерархии наследования удаляет зависимых потомков.

TABLESPACE имя_табличного_пространства

имя_табличного_пространства — это имя табличного пространства, в котором будет создана новая таблица. Если этот параметр не указан, используется значение default_tablespace или temp_tablespaces (если таблица временная). В случае партиционированных таблиц, поскольку для самой таблицы хранение не требуется, заданное табличное пространство переопределяет default_tablespace в качестве табличного пространства по умолчанию, используемого для любых вновь созданных партиций, когда явно не указано никакое другое табличное пространство.

USING INDEX TABLESPACE имя_табличного_пространства

Это предложение позволяет выбрать табличное пространство, в котором будет создан индекс, связанный с ограничением UNIQUE, PRIMARY KEY или EXCLUDE. Если этот параметр не указан, используется значение default_tablespace или temp_tablespaces (если таблица временная).

Параметры хранения

Предложение WITH может указывать параметры хранения для таблиц, а также для индексов, связанных с ограничением UNIQUE, PRIMARY KEY или EXCLUDE. Параметры хранения для индексов описаны на справочной странице команды CREATE INDEX. Ниже перечислены параметры хранения, доступные в настоящее время для таблиц. Как можно заметить, у многих из этих параметров существует дополнительный параметр с тем же именем и префиксом toast., который управляет поведением вторичной таблицы TOAST, если таковая имеется (дополнительную информацию о таких таблицах см. в разделе TOAST). Если установлено значение параметра таблицы, а аналогичный параметр toast. — нет, таблица TOAST будет использовать значение параметра таблицы. Указание этих параметров для партиционированных таблиц не поддерживается, но их можно задать для отдельных листовых партиций.

fillfactor (integer)

Коэффициент заполнения для таблицы, задается в процентах от 10 до 100. Значение по умолчанию равно 100 (полное заполнение). Если указан меньший коэффициент заполнения, то операции INSERT заполняют страницы таблицы только до указанного процента; оставшееся пространство на каждой странице резервируется для изменения строк на этой странице. Это дает UPDATE возможность разместить измененную копию строки на той же странице, что и оригинал, что более эффективно, чем размещение его на другой странице, и увеличивает возможность внесения изменений по принципу кортежи только в куче. Для таблицы, записи которой никогда не обновляются, лучшим выбором является полное заполнение, но в активно обновляемых таблицах целесообразнее использовать более мелкие коэффициенты заполнения. Этот параметр нельзя установить для таблиц TOAST.

toast_tuple_target (integer)

Параметр toast_tuple_target задает минимальную необходимую длину кортежа, после превышения которой мы попытаемся сжать и/или переместить значения длинных столбцов в таблицы TOAST, а также целевое значение, до которого мы попытаемся уменьшить оную длину после перехода к TOAST. Это касается столбцов, помеченных как External (внешние, которые могут переноситься), Main (основные, которые могут сжиматься) или Extended (расширенные, которые могут как сжиматься, так и просто переноситься) и применяется только к новым кортежам. На существующие строки это не влияет. По умолчанию значение этого параметра допускает размещение не менее 4 кортежей на блок, что при стандартном размере блока составляет 2040 байт. Допустимые значения находятся в диапазоне от 128 байт до размера блока за вычетом заголовка, по умолчанию 8160 байт. Изменение этого значения может быть не очень полезно для слишком коротких или слишком длинных строк. Обратите внимание, что значение по умолчанию часто практически оптимально, и вполне возможно, что перенастройка этого параметра в некоторых случаях может иметь негативные последствия. Этот параметр нельзя установить для таблиц TOAST.

parallel_workers (integer)

Этот параметр задает число рабочих процессов, которые должны использоваться для поддержки параллельного сканирования данной таблицы. Если этот параметр не задан, система определит значение на основе размера отношения. Фактическое число рабочих процессов, выбранных планировщиком или служебными операторами, использующими параллельное сканирование, может быть меньше, например, из-за значения параметра max_worker_processes.

autovacuum_enabled, toast.autovacuum_enabled (boolean)

Включает или выключает процесс «Автовакуум» для определенной таблицы. Если равен true, то процесс «Автовакуум» будет выполнять в этой таблице автоматические операции VACUUM и/или ANALYZE, следуя правилам, описанным в подразделе Процесс «Автовакуум». При значении false эта таблица не будет подвергаться автоочистке, за исключением случаев, когда необходимо предотвратить зацикливание идентификатора транзакции. Дополнительную информацию о предотвращении зацикливания см. в подразделе Предотвращение ошибок зацикливания идентификатора транзакции. Обратите внимание, что процесс «Автовакуум» не запускается вообще (кроме как для предотвращения зацикливания идентификатора транзакции), если параметр autovacuum равен false; установка параметров хранения для отдельных таблиц это не переопределяет. Поэтому явно задавать этому параметру хранения значение true не имеет особого смысла — полезно только значение false.

vacuum_index_cleanup, toast.vacuum_index_cleanup (boolean)

Включает или выключает очистку индекса при выполнении команды VACUUM в этой таблице. Значение по умолчанию равно AUTO. Со значением OFF очистка индекса выключается, с ON — включается, а с AUTO решение принимается динамически при каждом запуске VACUUM. Это динамическое поведение позволяет VACUUM избегать ненужного сканирования индексов, чтобы удалить лишь несколько неактивных кортежей. Принудительное полное выключение очистки индекса может значительно ускорить выполнение VACUUM, но также может привести к появлению сильно замусоренных индексов, если изменения таблицы происходят часто. Параметр INDEX_CLEANUP команды VACUUM, если он задан, переопределяет значение этого параметра.

vacuum_truncate, toast.vacuum_truncate (boolean)

Включает или выключает очистку, чтобы попытаться провести усечение всех пустых страниц в конце этой таблицы. Значение по умолчанию равно true. При значении true VACUUM и автоочистка проводят усечение, и дисковое пространство из-под усеченных страниц возвращается в операционную систему. Обратите внимание, что для усечения требуется блокировка ACCESS EXCLUSIVE для таблицы. Параметр TRUNCATE команды VACUUM, если он задан, переопределяет значение этого параметра.

autovacuum_vacuum_threshold, toast.autovacuum_vacuum_threshold (integer)

Значение параметра autovacuum_vacuum_threshold для таблицы.

autovacuum_vacuum_scale_factor, toast.autovacuum_vacuum_scale_factor (floating point)

Значение параметра autovacuum_vacuum_scale_factor для таблицы.

autovacuum_vacuum_insert_threshold, toast.autovacuum_vacuum_insert_threshold (integer)

Значение параметра autovacuum_vacuum_insert_threshold для таблицы. Для выключения очистки, вызываемой добавлением данных в таблицу, можно применить специальное значение -1.

autovacuum_vacuum_insert_scale_factor, toast.autovacuum_vacuum_insert_scale_factor (floating point)

Значение параметра autovacuum_vacuum_insert_scale_factor для таблицы.

autovacuum_analyze_threshold (integer)

Значение параметра autovacuum_analyze_threshold для таблицы.

autovacuum_analyze_scale_factor (floating point)

Значение параметра autovacuum_analyze_scale_factor для таблицы.

autovacuum_vacuum_cost_delay, toast.autovacuum_vacuum_cost_delay (floating point)

Значение параметра autovacuum_vacuum_cost_delay для таблицы.

autovacuum_vacuum_cost_limit, toast.autovacuum_vacuum_cost_limit (integer)

Значение параметра autovacuum_vacuum_cost_limit для таблицы.

autovacuum_freeze_min_age, toast.autovacuum_freeze_min_age (integer)

Значение параметра vacuum_freeze_min_age для таблицы. Обратите внимание, что автоочистка будет игнорировать табличные параметры autovacuum_freeze_min_age, превышающие половину значения общесистемного параметра autovacuum_freeze_max_age.

autovacuum_freeze_max_age, toast.autovacuum_freeze_max_age (integer)

Значение параметра autovacuum_freeze_max_age для таблицы. Обратите внимание, что автоочистка будет игнорировать табличные параметры autovacuum_freeze_max_age, превышающие значение общесистемного параметра (они могут быть установлены только в меньшее значение).

autovacuum_freeze_table_age, toast.autovacuum_freeze_table_age (integer)

Значение параметра vacuum_freeze_table_age для таблицы.

autovacuum_multixact_freeze_min_age, toast.autovacuum_multixact_freeze_min_age (integer)

Значение параметра vacuum_multixact_freeze_min_age для таблицы. Обратите внимание, что автоочистка будет игнорировать табличные параметры autovacuum_multixact_freeze_min_age, превышающие половину значения общесистемного параметра autovacuum_multixact_freeze_max_age.

autovacuum_multixact_freeze_max_age, toast.autovacuum_multixact_freeze_max_age (integer)

Значение параметра autovacuum_multixact_freeze_max_age для таблицы. Обратите внимание, что автоочистка будет игнорировать табличные параметры autovacuum_multixact_freeze_max_age, превышающие значение общесистемного параметра (они могут быть установлены только в меньшее значение).

autovacuum_multixact_freeze_table_age, toast.autovacuum_multixact_freeze_table_age (integer)

Значение параметра vacuum_multixact_freeze_table_age для таблицы.

log_autovacuum_min_duration, toast.log_autovacuum_min_duration (integer)

Значение параметра log_autovacuum_min_duration для таблицы.

user_catalog_table (boolean)

Объявить таблицу дополнительной таблицей каталога в целях логической репликации. Подробную информацию см. в подразделе Возможности главы «Логическое декодирование». Этот параметр нельзя задать для таблиц TOAST.

holdmem (OFF, POSSIBLY, ONLY)

Параметр, указывающий, что для таблицы будет использоваться особый кэш дисковых блоков. Подробную информацию см. в главе Параметр HOLDMEM и дополнительные кэши дисковых блоков.

Примечания

QHB автоматически создает индекс, обеспечивающий уникальность для каждого ограничения уникальности и ограничения первичного ключа. Таким образом, нет необходимости явно создавать индекс для столбцов первичного ключа. (Дополнительную информацию см. на справочной странице команды CREATE INDEX.)

Ограничения уникальности и первичные ключи в текущей реализации не наследуются. Поэтому сочетание наследования и ограничений уникальности довольно дисфункционально.

Таблица не может иметь более 1600 столбцов. (На практике эффективный предел обычно ниже из-за ограничений длины кортежа.)

Примеры

Создание таблиц films и distributors:

CREATE TABLE films (
    code        char(5) CONSTRAINT firstkey PRIMARY KEY,
    title       varchar(40) NOT NULL,
    did         integer NOT NULL,
    date_prod   date,
    kind        varchar(10),
    len         interval hour to minute
);

CREATE TABLE distributors (
     did    integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
     name   varchar(40) NOT NULL CHECK (name<> '')
);

Создание таблицы с двумерным массивом:

CREATE TABLE array_int (
    vector  int[][]
);

Определение ограничения уникальности таблицы для таблицы films. Ограничения уникальности таблицы могут быть определены для одного или нескольких столбцов этой таблицы:

CREATE TABLE films (
    code        char(5),
    title       varchar(40),
    did         integer,
    date_prod   date,
    kind        varchar(10),
    len         interval hour to minute,
    CONSTRAINT production UNIQUE(date_prod)
);

Определение проверочного ограничения для столбца:

CREATE TABLE distributors (
    did     integer CHECK (did > 100),
    name    varchar(40)
);

Определение проверочного ограничения для таблицы:

CREATE TABLE distributors (
    did     integer,
    name    varchar(40),
    CONSTRAINT con1 CHECK (did > 100 AND name<> '')
);

Определение ограничения первичного ключа для таблицы films:

CREATE TABLE films (
    code        char(5),
    title       varchar(40),
    did         integer,
    date_prod   date,
    kind        varchar(10),
    len         interval hour to minute,
    CONSTRAINT code_title PRIMARY KEY(code,title)
);

Определение ограничения первичного ключа для таблицы distributors. Следующие два примера равнозначны: первый использует синтаксис ограничения для таблицы, а второй — для столбца:

CREATE TABLE distributors (
    did     integer,
    name    varchar(40),
    PRIMARY KEY(did)
);

CREATE TABLE distributors (
    did     integer PRIMARY KEY,
    name    varchar(40)
);

Присвоение значений по умолчанию: для столбца name это литеральная константа, для столбца did оно генерируется путем выбора следующего значения объекта последовательности, для modtime это время добавления строки:

CREATE TABLE distributors (
    name      varchar(40) DEFAULT 'Luso Films',
    did       integer DEFAULT nextval('distributors_serial'),
    modtime   timestamp DEFAULT current_timestamp
);

Определение двух ограничений NOT NULL для столбцов в таблице distributors, одному из которых явно дано имя:

CREATE TABLE distributors (
    did     integer CONSTRAINT no_null NOT NULL,
    name    varchar(40) NOT NULL
);

Определение ограничения уникальности для столбца name:

CREATE TABLE distributors (
    did     integer,
    name    varchar(40) UNIQUE
);

То же самое, указанное в качестве ограничения для таблицы:

CREATE TABLE distributors (
    did     integer,
    name    varchar(40),
    UNIQUE(name)
);

Создание той же таблицы с указанием коэффициента заполнения 70% как для самой таблицы, так и для ее уникального индекса:

CREATE TABLE distributors (
    did     integer,
    name    varchar(40),
    UNIQUE(name) WITH (fillfactor=70)
)
WITH (fillfactor=70);

Создание таблицы circles с ограничением исключения, которое предотвращает перекрытие любых двух кругов:

CREATE TABLE circles (
    c circle,
    EXCLUDE USING gist (c WITH &&)
);

Создание таблицы cinemas в табличном пространстве diskvol1:

CREATE TABLE cinemas (
        id serial,
        name text,
        location text
) TABLESPACE diskvol1;

Создание составного типа и типизированной таблицы:

CREATE TYPE employee_type AS (name text, salary numeric);

CREATE TABLE employees OF employee_type (
    PRIMARY KEY (name),
    salary WITH OPTIONS DEFAULT 1000
);

Создание таблицы, партиционированной по диапазонам:

CREATE TABLE measurement (
    logdate         date not null,
    peaktemp        int,
    unitsales       int
) PARTITION BY RANGE (logdate);

Создание таблицы, партиционированной по диапазонам, с несколькими столбцами в ключе разбиения:

CREATE TABLE measurement_year_month (
    logdate         date not null,
    peaktemp        int,
    unitsales       int
) PARTITION BY RANGE (EXTRACT(YEAR FROM logdate), EXTRACT(MONTH FROM logdate));

Создание таблицы, партиционированной по списку:

CREATE TABLE cities (
    city_id      bigserial not null,
    name         text not null,
    population   bigint
) PARTITION BY LIST (left(lower(name), 1));

Создание хеш-партиционированной таблицы:

CREATE TABLE orders (
    order_id     bigint not null,
    cust_id      bigint not null,
    status       text
) PARTITION BY HASH (order_id);

Создание партиции таблицы, партиционированной по диапазонам:

CREATE TABLE measurement_y2016m07
    PARTITION OF measurement (
    unitsales DEFAULT 0
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');

Создание нескольких партиций таблицы, партиционированной по диапазонам, с несколькими столбцами в ключе разбиения:

CREATE TABLE measurement_ym_older
    PARTITION OF measurement_year_month
    FOR VALUES FROM (MINVALUE, MINVALUE) TO (2016, 11);

CREATE TABLE measurement_ym_y2016m11
    PARTITION OF measurement_year_month
    FOR VALUES FROM (2016, 11) TO (2016, 12);

CREATE TABLE measurement_ym_y2016m12
    PARTITION OF measurement_year_month
    FOR VALUES FROM (2016, 12) TO (2017, 01);

CREATE TABLE measurement_ym_y2017m01
    PARTITION OF measurement_year_month
    FOR VALUES FROM (2017, 01) TO (2017, 02);

Создание партиции таблицы с партиционированием по списку:

CREATE TABLE cities_ab
    PARTITION OF cities (
    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('a', 'b');

Создание партиции таблицы с партиционированием по списку, которая сама в дальнейшем партиционируется, а затем добавление в нее партиции:

CREATE TABLE cities_ab
    PARTITION OF cities (
    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('a', 'b') PARTITION BY RANGE (population);

CREATE TABLE cities_ab_10000_to_100000
    PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);

Создание партиций хеш-партиционированной таблицы:

CREATE TABLE orders_p1 PARTITION OF orders
    FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE orders_p2 PARTITION OF orders
    FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE orders_p3 PARTITION OF orders
    FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE orders_p4 PARTITION OF orders
    FOR VALUES WITH (MODULUS 4, REMAINDER 3);

Создание партиции по умолчанию:

CREATE TABLE cities_partdef
    PARTITION OF cities DEFAULT;

Совместимость

Команда CREATE TABLE соответствует стандарту SQL с исключениями, перечисленными ниже.

Временные таблицы

Хотя синтаксис CREATE TEMPORARY TABLE похож на имеющийся в стандарте SQL, их действие неодинаково. В стандарте временные таблицы определяются только один раз и автоматически существуют (начиная с пустого содержимого) в каждом сеансе, который нуждается в них. QHB вместо этого требует, чтобы каждый сеанс выдавал для каждой используемой временной таблицы свою собственную команду CREATE TEMPORARY TABLE. Это позволяет различным сеансам использовать одно и то же имя временной таблицы для различных целей, в то время как подход стандарта ограничивает все экземпляры данного имени временной таблицы одной и той же структурой таблицы.

Стандартное определение поведения временных таблиц по большей части игнорируется. Поведение QHB на этом этапе аналогично поведению нескольких других баз данных SQL.

Стандарт SQL также различает глобальные и локальные временные таблицы, причем локальная временная таблица имеет отдельный набор содержимого для каждого модуля SQL в каждом сеансе, хотя его определение по-прежнему является общим для всех сеансов. Поскольку QHB не поддерживает модули SQL, это различие в QHB не имеет смысла.

Для обеспечения совместимости QHB принимает в объявлении временной таблицы ключевые слова GLOBAL и LOCAL, но в настоящее время они не имеют никакого эффекта. Использование этих ключевых слов не рекомендуется, так как будущие версии QHB могут принять более стандартную интерпретацию их смысла.

Предложение ON COMMIT для временных таблиц также напоминает стандарт SQL, но имеет некоторые отличия. Если предложение ON COMMIT опущено, в стандарте указано, что поведением по умолчанию является ON COMMIT DELETE ROWS. Однако в QHB поведением по умолчанию является ON COMMIT PRESERVE ROWS. Параметра ON COMMIT DROP в SQL не существует.

Неотложные ограничения уникальности

Когда ограничение UNIQUE или PRIMARY KEY не является откладываемым, QHB проверяет уникальность непосредственно в момент добавления или изменения строки. В стандарте SQL говорится, что уникальность должна обеспечиваться только в конце оператора; это имеет значение, когда, например, одна команда изменяет множество ключевых значений. Чтобы получить поведение, соответствующее стандарту, объявите ограничение как DEFERRABLE (откладываемое), но не отложенное (т. е. INITIALLY IMMEDIATE). Имейте в виду, что это может быть значительно медленнее, чем немедленная проверка уникальности.

Проверочные ограничения для столбцов

В стандарте SQL говорится, что ограничения CHECK для столбца могут обращаться только к тому столбцу, к которому они применяются; только ограничения CHECK для таблицы могут обращаться к нескольким столбцам. QHB не применяет это условие; она обрабатывает проверочные ограничения для столбцов и для таблиц одинаково.

Ограничение EXCLUDE

Ограничение EXCLUDE является расширением QHB.

«Ограничение» NULL

«Ограничение» NULL (на самом деле не является ограничением) является расширением QHB для стандарта SQL, которое включено для совместимости с некоторыми другими СУБД (и для симметрии с ограничением NOT NULL). Поскольку это поведение по умолчанию для любого столбца, присутствие данного слова не несет смысловой нагрузки.

Именование ограничений

В стандарте SQL говорится, что ограничения таблицы и домена должны иметь имена, которые являются уникальными в схеме, содержащей таблицу или домен. QHB в этом плане либеральнее: она требует только, чтобы имена были уникальными среди ограничений, привязанных к определенной таблице или домену. Однако эта дополнительная свобода не существует для ограничений на основе индексов (ограничения UNIQUE, PRIMARY KEY и EXCLUDE), поскольку связанный индекс имеет то же имя, что и ограничение, а имена индексов должны быть уникальными во всех отношениях в пределах одной схемы.

В настоящее время QHB вообще не записывает имена для ограничения NOT NULL, поэтому на них ограничение уникальности не распространяется. Это может измениться в будущих выпусках.

Наследование

Множественное наследование через предложение INHERITS является языковым расширением QHB. SQL:1999 и более поздние версии определяют одиночное наследование, используя другой синтаксис и другую семантику. Наследование в стиле SQL:1999 в QHB пока еще не поддерживается.

Таблицы с нулем столбцов

QHB позволяет создавать таблицу без столбцов (например CREATE TABLE foo();). Это расширение стандарта SQL, который не допускает таблицы с нулем столбцов. Таблицы с нулем столбцов сами по себе не очень полезны, но их запрещение создает странные особые случаи с ALTER TABLE DROP COLUMN, поэтому кажется более правильным игнорировать это ограничение стандарта.

Множество столбцов идентификации

QHB позволяет таблице иметь более одного столбца идентификации. Стандарт же указывает, что таблица может иметь не более одного столбца идентификации. Это ограничение смягчено главным образом для обеспечения большей гибкости при выполнении изменений схемы или миграции. Обратите внимание, что команда INSERT поддерживает только одно предложение переопределения, которое применяется ко всему оператору, поэтому наличие нескольких столбцов идентификации с различным поведением не поддерживается должным образом.

Генерируемые столбцы

Параметр STORED не является стандартным, но также используется другими реализациями SQL. Стандарт SQL не определяет хранение генерируемых столбцов.

Предложение LIKE

Хотя предложение LIKE и существует в стандарте SQL, многие параметры, которые принимает для него QHB, не входят в стандарт, и наоборот, некоторые параметры стандарта не реализуются в QHB.

Предложение USING qss

Предложение USING с методом qss является расширением QHB.

Предложение USING append_only

Предложение USING с методом append_only является расширением QHB.

Предложение WITH

Предложение WITH является расширением QHB; параметры хранения не входят в стандарт.

Табличные пространства

Концепция табличных пространств QHB не является частью стандарта. Следовательно, пункты TABLESPACE и USING INDEX TABLESPACE являются расширениями.

Типизированные таблицы

Типизированные таблицы реализуют подмножество стандарта SQL. Согласно стандарту, типизированная таблица имеет столбцы, соответствующие нижележащему составному типу, а также еще один столбец, который является «самоссылающимся столбцом». QHB не поддерживает столбцы, ссылающиеся на себя, явно.

Предложение PARTITION BY

Предложение PARTITION BY является расширением QHB.

Предложение PARTITION OF

Предложение PARTITION OF является расширением QHB.

См. также

ALTER TABLE, DROP TABLE, CREATE TABLE AS, CREATE TABLESPACE, CREATE TYPE, qss

CREATE TABLE AS

CREATE TABLE AS — определить новую таблицу из результатов запроса

Синтаксис

CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] имя_таблицы
    [ (имя_столбца [, ...] ) ]
    [ USING метод ]
    [ WITH ( параметр_хранения [= значение] [, ... ] ) | WITHOUT OIDS ]
    [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
    [ TABLESPACE имя_табличного_пространства ]
    AS запрос
    [ WITH [ NO ] DATA ]

Описание

Команда CREATE TABLE AS создает таблицу и заполняет ее данными, вычисленными командой SELECT. Столбцы таблицы имеют имена и типы данных, связанные с выходными столбцами SELECT (за исключением того, что можно переопределить имена столбцов, задав явный список новых имен столбцов).

Команда CREATE TABLE AS имеет некоторое сходство с созданием представления, но на самом деле это совсем другое: она создает новую таблицу и вычисляет запрос только один раз, чтобы заполнить новую таблицу изначально. Новая таблица не будет отслеживать последующие изменения исходных таблиц запроса. Представление же, напротив, при каждом запросе к нему повторно вычисляет определяющую его команду SELECT.

Команде CREATE TABLE AS требуется право CREATE для схемы, используемой для этой таблицы.

Параметры

GLOBAL или LOCAL

Игнорируется для совместимости. Использовать эти ключевые слова не рекомендуется; подробную информацию см. на справочной странице команды CREATE TABLE.

TEMPORARY или TEMP

Если этот параметр указан, таблица создается как временная таблица. Подробную информацию см. на справочной странице команды CREATE TABLE.

UNLOGGED

Если этот параметр указан, таблица создается как незарегистрированная таблица. Подробную информацию см. на справочной странице команды CREATE TABLE.

IF NOT EXISTS

Не считать ошибкой, если отношение с таким именем уже существует; просто выдать замечание и оставить таблицу без изменений.

имя_таблицы

Имя создаваемой таблицы (может быть дополнено схемой).

имя_столбца

Имя столбца в новой таблице. Если имена столбцов не предоставлены, они берутся из имен выходных столбцов запроса.

USING метод

Это необязательное предложение определяет табличный метод доступа, который будет использоваться для хранения содержимого для новой таблицы; этот метод должен быть типа TABLE. Дополнительную информацию см. в главе Определение интерфейса табличных методов доступа. Если этот параметр не указан, то для новой таблицы выбирается табличный метод доступа по умолчанию. Дополнительную информацию см. в описании параметра default_table_access_method.

В QHB есть встроенный табличный метод доступа qss, при указании которого данные таблицы, ее индексов и журнала будут записываться на диск в зашифрованном виде. Дополнительную информацию см. в главе Модуль безопасного хранения QSS.

Также в QHB реализован встроенный метод доступа, реализующий хранилище append_only, для которого характерны следующие свойства:

Запрещена модификация данных (изменение и удаление).

Осуществляется максимально быстрая вставка данных.

Отсутствует необходимость в автоочистке.

Поддерживаются все типы индексов.

Для удаления старых данных можно использовать партиционированные таблицы и удалять данные партициями либо использовать команду TRUNCATE.

Не поддерживается механизм TOAST.

Дополнительную информацию см. в главе Таблицы APPEND_ONLY.

WITH ( параметр_хранения [= значение] [, ... ] )

В этом предложении задаются необязательные параметры хранения для новой таблицы. Дополнительную информацию см. в параграфе Параметры хранения на справочной странице команды CREATE TABLE. В целях обратной совместимости предложение WITH для таблицы также может включать в себя OIDS=FALSE, указывающее, что строки новой таблицы не должны содержать OID (идентификаторы объектов); OIDS=TRUE не поддерживается.

WITHOUT OIDS

Это синтаксис обратной совместимости для объявления таблицы WITHOUT OIDS; создание таблицы WITH OIDS больше не поддерживается.

ON COMMIT

Поведение временных таблиц в конце блока транзакции можно контролировать с помощью ON COMMIT. Существует три параметра:

PRESERVE ROWS
По завершении транзакций никаких специальных действий не предпринимается. Это поведение по умолчанию.

DELETE ROWS
В конце каждого блока транзакции все строки во временной таблице будут удалены. По существу, при каждой фиксации автоматически выполняется TRUNCATE.

DROP
В конце текущего блока транзакции временная таблица будет удалена.

TABLESPACE имя_табличного_пространства

имя_табличного_пространства — это имя табличного пространства, в котором будет создана новая таблица. Если этот параметр не указан, используется значение default_tablespace или temp_tablespaces (если таблица временная).

запрос

Команда SELECT, TABLE или VALUES либо команда EXECUTE, которая выполняет подготовленный запрос SELECT, TABLE или VALUES.

WITH [NO ] DATA

Это предложение определяет, следует ли копировать созданные запросом данные в новую таблицу. Если нет, то копируется только структура таблицы. По умолчанию выполняется копирование данных.

Примечания

Эта команда функционально похожа на SELECT INTO, но она предпочтительнее, так как меньше вероятность спутать ее с другими вариантами использования синтаксиса SELECT INTO. Кроме того, набор возможностей у CREATE TABLE AS больше, чем у SELECT INTO.

Примеры

Создание новой таблицы films_recent, состоящей только из последних записей из таблицы films:

CREATE TABLE films_recent AS
  SELECT * FROM films WHERE date_prod >= '2002-01-01';

Чтобы скопировать таблицу полностью, также можно использовать короткую форму с помощью команды TABLE:

CREATE TABLE films2 AS
  TABLE films;

Создание новой временной таблицы films_recent, состоящей только из последних записей таблицы films, используя подготовленный оператор. Новая таблица будет удалена при фиксации транзакции:

PREPARE recentfilms(date) AS
  SELECT * FROM films WHERE date_prod > $1;
CREATE TEMP TABLE films_recent ON COMMIT DROP AS
  EXECUTE recentfilms('2002-01-01');

Совместимость

Команда CREATE TABLE AS соответствует стандарту SQL. Ниже приведены нестандартные расширения:

Стандарт требует заключать предложение подзапроса в круглые скобки; в QHB эти скобки необязательны.

В стандарте требуется указывать предложение WITH [ NO ] DATA; в QHB это необязательно.

QHB обрабатывает временные таблицы не так, как указано в стандарте; подробную информацию см. на справочной странице команды CREATE TABLE.

Предложение WITH является расширением QHB; параметры хранения в стандарт не входят.

Концепция табличных пространств QHB не является частью стандарта. Следовательно, предложение TABLESPACE является расширением.

См. также

CREATE MATERIALIZED VIEW, CREATE TABLE, EXECUTE, SELECT, SELECT INTO, VALUES, qss

CREATE TABLESPACE

CREATE TABLESPACE — определить новое табличное пространство

Синтаксис

CREATE TABLESPACE имя_табличного_пространства
    [ OWNER { новый_владелец | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ]
    LOCATION 'каталог'
    [ WITH ( параметр_табличного_пространства = значение [, ... ] ) ]

Описание

Команда CREATE TABLESPACE регистрирует новое табличное пространство в масштабе кластера. Имя табличного пространства должно отличаться от имени любого существующего табличного пространства в кластере баз данных.

Табличное пространство позволяет суперпользователям определять альтернативное расположение в файловой системе, в котором могут находиться файлы данных, содержащие объекты базы данных (такие как таблицы и индексы).

Пользователь с соответствующими правами может передать параметр имя_табличного_пространства команде CREATE DATABASE, CREATE TABLE, CREATE INDEX или ADD CONSTRAINT, чтобы файлы данных для этих объектов хранились в указанном табличном пространстве.

ВНИМАНИЕ!
Табличное пространство не может использоваться независимо от кластера, в котором оно определено; см. раздел Табличные пространства.

Параметры

имя_табличного_пространства

Имя создаваемого табличного пространства. Имя не может начинаться с pg_, поскольку такие имена зарезервированы для системных табличных пространств.

имя_пользователя

Имя пользователя, которому будет принадлежать табличное пространство. Если этот параметр опущен, по умолчанию владельцем станет пользователь, выполняющий данную команду. Только суперпользователи могут создавать табличные пространства, но они могут назначать владельцами табличных пространств обычных пользователей.

каталог

Каталог, который будет использоваться для этого табличного пространства. Каталог должен существовать (CREATE TABLESPACE не будет его создавать), быть пустым и принадлежать системному пользователю QHB. Каталог должен быть задан абсолютным именем пути.

параметр_табличного_пространства

Параметр табличного пространства, который необходимо установить или сбросить. В настоящее время доступны только следующие параметры: seq_page_cost, random_page_cost, effective_io_concurrency и maintenance_io_concurrency. Установка этих значений для конкретного табличного пространства переопределит обычную оценку планировщиком стоимости чтения страниц из таблиц в этом табличном пространстве, установленную параметрами конфигурации с теми же именами (см. seq_page_cost, random_page_cost, effective_io_concurrency, maintenance_io_concurrency). Это может быть полезно, если одно табличное пространство расположено на диске, который быстрее или медленнее, чем остальная часть подсистемы ввода/вывода.

Примечания

Табличные пространства поддерживаются только в системах, поддерживающих символьные ссылки.

Команду CREATE TABLESPACE нельзя выполнить внутри блока транзакции.

Примеры

Чтобы создать табличное пространство dbspace в расположении файловой системы /opt/qhb/data/dbs, сначала создайте каталог с помощью средств операционной системы и установите правильного владельца:

mkdir /opt/qhb/data/dbs
chown qhb:qhb /opt/qhb/data/dbs

Затем выполните команду создания табличного пространства внутри QHB:

CREATE TABLESPACE dbspace LOCATION '/opt/qhb/data/dbs';

Чтобы создать табличное пространство, принадлежащее другому пользователю базы данных, используйте следующую команду:

CREATE TABLESPACE indexspace OWNER genevieve LOCATION '/opt/qhb/data/indexes';

Совместимость

Команда CREATE TABLESPACE является расширением QHB.

См. также

CREATE DATABASE, CREATE TABLE, CREATE INDEX, DROP TABLESPACE, ALTER TABLESPACE

CREATE TEXT SEARCH CONFIGURATION

CREATE TEXT SEARCH CONFIGURATION — определить новую конфигурацию текстового поиска

Синтаксис

CREATE TEXT SEARCH CONFIGURATION имя (
    PARSER = имя_анализатора |
    COPY = исходная_конфигурация
)

Описание

Команда CREATE TEXT SEARCH CONFIGURATION создает новую конфигурацию текстового поиска. Конфигурация текстового поиска задает синтаксический анализатор текстового поиска, который может разделить строку на синтаксические единицы, а также словари, которые могут использоваться для определения того, какие из синтаксических единиц представляют интерес для поиска.

Если указан только синтаксический анализатор, то новая конфигурация текстового поиска изначально не имеет сопоставлений между типами синтаксических единиц и словарями и поэтому будет игнорировать все слова. Для создания сопоставлений и извлечения пользы из конфигурации следом необходимо использовать команду ALTER TEXT SEARCH CONFIGURATION. Или же можно скопировать существующую конфигурацию текстового поиска.

Если задано имя схемы, то конфигурация текстового поиска создается в указанной схеме, в противном случае — в текущей.

Пользователь, определивший конфигурацию текстового поиска, становится ее владельцем.

Дополнительную информацию см. в главе Полнотекстовый поиск.

Параметры

имя

Имя создаваемой конфигурации текстового поиска (может быть дополнено схемой).

имя_анализатора

Имя синтаксического анализатора текстового поиска, используемого для этой конфигурации.

исходная_конфигурация

Имя существующей конфигурации текстового поиска для копирования.

Примечания

Параметры PARSER и COPY являются взаимоисключающими, поскольку при копировании существующей конфигурации копируется и выбранный в ней синтаксический анализатор.

Совместимость

В стандарте SQL нет команды CREATE TEXT SEARCH CONFIGURATION.

См. также

ALTER TEXT SEARCH CONFIGURATION, DROP TEXT SEARCH CONFIGURATION

CREATE TEXT SEARCH DICTIONARY

CREATE TEXT SEARCH DICTIONARY — определить новый словарь текстового поиска

Синтаксис

CREATE TEXT SEARCH DICTIONARY имя (
    TEMPLATE = шаблон
    [, параметр = значение [, ... ]]
)

Описание

Команда CREATE TEXT SEARCH DICTIONARY создает новый словарь текстового поиска. Словарь текстового поиска задает способ распознавания интересных или неинтересных для поиска слов. Словарь зависит от шаблона текстового поиска, который определяет функции, фактически выполняющие эту работу. Обычно словарь предоставляет некоторые параметры, которые управляют частным поведением функций шаблона.

Если задано имя схемы, то словарь текстового поиска создается в указанной схеме, в противном случае — в текущей.

Пользователь, определивший словарь текстового поиска, становится его владельцем.

Дополнительную информацию см. в главе Полнотекстовый поиск.

Параметры

имя

Имя создаваемого словаря текстового поиска (может быть дополнено схемой).

шаблон

Имя шаблона текстового поиска, который будет определять основное поведение этого словаря.

параметр

Имя специфичного для шаблона параметра, устанавливаемого для этого словаря.

значение

Значение, используемое для специфичного для шаблона параметра. Если это значение не является простым идентификатором или числом, оно должно быть заключено в кавычки (при желании его можно заключать в кавычки всегда).

Параметры могут отображаться в любом порядке.

Примеры

В следующем примере команда создает словарь на основе Snowball с нестандартным списком стоп-слов.

CREATE TEXT SEARCH DICTIONARY my_russian (
    template = snowball,
    language = russian,
    stopwords = myrussian
);

Совместимость

В стандарте SQL нет команды CREATE TEXT SEARCH DICTIONARY.

См. также

ALTER TEXT SEARCH DICTIONARY, DROP TEXT SEARCH DICTIONARY

CREATE TEXT SEARCH PARSER

CREATE TEXT SEARCH PARSER — определить новый синтаксический анализатор текстового поиска

Синтаксис

CREATE TEXT SEARCH PARSER имя (
    START = функция_начала ,
    GETTOKEN = функция_выдачи_синтаксической_единицы ,
    END = функция_окончания ,
    LEXTYPES = функция_лексических_типов
    [, HEADLINE = функция_заголовка ]
)

Описание

Команда CREATE TEXT SEARCH PARSER создает новый синтаксический анализатор текстового поиска. Синтаксический анализатор текстового поиска определяет способ разбиения текстовой строки на синтаксические единицы и назначения им типов (категорий). Синтаксический анализатор сам по себе не особенно полезен, но должен быть привязан к конфигурации текстового поиска вместе с некоторыми словарями текстового поиска, которые будут использоваться для поиска.

Если задано имя схемы, то синтаксический анализатор текстового поиска создается в указанной схеме, в противном случае — в текущей.

Чтобы использовать команду CREATE TEXT SEARCH PARSER, нужно быть суперпользователем. (Это ограничение сделано потому, что ошибочное определение синтаксического анализатора текстового поиска может нарушить или даже аварийно завершить работу сервера.)

Дополнительную информацию см. в главе Полнотекстовый поиск.

Параметры

имя

Имя создаваемого синтаксического анализатора текстового поиска (может быть дополнено схемой).

функция_начала

Имя функции запуска для синтаксического анализатора.

функция_выдачи_синтаксической_единицы

Имя функции, выдающей следующую синтаксическую единицу для синтаксического анализатора.

функция_окончания

Имя конечной функции для синтаксического анализатора.

функция_лексических_типов

Имя функции перечисления лексических типов для синтаксического анализатора (функции, которая возвращает информацию о производимом ей наборе типов синтаксических единиц).

функция_заголовка

Имя функции заголовка для синтаксического анализатора (функции, которая выделяет краткое содержание для набора синтаксических единиц).

При необходимости имена функций могут быть дополнены схемой. Типы аргументов не задаются, так как список аргументов для каждого типа функции предопределен. Все функции, кроме функции заголовка, являются обязательными.

Аргументы могут отображаться в любом порядке, а не только в показанном выше.

Совместимость

В стандарте SQL нет команды CREATE TEXT SEARCH PARSER.

См. также

ALTER TEXT SEARCH PARSER, DROP TEXT SEARCH PARSER

CREATE TEXT SEARCH TEMPLATE

CREATE TEXT SEARCH TEMPLATE — определить новый шаблон текстового поиска

Синтаксис

CREATE TEXT SEARCH TEMPLATE имя (
    [ INIT = функция_инициализации , ]
    LEXIZE = функция_выделения_лексем
)

Описание

Команда CREATE TEXT SEARCH TEMPLATE создает новый шаблон текстового поиска. Шаблоны текстового поиска определяют функции, реализующие словари текстового поиска. Сам по себе шаблон бесполезен, но его нужно установить как словарь для будущего использования. В словаре обычно указываются параметры, которые должны быть заданы функциям шаблона.

Если задано имя схемы, то шаблон текстового поиска создается в указанной схеме, в противном случае — в текущей.

Чтобы использовать команду CREATE TEXT SEARCH TEMPLATE, нужно быть суперпользователем. Это ограничение сделано из-за того, что ошибочное определение шаблона текстового поиска может нарушить или даже аварийно завершить работу сервера. Причина отделения шаблонов от словарей заключается в том, что шаблон инкапсулирует «небезопасные» аспекты определения словаря. Параметры, которые можно задать при определении словаря, безопасны для установки непривилегированными пользователями, поэтому создание словаря не требует особых прав.

Дополнительную информацию см. в главе Полнотекстовый поиск.

Параметры

имя

Имя создаваемого шаблона текстового поиска (может быть дополнено схемой).

функция_инициализации

Имя функции инициализации для шаблона.

функция_выделения_лексем

Имя функции выделения лексем для шаблона.

При необходимости имена функций могут быть дополнены схемой. Типы аргументов не задаются, так как список аргументов для каждого типа функции предопределен. Функция выделения лексем является необходимой, но функция инициализации необязательна.

Аргументы могут отображаться в любом порядке, а не только в показанном выше.

Совместимость

В стандарте SQL нет команды CREATE TEXT SEARCH TEMPLATE.

См. также

ALTER TEXT SEARCH TEMPLATE, DROP TEXT SEARCH TEMPLATE

CREATE TRANSFORM

CREATE TRANSFORM — определить новую трансформацию

Синтаксис

CREATE [ OR REPLACE ] TRANSFORM FOR имя_типа LANGUAGE имя_языка (
    FROM SQL WITH FUNCTION имя_функции_из_sql [ (тип_аргумента [, ...]) ],
    TO SQL WITH FUNCTION имя_функции_в_sql [ (тип_аргумента [, ...]) ]
);

Описание

Команда CREATE TRANSFORM определяет новую трансформацию. CREATE OR REPLACE TRANSFORM либо создаст новую трансформацию, либо заменит существующее определение.

Трансформация указывает, как адаптировать тип данных к процедурному языку. Языковые реализации обычно по умолчанию используют текстовое представление, но это неудобно, когда, например, более подходящими были бы ассоциативный массив или список.

Трансформация задает две функции:

Функция «из SQL», которая преобразует тип из среды SQL в среду языка. Эта функция будет вызываться для аргументов функции, написанной на этом языке.

Функция «в SQL», которая преобразует тип из среды языка в среду SQL. Эта функция будет вызываться для значения, возвращаемого из функции, написанной на этом языке.

Нет необходимости предоставлять обе эти функции одновременно. Если одна из них не указана, при необходимости будет использоваться поведение по умолчанию для конкретного языка. (Чтобы полностью исключить возможность трансформации в одном направлении, можно написать функцию трансформации, которая будет всегда выдавать ошибку.)

Чтобы иметь возможность создать трансформацию, нужно быть владельцем и иметь право USAGE для данного типа, иметь право USAGE для языка и быть владельцем и иметь право EXECUTE для функций из-SQL и в-SQL, если те заданы.

Параметры

имя_типа

Имя типа данных, для которого предназначена трансформация.

имя_языка

Имя языка, для которого предназначена трансформация.

имя_функции_из_sql[(тип_аргумента [, ...])]

Имя функции для преобразования типа из среды SQL в среду языка. Она должна принимать один аргумент типа internal и возвращать тип internal. Фактический аргумент будет иметь тип, предназначенный для трансформирования, и функция должна быть закодирована под это. (Но нельзя объявить функцию, возвращающую тип internal, на уровне SQL без как минимум одного входного аргумента типа internal.) Фактическое возвращаемое значение будет определяться реализацией языка. Если не указан список аргументов, имя функции должно быть уникальным в схеме.

имя_функции_в_sql[(тип_аргумента [, ...])]

Имя функции для преобразования типа из среды языка в среду SQL. Она должна принимать один аргумент типа internal и возвращать тип, для которого и предназначена трансформация. Фактическое значение аргумента будет определяться реализацией языка. Если не указан список аргументов, имя функции должно быть уникальным в схеме.

Примечания

Для удаления трансформации воспользуйтесь командой DROP TRANSFORM.

Примеры

Чтобы создать трансформацию для типа hstore и языка plpythonu, сначала нужно установить тип и язык:

CREATE TYPE hstore ...;

CREATE EXTENSION plpythonu;

Затем создать необходимые функции:

CREATE FUNCTION hstore_to_plpython(val internal) RETURNS internal
LANGUAGE C STRICT IMMUTABLE
AS ...;

CREATE FUNCTION plpython_to_hstore(val internal) RETURNS hstore
LANGUAGE C STRICT IMMUTABLE
AS ...;

И наконец создать трансформацию, чтобы соединить их вместе:

CREATE TRANSFORM FOR hstore LANGUAGE plpythonu (
    FROM SQL WITH FUNCTION hstore_to_plpython(internal),
    TO SQL WITH FUNCTION plpython_to_hstore(internal)
);

На практике эти команды будут обернуты в расширение.

Каталог share/extension содержит ряд расширений, предоставляющих трансформации, которые могут служить в качестве реальных примеров.

Совместимость

Эта форма CREATE TRANSFORM является расширением QHB. В стандарте SQL существует команда CREATE TRANSFORM, но она предназначена для адаптации типов данных к языкам на стороне клиента. Такой вариант использования QHB не поддерживается.

См. также

CREATE FUNCTION, CREATE LANGUAGE, CREATE TYPE, DROP TRANSFORM

CREATE TRIGGER

CREATE TRIGGER — определить новый триггер

Синтаксис

CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER имя { BEFORE | AFTER | INSTEAD OF } { событие [ OR ... ] }
    ON имя_таблицы
    [ FROM имя_ссылочной_таблицы ]
    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
    [ REFERENCING { { OLD | NEW } TABLE [ AS ] имя_переходного_отношения } [ ... ] ]
    [ FOR [ EACH ] { ROW | STATEMENT } ]
    [ WHEN ( условие ) ]
    EXECUTE { FUNCTION | PROCEDURE } имя_функции ( аргументы )

где событием может быть:

    INSERT
    UPDATE [ OF имя_столбца [, ... ] ]
    DELETE
    TRUNCATE

Описание

Команда CREATE TRIGGER создает новый триггер. CREATE OR REPLACE TRIGGER либо создаст новый триггер, либо заменит существующий. Этот триггер будет связан с заданной таблицей, представлением или сторонней таблицей и будет выполнять указанную функцию имя_функции при выполнении определенных типов операций с этой таблицей.

Для замены текущего определения существующего триггера воспользуйтесь командой CREATE OR REPLACE TRIGGER, указав в ней имя существующего триггера и родительскую таблицу. Все остальные свойства этого триггера заменяются.

Триггер можно настроить так, чтобы он срабатывал до попытки выполнить операцию со строкой (до проверки ограничений и попытки выполнить INSERT, UPDATE или DELETE), или после завершения операции (после проверки ограничений и завершения INSERT, UPDATE или DELETE), или вместо операции (в случае добавлений, изменений или удалений в представлении). Если триггер срабатывает до или вместо события, он может пропустить операцию с текущей строкой или изменить добавляемую строку (только для операций INSERT и UPDATE). Если триггер срабатывает после события, все изменения, включая результаты действия других триггеров, будут для него «видимыми».

Триггер, помеченный как FOR EACH ROW, вызывается один раз для каждой строки, которую изменяет операция. Например, операция DELETE, которая затрагивает 10 строк, вызовет срабатывание всех триггеров ON DELETE для целевого отношения 10 раз подряд: по одному разу для каждой удаляемой строки. И наоборот, триггер с указанием FOR EACH STATEMENT срабатывает только один раз для любой заданной операции, независимо от того, сколько строк она изменяет (в частности, операция, которая не изменяет ни одной строки, все равно приведет к выполнению всех применимых триггеров FOR EACH STATEMENT).

Триггеры, предназначенные для запуска вместо события, т. е. INSTEAD OF, должны быть помечены как FOR EACH ROW и могут быть определены только для представлений. Триггеры BEFORE и AFTER для представления должны быть помечены как FOR EACH STATEMENT.

Кроме того, триггеры могут быть определены для операции TRUNCATE, но только с указанием FOR EACH STATEMENT.

В следующей таблице перечислены типы триггеров, которые могут применяться для таблиц, представлений и сторонних таблиц:

Когда | Событие | На уровне строк | На уровне оператора
BEFORE | INSERT/UPDATE/DELETE | Таблицы и сторонние таблицы | Таблицы, представления и сторонние таблицы
BEFORE | TRUNCATE | — | Таблицы
AFTER | INSERT/UPDATE/DELETE | Таблицы и сторонние таблицы | Таблицы, представления и сторонние таблицы
AFTER | TRUNCATE | — | Таблицы
INSTEAD OF | INSERT/UPDATE/DELETE | Представления | —
INSTEAD OF | TRUNCATE | — | —

Кроме того, в определении триггера можно указать логическое условие WHEN, при проверке которого определяется, должен ли триггер сработать. В триггерах на уровне строк условие WHEN может проверять старые и/или новые значения столбцов строки. Триггеры на уровне оператора также могут содержать условия WHEN, хотя для них эта функциональность не так полезна, поскольку в условии нельзя сослаться на какие-либо значения в таблице.

Если для одного и того же события определено несколько триггеров одного вида, они будут срабатывать в алфавитном порядке их имен.

Если указан параметр CONSTRAINT, команда создает триггер ограничения. Он такой же, как и обычный триггер, за исключением того, что время его срабатывания можно регулировать с помощью команды SET CONSTRAINTS. Триггеры ограничений должны создаваться с указанием AFTER ROW для обычных таблиц (не для сторонних). Они могут срабатывать либо в конце оператора, вызывающего целевое событие, либо в конце содержащей оператор транзакции; в последнем случае они считаются отложенными. Также срабатывание ожидающего отложенного триггера можно вызывать принудительно с помощью команды SET CONSTRAINTS. Ожидается, что триггеры ограничений должны генерировать исключение, когда нарушаются реализуемые ими ограничения.

Если указан параметр REFERENCING, для триггера собираются переходные отношения, которые представляют собой наборы строк, включающие все строки, которые были добавлены, удалены или изменены текущим оператором SQL. Эта функциональность позволяет триггеру иметь глобальное представление о том, что сделал оператор, а не только об одной строке за раз. Это указание допускается только для триггера AFTER, который не является триггером ограничения; кроме того, если такой триггер является триггером UPDATE, у него должен отсутствовать список имен_столбцов. Указание OLD TABLE можно задать только один раз и только для триггера, который срабатывает для операций UPDATE или DELETE; это указание создает переходное отношение, которое содержит образы до изменения всех строк, измененных или удаленных оператором. Аналогично указание NEW TABLE можно задать только один раз и только для триггера, который срабатывает для операций UPDATE или INSERT; это указание создает переходное отношение, которое содержит образы после изменения всех строк, измененных или добавленных оператором.

Операция SELECT не изменяет строки, поэтому нельзя создать триггеры SELECT. Для решения проблем, в которых, возможно, требуются такие триггеры, могут подойти правила и представления

Дополнительную информацию о триггерах см. в главе Триггеры.

Параметры

имя

Имя, задаваемое новому триггеру. Оно должно отличаться от имени любого другого триггера для той же таблицы. Имя триггера не может быть дополнено схемой, так как он наследует схему своей таблицы. Для триггера ограничения это имя также используется для изменения поведения триггера с помощью команды SET CONSTRAINTS.

BEFORE
AFTER
INSTEAD OF

Определяет, вызывается ли функция до, после или вместо события. Для триггера ограничения можно задать только значение AFTER.

событие

Принимает одно из значений: INSERT, UPDATE, DELETE или TRUNCATE; этот параметр указывает на событие, которое приведет к срабатыванию триггера. Можно указать несколько событий с помощью слова OR, за исключением случаев, когда запрашиваются переходные отношения.

Для событий UPDATE можно указать список столбцов, используя следующий синтаксис:

UPDATE OF имя_столбца1 [, имя_столбца2 ... ]

Триггер будет срабатывать только в том случае, если хотя бы один из перечисленных столбцов указан как цель команды UPDATE или является генерируемым и при этом зависящим от столбца, который фигурирует в UPDATE.

Для событий INSTEAD OF UPDATE не допускается использование списка столбцов. Список столбцов также нельзя указывать при запросе переходных отношений.

имя_таблицы

Имя таблицы, представления или сторонней таблицы, для которой предназначен триггер (может быть дополнено схемой).

имя_ссылочной_таблицы

Имя другой таблицы, на которую ссылается ограничение (может быть дополнено схемой). Этот параметр используется для ограничений внешнего ключа и не рекомендуется для обычного использования. Этот параметр можно указать только для триггеров ограничений.

DEFERRABLE
NOT DEFERRABLE
INITIALLY IMMEDIATE
INITIALLY DEFERRED

Время срабатывания триггера по умолчанию. Подробную информацию об этих параметрах см. на справочной странице команды CREATE TABLE. Этот параметр можно указать только для триггеров ограничений.

REFERENCING

Это ключевое слово непосредственно предшествует объявлению одного или двух имен отношений, которые обеспечивают доступ к переходным отношениям при выполнении операторов, вызванных триггером.

OLD TABLE
NEW TABLE

Это предложение указывает, относится ли следующее имя отношения к отношению перехода образа до изменения или к отношению перехода образа после изменения.

имя_переходного_отношения

Имя (без указания схемы), которое будет использоваться в триггере для этого переходного отношения.

FOR EACH ROW
FOR EACH STATEMENT

Этот параметр указывает, запускать ли функцию триггера по одному разу для каждой строки, на которую влияет событие триггера, или только один раз для оператора SQL. Если ничего не указано, по умолчанию подразумевается FOR EACH STATEMENT. Для триггеров ограничений можно указать только FOR EACH ROW.

условие

Логическое выражение, определяющее, будет ли фактически выполняться функция триггера. Если указано WHEN, функция будет вызываться только в том случае, если условие возвращает true. В триггерах FOR EACH ROW условие WHEN может ссылаться на столбцы старых и/или новых значений строк в виде записи OLD.имя_столбца или NEW.имя_столбца соответственно. Конечно, триггеры INSERT не могут ссылаться на OLD, а триггеры DELETE не могут ссылаться на NEW.

Триггеры INSTEAD OF не поддерживают условия WHEN.

В настоящее время выражения WHEN не могут содержать подзапросы.

Обратите внимание, что для триггеров ограничений вычисление условия WHEN не откладывается, а выполняется сразу после осуществления операции изменения строки. Если это условие не вычисляется в true, то триггер не помещается в очередь для отложенного выполнения.

имя_функции

Функция, заданная пользователем, которая объявляется как функция, не принимающая аргументы и возвращающая тип trigger, и которая выполняется при срабатывании триггера.

В синтаксисе команды CREATE TRIGGER ключевые слова FUNCTION и PROCEDURE равнозначны, но указанная функция должна в любом случае быть функцией, а не процедурой. Ключевое слово PROCEDURE оставлено по историческим причинам и является устаревшим.

аргументы

Необязательный список аргументов, разделенных запятыми, которые будут переданы функции при срабатывании триггера. Эти аргументы являются строковыми константами. Также в качестве аргументов можно указать простые имена и числовые константы, но все они будут преобразованы в строки. Следует проверить описание языка реализации триггерной функции, чтобы точно знать порядок обращения к таким аргументам внутри функции, так как он может отличаться от порядка для обычных аргументов функции.

Примечания

Чтобы создать триггер, необходимо иметь право TRIGGER для таблицы, а также право EXECUTE для функции триггера.

Для удаления триггера воспользуйтесь командой DROP TRIGGER.

Создание триггера уровня строк для партиционированной таблицы приведет к созданию идентичного «клонированного» триггера для каждой из ее существующих партиций, и у каждой партиции, созданной или присоединенной впоследствии, тоже появится идентичный триггер. Если в дочерней партиции уже имеется триггер с конфликтующим именем, возникнет ошибка, если только не выполняется CREATE OR REPLACE TRIGGER, поскольку в таком случае этот триггер заменяется клонированным. Когда партиция отсоединяется от родительской таблицы, ее клонированные триггеры удаляются.

Триггер, специфичный для столбцов (определенный с помощью синтаксиса UPDATE OF имя_столбца), будет срабатывать, когда любой из его столбцов перечисляется в качестве целевого в списке SET команды UPDATE. Значение столбца можно изменить, даже когда триггер не запускается, потому что изменения содержимого строки, внесенные с помощью триггеров BEFORE UPDATE, не рассматриваются. И наоборот, команда вроде UPDATE ... SET x = x ... запустит триггер по столбцу x, даже если значение столбца не изменилось.

В триггере BEFORE условие WHEN вычисляется непосредственно перед фактическим или возможным выполнением функции, поэтому использование WHEN существенно не отличается от проверки того же условия в начале триггерной функции. В частности, обратите внимание, что строка NEW, рассматриваемая условием, содержит текущее значение, которое, возможно, было изменено предыдущими триггерами. Кроме того, условию WHEN триггера BEFORE не разрешено проверять системные столбцы строки NEW (например ctid), потому что они еще не были установлены.

В триггере AFTER условие WHEN вычисляется сразу после того, как происходит изменение строки, и это определяет, помещается ли событие в очередь для запуска триггера в конце оператора. Поэтому когда условие WHEN триггера AFTER не возвращает true, нет необходимости ставить событие в очередь или повторно считывать строку в конце оператора. Это может привести к значительному ускорению операторов, изменяющих много строк, если триггер должен срабатывать только для нескольких из них.

В некоторых случаях одна команда SQL может запустить несколько видов триггеров. Например, команда INSERT с предложением ON CONFLICT DO UPDATE может вызвать как операции добавления, так и операции изменения, поэтому она будет по мере необходимости запускать оба вида триггеров. Отношения перехода, предоставляемые триггерам, являются специфичными для их типа событий; таким образом, триггер INSERT будет видеть только добавленные строки, в то время как триггер UPDATE будет видеть только измененные строки.

Изменения или удаления строк, вызванные принудительными действиями внешнего ключа, такими как ON UPDATE CASCADE или ON DELETE SET NULL, рассматриваются как часть команды SQL, которая их вызвала (обратите внимание, что такие действия никогда не откладываются). В затрагиваемой таблице будут запущены соответствующие триггеры, так что это дает команде SQL еще один способ запускать триггеры, не вполне соответствующие их типу. В простых случаях триггеры, которые запрашивают отношения перехода, будут видеть все изменения, сделанные в их таблице одной исходной командой SQL, как одно отношение перехода. Однако существуют случаи, в которых присутствие триггера AFTER ROW, который запрашивает отношения перехода, приведет к тому, что действия принудительного применения внешнего ключа, инициированные одной командой SQL, будут разделены на несколько этапов, каждый со своим собственным отношением (или отношениями) перехода. В таких случаях любые имеющиеся триггеры уровня оператора будут вызваны один раз при создании отношения перехода, гарантируя, что триггеры будут видеть каждую затронутую строку в отношения перехода один и только один раз.

Триггеры уровня операторов для представления срабатывают только в том случае, если операция с представлением обрабатывается триггером INSTEAD OF уровня строк. Если операция обрабатывается по правилу INSTEAD, то все генерируемые этим правилом операторы выполняются вместо исходного оператора, обращающегося к этому представлению, так что триггеры, которые будут запущены, являются триггерами для таблиц, к которым обращаются заменяющие операторы. Аналогично если представление может изменяться автоматически, то операция обрабатывается путем автоматического переписывания оператора в действие с базовой таблицей представления, чтобы запускались триггеры уровня операторов базовой таблицы.

Изменение данных в партиционированной таблице или таблице, у которой есть потомки, запускает триггеры уровня оператора, связанные с явно именованной таблицей, но не триггеры уровня оператора для ее партиций или дочерних таблиц. Напротив, триггеры уровня строк запускаются для строк в затронутых партициях или дочерних таблицах, даже если они явно не названы в запросе. Если триггер уровня оператора был определен с отношениями перехода, указанными в предложении REFERENCING, то образы строк до и после изменения видны из всех затронутых партиций или дочерних таблиц. В случае потомков в иерархии наследования образы строк включают только столбцы, которые присутствуют в таблице, связанной с триггером.

В настоящее время триггеры уровня строк с отношениями перехода нельзя определить для партиций или дочерних таблиц в иерархии наследования. Кроме того, триггеры для партиционированных таблиц не могут быть INSTEAD OF.

В настоящее время параметр OR REPLACE не поддерживается для триггеров ограничений.

Заменять существующий триггер в рамках транзакции, которая уже выполнила изменяющие действия в таблице триггера, не рекомендуется. Решения по срабатыванию триггера или их составляющие, которые уже были приняты, не будут пересмотрены, поэтому последствия такой операции могут быть неожиданными.

Существуют встроенные триггерные функции, которые можно использовать для решения общих проблем без необходимости в написании собственного кода триггера; см. раздел Триггерные функции.

Примеры

Выполнение функции check_account_update всякий раз перед изменением строк таблицы accounts:

CREATE TRIGGER check_update
    BEFORE UPDATE ON accounts
    FOR EACH ROW
    EXECUTE FUNCTION check_account_update();

Изменение определения этого триггера, чтобы функция выполнялась, только если столбец balance указан как целевой в команде UPDATE:

CREATE TRIGGER check_update
    BEFORE UPDATE OF balance ON accounts
    FOR EACH ROW
    EXECUTE FUNCTION check_account_update();

В этой форме функция будет выполняться, только если значение столбца balance действительно изменилось:

CREATE TRIGGER check_update
    BEFORE UPDATE ON accounts
    FOR EACH ROW
    WHEN (OLD.balance IS DISTINCT FROM NEW.balance)
    EXECUTE FUNCTION check_account_update();

Выполнение для каждой строки функции view_insert_row, которая будет добавлять строки в нижележащие таблицы представления:

CREATE TRIGGER view_insert
    INSTEAD OF INSERT ON my_view
    FOR EACH ROW
    EXECUTE FUNCTION view_insert_row();

Выполнение функции check_transfer_balances_to_zero для каждого оператора, чтобы убедиться, что строки таблицы transfer в совокупности дают нулевой баланс:

CREATE TRIGGER transfer_insert
    AFTER INSERT ON transfer
    REFERENCING NEW TABLE AS inserted
    FOR EACH STATEMENT
    EXECUTE FUNCTION check_transfer_balances_to_zero();

Выполнение функции check_matching_pairs для каждой строки, чтобы убедиться, что соответствующие пары пунктов изменяются синхронно (одним оператором):

CREATE TRIGGER paired_items_update
    AFTER UPDATE ON paired_items
    REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab
    FOR EACH ROW
    EXECUTE FUNCTION check_matching_pairs();

В разделе Полный пример триггера содержится полный пример триггерной функции, написанной на языке C/RUST.

Совместимость

Команда CREATE TRIGGER в QHB реализует подмножество возможностей, описанных в стандарте SQL. В настоящее время отсутствуют следующие функциональные возможности:

В то время как имена переходных таблиц для триггеров AFTER задаются с помощью предложения REFERENCING стандартным образом, переменные строк, используемые в триггерах FOR EACH ROW в предложении REFERENCING указывать нельзя. Порядок обращения к таким строкам зависит от языка, на котором написана триггерная функция, но для каждого языка он вполне определенный. Некоторые языки по сути ведут себя так, как будто в команде присутствует предложение REFERENCING, содержащее указание OLD ROW AS OLD NEW ROW AS NEW.

Стандарт позволяет использовать переходные таблицы со специфичными для столбцов триггерами UPDATE, но тогда набор строк, которые должны быть видны в переходных таблицах, зависит от списка целевых столбцов триггера. В настоящее время в QHB это не реализовано.

QHB разрешает выполнять в качестве действия триггера только пользовательскую функцию. Стандарт же позволяет выполнять в качестве действия триггера ряд других команд SQL, таких как CREATE TABLE. Это ограничение нетрудно обойти, создав пользовательскую функцию, которая выполняет желаемые команды.

В стандарте SQL определено, что несколько триггеров должны срабатывать в том порядке, в каком они были созданы. QHB использует порядок имен, который был сочтен более удобным.

В стандарте SQL указано, что триггеры BEFORE DELETE для каскадного удаления запускаются после завершения каскадного DELETE. В QHB триггеры BEFORE DELETE всегда запускаются перед операцией удаления, даже каскадной. Это поведение выбрано как более логичное. Существует еще одно нестандартное поведение, когда триггеры BEFORE изменяют строки или препятствуют изменениям во время обновления, вызванного ссылочным действием. Это может привести к нарушениям ограничений или сохранению данных, которые не соблюдают ссылочную целостность.

Возможность задать несколько действий для одного триггера с помощью слова OR является расширением стандарта SQL, реализованным в QHB.

Возможность вызова триггеров для операции TRUNCATE является расширением стандарта SQL, реализованным в QHB, как и возможность определять триггеры уровня операторов для представлений.

Вариант команды CREATE CONSTRAINT TRIGGER является расширением стандарта SQL, реализованным в QHB, как и параметр OR REPLACE.

См. также

ALTER TRIGGER, DROP TRIGGER, CREATE FUNCTION, SET CONSTRAINTS

CREATE TYPE

CREATE TYPE — определить новый тип данных

Синтаксис

CREATE TYPE имя AS
    ( [ имя_атрибута тип_данных [ COLLATE правило_сортировки ] [, ... ] ] )

CREATE TYPE имя AS ENUM
    ( [ 'метка' [, ... ] ] )

CREATE TYPE имя AS RANGE (
    SUBTYPE = подтип
    [ , SUBTYPE_OPCLASS = класс_операторов_подтипа ]
    [ , COLLATION = правило_сортировки ]
    [ , CANONICAL = каноническая_функция ]
    [ , SUBTYPE_DIFF = функция_разницы_подтипа ]
    [ , MULTIRANGE_TYPE_NAME = имя_мультидиапазонного_типа ]
)

CREATE TYPE имя (
    INPUT = функция_ввода,
    OUTPUT = функция_вывода
    [ , RECEIVE = функция_получения ]
    [ , SEND = функция_отправки ]
    [ , TYPMOD_IN = функция_ввода_модификатора_типа ]
    [ , TYPMOD_OUT = функция_вывода_модификатора_типа ]
    [ , ANALYZE = функция_анализа ]
    [ , SUBSCRIPT = функция_обращения_по_индексу ]
    [ , INTERNALLENGTH = { внутренняя_длина | VARIABLE } ]
    [ , PASSEDBYVALUE ]
    [ , ALIGNMENT = выравнивание ]
    [ , STORAGE = хранение ]
    [ , LIKE = тип_образец ]
    [ , CATEGORY = категория ]
    [ , PREFERRED = предпочитаемый ]
    [ , DEFAULT = по_умолчанию ]
    [ , ELEMENT = элемент ]
    [ , DELIMITER = разделитель ]
    [ , COLLATABLE = сортируемый ]
)

CREATE TYPE имя

Описание

Команда CREATE TYPE регистрирует новый тип данных для использования в текущей базе данных. Пользователь, определивший тип, становится его владельцем.

Если задано имя схемы, то тип будет создан в указанной схеме, в противном случае — в текущей. Имя типа должно отличаться от имени любого существующего типа или домена в той же схеме. (Поскольку таблицы связаны с типами данных, имя типа также должно отличаться от имени любой существующей таблицы в той же схеме.)

Существует пять форм команды CREATE TYPE, как показано выше в блоке синтаксиса. Они создают соответственно: составной тип, перечислимый тип, диапазонный тип, базовый тип или тип-оболочку. Первые четыре из них рассматриваются ниже. Тип-оболочка представляет собой просто заготовку для типа, который будет определен позже; он создается командой CREATE TYPE без параметров, только с указанием имени типа. Типы-оболочки необходимы в качестве прямых ссылок при создании диапазонных и базовых типов, как описывается в соответствующих разделах.

Составные типы

Первая форма CREATE TYPE создает составной тип. Составной тип задается списком имен атрибутов и типов данных. Для атрибута может быть также указано правило сортировки, если тип данных является сортируемым. Составной тип по существу совпадает с типом строки таблицы, но использование CREATE TYPE позволяет избежать необходимости создавать настоящую таблицу, когда требуется всего лишь определить тип. Отдельный составной тип может быть полезен, например, в качестве аргумента или возвращаемого типа функции.

Чтобы создать составной тип, необходимо иметь право USAGE для всех типов его атрибутов.

Перечислимые типы

Вторая форма CREATE TYPE создает перечислимый тип, описываемый в разделе Перечислимые типы. Перечислимые типы принимают список заключенных в кавычки меток, каждая из которых может иметь длину не больше NAMEDATALEN байт (64 байта в стандартной сборке QHB). (Можно создать перечислимый тип без меток, но такой тип нельзя использовать для хранения значений, пока хотя бы одна метка не будет добавлена с помощью команды ALTER TYPE.)

Диапазонные типы

Третья форма CREATE TYPE создает новый диапазонный тип, описываемый в разделе Диапазонные типы.

подтип диапазонного типа может быть любым типом со связанным классом операторов B-дерева (для определения порядка значений в диапазонном типе). Обычно для определения порядка используется класс операторов B-дерева по умолчанию для данного подтипа; чтобы изменить класс операторов, укажите его имя в параметре класс_операторов_подтипа. Если подтип является сортируемым и вы хотите использовать правило сортировки, отличное от заданного по умолчанию, укажите желаемое правило сортировки в параметре правило_сортировки.

Необязательная каноническая_функция должна принимать один аргумент определяемого диапазонного типа и возвращать значение того же типа. Это используется для преобразования значений диапазона в каноническую форму, когда это применимо. Дополнительную информацию см. в подразделе Определение новых диапазонных типов. Создание канонической_функции немного сложнее, так как она должна быть определена до того, как можно будет объявить диапазонный тип. Для этого необходимо сначала создать тип-оболочку, который является заглушкой, не имеющей никаких свойств, кроме имени и владельца. Это делается путем выполнения команды CREATE TYPE имя без дополнительных параметров. Затем можно объявить функцию с типом-оболочкой в качестве аргумента и результата, и, наконец, можно объявить диапазонный тип с тем же именем. Это автоматически заменяет тип-оболочку на допустимый диапазонный тип.

Необязательная функция_разницы_подтипа должна принимать в качестве аргументов два значения типа подтип и возвращать значение double precision, представляющее собой разницу между двумя заданными значениями. Хотя эта функция необязательная, с ее помощью можно кардинально увеличить эффективность индексов GiST для столбцов диапазонного типа. Дополнительную информацию см. в подразделе Определение новых диапазонных типов.

Необязательный параметр имя_мультидиапазонного_типа задает имя соответствующего мультидиапазонного типа. Если этот параметр не указывается, данное имя выбирается автоматически следующим образом. Если имя диапазонного типа содержит подстроку range, то имя мультидиапазонного типа формируется путем замены в имени диапазонного типа подстроки range на подстроку multirange. В противном случае имя мультидиапазонного типа формируется путем добавления к имени диапазонного типа суффикса _multirange.

Базовые типы

Четвертая форма CREATE TYPE создает новый базовый тип (скалярный тип). Чтобы создать новый базовый тип, нужно быть суперпользователем. (Это ограничение сделано потому, что ошибочное определение типа может вызвать нарушения или даже сбой в работе сервера.)

Параметры могут перечисляться в любом порядке, а не только в том, который показан выше, и большинство из них являются необязательными. Перед определением типа необходимо зарегистрировать две или более функций (с помощью CREATE FUNCTION). Обязательными вспомогательными функциями являются функция_ввода и функция_вывода, тогда как функции функция_получения, функция_отправки, функция_ввода_модификатора_типа, функция_вывода_модификатора_типа и функция_анализа можно не указывать. Как правило, эти функции должны быть написаны на C/RUST или другом низкоуровневом языке.

функция_ввода преобразует внешнее текстовое представление типа во внутреннее, используемое операторами и функциями, определенными для данного типа. функция_вывода выполняет обратное преобразование. Функцию ввода можно объявить как принимающую один аргумент типа cstring или как принимающую три аргумента с типами cstring, oid, integer. Первый аргумент — это вводимый текст в виде строки в стиле C/RUST, второй аргумент — собственный OID типа (за исключением типов массивов, для которых передается OID типа элемента), а третий — это typmod для целевого столбца, если он известен (и -1, если нет). Функция ввода должна возвращать значение самого типа данных. Обычно функция ввода должна объявляться как строгая (STRICT); если это не так, то при получении на вход значения NULL она будет вызываться с первым параметром NULL. В этом случае функция все равно должна возвращать NULL, иначе возникнет ошибка. (Этот случай в основном предназначен для поддержки функций ввода доменных типов, которым может понадобиться отклонять входные данные NULL.) Функция вывода должна объявляться как принимающая один аргумент нового типа данных, а возвращать должна тип cstring. Функции вывода не вызываются для значений NULL.

Необязательная функция_получения преобразует внешнее двоичное представление типа во внутреннее представление. Если эта функция не указана, тип не может участвовать в двоичном вводе. Двоичное представление должно быть выбрано так, чтобы оно легко преобразовывалось во внутреннюю форму, будучи при этом достаточно переносимым. (Например, для стандартных целочисленных типов данных в качестве внешнего двоичного представления используют сетевой порядок байтов, в то время как внутреннее представление определяется порядком байтов в процессоре.) Функция получения должна выполнять адекватную проверку на допустимость значения. Функцию получения можно объявить как принимающую один аргумент типа internal либо как принимающую три аргумента с типами internal, oid, integer. Первый аргумент — это указатель на буфер StringInfo, содержащий полученную байтовую строку; а необязательные аргументы такие же, как и для функции ввода текста. Функция получения должна возвращать значение самого типа данных. Обычно функция получения должна объявляться как строгая (STRICT); если это не так, при прочтении входного значения NULL она будет вызываться с первым параметром NULL. В этом случае функция все равно должна возвращать NULL, иначе возникнет ошибка. (Этот случай в основном предназначен для поддержки функций ввода доменных типов, которым может понадобиться отклонять входные данные NULL.) Аналогично необязательная функция_отправки преобразует данные из внутреннего представления во внешнее двоичное представление. Если эта функция не указана, тип не может участвовать в двоичном выводе. Функция отправки должна объявляться как принимающая один аргумент нового типа данных, а возвращать тип bytea. Функции отправки не вызываются для значений NULL.

На этом этапе может возникнуть вопрос, как функции ввода и вывода могут быть объявлены с результатами или аргументами нового типа, если они должны быть созданы до создания нового типа. Ответ прост: тип сначала должен быть определен как тип-оболочка, который является заготовкой и не имеет никаких свойств, кроме имени и владельца. Он объявляется командой CREATE TYPE имя без дополнительных параметров. Затем, ссылаясь на этот пустой тип, можно будет определить функции ввода/вывода. Наконец, команда CREATE TYPE с полным определением заменит тип-оболочку на полное, допустимое определение типа, после чего новый тип можно будет использовать как обычно.

Необязательные функция_ввода_модификатора_типа и функция_вывода_модификатора_типа необходимы, если тип поддерживает модификаторы, то есть дополнительные ограничения, связанные с объявлением типа, например char(5) или numeric(30,2). QHB позволяет создавать типы, которые могут принимать в качестве модификаторов одну или несколько простых констант или идентификаторов. Однако эта информация должна быть упакована в одно неотрицательное целочисленное значение для хранения в системных каталогах. функция_ввода_модификатора_типа получает объявленный модификатор (или модификаторы) в виде строки cstring. Функция должна проверить значения на допустимость (и вызвать ошибку, если они неверны) и, если они верны, выдать одно неотрицательное значение integer, которое будет сохранено в столбце «typmod». Модификаторы типа будут отклонены, если функция_ввода_модификатора_типа для типа не указана. функция_вывода_модификатора_типа преобразует внутреннее целочисленное значение typmod обратно в правильную форму для отображения пользователю. Она должна вернуть значение cstring, которое именно в таком виде добавляется к имени типа; например, функция numeric должна вернуть (30,2). функцию_вывода_модификатора_типа можно опустить; в этом случае отображением по умолчанию будет сохраненное целочисленное значение typmod, заключенное в круглые скобки.

Необязательная функция_анализа выполняет сбор специфичной для типа статистики по столбцам с этим типом данных. По умолчанию команда ANALYZE попытается собрать статистику, используя операторы «равно» и «меньше, чем», если для типа определен класс операторов B-дерева по умолчанию. Для нескалярных типов такое поведение в большинстве случаев не подходит, поэтому его можно переопределить, указав собственную функцию анализа. Функция анализа должна быть объявлена как принимающая один аргумент типа internal и возвращающая результат boolean.

Необязательная функция_обращения_по_индексу позволяет обращаться к этому типу данных по индексу в командах SQL. Указание этой функции не заставляет рассматривать данный тип как «истинный» массив; например, он не станет кандидатом в тип результата конструкций ARRAY[]. Но если добавление индекса к значению типа является естественной нотацией для извлечения из него данных, то написание функции_обращения_по_индексу позволяет определить, что это означает. Функция обращения по индексу должна объявляться как принимающая один аргумент типа internal и возвращающая результат internal, который является указателем на структуру с методами (функциями), реализующими обращение по индексу.

Дополнительная информация представлена в параграфе Типы массивов ниже.

Хотя подробные сведения о внутреннем представлении нового типа известны только функциям ввода/вывода и другим функциям, созданным для работы с этим типом, существует еще несколько свойств внутреннего представления, которые должны быть объявлены в QHB. Прежде всего, это внутренняя_длина. Базовый тип данных может быть фиксированной длины, и тогда внутренняя_длина указывается в виде положительного целого числа, а если это тип переменной длины, то во внутренней_длине задается значение VARIABLE. (Внутри же typlen принимает значение -1.) Внутреннее представление всех типов переменной длины должно начинаться с 4-байтового целого числа, задающего общую длину этого значения типа. (Обратите внимание, что поле длины часто кодируется, как описано в разделе TOAST; неразумно обращаться к нему напрямую.)

Необязательный флаг PASSEDBYVALUE показывает, что значения создаваемого типа данных передаются по значению, а не по ссылке. Типы, передаваемые по значению, должны быть фиксированной длины, и их внутреннее представление не может быть больше размера типа Datum (4 байта на одних машинах, 8 байт на других).

Параметр выравнивание указывает, как требуется выравнивать данные этого типа. Допустимые значения выравнивания по границам: 1, 2, 4 или 8 байт. Обратите внимание, что типы переменной длины должны иметь выравнивание не менее 4 байт, так как они обязательно содержат в качестве первого компонента int4.

Параметр хранение позволяет выбирать стратегии хранения для типов данных переменной длины. (Для типов фиксированной длины допускается только вариант plain.) Вариант plain указывает, что данные этого типа всегда будут храниться внутри и не будут сжаты. Вариант extended указывает, что система сначала попытается сжать длинное значение данных, а затем, если оно и после сжатия окажется слишком длинным, переместит его из строки основной таблицы. Вариант external позволяет переместить значение из основной таблицы, но система не будет пытаться сжать его. Вариант main разрешает сжатие, но препятствует перемещению значения из основной таблицы. (Элементы данных с этим вариантом хранения все равно можно переместить из основной таблицы, если нет другого способа уместить их в строке, но предпочтение будет отдано сохранению в основной таблице, в отличие от вариантов extended и external.)

Все значения параметра хранение, кроме plain, подразумевают, что функции типа данных могут принимать значения в формате toast, как описано в разделе TOAST и подразделе Особенности TOAST. Такое значение просто определяет вариант хранения TOAST по умолчанию для столбцов отделяемого в TOAST типа данных; другие стратегии для отдельных столбцов можно выбирать с помощью команды ALTER TABLE SET STORAGE.

Параметр тип_образец предоставляет альтернативный метод для указания основных свойств представления типа данных: скопировать их из другого существующего типа. Из указанного типа будут скопированы значения свойств внутренняя_длина, передача_по_значению, выравнивание и хранение. (Можно, хотя обычно это нежелательно, переопределить некоторые из этих значений, указав их вместе с предложением LIKE) Указывать представление типа таким образом особенно удобно при низкоуровневой реализации нового типа, которая некоторым образом «опирается» на существующий тип.

Параметры категория и предпочитаемый могут использоваться для поддержки в управлении тем, какое неявное приведение будет применяться в неоднозначных ситуациях. Каждый тип данных относится к категории, обозначаемой одним символом ASCII, и каждый тип в этой категории является либо «предпочтительным» либо нет. Синтаксический анализатор постарается выбрать приведение к предпочтительному типу (но только из других типов в той же категории), когда это правило может помочь при разрешении перегруженных функций или операторов. Более подробную информацию см. в главе PL/pgSQL. Для типов, которые не имеют неявных приведений к какому-либо другому типу или из него, достаточно оставить значения по умолчанию. Однако для группы связанных типов, которые имеют неявные приведения, часто бывает полезно пометить их все как принадлежащие к категории и выбрать один или два «наиболее общих» типа как предпочтительные в этой категории. Параметр категория особенно полезен при добавлении пользовательского типа в существующую встроенную категорию, например числовые или строковые типы. Однако также можно создавать новые, полностью пользовательские категории типов. В качестве имени такой категории можно выбрать любой ASCII-символ, кроме латинской заглавной буквы.

В случае, если пользователь желает назначить столбцам с этим типом данных значение по умолчанию, отличное от NULL, его можно указать в этой команде после ключевого слова DEFAULT. (Такое значение по умолчанию может быть переопределено явным предложением DEFAULT, добавленным при создании определенного столбца.)

Чтобы указать, что тип является массивом постоянной длины, задайте тип элементов массива с помощью ключевого слова ELEMENT. Например, чтобы определить массив 4-байтовых целых чисел (int4), задайте ELEMENT = int4. Дополнительная информация представлена в параграфе Типы массивов ниже.

Для указания разделителя, который будет использоваться между значениями во внешнем представлении массивов этого типа, можно установить в параметре разделитель определенный символ. Разделителем по умолчанию является запятая (,). Обратите внимание, что разделитель связан с типом элементов массива, а не с самим типом массива.

Если в необязательном логическом параметре сортируемый установлено значение true, определения столбцов и выражения этого типа могут содержать информацию о правилах сортировки, в предложении COLLATE. Получение фактической пользы от этих правил зависит от реализаций функций, работающих с типом; просто от того, что тип помечен как сортируемый, эти правила действовать не будут.

Типы массивов

Каждый раз при создании пользовательского типа QHB автоматически создает связанный тип массива, имя которого состоит из имени типа элемента и знака подчеркивания перед ним. При необходимости полученное имя усекается, если его длина превышает NAMEDATALEN байт. (Если имя, созданное таким образом, конфликтует с именем существующего типа, процесс повторяется до тех пор, пока не будет найдено непротиворечивое имя.) Этот неявно созданный тип массива имеет переменную длину и использует встроенные функции ввода и вывода array_in и array_out. Более того, именно этот тип система использует для конструкций вроде ARRAY[] поверх пользовательского типа. Тип массива отражает любые изменения владельца или схемы его типа элемента и удаляется при удалении типа элемента.

Вы можете задать вопрос, зачем существует параметр ELEMENT, если система создает правильный тип массива автоматически. Единственный случай, когда полезно использовать ELEMENT, это когда вы создаете тип фиксированной длины, который внутренне оказывается массивом нескольких одинаковых элементов, и вы хотите, чтобы эти элементы были доступны напрямую путем обращения по индексу, в дополнение к любым операциям, которые вы планируете реализовать для типа в целом. Например, тип point представляется в виде двух чисел с плавающей запятой, доступ к которым можно получить с помощью point[0] и point[1]. Обратите внимание, что эта возможность работает только с типами фиксированной длины, внутренняя форма которых представляет собой точную последовательность одинаковых полей фиксированной длины. По историческим причинам (т. е. это определенно некорректно, но слишком поздно это менять), индексы в массивах фиксированной длины начинаются с нуля, а не с единицы, как у массивов переменной длины.

Указание параметра SUBSCRIPT позволяет обращаться к типу данных по индексу, даже если в ином случае система не считает его типом массива. Поведение, описанное выше для массивов фиксированной длины, фактически реализуется функцией raw_array_subscript_handler, являющейся обработчиком SUBSCRIPT, которая используется автоматически при указании для типа фиксированной длины параметра ELEMENT, но не параметра SUBSCRIPT.

Когда задается нестандартная функция SUBSCRIPT, указывать ELEMENT обязательно, только если функция обработки SUBSCRIPT должна узнать typelem, чтобы понять, какой тип возвращать. Учтите, что с указанием ELEMENT система будет предполагать, что новый тип содержит тип элемента или каким-то образом физически зависит от него; таким образом, к примеру, нельзя будет изменить свойства типа элемента, если имеются какие-либо столбцы зависимого типа.

Параметры

имя

Имя создаваемого типа (может быть дополнено схемой).

имя_атрибута

Имя атрибута (столбца) для составного типа.

тип_данных

Имя существующего типа данных, который станет столбцом составного типа.

правило_сортировки

Имя существующего правила сортировки, которое будет связано со столбцом составного типа или с диапазонным типом.

метка

Строковый литерал, представляющий текстовую метку, связанную с одним значением перечислимого типа.

подтип

Имя типа элемента, который будет представлять диапазоны для диапазонного типа.

класс_операторов_подтипа

Имя класса операторов B-дерева для подтипа.

каноническая_функция

Имя функции канонизации для диапазонного типа.

функция_разницы_подтипа

Имя функции разницы для данного подтипа.

имя_мультидиапазонного_типа

Имя соответствующего мультидиапазонного типа.

функция_ввода

Имя функции, преобразующей данные из внешней текстовой формы типа в его внутреннюю форму.

функция_вывода

Имя функции, преобразующей данные из внутренней формы типа во внешнюю текстовую форму.

функция_получения

Имя функции, преобразующей данные из внешней двоичной формы типа в его внутреннюю форму.

функция_отправки

Имя функции, преобразующей данные из внутренней формы типа в его внешнюю двоичную форму.

функция_ввода_модификатора_типа

Имя функции, преобразующей массив модификаторов для данного типа во внутреннюю форму.

функция_вывода_модификатора_типа

Имя функции, преобразующей внутреннюю форму модификатора (или модификаторов) типа во внешнюю текстовую форму.

функция_анализа

Имя функции, выполняющей статистический анализ для этого типа данных.

внутренняя_длина

Числовая константа, задающая длину внутреннего представления нового типа в байтах. По умолчанию предполагается, что тип имеет переменную длину.

выравнивание

Требуемое выравнивание для типа данных. Если указано, то допустимые значения: char, int2, int4 или double; значение по умолчанию — int4.

хранение

Стратегия хранения для этого типа данных. Если указана, то допустимые значения: plain, external, extended или main; значение по умолчанию — plain.

тип_образец

Имя существующего типа данных, от которого новый тип получит свойства представления. Будут скопированы значения следующих параметров этого типа: внутренняя_длина, передача_по_значению, выравнивание и хранение — если только они не будут переопределены явными указаниями в этой команде CREATE TYPE.

категория

Код категории (один символ ASCII) для этого типа. Значение по умолчанию: 'U', что означает «пользовательский тип» (user-defined). Другие коды категорий можно найти в таблице Коды typcategory. Также можно выбрать другие символы ASCII для создания нестандартных категорий.

предпочитаемый

true, если этот тип является предпочтительным типом в своей категории типов, иначе false. Значение по умолчанию — false. Будьте очень осторожны при создании нового предпочтительного типа в рамках существующей категории типов, так как это может вызвать неожиданные изменения в поведении.

по_умолчанию

Значение по умолчанию для данного типа данных. Если этот параметр опущен, значение по умолчанию равно NULL.

элемент

Создаваемый тип является массивом; это параметр определяет тип элементов массива.

разделитель

Символ-разделитель, используемый между значениями в массивах, образованных из значений этого типа.

сортируемый

True, если в операциях с этим типом может учитываться информация о правиле сортировки. Значение по умолчанию равно false.

Примечания

Поскольку нет никаких ограничений на использование типа данных после его создания, создание базового или диапазонного типа равносильно предоставлению всем права на выполнение функций, упомянутых в определении этого типа. Это обычно не является проблемой для тех функций, которые используются в определении типа. Но стоит подумать дважды, прежде чем создавать тип, преобразование которого во внешнюю форму и обратно потребует использования «секретной» информации.

Имя генерируемого типа массива обычно образуется из имени типа элемента и добавленного перед ним символа подчеркивания (_), но оно может быть и другим в случае достижения максимальной длины имени или конфликтов с именами пользовательских типов, начинающихся с подчеркивания. Поэтому написание кода, полагающегося на это соглашение, считается устаревшим подходом. Вместо этого для определения типа массива, связанного с данным типом, лучше воспользоваться pg_type.typarray.

Рекомендуется вообще избегать использования имен типов и таблиц, начинающихся с символа подчеркивания. Хотя сервер сможет сгенерировать другое имя типа массива, чтобы избежать конфликтов с пользовательскими именами, по-прежнему существует риск возникновения путаницы, особенно со старыми клиентскими приложениями, которые могут предположить, что имена типов, начинающиеся с подчеркивания, относится к типу массива.

Примеры

В этом примере создается составной тип, который затем используется в определении функции:

CREATE TYPE compfoo AS (f1 int, f2 text);

CREATE FUNCTION getfoo() RETURNS SETOF compfoo AS $$
    SELECT fooid, fooname FROM foo
$$ LANGUAGE SQL;

В этом примере создается перечислимый тип, который затем используется в определении таблицы:

CREATE TYPE bug_status AS ENUM ('new', 'open', 'closed');

CREATE TABLE bug (
    id serial,
    description text,
    status bug_status
);

В этом примере создается диапазонный тип:

CREATE TYPE float8_range AS RANGE (subtype = float8, subtype_diff = float8mi);

В этом примере создается базовый тип данных box, который затем используется в определении таблицы:

CREATE TYPE box;

CREATE FUNCTION my_box_in_function(cstring) RETURNS box AS ... ;
CREATE FUNCTION my_box_out_function(box) RETURNS cstring AS ... ;

CREATE TYPE box (
    INTERNALLENGTH = 16,
    INPUT = my_box_in_function,
    OUTPUT = my_box_out_function
);

CREATE TABLE myboxes (
    id integer,
    description box
);

Если бы внутренняя структура box была массивом из четырех элементов float4, можно было бы вместо этого использовать:

CREATE TYPE box (
    INTERNALLENGTH = 16,
    INPUT = my_box_in_function,
    OUTPUT = my_box_out_function,
    ELEMENT = float4
);

В этом случае к числам, составляющим значение этого типа, можно было бы обращаться по индексу. В остальном поведение этого типа будет таким же.

В этом примере создается тип большого объекта, который используется в определении таблицы:

CREATE TYPE bigobj (
    INPUT = lo_filein, OUTPUT = lo_fileout,
    INTERNALLENGTH = VARIABLE
);
CREATE TABLE big_objs (
    id integer,
    obj bigobj
);

Дополнительные примеры, включая соответствующие функции ввода и вывода, приведены в разделе Пользовательские типы.

Совместимость

Первая форма команды CREATE TYPE, которая создает составной тип, соответствует стандарту SQL. Другие формы являются расширением QHB. Команда CREATE TYPE в стандарте SQL также имеет другие формы, которые не реализованы в QHB.

Возможность создания составного типа без атрибутов является специфическим для QHB отклонением от стандарта (аналогично CREATE TABLE).

См. также

ALTER TYPE, CREATE DOMAIN, CREATE FUNCTION, DROP TYPE

CREATE USER

CREATE USER — определить новую роль в базе данных

Синтаксис

CREATE USER имя [ [ WITH ] параметр [ ... ] ]

где параметром может быть:

      SUPERUSER | NOSUPERUSER
    | CREATEDB | NOCREATEDB
    | CREATEROLE | NOCREATEROLE
    | INHERIT | NOINHERIT
    | LOGIN | NOLOGIN
    | REPLICATION | NOREPLICATION
    | BYPASSRLS | NOBYPASSRLS
    | CONNECTION LIMIT предел_подключений
    | [ ENCRYPTED ] PASSWORD 'пароль' | PASSWORD NULL
    | VALID UNTIL 'метка_времени'
    | IN ROLE имя_роли [, ...]
    | IN GROUP имя_роли [, ...]
    | ROLE имя_роли [, ...]
    | ADMIN имя_роли [, ...]
    | USER имя_роли [, ...]
    | SYSID uid

Описание

Сейчас команда CREATE USER является псевдонимом для команды CREATE ROLE. Единственное отличие заключается в том, что в команде CREATE USER по умолчанию подразумевается параметр LOGIN, в то время как в команде CREATE ROLE по умолчанию подразумевается параметр NOLOGIN.

Совместимость

Команда CREATE USER является расширением QHB. Стандарт SQL оставляет определение пользователей на усмотрение реализации.

См. также

CREATE ROLE

CREATE USER MAPPING

CREATE USER MAPPING — определить новое сопоставление пользователя для стороннего сервера

Синтаксис

CREATE USER MAPPING [ IF NOT EXISTS ] FOR { имя_пользователя | USER | CURRENT_ROLE | CURRENT_USER | PUBLIC }
    SERVER имя_сервера
    [ OPTIONS ( параметр 'значение' [ , ... ] ) ]

Описание

Команда CREATE USER MAPPING определяет сопоставление пользователя на стороннем сервере. Сопоставление пользователя обычно содержит информацию о подключении, которую обертка сторонних данных будет использовать вместе с информацией, инкапсулированной сторонним сервером, для доступа к внешнему источнику данных.

Владелец стороннего сервера может создавать сопоставления для любых пользователей на этом сервере. Кроме того, пользователь может создавать сопоставление пользователя для своего собственного имени, если у него есть право USAGE на данном сервере.

Параметры

IF NOT EXISTS

Не выдавать ошибку, если сопоставление данного пользователя с данным сторонним сервером уже существует. В этом случае выдается только замечание. Обратите внимание, что нет никакой гарантии, что существующее сопоставление пользователя как-то соотносится с тем, которое было бы создано.

имя_пользователя

Имя существующего пользователя, для которого создается сопоставление на стороннем сервере. Ключевые слова CURRENT_ROLE, CURRENT_USER и USER соответствуют имени текущего пользователя. Если указывается PUBLIC, то создается так называемое общее сопоставление, которое используется, когда сопоставление для конкретного пользователя не применимо.

имя_сервера

Имя существующего сервера, для которого создается сопоставление пользователя.

OPTIONS ( параметр 'значение' [, ... ] )

В этом предложении задаются параметры сопоставления пользователя. Эти параметры обычно определяют фактическое имя и пароль сопоставляемого пользователя. Имена параметров должны быть уникальными. Набор допустимых имен и значений параметров определяется оберткой сторонних данных сервера.

Примеры

Создание пользовательского сопоставления для пользователя bob, сервер foo:

CREATE USER MAPPING FOR bob SERVER foo OPTIONS (user 'bob', password 'secret');

Совместимость

Команда CREATE USER MAPPING соответствует стандарту ISO/IEC 9075-9 (SQL/MED).

См. также

ALTER USER MAPPING, DROP USER MAPPING, CREATE FOREIGN DATA WRAPPER, CREATE SERVER

CREATE VIEW

CREATE VIEW — определить новое представление

Синтаксис

CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ RECURSIVE ] VIEW имя [ ( имя_столбца [, ...] ) ]
    [ WITH ( имя_параметра_представления [= значение_параметра_представления] [, ... ] ) ]
    AS запрос
    [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]

Описание

Команда CREATE VIEW определяет представление запроса. Так как это представление физически не материализуется, то указанный запрос будет выполняться при каждом обращении к представлению в нем.

CREATE OR REPLACE VIEW действует схожим образом, но если представление с тем же именем уже существует, оно заменяется. Новый запрос должен генерировать те же столбцы, что и существующий запрос представления (то есть те же имена столбцов в том же порядке и с теми же типами данных), но он может и добавлять дополнительные столбцы в конец этого списка. Вычисления, в результате которых формируются выходные столбцы, могут быть совершенно другими.

Если задано имя схемы (например CREATE VIEW myschema.myview ...), то представление создается в указанной схеме, в противном случае — в текущей. Временные представления существуют в специальной схеме, поэтому при создании таких представлений имя схемы задать нельзя. Имя представления должно отличаться от имен других представлений, таблиц, последовательностей, индексов или сторонних таблиц в этой схеме.

Параметры

TEMPORARY или TEMP

Если этот параметр указан, то представление создается как временное. Временные представления автоматически удаляются в конце текущего сеанса. Пока существует временное представление, существующие постоянные отношения с тем же именем не видимы в текущем сеансе, однако к ним можно обратиться, дополнив имя схемой.

Если какая-либо из задействованных в определении представления таблиц является временной, то такое представление создается как временное вне зависимости от того, указан ли параметр TEMPORARY.

RECURSIVE

Создает рекурсивное представление. Синтаксис

CREATE RECURSIVE VIEW [ схема . ] имя_представления (имена_столбцов) AS SELECT ...;

равнозначен

CREATE VIEW [ схема . ] имя_представления AS WITH RECURSIVE имя_представления (имена_столбцов) AS (SELECT ...) SELECT имена_столбцов FROM имя_представления;

Для рекурсивного представления обязательно должен задаваться список с именами столбцов.

имя

Имя создаваемого представления (может быть дополнено схемой).

имя_столбца

Необязательный список имен, назначаемых столбцам этого представления. При его отсутствии имена столбцов выводятся из запроса.

WITH ( имя_параметра_представления [= значение_параметра_представления] [, ... ] )

В этом предложении указываются необязательные параметры для представления. Поддерживаются следующие параметры:

check_option (enum)
Этот параметр может принимать значение local (локально) или cascaded (каскадно) и равнозначен указанию WITH [ CASCADED | LOCAL ] CHECK OPTION (см. ниже). В существующих представлениях этот параметр можно изменить с помощью команды ALTER VIEW.

security_barrier (boolean)
Этот параметр следует использовать, если представление должно обеспечивать защиту на уровне строк. Дополнительную информацию см. в разделе Политики защиты строк.

запрос

Команда SELECT или VALUES, которая предоставляет столбцы и строки этого представления.

WITH [ CASCADED | LOCAL ] CHECK OPTION

Этот параметр управляет поведением автоматически обновляемых представлений. Если этот параметр задан, то команды INSERT и UPDATE в представлении будут проверяться на предмет того, удовлетворяют ли новые строки условию, определяющему представление (то есть будет выполнена проверка новых строк на видимость через это представление). Если строки не видны, изменение будет отклонено. Если параметр CHECK OPTION не указан, то командам INSERT и UPDATE в представлении разрешается создавать строки, которые не будут видны через это представление. Поддерживаются следующие варианты проверки:

LOCAL
Новые строки проверяются только на соответствие условиям, определенным непосредственно в самом представлении. Любые условия, определенные в нижележащих базовых представлениях, не проверяются (за исключением случаев, когда в них тоже есть указание CHECK OPTION).

CASCADED
Новые строки проверяются на соответствие условиям этого представления и всех нижележащих базовых представлений. Если указано CHECK OPTION, а LOCAL и CASCADED опущены, то предполагается значение CASCADED.

Не допускается использование CHECK OPTION с рекурсивными (RECURSIVE) представлениями.

Обратите внимание, что CHECK OPTION поддерживается только для автоматически изменяемых представлений, не имеющих триггеров INSTEAD OF или правил INSTEAD. Если автоматически изменяемое представление определено поверх базового представления с триггерами INSTEAD OF, то параметр LOCAL CHECK OPTION можно использовать для проверки ограничений автоматически изменяемого представления, но условия базового представления с триггерами INSTEAD OF проверяться не будут (каскадный вариант проверки не будет спускаться ниже к представлению, изменяемому триггером, и любые варианты проверки, определенные напрямую для такого представления, будут игнорироваться). Если для представления или любого из его базовых отношений определено правило INSTEAD, которое приводит к перезаписи команды INSERT или UPDATE, то все варианты проверки в перезаписанном запросе будут игнорироваться, в том числе любые проверки из автоматически обновляемых представлений, определенных поверх отношения с правилом INSTEAD.

Примечания

Для удаления представлений воспользуйтесь командой DROP VIEW.

Позаботьтесь о том, чтобы столбцам представления были присвоены желаемые имена и типы. Например, такая команда:

CREATE VIEW vista AS SELECT 'Hello World';

плоха тем, что по умолчанию именем столбца будет ?column?, а типом данных — text, а это может быть совсем не тем, что вы хотели. Лучше записывать строковую константу в результате представления примерно так:

CREATE VIEW vista AS SELECT text 'Hello World' AS hello;

Доступ к таблицам, задействованным в представлении, определяется правами владельца этого представления. В некоторых случаях это позволяет организовать безопасный, но ограниченный доступ к нижележащим таблицам. Однако учтите, что не все представления могут быть защищены от несанкционированного доступа. Функции, вызываемые в представлении, выполняются так, будто они вызываются непосредственно из запроса, обращающегося к этому представлению. Поэтому пользователь представления должен иметь права, необходимые для вызова всех функций, задействованных в представлении.

При выполнении CREATE OR REPLACE VIEW для существующего представления изменяется только правило SELECT, определяющее представление. Другие свойства представления, включая владельца, права и правила, отличные от SELECT, остаются неизменными. Чтобы изменить определение представления, необходимо быть его владельцем (или членом роли-владельца).

Изменяемые представления

Простые представления становятся изменяемыми автоматически: система позволит выполнять команды INSERT, UPDATE и DELETE с таким представлением так же, как и с обычной таблицей. Представление будет автоматически изменяемым, если оно удовлетворяет одновременно всем следующим условиям:

Список FROM в запросе, определяющем представление, должен содержать ровно один элемент, и это должна быть таблица или другое изменяемое представление.

Определение представления не должно содержать предложения WITH, DISTINCT, GROUP BY, HAVING, LIMIT и OFFSET BY на верхнем уровне запроса.

Определение представления не должно содержать операций с множествами (UNION, INTERSECT или EXCEPT) на верхнем уровне запроса.

Список выборки представления не должен содержать агрегаты, оконные функции или функции, возвращающие множества.

Автоматически изменяемое представление может содержать как изменяемые, так и неизменяемые столбцы. Столбец является изменяемым, если это простая ссылка на изменяемый столбец нижележащего базового отношения; в противном случае этот столбец будет доступен только для чтения, и если команда INSERT или UPDATE попытается записать в него значение, то возникнет ошибка.

Если представление автоматически изменяемое, то система будет преобразовывать обращающиеся к нему команды INSERT, UPDATE и DELETE в соответствующие операторы, обращающиеся к нижележащему базовому отношению. При этом в полной мере поддерживаются команды INSERT с предложением ON CONFLICT UPDATE.

Если автоматически изменяемое представление содержит условие WHERE, то это условие ограничивает набор строк базового отношения, которые доступны для модификаций командами UPDATE и DELETE в этом представлении. Однако UPDATE может изменить строку так, что она перестанет соответствовать условию WHERE и, как следствие, больше не будет видна через представление. Аналогично команда INSERT может добавить в базовое отношение строки, которые не удовлетворят условию WHERE и поэтому не будут видны через представление (ON CONFLICT UPDATE может подобным образом воздействовать на существующую строку, не видимую через представление). Чтобы предотвратить создание подобных невидимых строк командами INSERT и UPDATE, можно воспользоваться указанием CHECK OPTION.

Если автоматически изменяемое представление помечено как имеющее свойство security_barrier (барьер безопасности), то все условия WHERE этого представления (и все условия с герметичными операторами (LEAKPROOF)) будут всегда вычисляться перед любыми условиями, добавленными пользователем представления.

Обратите

внимание, что по этой причине строки, которые в итоге не возвращаются (потому что не прошли проверку в пользовательском условии WHERE), могут все же остаться заблокированными. Чтобы определить, какие условия применяются на уровне отношения (и, как следствие, не блокируют строки), а какие нет, можно воспользоваться командой EXPLAIN.

Более сложные представления, не удовлетворяющие этим условиям, по умолчанию доступны только для чтения: система не позволит выполнить операции добавления, изменения или удаления строк в таком представлении. Получить для них эффект изменяемого представления можно, создав триггеры INSTEAD OF, которые будут преобразовывать запросы на изменение данных в соответствующие действия с другими таблицами. Подробную информацию см. на справочной странице команды CREATE TRIGGER. Также есть возможность создавать правила (см. CREATE RULE), но на практике триггеры проще для понимания и корректного применения.

Учтите, что пользователь, выполняющий операции добавления, изменения или удаления данных в представлении, должен иметь соответствующие права для этого представления. Кроме того, владелец представления должен иметь соответствующие права в нижележащих базовых отношениях, хотя пользователь, выполняющий эти операции, в таких правах не нуждается.

Примеры

Создание представления, содержащего все комедийные фильмы:

CREATE VIEW comedies AS
    SELECT *
    FROM films
    WHERE kind = 'Comedy';

Эта команда создаст представление со столбцами, которые имеются в таблице film в момент выполнения команды. Хотя при создании представления было указано *, столбцы, добавляемые в таблицу позже, не будут являться частью представления.

Создание представления с указанием LOCAL CHECK OPTION:

CREATE VIEW universal_comedies AS
    SELECT *
    FROM comedies
    WHERE classification = 'U'
    WITH LOCAL CHECK OPTION;

Эта команда создаст представление на базе представления comedies, выводящее только комедии (kind = 'Comedy') универсальной возрастной категории (classification = 'U'). Любая попытка выполнить в представлении INSERT или UPDATE со строкой, не удовлетворяющей условию classification = 'U', будет отвергнута, но ограничение по полю kind (тип фильма) проверяться не будет.

Создание представления с указанием CASCADED CHECK OPTION:

CREATE VIEW pg_comedies AS
    SELECT *
    FROM comedies
    WHERE classification = 'PG'
    WITH CASCADED CHECK OPTION;

Это представление будет проверять, удовлетворяют ли новые строки обоим условиям: по столбцу kind и по столбцу classification.

Создание представления с изменяемыми и неизменяемыми столбцами:

CREATE VIEW comedies AS
    SELECT f.*,
           country_code_to_name(f.country_code) AS country,
           (SELECT avg(r.rating)
            FROM user_ratings r
            WHERE r.film_id = f.id) AS avg_rating
    FROM films f
    WHERE f.kind = 'Comedy';

Это представление будет поддерживать операции INSERT, UPDATE и DELETE. Изменяемыми будут все столбцы из таблицы films, тогда как вычисляемые столбцы country и avg_rating будут доступны только для чтения.

Создание рекурсивного представления, состоящего из чисел от 1 до 100:

CREATE RECURSIVE VIEW public.nums_1_100 (n) AS
    VALUES (1)
UNION ALL
    SELECT n+1 FROM nums_1_100 WHERE n < 100;

Обратите внимание, что несмотря на то, что имя рекурсивного представления в этой команде CREATE дополнено схемой, внутренняя ссылка представления на себя же схемой не дополняется. Это связано с тем, что имя неявно создаваемого общего табличного выражения (ОТВ) не может дополняться схемой.

Совместимость

Команда CREATE OR REPLACE VIEW является языковым расширением QHB. Также расширением является предложение WITH ( ... ) и концепция временного представления.

См. также

ALTER VIEW, DROP VIEW, CREATE MATERIALIZED VIEW

DEALLOCATE

DEALLOCATE — освободить подготовленный оператор

Синтаксис

DEALLOCATE [ PREPARE ] { имя | ALL }

Описание

Команда DEALLOCATE используется для освобождения заранее подготовленного оператора SQL. Если не освободить подготовленный оператор явно, он освобождается по окончании сеанса.

Дополнительную информацию о подготовленных операторах см. на справочной странице команды PREPARE.

Параметры

PREPARE

Это ключевое слово игнорируется.

имя

Имя освобождаемого подготовленного оператора.

ALL

Освобождает все подготовленные операторы.

Совместимость

Стандарт SQL включает в себя команду DEALLOCATE, но она предназначена только для использования во встраиваемом SQL.

См. также

EXECUTE, PREPARE

DECLARE

DECLARE — определить курсор

Синтаксис

DECLARE имя [ BINARY ] [ ASENSITIVE | INSENSITIVE ] [ [ NO ] SCROLL ]
    CURSOR [ { WITH | WITHOUT } HOLD ] FOR запрос

Описание

Команда DECLARE позволяет пользователю создавать курсоры, которые можно использовать для извлечения из большого запроса небольшого количества строк за раз. После создания курсора строки извлекаются из него с помощью команды FETCH.

Примечание
Эта страница описывает использование курсоров на уровне команд SQL. Если вы пытаетесь использовать курсоры внутри функции PL/pgSQL, правила будут другими — см. раздел Структура PL/pgSQL.

Параметры

имя

Имя создаваемого курсора.

BINARY

Курсор будет возвращать данные в двоичном, а не в текстовом формате.

ASENSITIVE
INSENSITIVE

Чувствительность курсора определяет, будут ли изменения нижележащих данных курсора, сделанные в той же транзакции после объявления этого курсора, видны в нем. INSENSITIVE означает, что они не видны, ASENSITIVE означает что поведение зависит от реализации. Третий вариант, SENSITIVE, означающий, что такие изменения видны в курсоре, в QHB не доступен. В QHB все курсоры являются нечувствительными, поэтому данные ключевые слова не имеют никакого эффекта и принимаются только для совместимости со стандартом SQL.

Указание INSENSITIVE вместе с FOR UPDATE или FOR SHARE является ошибкой.

SCROLL
NO SCROLL

SCROLL указывает, что курсор можно использовать для получения строк непоследовательным образом (например в обратном порядке). В зависимости от сложности плана выполнения запроса, указание SCROLL может привести к снижению производительности во время выполнения запроса. NO SCROLL указывает, что курсор нельзя использовать для получения строк непоследовательным образом. По умолчанию прокрутка в некоторых случаях разрешается, но это не равнозначно эффекту указания SCROLL. Подробную информацию см. в Примечаниях ниже.

WITH HOLD
WITHOUT HOLD

WITH HOLD указывает, что курсор можно продолжать использовать после успешной фиксации транзакции, которая его создала. WITHOUT HOLD указывает, что курсор не может использоваться вне транзакции, которая его создала. Если не указано ни WITHOUT HOLD, ни WITH HOLD, значением по умолчанию является WITHOUT HOLD.

запрос

Команда SELECT или VALUES, выдающая строки, которые будет возвращать курсор.

Ключевые слова BINARY, INSENSITIVE и SCROLL можно указывать в любом порядке.

Примечания

Обычные курсоры возвращают данные в текстовом формате, в каком их выдает SELECT. Параметр BINARY указывает, что курсор должен возвращать данные в двоичном формате. Это упрощает операции преобразования данных для сервера и клиента за счет дополнительных усилий программиста по работе с зависимыми от платформы двоичными форматами данных. Например, если запрос возвращает значение единица из целочисленного столбца, с курсором по умолчанию вы получите строку, содержащую 1, тогда как с двоичным курсором вы получите 4-байтовое поле, содержащее внутреннее представление значения (с сетевым порядком байтов).

Двоичные курсоры следует использовать осторожно. Многие приложения, включая psql, не готовы иметь дело с двоичными курсорами и ожидают, что данные вернутся в текстовом формате.

Примечание
Когда клиентское приложение использует протокол «расширенный запрос» для выполнения команды FETCH, сообщение протокола Bind определяет, в каком формате, текстовом или двоичном, следует извлекать данные. Этот выбор переопределяет свойство курсора, заданное в его определении. Таким образом, понятие двоичного курсора как такового устарело при использовании расширенного протокола запросов — любой курсор может рассматриваться как текстовый или двоичный.

Если в команде объявления курсора не указано WITH HOLD, созданный этой командой курсор можно использовать только в рамках текущей транзакции. Таким образом, DECLARE без WITH HOLD бесполезна вне блока транзакции: курсор будет существовать только до завершения этого оператора. Поэтому QHB сообщает об ошибке, если такая команда используется вне блока транзакции. Для определения блока транзакции воспользуйтесь командами BEGIN и COMMIT (или ROLLBACK).

Если в объявлении курсора указано WITH HOLD (удерживаемый) и создавшая его транзакция успешно фиксируется, курсор может и далее быть доступным для последующих транзакций в том же сеансе. (Но если создание транзакции прерывается, курсор удаляется.) Курсор, созданный с WITH HOLD, закрывается, когда для него явно выполняется команда CLOSE или по завершении сеанса. В текущей реализации строки, представленные удерживаемым курсором, копируются во временный файл или область памяти, чтобы оставаться доступными для последующих транзакций.

Свойство WITH HOLD можно не указывать, если запрос включает в себя указания FOR UPDATE или FOR SHARE.

Параметр SCROLL следует задавать при определении курсора, который будет использоваться для выборки данных в обратном порядке. Это требуется стандартом SQL. Однако для совместимости с более ранними версиями в QHB разрешается выборка в обратном направлении и без указания SCROLL, если план запроса курсора достаточно прост и для его поддержки не требуются никакие дополнительные затраты. Однако разработчикам приложений рекомендуется не полагаться на использование выборки в обратном порядке из курсора, который не был создан с помощью SCROLL. С указанием NO SCROLL прокрутка назад запрещается в любом случае.

Кроме того, выборка в обратном направлении запрещается, если запрос включает в себя указания FOR UPDATE и FOR SHARE; в этом случае SCROLL можно не указывать.

ВНИМАНИЕ!
Прокручиваемые курсоры могут давать неожиданные результаты, если они вызывают любые изменчивые функции (см. раздел Категории изменчивости функций). Когда ранее извлеченная строка выбирается повторно, функции также могут выполниться повторно, возможно, выдав результаты, отличные от полученных в первый раз. В запросах, содержащих изменчивые функции, лучше всего указывать NO SCROLL. Если этот вариант не подходит, одним из обходных путей для таких случаев является объявление курсора с указанием WITH HOLD и фиксация транзакции перед чтением любых строк из него. При этом все выходные данные курсора будут материализованы во временном хранилище, так что изменчивые функции будут выполняться для каждой строки ровно один раз.

Если запрос курсора включает указания FOR UPDATE или FOR SHARE, то возвращаемые строки блокируются в момент первой выборки, аналогично происходящему при выполнении обычной команды SELECT с этими указаниями. Кроме того, возвращаемые строки будут самыми актуальными версиями.

ВНИМАНИЕ!
Обычно рекомендуется использовать FOR UPDATE, если курсор предназначен для использования в командах UPDATE ... WHERE CURRENT OF или DELETE ... WHERE CURRENT OF. Указание FOR UPDATE не дает другим сеансам изменять строки между моментом их извлечения и моментом их изменения. Без указания FOR UPDATE последующая команда с WHERE CURRENT OF не сработает, если строка была изменена с момента создания курсора.

Еще одна причина использовать FOR UPDATE состоит в том, что без него при выполнении последующих команд с WHERE CURRENT OF может произойти сбой, если запрос курсора не удовлетворяет оговоренному в стандарте SQL критерию «простой изменяемости» (в частности, курсор должен ссылаться только на одну таблицу и не должен использовать группировку или сортировку (ORDER BY).) Курсоры, не удовлетворяющие этому критерию, могут работать или не работать, в зависимости от конкретного выбранного плана; так что в худшем случае приложение может работать в тестовой, но сломается в производственной среде. С указанием FOR UPDATE курсор гарантированно будет изменяемым.

Главная причина не использовать FOR UPDATE вместе с WHERE CURRENT OF возникает, если требуется получить прокручиваемый курсор или курсор, изолированный от параллельных изменений (то есть продолжающий показывать старые данные). Если это действительно необходимо, обязательно учитывайте приведенные выше предостережения.

Стандарт SQL содержит только положения для курсоров во встраиваемом SQL. Сервер QHB не реализует оператор OPEN для курсоров; курсор считается открытым при его объявлении. Однако ECPG, встроенный препроцессор SQL для QHB, поддерживает стандартные соглашения о курсорах SQL, в том числе включающие операторы DECLARE и OPEN.

Все доступные курсоры можно просмотреть, запросив системное представление pg_cursors.

Примеры

Объявление курсора:

DECLARE liahona CURSOR FOR SELECT * FROM films;

Дополнительные примеры использования курсора см. на справочной странице команды FETCH.

Совместимость

Стандарт SQL позволяет использовать курсоры только во встраиваемом SQL и в модулях. QHB позволяет использовать курсоры интерактивно.

Согласно стандарту SQL, изменения, производимые нечувствительными курсорами с помощью команд UPDATE ... WHERE CURRENT OF и DELETE ... WHERE CURRENT OF, видимы в этом курсоре. QHB обращается с такими командами так же, как и со всеми остальными командами, изменяющими данные, то есть их результаты в нечувствительных курсорах не видны.

Двоичные курсоры являются расширением QHB.

См. также

CLOSE, FETCH, MOVE

DELETE

DELETE — удалить строки таблицы

Синтаксис

[ WITH [ RECURSIVE ] запрос_WITH [, ...] ]
DELETE FROM [ ONLY ] имя_таблицы [ * ] [ [ AS ] псевдоним ]
    [ USING элемент_FROM [, ...] ]
    [ WHERE условие | WHERE CURRENT OF имя_курсора ]
    [ RETURNING * | выражение_результата [ [ AS ] имя_результата ] [, ...] ]

Описание

Команда DELETE удаляет из указанной таблицы строки, удовлетворяющие предложению WHERE. Если предложение WHERE отсутствует, действие команды будет заключаться в удалении всех строк таблицы. Результатом станет допустимая, но пустая таблица.

Совет
Команда TRUNCATE предоставляет более быстрый механизм для удаления всех строк из таблицы.

Существует два способа удаления строк в таблице с помощью информации, содержащейся в других таблицах базы данных: с помощью подзапросов SELECT или путем указания дополнительных таблиц в предложении USING. Какой метод является более подходящим, зависит от конкретных обстоятельств.

Необязательное предложение RETURNING указывает, что команда DELETE должна вычислить и возвратить значение (или значения) для каждой фактически удаленной строки. Вычислить в ней можно любое выражение со столбцами целевой таблицы и/или столбцами других таблиц, упомянутых в USING. Список RETURNING имеет тот же синтаксис, что и список результатов SELECT.

Чтобы удалять данные из таблицы, необходимо иметь право DELETE для нее, а также право SELECT для всех таблиц в предложении USING или таблиц, значения которых считываются в условии.

Параметры

запрос_WITH

Предложение WITH позволяет указать один или несколько подзапросов, на которые можно ссылаться по имени в запросе DELETE. Подробную информацию см. в разделе Запросы WITH и на справочной странице команды SELECT.

имя_таблицы

Имя таблицы, из которой будут удалены строки (может быть дополнено схемой). Если перед именем таблицы указывается ONLY, соответствующие строки удаляются только из именованной таблицы. Если ONLY не указано, соответствующие строки также удаляются из всех таблиц, наследованных от указанной таблицы. После имени таблицы можно добавить необязательное указание *, чтобы явно обозначить, что операция затрагивает все дочерние таблицы.

псевдоним

Альтернативное имя целевой таблицы. Когда предоставляется псевдоним, он полностью скрывает фактическое имя таблицы. Например, в запросе DELETE FROM foo AS f остальная часть оператора DELETE должна ссылаться на эту таблицу по имени f, а не foo.

элемент_FROM

Табличное выражение, позволяющее добавить в условие WHERE столбцы из других таблиц. В этом выражении используется тот же синтаксис, что и в предложении FROM команды SELECT; например, в нем можно определить псевдоним для имени таблицы. Повторять в элементе_FROM имя целевой таблицы нужно, только если требуется установить замкнутое соединение (в этом случае для данного имени должен определяться псевдоним).

условие

Выражение, которое возвращает значение типа boolean. Будут удалены только те строки, для которых это выражение возвращает true.

имя_курсора

Имя курсора, который будет использоваться в условии WHERE CURRENT OF. Удалена будет строка, извлеченная из этого курсора самой последней. Курсор должен быть запросом без группировки к целевой таблице команды DELETE. Обратите внимание, что WHERE CURRENT OF нельзя указывать вместе с логическим условием. Дополнительную информацию об использовании курсоров с WHERE CURRENT OF см. на справочной странице команды DECLARE.

выражение_результата

Выражение, вычисляемое и возвращаемое командой DELETE после удаления каждой строки. Выражение может использовать любые имена столбцов таблицы имя_таблицы или таблиц, перечисленных в USING. Чтобы вернуть все столбцы, напишите *.

имя_результата

Имя, используемое для возвращаемого столбца.

Выводимая информация

После успешного завершения команда DELETE возвращает метку команды в виде

DELETE число

Где число — количество удаленных строк. Обратите внимание, что это количество может быть меньше количества строк, соответствующих условию, если удаления были подавлены триггером BEFORE DELETE. Если число равно 0, этим запросом не была удалена ни одна строка (это не считается ошибкой).

Если команда DELETE содержит предложение RETURNING, результат будет похож на результат оператора SELECT (содержащий столбцы и значения, определенные в списке RETURNING), но будет вычислен для строк, удаленных этой командой.

Примечания

QHB позволяет ссылаться на столбцы других таблиц в условии WHERE, когда эти таблицы перечисляются в предложении USING. Например, чтобы удалить все фильмы, сделанные определенным продюсером, можно написать:

DELETE FROM films USING producers
  WHERE producer_id = producers.id AND producers.name = 'foo';

По существу происходит соединение таблиц films и producers, причем все успешно включенные в соединение строки в films помечаются для удаления. Этот синтаксис не является стандартным. Более стандартный способ сделать это:

DELETE FROM films
  WHERE producer_id IN (SELECT id FROM producers WHERE name = 'foo');

В некоторых случаях нотацию в стиле соединения легче написать, и она может выполняться быстрее, чем нотация в стиле подзапроса.

Примеры

Удаление всех фильмов, кроме мюзиклов:

DELETE FROM films WHERE kind <> 'Musical';

Очистка таблицы films:

DELETE FROM films;

Удаление завершенных задач с получением всех данных удаленных строк:

DELETE FROM tasks WHERE status = 'DONE' RETURNING *;

Удаление из tasks строки, на которой в текущий момент располагается курсор c_tasks:

DELETE FROM tasks WHERE CURRENT OF c_tasks;

Совместимость

Команда DELETE соответствует стандарту SQL, за исключением того, что предложения USING и RETURNING являются расширениями QHB, как и возможность использовать WITH с этой командой.

См. также

TRUNCATE

DISCARD

DISCARD — сбросить состояние сеанса

Синтаксис

DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }

Описание

Команда DISCARD освобождает внутренние ресурсы, связанные с сеансом использования базы данных. Эта команда полезна для частичного или полного сброса состояния сеанса. Существует несколько подкоманд для освобождения различных типов ресурсов; вариант DISCARD ALL объединяет все остальные, а также сбрасывает дополнительное состояние.

Параметры

PLANS

Освобождает все кэшированные планы запросов, заставляя сервер провести повторное планирование при следующем использовании связанного подготовленного оператора.

SEQUENCES

Сбрасывает все кэшированные состояния, связанные с последовательностями, включая информацию currval()/lastval() и любые предварительно выделенные значения последовательности, которые еще не были возвращены nextval(). (Описание предварительно выделенных значений последовательности см. на справочной странице команды CREATE SEQUENCE).

TEMPORARY или TEMP

Удаляет все временные таблицы, созданные в текущем сеансе.

ALL

Освобождает все временные ресурсы, связанные с текущим сеансом, и сбрасывает сеанс до исходного состояния. В настоящее время это действует так же, как выполнение следующей последовательности операторов:

CLOSE ALL;
SET SESSION AUTHORIZATION DEFAULT;
RESET ALL;
DEALLOCATE ALL;
UNLISTEN *;
SELECT pg_advisory_unlock_all();
DISCARD PLANS;
DISCARD TEMP;
DISCARD SEQUENCES;

Примечания

Команду DISCARD ALL нельзя выполнить внутри блока транзакции.

Совместимость

Команда DISCARD является расширением QHB.

DO

DO — выполнить анонимный блок кода

Синтаксис

DO [ LANGUAGE имя_языка ] код

Описание

Команда DO выполняет анонимный блок кода или, другими словами, транзитную анонимную функцию на процедурном языке.

Блок кода обрабатывается так, как если бы это было тело функции без параметров, возвращающее void. Этот код анализируется и выполняется один раз.

Необязательное предложение LANGUAGE можно написать как до, так и после блока кода.

Параметры

код

Выполняемый код на процедурном языке. Он должен задаваться в виде строкового литерала, как и код в CREATE FUNCTION. Рекомендуется заключать его в знаки доллара.

имя_языка

Имя процедурного языка, на котором написан код. Если этот параметр опущен, значение по умолчанию равно plpgsql.

Примечания

Используемый процедурный язык должен быть уже установлен в текущую базу данных с помощью команды CREATE EXTENSION. plpgsql устанавливается по умолчанию, но другие языки — нет.

Необходимо иметь право USAGE для данного процедурного языка либо быть суперпользователем, если язык недоверенный. Такие же права требуются и при создании функции на этом языке.

Если команда DO выполняется в блоке транзакции, то код процедуры не может выполнять операторы управления транзакциями. Такие операторы допускаются только в том случае, если DO выполняется в собственной транзакции.

Примеры

Предоставление роли webuser всех прав на все представления в схеме public:

DO $$DECLARE r record;
BEGIN
    FOR r IN SELECT table_schema, table_name FROM information_schema.tables
             WHERE table_type = 'VIEW' AND table_schema = 'public'
    LOOP
        EXECUTE 'GRANT ALL ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' TO webuser';
    END LOOP;
END$$;

Совместимость

В стандарте SQL нет команды DO.

См. также

CREATE LANGUAGE

DROP ACCESS METHOD

DROP ACCESS METHOD — удалить метод доступа

Синтаксис

DROP ACCESS METHOD [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP ACCESS METHOD удаляет существующий метод доступа. Удалять методы доступа могут только суперпользователи.

Параметры

IF EXISTS

Не считать ошибкой, если этот метод доступа не существует. В этом случае выдается только замечание.

имя

Имя существующего метода доступа.

CASCADE

Автоматически удалять объекты, зависящие от этого метода доступа (например, классы операторов, семейства операторов и индексы), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении метода доступа, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление метода доступа heptree:

DROP ACCESS METHOD heptree;

Совместимость

Команда DROP ACCESS METHOD является расширением QHB.

См. также

CREATE ACCESS METHOD

DROP AGGREGATE

DROP AGGREGATE — удалить агрегатную функцию

Синтаксис

DROP AGGREGATE [ IF EXISTS ] имя ( сигнатура_агрегата ) [, ...] [ CASCADE | RESTRICT ]

где сигнатура_агрегата:

* |
[ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] |
[ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] ] ORDER BY [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ]

Описание

Команда DROP AGGREGATE удаляет существующую агрегатную функцию. Для выполнения этой команды нужно быть владельцем соответствующей агрегатной функции.

Параметры

IF EXISTS

Не считать ошибкой, если эта агрегатная функция не существует. В этом случае выдается только замечание.

имя

Имя существующей агрегатной функции (может быть дополнено схемой).

режим_аргумента

Режим аргумента: IN (входной) или VARIADIC (переменный). Если этот параметр опущен, значение по умолчанию равно IN.

имя_аргумента

Имя аргумента. Обратите внимание, что DROP AGGREGATE фактически не обращает внимания на имена аргументов, поскольку для идентификации агрегатной функции необходимы только типы данных ее аргументов.

тип_аргумента

Тип входных данных, с которыми работает агрегатная функция. Чтобы сослаться на агрегатную функцию без аргументов, напишите вместо списка аргументов *. Чтобы сослаться на сортирующую агрегатную функцию, напишите ORDER BY между указаниями непосредственных и агрегированных аргументов.

CASCADE

Автоматически удалять объекты, зависящие от этой агрегатной функции (например, использующие ее представления), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении агрегатной функции, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примечания

Альтернативные синтаксисы для ссылки на сортирующие агрегаты описаны на справочной странице команды ALTER AGGREGATE.

Примеры

Удаление агрегатной функции myavg для типа integer:

DROP AGGREGATE myavg(integer);

Удаление гипотезирующей агрегатной функции myrank, которая принимает произвольный список сортируемых столбцов и соответствующий список непосредственных аргументов:

DROP AGGREGATE myrank(VARIADIC "any" ORDER BY VARIADIC "any");

Удаление нескольких агрегатных функций в одной команде:

DROP AGGREGATE myavg (integer), myavg (bigint);

Совместимость

В стандарте SQL нет команды DROP AGGREGATE.

См. также

ALTER AGGREGATE, CREATE AGGREGATE

DROP CAST

DROP CAST — удалить приведение типа

Синтаксис

DROP CAST [ IF EXISTS ] (исходный_тип AS целевой_тип) [ CASCADE | RESTRICT ]

Описание

Команда DROP CAST удаляет ранее определенное приведение типа.

Чтобы удалить приведение типа, нужно быть владельцем исходного или целевого типа данных. Эти же требования действуют и при создании приведения типа.

Параметры

IF EXISTS

Не считать ошибкой, если это приведение не существует. В этом случае выдается только замечание.

исходный_тип

Имя исходного типа данных приведения.

целевой_тип

Имя целевого типа данных приведения.

CASCADE
RESTRICT

Эти ключевые слова не имеют эффекта, так как у приведений нет зависимых объектов.

Примеры

Удаление приведения типа text к типу int:

DROP CAST (text AS int);

Совместимость

Команда DROP CAST соответствует стандарту SQL.

См. также

CREATE CAST

DROP COLLATION

DROP COLLATION — удалить правило сортировки

Синтаксис

DROP COLLATION [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP COLLATION удаляет ранее определенное правило сортировки. Чтобы удалить правило сортировки, нужно быть его владельцем.

Параметры

IF EXISTS

Не считать ошибкой, если это правило сортировки не существует. В этом случае выдается только замечание.

имя

Имя правила сортировки (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого правила сортировки, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении правила сортировки, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление правила сортировки с именем german:

DROP COLLATION german;

Совместимость

Команда DROP COLLATION соответствует стандарту SQL, за исключением параметра IF EXISTS, который является расширением QHB.

См. также

ALTER COLLATION, CREATE COLLATION

DROP CONVERSION

DROP CONVERSION — удалить преобразование

Синтаксис

DROP CONVERSION [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP CONVERSION удаляет ранее определенное преобразование. Чтобы иметь возможность удалить преобразование, нужно быть его владельцем.

Параметры

IF EXISTS

Не считать ошибкой, если это преобразование не существует. В этом случае выдается только замечание.

имя

Имя преобразования (может быть дополнено схемой).

CASCADE
RESTRICT

Эти ключевые слова не имеют эффекта, так как у преобразований нет зависимых объектов.

Примеры

Удаление преобразования с именем myname:

DROP CONVERSION myname;

Совместимость

В стандарте SQL нет команды DROP CONVERSION, но есть команда DROP TRANSLATION, сопутствующая команде CREATE TRANSLATION, которая, в свою очередь, похожа на команду CREATE CONVERSION в QHB.

См. также

ALTER CONVERSION, CREATE CONVERSION

DROP DATABASE

DROP DATABASE — удалить базу данных

Синтаксис

DROP DATABASE [ IF EXISTS ] имя [ [ WITH ] ( параметр [, ...] ) ]

где параметром может быть:

  FORCE

Описание

Команда DROP DATABASE удаляет базу данных. Она удаляет записи из каталогов, которые относятся к базе данных, и удаляет каталог, содержащий данные. Выполнить эту команду может только владелец базы данных. Также ее нельзя выполнить, пока вы подключены к целевой базе данных. (Чтобы выполнить эту команду, подключитесь к qhb или любой другой базе данных.) Кроме того, если к целевой базе данных подключен кто-то другой, эта команда не завершится ошибкой, только если применить описанный ниже параметр FORCE.

Действие команды DROP DATABASE нельзя отменить. Используйте ее с осторожностью!

Параметры

IF EXISTS

Не считать ошибкой, если эта база данных не существует. В этом случае выдается только замечание.

имя

Имя удаляемой базы данных.

FORCE

Попытаться принудительно завершить все существующие подключения к целевой базе данных. Подключения не завершаются, если в целевой базе данных находятся подготовленные транзакции, активные слоты логической репликации или подписки.

Данная операция завершится ошибкой, если у текущего пользователя нет прав завершать другие подключения. Это те же права, что требуются для выполнения функции pg_terminate_backend, описанной в подразделе Функции для передачи сигналов серверу). Также ошибка произойдет, если завершить подключения не удастся.

Примечания

Команду DROP DATABASE нельзя выполнить внутри блока транзакции.

Эту команду нельзя выполнить, если установлено подключение к целевой базе данных. В связи с этим, возможно, будет удобнее воспользоваться программой dropdb, которая является ее оболочкой.

Совместимость

В стандарте SQL нет команды DROP DATABASE.

См. также

CREATE DATABASE

DROP DOMAIN

DROP DOMAIN — удалить домен

Синтаксис

DROP DOMAIN [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP DOMAIN удаляет домен. Удалить домен может только его владелец.

Параметры

IF EXISTS

Не считать ошибкой, если этот домен не существует. В этом случае выдается только замечание.

имя

Имя существующего домена (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого домена (например, столбцы таблицы), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении домена, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление домена box:

DROP DOMAIN box;

Совместимость

Команда DROP DOMAIN соответствует стандарту SQL, за исключением параметра IF EXISTS, который является расширением QHB.

См. также

CREATE DOMAIN, ALTER DOMAIN

DROP EVENT TRIGGER

DROP EVENT TRIGGER — удалить триггер события

Синтаксис

DROP EVENT TRIGGER [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP EVENT TRIGGER удаляет существующий триггер события. Для выполнения этой команды нужно быть владельцем соответствующего триггера события.

Параметры

IF EXISTS

Не считать ошибкой, если этот триггер события не существует. В этом случае выдается только замечание.

имя

Имя удаляемого триггера события.

CASCADE

Автоматически удалять объекты, зависящие от этого триггера, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении триггера, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление триггера snitch:

DROP EVENT TRIGGER snitch;

Совместимость

В стандарте SQL нет команды DROP EVENT TRIGGER.

См. также

CREATE EVENT TRIGGER, ALTER EVENT TRIGGER

DROP EXTENSION

DROP EXTENSION — удалить расширение

Синтаксис

DROP EXTENSION [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP EXTENSION удаляет расширения из базы данных. Удаление расширения приводит к удалению составляющих его объектов и других явно зависимых подпрограмм (см. ALTER ROUTINE, действие DEPENDS ON EXTENSION имя_расширения).

Для выполнения команды DROP EXTENSION нужно быть владельцем соответствующего расширения.

Параметры

IF EXISTS

Не считать ошибкой, если это расширение не существует. В этом случае выдается только замечание.

имя

Имя установленного расширения.

CASCADE

Автоматически удалять объекты, зависящие от этого расширения, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Этот параметр не позволяет удалить заданные расширения, если от них зависят какие-либо объекты, кроме самих этих расширений, их объектов и явно зависящих от них подпрограмм. Это поведение по умолчанию.

Примеры

Удаление расширения hstore из текущей базы данных:

DROP EXTENSION hstore;

Эта команда не будет выполнена, если какой-либо объект из hstore используется в базе данных, например, если в какой-либо таблице есть столбец типа hstore. Добавив параметр CASCADE, можно принудительно удалить эти зависимые объекты.

Совместимость

Команда DROP EXTENSION является расширением QHB.

См. также

CREATE EXTENSION, ALTER EXTENSION

DROP FOREIGN DATA WRAPPER

DROP FOREIGN DATA WRAPPER — удалить обертку сторонних данных

Синтаксис

DROP FOREIGN DATA WRAPPER [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP FOREIGN DATA WRAPPER удаляет существующую обертку сторонних данных. Для выполнения этой команды текущий пользователь должен быть владельцем соответствующей обертки сторонних данных.

Параметры

IF EXISTS

Не считать ошибкой, если эта обертка сторонних данных не существует. В этом случае выдается только замечание.

имя

Имя существующей обертки сторонних данных.

CASCADE

Автоматически удалять объекты, зависящие от этой обертки сторонних данных (например, сторонние таблицы и серверы), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой обертки сторонних данных, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление обертки сторонних данных dbi:

DROP FOREIGN DATA WRAPPER dbi;

Совместимость

Команда DROP FOREIGN DATA WRAPPER соответствует стандарту ISO/IEC 9075-9 (SQL/MED). Предложение IF EXISTS является расширением QHB.

См. также

CREATE FOREIGN DATA WRAPPER, ALTER FOREIGN DATA WRAPPER

DROP FOREIGN TABLE

DROP FOREIGN TABLE — удалить стороннюю таблицу

Синтаксис

DROP FOREIGN TABLE [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP FOREIGN TABLE удаляет стороннюю таблицу. Удалить стороннюю таблицу может только ее владелец.

Параметры

IF EXISTS

Не считать ошибкой, если эта сторонняя таблица не существует. В этом случае выдается только замечание.

имя

Имя удаляемой сторонней таблицы (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этой сторонней таблицы (например, представления), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой сторонней таблицы, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление двух сторонних таблиц, films и distributors:

DROP FOREIGN TABLE films, distributors;

Совместимость

Команда DROP FOREIGN TABLE соответствует стандарту ISO/IEC 9075-9 (SQL/MED), за исключением параметра IF EXISTS, который является расширением QHB, и того, что стандарт позволяет удалять в одной команде только одну стороннюю таблицу.

См. также

ALTER FOREIGN TABLE, CREATE FOREIGN TABLE

DROP FUNCTION

DROP FUNCTION — удалить функцию

Синтаксис

DROP FUNCTION [ IF EXISTS ] имя ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] [, ...]
    [ CASCADE | RESTRICT ]

Описание

Команда DROP FUNCTION удаляет определение существующей функции. Для выполнения этой команды пользователь должен быть владельцем соответствующей функции. Помимо имени функции следует также указать типы ее аргументов, поскольку могут существовать несколько различных функций с одинаковым именем и разными списками аргументов.

Параметры

IF EXISTS

Не считать ошибкой, если эта функция не существует. В этом случае выдается только замечание.

имя

Имя существующей функции (может быть дополнено схемой). Если список аргументов не указан, имя должно быть уникальным в своей схеме.

режим_аргумента

Режим аргумента: IN (входной), OUT (выходной), INOUT (входной и выходной) или VARIADIC (переменный). Если этот параметр опущен, значением по умолчанию является IN. Обратите внимание, что команда DROP FUNCTION на самом деле не обращает внимания на аргументы OUT, поскольку для идентификации функции нужны только аргументы IN. Поэтому достаточно перечислить аргументы IN, INOUT и VARIADIC.

имя_аргумента

Имя аргумента. Обратите внимание, что команда DROP FUNCTION фактически не обращает внимания на имена аргументов, поскольку для идентификации функции нужны только типы данных ее аргументов.

тип_аргумента

Тип(ы) данных аргументов функции (могут быть дополнены схемой), если таковые имеются.

CASCADE

Автоматически удалять объекты, зависящие от этой функции (например, операторы или триггеры), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой функции, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Эта команда удаляет функцию, вычисляющую квадратный корень:

DROP FUNCTION sqrt(integer);

Удаление нескольких функций одной командой:

DROP FUNCTION sqrt(integer), sqrt(bigint);

Если имя функции уникально в ее схеме, на нее можно ссылаться без списка аргументов:

DROP FUNCTION update_employee_salaries;

Обратите внимание, что это отличается от

DROP FUNCTION update_employee_salaries();

Данная форма ссылается на функцию без аргументов, тогда как первый вариант подходит для функции с любым числом аргументов (в том числе и совсем без них), если ее имя уникально.

Совместимость

Команда DROP FUNCTION соответствует стандарту SQL со следующими расширениями QHB:

Стандарт позволяет удалить в одной команде только одну функцию.

Параметр IF EXISTS

Возможность задавать режимы и имена аргументов

См. также

CREATE FUNCTION, ALTER FUNCTION, DROP PROCEDURE, DROP ROUTINE

DROP GROUP

DROP GROUP — удалить роль в базе данных

Синтаксис

DROP GROUP [ IF EXISTS ] имя [, ...]

Описание

Сейчас команда DROP GROUP является псевдонимом команды DROP ROLE.

Совместимость

В стандарте SQL нет команды DROP GROUP.

См. также

DROP ROLE

DROP INDEX

DROP INDEX — удалить индекс

Синтаксис

DROP INDEX [ CONCURRENTLY ] [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP INDEX удаляет существующий индекс из СУБД. Для выполнения этой команды нужно быть владельцем соответствующего индекса.

Параметры

CONCURRENTLY

Удалить индекс без блокировки параллельных операций выборки, добавления, изменения и удаления данных в таблице индекса. Обычная команда DROP INDEX запрашивает на эту таблицу блокировку ACCESS EXCLUSIVE, блокируя другие обращения к ней до завершения удаления индекса. С этим параметром команда вместо этого будет ожидать завершения конфликтующих транзакций.

Есть несколько особенностей, которые следует учитывать при использовании этого параметра. Можно указать только одно имя индекса, и не поддерживается указание CASCADE. (Следовательно, индекс, поддерживающий ограничение UNIQUE или PRIMARY KEY, не может быть удален таким способом.) Кроме того, обычные команды DROP INDEX могут выполняться в блоке транзакции, а DROP INDEX CONCURRENTLY не может. И наконец, с помощью этого параметра нельзя удалить индексы по партиционированным таблицам.

На временных таблицах команда DROP INDEX всегда выполняется неблокирующим способом, так как другие сеансы не могут обращаться к этим таблицам, а кроме того неблокирующее удаление индекса менее затратно.

IF EXISTS

Не считать ошибкой, если этот индекс не существует. В этом случае выдается только замечание.

имя

Имя удаляемого индекса (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого индекса, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого индекса, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Эта команда удалит индекс title_idx:

DROP INDEX title_idx;

Совместимость

Команда DROP INDEX является языковым расширением QHB. В стандарте SQL нет положений для индексов.

См. также

CREATE INDEX

DROP LANGUAGE

DROP LANGUAGE — удалить процедурный язык

Синтаксис

DROP [ PROCEDURAL ] LANGUAGE [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP LANGUAGE удаляет определение ранее зарегистрированного процедурного языка. Чтобы использовать команду DROP LANGUAGE, нужно быть суперпользователем или владельцем соответствующего языка.

Примечание
Большинство процедурных языков были преобразованы в «расширения», поэтому теперь их следует удалять командой DROP EXTENSION, а не DROP LANGUAGE.

Параметры

IF EXISTS

Не считать ошибкой, если этот язык не существует. В этом случае выдается только замечание.

имя

Имя существующего процедурного языка.

CASCADE

Автоматически удалять объекты, зависящие от этого языка (например, функции на этом языке), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого языка, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Эта команда удаляет процедурный язык plsample:

DROP LANGUAGE plsample;

Совместимость

В стандарте SQL нет команды DROP LANGUAGE.

См. также

ALTER LANGUAGE, CREATE LANGUAGE

DROP MATERIALIZED VIEW

DROP MATERIALIZED VIEW — удалить материализованное представление

Синтаксис

DROP MATERIALIZED VIEW [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP MATERIALIZED VIEW удаляет существующее материализованное представление. Для выполнения этой команды нужно быть владельцем соответствующего материализованного представления.

Параметры

IF EXISTS

Не считать ошибкой, если это материализованное представление не существует. В этом случае выдается только замечание.

имя

Имя удаляемого материализованного представления (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого материализованного представления (например, другие материализованные или обычные представления), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого материализованного представления, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Эта команда удаляет материализованное представление с именем order_summary:

DROP MATERIALIZED VIEW order_summary;

Совместимость

Команда DROP MATERIALIZED VIEW является расширением QHB.

См. также

CREATE MATERIALIZED VIEW, ALTER MATERIALIZED VIEW, REFRESH MATERIALIZED VIEW

DROP OPERATOR

DROP OPERATOR — удалить оператор

Синтаксис

DROP OPERATOR [ IF EXISTS ] имя ( { тип_слева | NONE } , { тип_справа } ) [, ... ] [ CASCADE | RESTRICT ]

Описание

Команда DROP OPERATOR удаляет существующий оператор из СУБД. Для выполнения этой команды нужно быть владельцем соответствующего оператора.

Параметры

IF EXISTS

Не считать ошибкой, если этот оператор не существует. В этом случае выдается только замечание.

имя

Имя существующего оператора (может быть дополнено схемой).

тип_слева

Тип данных левого операнда этого оператора; если у оператора нет левого операнда, напишите NONE.

тип_справа

Тип данных правого операнда этого оператора.

CASCADE

Автоматически удалять объекты, зависящие от этого оператора (например, использующие его представления), и в, свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого оператора, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление оператора возведения в степень a^b для типа integer:

DROP OPERATOR ^ (integer, integer);

Удаление префиксного оператора двоичного дополнения ~b для типа bit:

DROP OPERATOR ~ (none, bit);

Удаление нескольких операторов одной командой:

DROP OPERATOR ~ (none, bit), ! (bigint, none);

Совместимость

В стандарте SQL нет команды DROP OPERATOR.

См. также

CREATE OPERATOR, ALTER OPERATOR

DROP OPERATOR CLASS

DROP OPERATOR CLASS — удалить класс операторов

Синтаксис

DROP OPERATOR CLASS [ IF EXISTS ] имя USING индексный_метод [ CASCADE | RESTRICT ]

Описание

Команда DROP OPERATOR CLASS удаляет существующий класс операторов. Для выполнения этой команды нужно быть владельцем соответствующего класса операторов.

DROP OPERATOR CLASS не удаляет операторы или функции, связанные с этим классом. Если существуют какие-либо индексы, зависящие от этого класса операторов, для успешного завершения удаления нужно будет указать CASCADE.

Параметры

IF EXISTS

Не считать ошибкой, если этот класс операторов не существует. В этом случае выдается только замечание.

имя

Имя существующего класса операторов (может быть дополнено схемой).

индексный_метод

Имя индексного метода доступа, для которого предназначен этот класс операторов.

CASCADE

Автоматически удалять объекты, зависящие от этого класса операторов (например, индексы), и, в свою очередь, все объекты, зависящие от этих объектов (см.раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого класса операторов, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примечания

Команда DROP OPERATOR CLASS не удаляет семейство операторов, содержащее заданный класс, даже если в семействе больше ничего не останется (в частности, в том случае, когда семейство было неявно создано командой CREATE OPERATOR CLASS). Пустое семейство операторов безвредно, но из соображений аккуратности его можно удалить командой DROP OPERATOR FAMILY. Или, возможно, лучше изначально использовать DROP OPERATOR FAMILY.

Примеры

Удаление класса операторов B-дерева widget_ops:

DROP OPERATOR CLASS widget_ops USING btree;

Эта команда не будет выполнена, если существуют какие-либо индексы, использующие этот класс операторов. Для удаления таких индексов вместе с классом операторов нужно добавить в команду указание CASCADE.

Совместимость

В стандарте SQL нет команды DROP OPERATOR CLASS.

См. также

ALTER OPERATOR CLASS, CREATE OPERATOR CLASS, DROP OPERATOR FAMILY

DROP OPERATOR FAMILY

DROP OPERATOR FAMILY — удалить семейство операторов

Синтаксис

DROP OPERATOR FAMILY [ IF EXISTS ] имя USING индексный_метод [ CASCADE | RESTRICT ]

Описание

Команда DROP OPERATOR FAMILY удаляет существующее семейство операторов. Для выполнения этой команды нужно быть владельцем соответствующего семейства операторов.

Команда DROP OPERATOR FAMILY удаляет также и все классы операторов, содержащиеся в заданном семействе, но не удаляет связанные с ним операторы и функции. Если внутри семейства существуют какие-либо индексы, зависящие от классов операторов, для успешного завершения удаления необходимо будет указать CASCADE.

Параметры

IF EXISTS

Не считать ошибкой, если это семейство операторов не существует. В этом случае выдается только замечание.

имя

Имя существующего семейства операторов (может быть дополнено схемой).

индексный_метод

Имя индексного метода доступа, для которого предназначено это семейство операторов.

CASCADE

Автоматически удалять объекты, зависящие от этого семейства операторов, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого семейство операторов, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление семейства операторов B-дерева float_ops:

DROP OPERATOR FAMILY float_ops USING btree;

Эта команда не будет выполнена, если существуют какие-либо индексы, использующие классы операторов внутри данного семейства. Для удаления таких индексов вместе с семейством операторов нужно добавить указание CASCADE.

Совместимость

В стандарте SQL нет команды DROP OPERATOR FAMILY.

См. также

ALTER OPERATOR FAMILY, CREATE OPERATOR FAMILY, ALTER OPERATOR CLASS CREATE OPERATOR CLASS, DROP OPERATOR CLASS

DROP OWNED

DROP OWNED — удалить объекты базы данных, принадлежащие роли базы данных

Синтаксис

DROP OWNED BY { имя | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP OWNED удаляет все объекты в текущей базе данных, принадлежащие указанным ролям. Все права, предоставленные данным ролям для объектов в текущей базе данных или для разделяемых объектов (баз данных, табличных пространств), также будут отозваны.

Параметры

имя

Имя роли, объекты которой будут удалены и права которой будут отозваны.

CASCADE

Автоматически удалять объекты, зависящие от затронутых объектов, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении объектов, принадлежащих роли, если какие-либо другие объекты базы данных зависят от одного из затронутых объектов. Это поведение по умолчанию.

Примечания

Команда DROP OWNED часто используется для подготовки к удалению одной или нескольких ролей. Поскольку DROP OWNED влияет только на объекты в текущей базе данных, обычно необходимо выполнить эту команду в каждой базе данных, содержащей объекты, принадлежащие удаляемой роли.

С параметром CASCADE эта команда рекурсивно удаляет объекты, принадлежащие и другим пользователям.

Команда REASSIGN OWNED является альтернативой DROP OWNED, которая переназначает владельца всех объектов базы данных, принадлежащих одной или нескольким ролям. Однако REASSIGN OWNED не затрагивает никакие права для других объектов.

Базы данных и табличные пространства, принадлежащие роли(ям), удалены не будут.

Дополнительную информацию см. в разделе Удаление ролей.

Совместимость

Команда DROP OWNED является расширением QHB.

См. также

REASSIGN OWNED, DROP ROLE

DROP POLICY

DROP POLICY — удалить политику защиты на уровне строк из таблицы

Синтаксис

DROP POLICY [ IF EXISTS ] имя ON имя_таблицы [ CASCADE | RESTRICT ]

Описание

Команда DROP POLICY удаляет заданную политику из таблицы. Обратите внимание, что если из таблицы удаляется последняя политика и для таблицы по-прежнему включена защита на уровне строк (с помощью ALTER TABLE), то использоваться будет политика запрета по умолчанию. Для выключения защиты на уровне строк для таблицы независимо от того, существуют ли для этой таблицы политики или нет, можно воспользоваться командой ALTER TABLE ... DISABLE ROW LEVEL SECURITY.

Параметры

IF EXISTS

Не считать ошибкой, если эта политика не существует. В этом случае выдается только замечание.

имя

Имя удаляемой политики.

имя_таблицы

Имя таблицы, для которой применяется эта политика (может быть дополнено схемой).

CASCADE
RESTRICT

Эти ключевые слова не имеют эффекта, так как у политик нет зависимых объектов.

Примеры

Удаление политики p1 из таблицы my_table:

DROP POLICY p1 ON my_table;

Совместимость

Команда DROP POLICY является расширением QHB.

См. также

CREATE POLICY, ALTER POLICY

DROP PROCEDURE

DROP PROCEDURE — удалить процедуру

Синтаксис

DROP PROCEDURE [ IF EXISTS ] имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] [, ...]
    [ CASCADE | RESTRICT ]

Описание

Команда DROP PROCEDURE удаляет определение одной или нескольких существующих процедур. Для выполнения этой команды пользователь должен быть владельцем соответствующих(ей) процедур(ы). Обычно следует указывать типы аргументов процедуры, так как может существовать несколько различных процедур с одинаковыми именами и разными списками аргументов.

Параметры

IF EXISTS

Не считать ошибкой, если эта процедура не существует. В этом случае выдается только замечание.

имя

Имя существующей процедуры (может быть дополнено схемой).

режим_аргумента

Режим аргумента: IN (входной), OUT (выходной), INOUT (входной и выходной) или VARIADIC (переменный). Если этот параметр опущен, значение по умолчанию равно IN (но см. ниже).

имя_аргумента

Имя аргумента. Обратите внимание, что команда DROP PROCEDURE фактически не обращает внимания на имена аргументов, поскольку для идентификации процедуры используются только типы данных ее аргументов.

тип_аргумента

Тип(ы) данных аргументов этой процедуры (могут быть дополнены схемой), если таковые имеются. Подробную информацию см. ниже.

CASCADE

Автоматически удалять объекты, зависящие от этой процедуры, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой процедуры, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примечания

Если существует только одна процедура с данным именем, то список аргументов можно опустить. В этом случает нужно опустить и скобки.

В QHB достаточно перечислить входные аргументы (включая INOUT), поскольку одноименным подпрограммам запрещено иметь одинаковые списки входных аргументов. Более того, эта команда DROP на самом деле не будет проверять, корректно ли вы написали типы аргументов OUT, поэтому все аргументы, явно помеченные как OUT, ни на что не влияют. Однако их все равно рекомендуется писать для согласованности с соответствующей командой CREATE.

Для совместимости со стандартом SQL также разрешается писать все типы данных аргументов (включая типы аргументов OUT) без маркеров режим_аргумента. При таком подходе типы аргументов OUT этой процедуры будут сверяться с указанными в ее команде CREATE. Эта возможность создает неоднозначность, поскольку когда список аргументов не содержит маркеры режим_аргумента, неясно, какое правило подразумевается. Команда DROP попытается выполнить поиск обоими способами и выдаст ошибку, если будут найдены две разные процедуры. Во избежание риска возникновения подобной неоднозначности рекомендуется не позволять маркерам IN подразумеваться по умолчанию, а писать их явно, тем самым вынуждая команду использовать традиционную интерпретацию QHB.

Рассмотренные правила поиска также действуют и для других команд, работающих с существующими процедурами, например ALTER PROCEDURE и COMMENT ON PROCEDURE.

Примеры

Если существует только одна процедура do_db_maintenance, этой команды достаточно, чтобы удалить ее:

DROP PROCEDURE do_db_maintenance;

При таком определении процедуры:

CREATE PROCEDURE do_db_maintenance(IN target_schema text, OUT results text) ...

ее сможет удалить любая из этих команд:

DROP PROCEDURE do_db_maintenance(IN target_schema text, OUT results text);
DROP PROCEDURE do_db_maintenance(IN text, OUT text);
DROP PROCEDURE do_db_maintenance(IN text);
DROP PROCEDURE do_db_maintenance(text);
DROP PROCEDURE do_db_maintenance(text, text);  -- потенциально неоднозначная

Однако последний пример может быть неоднозначным, если существует, скажем, процедура

CREATE PROCEDURE do_db_maintenance(IN target_schema text, IN options text) ...

Совместимость

Эта команда соответствует стандарту SQL со следующими расширениями QHB:

Стандарт позволяет удалять одной командой только одну процедуру.

Параметр IF EXISTS является расширением.

Возможность задавать режимы и имена аргументов является расширением, а правила поиска меняются, когда задаются режимы.

См. также

CREATE PROCEDURE, ALTER PROCEDURE, DROP FUNCTION, DROP ROUTINE

DROP PUBLICATION

DROP PUBLICATION — удалить публикацию

Синтаксис

DROP PUBLICATION [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP PUBLICATION удаляет существующую публикацию из базы данных.

Публикацию может удалить только ее владелец или суперпользователь.

Параметры

IF EXISTS

Не считать ошибкой, если эта публикация не существует. В этом случае выдается только замечание.

имя

Имя существующей публикации.

CASCADE
RESTRICT

Эти ключевые слова не имеют эффекта, так как у публикаций нет зависимых объектов.

Примеры

Удаление публикации:

DROP PUBLICATION mypublication;

Совместимость

Команда DROP PUBLICATION является расширением QHB.

См. также

CREATE PUBLICATION, ALTER PUBLICATION

DROP ROLE

DROP ROLE — удалить роль в базе данных

Синтаксис

DROP ROLE [ IF EXISTS ] имя [, ...]

Описание

Команда DROP ROLE удаляет заданную(ые) роль(и). Чтобы удалить роль суперпользователя, нужно самому быть суперпользователем; чтобы удалять обычные роли, нужно иметь право CREATEROLE.

Роль нельзя удалить, если на нее все еще есть ссылки в любой базе данных кластера; в этом случае возникнет ошибка. Перед удалением роли необходимо удалить все принадлежащие ей объекты (или сменить их владельца) и отозвать все права, предоставленные этой роли для других объектов. Для этой цели могут быть полезны команды REASSIGN OWNED и DROP OWNED; дополнительную информацию см. в разделе Удаление ролей.

Тем не менее нет необходимости удалять членство в ролях, включающее эту роль; DROP ROLE автоматически отзывает любое членство целевой роли в других ролях и каких-либо других ролей в целевой роли. Другие роли при этом не удаляются и не затрагиваются иным образом.

Параметры

IF EXISTS

Не считать ошибкой, если эта роль не существует. В этом случае выдается только замечание.

имя

Имя удаляемой роли.

Примечания

QHB включает в себя утилиту dropuser, которая имеет ту же функциональность, что и эта команда (фактически она вызывает эту команду), но может быть запущена из командной оболочки.

Примеры

Удаление роли:

DROP ROLE jonathan;

Совместимость

В стандарте SQL описана команда DROP ROLE, но она позволяет удалять только одну роль за раз и определяет требования к правам, отличные от применяемых в QHB.

См. также

CREATE ROLE, ALTER ROLE, SET ROLE

DROP ROUTINE

DROP ROUTINE — удалить подпрограмму

Синтаксис

DROP ROUTINE [ IF EXISTS ] имя [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] [, ...]
    [ CASCADE | RESTRICT ]

Описание

Команда DROP ROUTINE удаляет определение одной или нескольких существующих подпрограмм. Термин «подпрограмма» включает в себя агрегатные и обычные функции и процедуры. Описание параметров, а также дополнительные примеры и сведения см. на справочных страницах команд DROP AGGREGATE, DROP FUNCTION и DROP PROCEDURE.

Примечания

Правила поиска, использующиеся командой DROP ROUTINE фундаментально ничем не отличаются от правил DROP PROCEDURE; в частности DROP ROUTINE демонстрирует то же поведение, рассматривая список аргументов без маркеров режим_аргумента как соответствующий определению стандарта SQL, где аргументы OUT включаются в этот список. (DROP AGGREGATE и DROP FUNCTION этого не делают.)

В некоторых случаях, когда несколько подпрограмм разных видов имеют одно имя, DROP ROUTINE может не выполниться, выдав ошибку неоднозначности, тогда как более конкретная команда (DROP FUNCTION и т. д.) могла бы сработать. Эти проблемы тоже можно решить, задав список типов аргументов более подробно.

Эти правила поиска также действуют и для других команд, работающих с существующими подпрограммами, например ALTER ROUTINE и COMMENT ON ROUTINE.

Примеры

Удаление подпрограммы foo для типа integer:

DROP ROUTINE foo(integer);

Эта команда будет работать независимо от того, является ли foo агрегатом, функцией или процедурой.

Совместимость

Команда DROP ROUTINE соответствует стандарту SQL со следующими расширениями QHB:

Стандарт позволяет удалить одной командой только одну подпрограмму.

Параметр IF EXISTS является расширением.

Возможность задавать режимы и имена аргументов является расширением, а правила поиска меняются, когда задаются режимы.

Пользовательские агрегатные функции являются расширением.

См. также

DROP AGGREGATE, DROP FUNCTION, DROP PROCEDURE, ALTER ROUTINE
Обратите внимание, что команды CREATE ROUTINE не существует.

DROP RULE

DROP RULE — удалить правило перезаписи

Синтаксис

DROP RULE [ IF EXISTS ] имя ON имя_таблицы [ CASCADE | RESTRICT ]

Описание

Команда DROP RULE удаляет правило перезаписи.

Параметры

IF EXISTS

Не считать ошибкой, если это правило не существует. В этом случае выдается только замечание.

имя

Имя удаляемого правила.

имя_таблицы

Имя таблицы (или представления), к которой применяется правило (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого правила, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого правила, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление правила перезаписи newrule:

DROP RULE newrule ON mytable;

Совместимость

Команда DROP RULE является языковым расширением QHB, как и вся система перезаписи запросов.

См. также

CREATE RULE, ALTER RULE

DROP SCHEMA

DROP SCHEMA — удалить схему

Синтаксис

DROP SCHEMA [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP SCHEMA удаляет схемы из базы данных.

Схему может удалить только ее владелец или суперпользователь. Обратите внимание, что владелец может удалить схему (и, как следствие, все содержащиеся в ней объекты), даже если он не владеет некоторыми объектами внутри этой схемы.

Параметры

IF EXISTS

Не считать ошибкой, если эта схема не существует. В этом случае выдается только замечание.

имя

Имя схемы.

CASCADE

Автоматически удалять объекты (таблицы, функции и т. д.), которые содержатся в этой схеме, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой схемы, если она содержит какие-либо объекты. Это поведение по умолчанию.

Примечания

С параметром CASCADE эта команда может удалить объекты и в других схемах помимо заданной.

Примеры

Удаление схемы mystuff из базы данных вместе со всем, что в ней содержится:

DROP SCHEMA mystuff CASCADE;

Совместимость

Команда DROP SCHEMA полностью соответствует стандарту SQL, за исключением параметра IF EXISTS, который является расширением QHB, а также того, что стандарт позволяет удалять в одной команде только одну схему.

См. также

ALTER SCHEMA, CREATE SCHEMA

DROP SEQUENCE

DROP SEQUENCE — удалить последовательность

Синтаксис

DROP SEQUENCE [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP SEQUENCE удаляет генераторы числовых последовательностей. Последовательность может удалить только ее владелец или суперпользователь.

Параметры

IF EXISTS

Не считать ошибкой, если эта последовательность не существует. В этом случае выдается только замечание.

имя

Имя последовательности (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этой последовательности, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой последовательности, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление последовательности serial:

DROP SEQUENCE serial;

Совместимость

Команда DROP SEQUENCE соответствует стандарту SQL, за исключением параметра IF EXISTS, который является расширением QHB, а также того, что стандарт позволяет удалять в одной команде только одну последовательность.

См. также

CREATE SEQUENCE, ALTER SEQUENCE

DROP SERVER

DROP SERVER — удалить дескриптор стороннего сервера

Синтаксис

DROP SERVER [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP SERVER удаляет существующий дескриптор стороннего сервера. Для выполнения этой команды текущий пользователь должен быть владельцем соответствующего стороннего сервера.

Параметры

IF EXISTS

Не считать ошибкой, если этот сервер не существует. В этом случае выдается только замечание.

имя

Имя существующего сервера.

CASCADE

Автоматически удалять объекты, зависящие от этого сервера (например, сопоставления пользователей), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого сервера, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление сервера foo, если он существует:

DROP SERVER IF EXISTS foo;

Совместимость

Команда DROP SERVER соответствует стандарту ISO/IEC 9075-9 (SQL/MED). Предложение IF EXISTS является расширением QHB.

См. также

CREATE SERVER, ALTER SERVER

DROP STATISTICS

DROP STATISTICS — удалить расширенную статистику

Синтаксис

DROP STATISTICS [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP STATISTICS удаляет объект(ы) статистики из базы данных. Только владелец соответствующего объекта статистики, владелец схемы или суперпользователь могут удалить объект статистики.

Параметры

IF EXISTS

Не считать ошибкой, если этот объект статистики не существует. В этом случае выдается только замечание.

имя

Имя удаляемого объекта статистики (может быть дополнено схемой).

CASCADE
RESTRICT

Эти ключевые слова не имеют эффекта, так как у статистики нет зависимых объектов.

Примеры

Удаление двух объектов статистики в разных схемах без выдачи ошибки, если эти объекты не существуют:

DROP STATISTICS IF EXISTS
    accounting.users_uid_creation,
    public.grants_user_role;

Совместимость

В стандарте SQL нет команды DROP STATISTICS.

См. также

ALTER STATISTICS, CREATE STATISTICS

DROP SUBSCRIPTION

DROP SUBSCRIPTION — удалить подписку

Синтаксис

DROP SUBSCRIPTION [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP SUBSCRIPTION удаляет подписку из кластера баз данных.

Подписку может удалить только суперпользователь.

Команду DROP SUBSCRIPTION нельзя выполнить внутри блока транзакции, если подписка связана со слотом репликации. (Для освобождения слота можно воспользоваться командой ALTER SUBSCRIPTION.)

Параметры

имя

Имя удаляемой подписки.

CASCADE
RESTRICT

Эти ключевые слова не имеют эффекта, так как у подписок нет зависимых объектов.

Примечания

При удалении подписки, связанной со слотом репликации на удаленном хосте (обычное состояние), команда DROP SUBSCRIPTION подключится к этому удаленному хосту и попытается удалить слот репликации (и все остальные слоты синхронизации таблиц) в рамках своей операции. Это необходимо для освобождения ресурсов, выделенных для подписки на удаленном хосте. Если при этом происходит сбой — либо из-за недоступности удаленного хоста, либо из-за того, что слот репликации нельзя удалить, или он не существует, или вообще никогда не существовал, — команда DROP SUBSCRIPTION прерывается. Чтобы продолжить работу в такой ситуации, отключите подписку от слота репликации, выполнив ALTER SUBSCRIPTION ... SET (slot_name = NONE). После этого DROP SUBSCRIPTION больше не будет пытаться что-либо делать на удаленном хосте. Обратите внимание, что если удаленный слот репликации все еще существует, его (и все связанные слоты синхронизации таблиц) следует удалить вручную; в противном случае он/они продолжат резервировать WAL, что может в конечном итоге привести к переполнению диска. См. также подраздел Управление слотами репликации.

Если подписка связана со слотом репликации, то команду DROP SUBSCRIPTION нельзя выполнять внутри блока транзакции.

Примеры

Удаление подписки:

DROP SUBSCRIPTION mysub;

Совместимость

Команда DROP SUBSCRIPTION является расширением QHB.

См. также

CREATE SUBSCRIPTION, ALTER SUBSCRIPTION

DROP TABLE

DROP TABLE — удалить таблицу

Синтаксис

DROP TABLE [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP TABLE удаляет таблицы из базы данных. Только владелец соответствующей таблицы, владелец схемы и суперпользователь могут удалить таблицу. Чтобы удалить содержимое таблицы, не удаляя ее саму, воспользуйтесь командами DELETE или TRUNCATE.

Команда DROP TABLE всегда удаляет все индексы, правила, триггеры и ограничения, которые существуют для целевой таблицы. Однако для удаления таблицы, на которую ссылается представление или ограничение внешнего ключа другой таблицы, следует указать CASCADE. (CASCADE полностью удалит зависимое представление, но в случае внешнего ключа будет удалено только ограничение внешнего ключа, а не вся таблица, с которой оно связано.)

Параметры

IF EXISTS

Не считать ошибкой, если эта таблица не существует. В этом случае выдается только замечание.

имя

Имя удаляемой таблицы (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этой таблицы (например, представления), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой таблицы, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление двух таблиц, films и distributors:

DROP TABLE films, distributors;

Совместимость

Команда DROP TABLE соответствует стандарту SQL, за исключением параметра IF EXISTS, который является расширением QHB, а также того, что стандарт позволяет удалять в одной команде только одну таблицу.

См. также

ALTER TABLE, CREATE TABLE

DROP TABLESPACE

DROP TABLESPACE — удалить табличное пространство

Синтаксис

DROP TABLESPACE [ IF EXISTS ] имя

Описание

Команда DROP TABLESPACE удаляет табличное пространство из системы.

Табличное пространство может быть удалено только его владельцем или суперпользователем. Перед удалением табличное пространство необходимо очистить от всех объектов базы данных. Вполне возможно, что даже если в текущей базе данных не будет ни одного объекта, использующего это табличное пространство, в нем вполне могут оставаться объекты других баз данных. Кроме того, если табличное пространство указано в параметре temp_tablespaces любого активного сеанса, в этом пространстве окажутся временные файлы, из-за чего команда DROP может завершиться ошибкой.

Параметры

IF EXISTS

Не считать ошибкой, если это табличное пространство не существует. В этом случае выдается только замечание.

имя

Имя табличного пространства.

Примечания

Команду DROP TABLESPACE нельзя выполнять внутри блока транзакции.

Примеры

Удаление табличного пространства mystuff из системы:

DROP TABLESPACE mystuff;

Совместимость

Команда DROP TABLESPACE является расширением QHB.

См. также

CREATE TABLESPACE, ALTER TABLESPACE

DROP TEXT SEARCH CONFIGURATION

DROP TEXT SEARCH CONFIGURATION — удалить конфигурацию текстового поиска

Синтаксис

DROP TEXT SEARCH CONFIGURATION [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP TEXT SEARCH CONFIGURATION удаляет существующую конфигурацию текстового поиска. Для выполнения этой команды нужно быть владельцем соответствующей конфигурации.

Параметры

IF EXISTS

Не считать ошибкой, если эта конфигурация текстового поиска не существует. В этом случае выдается только замечание.

имя

Имя существующей конфигурации текстового поиска (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этой конфигурации текстового поиска, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой конфигурации текстового поиска, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление конфигурации текстового поиска my_english:

DROP TEXT SEARCH CONFIGURATION my_english;

Эта команда не будет выполнена, если существуют какие-либо индексы, ссылающиеся на эту конфигурацию в вызовах to_tsvector. Чтобы удалить такие индексы вместе с конфигурацией текстового поиска, нужно дополнительно указать CASCADE.

Совместимость

В стандарте SQL нет команды DROP TEXT SEARCH CONFIGURATION.

См. также

ALTER TEXT SEARCH CONFIGURATION, CREATE TEXT SEARCH CONFIGURATION

DROP TEXT SEARCH DICTIONARY

DROP TEXT SEARCH DICTIONARY — удалить словарь текстового поиска

Синтаксис

DROP TEXT SEARCH DICTIONARY [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP TEXT SEARCH DICTIONARY удаляет существующий словарь текстового поиска. Для выполнения этой команды нужно быть владельцем соответствующего словаря.

Параметры

IF EXISTS

Не считать ошибкой, если этот словарь текстового поиска не существует. В этом случае выдается только замечание.

имя

Имя существующего словаря текстового поиска (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого словаря текстового поиска, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого словаря текстового поиска, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление словаря текстового поиска english:

DROP TEXT SEARCH DICTIONARY english;

Эта команда не будет выполнена, если существуют какие-либо конфигурации текстового поиска, использующие этот словарь. Чтобы удалить такие конфигурации вместе со словарем, нужно дополнительно указать CASCADE.

Совместимость

В стандарте SQL нет команды DROP TEXT SEARCH DICTIONARY.

См. также

ALTER TEXT SEARCH DICTIONARY, CREATE TEXT SEARCH DICTIONARY

DROP TEXT SEARCH PARSER

DROP TEXT SEARCH PARSER — удалить синтаксический анализатор текстового поиска

Синтаксис

DROP TEXT SEARCH PARSER [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP TEXT SEARCH PARSER удаляет существующий синтаксический анализатор текстового поиска. Чтобы выполнить эту команду, нужно быть суперпользователем.

Параметры

IF EXISTS

Не считать ошибкой, если этот синтаксический анализатор текстового поиска не существует. В этом случае выдается только замечание.

имя

Имя существующего синтаксического анализатора текстового поиска (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого синтаксического анализатора текстового поиска, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого синтаксического анализатора текстового поиска, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление синтаксического анализатора текстового поиска my_parser:

DROP TEXT SEARCH PARSER my_parser;

Эта команда не будет выполнена, если существуют какие-либо конфигурации текстового поиска, использующие этот синтаксический анализатор. Чтобы удалить такие конфигурации вместе с синтаксическим анализатором, нужно дополнительно указать CASCADE.

Совместимость

В стандарте SQL нет команды DROP TEXT SEARCH PARSER.

См. также

ALTER TEXT SEARCH PARSER, CREATE TEXT SEARCH PARSER

DROP TEXT SEARCH TEMPLATE

DROP TEXT SEARCH TEMPLATE — удалить шаблон текстового поиска

Синтаксис

DROP TEXT SEARCH TEMPLATE [ IF EXISTS ] имя [ CASCADE | RESTRICT ]

Описание

Команда DROP TEXT SEARCH TEMPLATE удаляет существующий шаблон текстового поиска. Чтобы выполнить эту команду, нужно быть суперпользователем.

Параметры

IF EXISTS

Не считать ошибкой, если этот шаблон текстового поиска не существует. В этом случае выдается только замечание.

имя

Имя существующего шаблона текстового поиска (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого шаблона текстового поиска, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого шаблона текстового поиска, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление шаблона текстового поиска thesaurus:

DROP TEXT SEARCH TEMPLATE thesaurus;

Эта команда не будет выполнена, если существуют какие-либо словари текстового поиска, использующие этот шаблон. Чтобы удалить такие словари вместе с шаблоном, нужно дополнительно указать CASCADE.

Совместимость

В стандарте SQL нет команды DROP TEXT SEARCH TEMPLATE.

См. также

ALTER TEXT SEARCH TEMPLATE, CREATE TEXT SEARCH TEMPLATE

DROP TRANSFORM

DROP TRANSFORM — удалить трансформацию

Синтаксис

DROP TRANSFORM [ IF EXISTS ] FOR имя_типа LANGUAGE имя_языка [ CASCADE | RESTRICT ]

Описание

Команда DROP TRANSFORM удаляет ранее определенную трансформацию.

Чтобы иметь возможность удалить трансформацию, нужно быть владельцем соответствующих типа и языка. Те же права требуются и при создании трансформации.

Параметры

IF EXISTS

Не считать ошибкой, если эта трансформация не существует. В этом случае выдается только замечание.

имя_типа

Имя типа данных трансформации.

имя_языка

Имя языка трансформации.

CASCADE

Автоматически удалять объекты, зависящие от этой трансформации, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этой трансформации, если от нее зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление трансформации для типа hstore и языка plpythonu:

DROP TRANSFORM FOR hstore LANGUAGE plpythonu;

Совместимость

Эта форма DROP TRANSFORM является расширением QHB. Дополнительную информацию см. на справочной странице команды CREATE TRANSFORM.

См. также

CREATE TRANSFORM

DROP TRIGGER

DROP TRIGGER — удалить триггер

Синтаксис

DROP TRIGGER [ IF EXISTS ] имя ON имя_таблицы [ CASCADE | RESTRICT ]

Описание

Команда DROP TRIGGER удаляет существующее определение триггера. Для выполнения этой команды нужно быть владельцем таблицы, для которой определен заданный триггер.

Параметры

IF EXISTS

Не считать ошибкой, если этот триггер не существует. В этом случае выдается только замечание.

имя

Имя удаляемого триггера.

имя_таблицы

Имя таблицы, для которой определен триггер (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого триггера, и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого триггера, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление триггера if_dist_exists в таблице films:

DROP TRIGGER if_dist_exists ON films;

Совместимость

Команда DROP TRIGGER в QHB несовместима со стандартом SQL. В стандарте SQL имена триггеров не являются локальными для таблиц, поэтому команда записывается просто как DROP TRIGGER имя.

См. также

CREATE TRIGGER

DROP TYPE

DROP TYPE — удалить тип данных

Синтаксис

DROP TYPE [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP TYPE удаляет пользовательский тип данных. Только владелец типа может его удалить.

Параметры

IF EXISTS

Не считать ошибкой, если этот тип не существует. В этом случае выдается только замечание.

имя

Имя удаляемого типа данных (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого типа (например столбцы таблицы, функции и операторы), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого типа, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Удаление типа данных box:

DROP TYPE box;

Совместимость

Эта команда аналогична соответствующей команде в стандарте SQL, за исключением параметра IF EXISTS, который является расширением QHB. Однако обратите внимание, что команда CREATE TYPE и механизмы расширения типов данных в QHB отличаются от стандарта SQL.

См. также

ALTER TYPE, CREATE TYPE

DROP USER

DROP USER — удалить роль в базе данных

Синтаксис

DROP USER [ IF EXISTS ] имя [, ...]

Описание

Команда DROP USER — это просто альтернативное написание команды DROP ROLE.

Совместимость

Команда DROP USER является расширением QHB. В стандарте SQL определение пользователей считается зависящим от реализации.

См. также

DROP ROLE

DROP USER MAPPING

DROP USER MAPPING — удалить сопоставление пользователя для стороннего сервера

Синтаксис

DROP USER MAPPING [ IF EXISTS ] FOR { имя_пользователя | USER | CURRENT_ROLE | CURRENT_USER | PUBLIC } SERVER имя_сервера

Описание

Команда DROP USER MAPPING удаляет существующее сопоставление пользователя для стороннего сервера.

Владелец стороннего сервера может удалять заданные для этого сервера сопоставления любых пользователей. Кроме того, обычный пользователь может удалить сопоставление для собственного имени, если у него есть право USAGE для соответствующего сервера.

Параметры

IF EXISTS

Не считать ошибкой, если это сопоставление пользователя не существует. В этом случае выдается только замечание.

имя_пользователя

Имя пользователя для сопоставления. Значения CURRENT_ROLE, CURRENT_USER и USER соответствуют имени текущего пользователя. Значение PUBLIC используется для сопоставления всех текущих и будущих имен пользователей в системе.

имя_сервера

Имя сервера, в котором задано это сопоставление пользователя.

Примеры

Удаление сопоставления пользователя bob для сервера foo, если оно существует:

DROP USER MAPPING IF EXISTS FOR bob SERVER foo;

Совместимость

Команда DROP USER MAPPING соответствует стандарту ISO/IEC 9075-9 (SQL/MED). Предложение IF EXISTS является расширением QHB.

См. также

CREATE USER MAPPING, ALTER USER MAPPING

DROP VIEW

DROP VIEW — удалить представление

Синтаксис

DROP VIEW [ IF EXISTS ] имя [, ...] [ CASCADE | RESTRICT ]

Описание

Команда DROP VIEW удаляет существующее представление. Для выполнения этой команды нужно быть владельцем соответствующего представления.

Параметры

IF EXISTS

Не считать ошибкой, если это представление не существует. В этом случае выдается только замечание.

имя

Имя удаляемого представления (может быть дополнено схемой).

CASCADE

Автоматически удалять объекты, зависящие от этого представления (например, другие представления), и, в свою очередь, все объекты, зависящие от этих объектов (см. раздел Отслеживание зависимостей).

RESTRICT

Отказать в удалении этого представления, если от него зависят какие-либо объекты. Это поведение по умолчанию.

Примеры

Эта команда удалит представление с именем kinds:

DROP VIEW kinds;

Совместимость

Эта команда соответствует стандарту SQL, за исключением параметра IF EXISTS, который является расширением QHB, а также того, что стандарт позволяет удалять в одной команде только одно представление.

См. также

ALTER VIEW, CREATE VIEW

END

END — зафиксировать текущую транзакцию

Синтаксис

END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]

Описание

Команда END фиксирует текущую транзакцию. Все изменения, внесенные этой транзакцией, становятся видимыми для других и гарантированно будут сохранены, если произойдет сбой. Эта команда является расширением QHB и равнозначна команде COMMIT.

Параметры

WORK
TRANSACTION

Необязательные ключевые слова. Они не имеют эффекта.

AND CHAIN

Если указывается AND CHAIN, то сразу после окончания текущей транзакции начинается новая с теми же характеристиками (см. справочную страницу команды SET TRANSACTION), что и только что завершенная. В противном случае новая транзакция не запускается.

Примечания

Чтобы прервать транзакцию, воспользуйтесь командой ROLLBACK.

Попытка выполнить команду END вне транзакции не причинит вреда, но будет выдано предупреждающее сообщение.

Примеры

Фиксация текущей транзакции и сохранение всех изменений:

END;

Совместимость

Команда END является расширением QHB и предоставляет функциональные возможности, аналогичные таковым команды COMMIT, которая указана в стандарте SQL.

См. также

BEGIN, COMMIT, ROLLBACK

EXECUTE

EXECUTE — выполнить подготовленный оператор

Синтаксис

EXECUTE имя [ ( параметр [, ...] ) ]

Описание

Команда EXECUTE используется для выполнения ранее подготовленного оператора. Поскольку подготовленные операторы существуют только в рамках сеанса, они должны создаваться командой PREPARE, выполненной ранее в текущем сеансе.

Если в команде PREPARE, создавшей оператор, указаны некоторые параметры, то в оператор EXECUTE должен быть передан совместимый набор параметров, иначе возникает ошибка. Обратите внимание, что (в отличие от функций) подготовленные операторы не перегружаются на основе типа или числа их параметров; имя подготовленного оператора должно быть уникальным в рамках текущего сеанса базы данных.

Дополнительную информацию о создании и использовании подготовленных операторов см. на справочной странице команды PREPARE.

Параметры

имя

Имя выполняемого подготовленного оператора.

параметр

Фактическое значение параметра для подготовленного оператора. Это должно быть выражение, выдающее значение, совместимое с типом данных этого параметра, как было определено при создании подготовленного оператора.

Выводимая информация

Метка команды, возвращаемая EXECUTE, соответствует подготовленному оператору, а не оператору EXECUTE.

Примеры

Примеры приведены в параграфе Примеры на справочной странице команды PREPARE.

Совместимость

В стандарт SQL входит команда EXECUTE, но она предназначена только для использования во встраиваемом SQL. Кроме того, данная версия команды EXECUTE имеет несколько другой синтаксис.

См. также

DEALLOCATE, PREPARE

EXPLAIN

EXPLAIN — показать план выполнения оператора

Синтаксис

EXPLAIN [ ( параметр [, ...] ) ] оператор
EXPLAIN [ ANALYZE ] [ VERBOSE ] оператор

где параметром может быть:

    ANALYZE [ логическое_значение ]
    VERBOSE [ логическое_значение ]
    COSTS [ логическое_значение ]
    SETTINGS [ логическое_значение ]
    BUFFERS [ логическое_значение ]
    WAL [ логическое_значение ]
    TIMING [ логическое_значение ]
    SUMMARY [ логическое_значение ]
    FORMAT { TEXT | XML | JSON | YAML }

Описание

Эта команда отображает план выполнения, который планировщик QHB генерирует для предоставленного оператора. План выполнения показывает, как будет сканироваться таблица(ы), на которую ссылается оператор: простым последовательным сканированием, сканированием по индексу и т. д. — и если оператор ссылается на несколько таблиц, то какие алгоритмы соединения будут использоваться для объединения требуемых строк из каждой входной таблицы.

Наиболее важной частью выводимой информации является расчетная стоимость выполнения оператора, которая представляет собой предположение планировщика о том, сколько времени потребуется для выполнения этого оператора (это значение измеряется в единицах стоимости, которые не имеют точного определения, но обычно подразумевают обращение к странице на диске). На самом деле выводятся два числа: начальная стоимость до выдачи первой строки и общая стоимость выдачи всех строк. Для большинства запросов важна общая стоимость, но в таких контекстах, как подзапрос в EXISTS, планировщик выбирает вместо нее наименьшую стартовую стоимость (так как исполнитель все равно остановится сразу после получения одной строки). Кроме того, если ограничить число возвращаемых строк предложением LIMIT, то планировщик делает подходящую интерполяцию между этими предельными значениями стоимости, выбирая наиболее выгодный план.

С параметром ANALYZE оператор будет действительно выполнен, а не только запланирован. При этом в результат добавляются фактические статистические данные времени выполнения, включая общее затраченное время в каждом узле плана (в миллисекундах) и общее количество строк, которые он фактически вернул. Это помогает проверить, близки ли оценки планировщика к реальности.

Важно

Имейте в виду, что с указанием ANALYZE оператор действительно выполняется. Хотя EXPLAIN отбросит результат, который вернул бы SELECT, в остальном все действия произойдут как обычно. Если вы хотите выполнить EXPLAIN ANALYZE с командой INSERT, UPDATE, DELETE, CREATE TABLE AS или EXECUTE, не допуская изменения данных этой командой, используйте следующий подход:

BEGIN;
EXPLAIN ANALYZE ...;
ROLLBACK;

Без скобок для этого оператора можно указать только параметры ANALYZE и VERBOSE, и только в таком порядке. Ожидается, что все новые параметры будут поддерживаться только при условии синтаксиса со скобками

Параметры

ANALYZE

Выполнить команду и показать фактическое время выполнения и другую статистику. По умолчанию этот параметр имеет значение FALSE.

VERBOSE

Вывести дополнительную информацию о плане. В частности, включить список выходных столбцов для каждого узла в дереве плана, имена таблиц и функций, дополненных схемами, всегда помечать переменные в выражениях псевдонимом их диапазонной таблицы, а также выводить имена всех триггеров, для которых отображается статистика. По умолчанию этот параметр имеет значение FALSE.

COSTS

Включить информацию о рассчитанной стоимости запуска и общей стоимости каждого узла плана, а также предполагаемое количество строк и предполагаемую ширину каждой строки. По умолчанию этот параметр имеет значение TRUE.

SETTINGS

Включить информацию о параметрах конфигурации. В частности, включить параметры, влияющие на планирование запроса, имеющие значение, отличное от встроенного значения по умолчанию. По умолчанию этот параметр имеет значение FALSE.

BUFFERS

Включить информацию об использовании буфера. В частности, включить количество прочитанных, загрязненных и записанных разделяемых блоков, а также количество попаданий в них, количество прочитанных, загрязненных и записанных локальных блоков, а также количество попаданий в них, количество прочитанных и записанных временных блоков, а также время, потраченное на чтение и запись блоков файлов данных (в миллисекундах), если включен track_io_timing. Попадание означает, что чтения удалось избежать, поскольку найденный блок уже находился в кэше. Разделяемые блоки содержат данные из обычных таблиц и индексов, локальные — данные из временных таблиц и индексов, а временные — временные рабочие данные, используемые при сортировке, хешировании, материализации узлов плана и тому подобном. Количество загрязненных блоков показывает, сколько ранее не модифицированных блоков изменила данная операция, в то время как количество записанных блоков показывает, сколько ранее загрязненных блоков вытеснил из кэша этот обслуживающий процесс во время обработки запроса. Количество блоков, показанных для узла верхнего уровня, включает блоки, используемые всеми его дочерними узлами. В текстовом формате печатаются только ненулевые значения. По умолчанию этот параметр имеет значение FALSE.

WAL

Включить информацию о генерировании записей WAL. В частности, вывести количество записей, количество полных образов страниц (full page images, fpi) и количество сгенерированных WAL в байтах. В текстовом формате печатаются только ненулевые значения. Этот параметр можно использовать только при включенном параметре ANALYZE. По умолчанию имеет значение FALSE.

TIMING

Включить фактическое время запуска и время, затраченное в каждом узле на выходе. Затраты на многократное чтение системных часов могут замедлить запрос в некоторых системах, поэтому когда требуется только подсчет фактического количества строк, а не точное время, может быть полезно установить этот параметр в FALSE. Время выполнения всего оператора всегда измеряется, даже если вывод времени на уровне узла выключен с помощью этого параметра. Этот параметр можно использовать только при включенном параметре ANALYZE. По умолчанию имеет значение TRUE.

SUMMARY

Включить сводку (например информацию о суммарном времени) после плана запроса. При использовании ANALYZE сводная информация выводится по умолчанию, но этот параметр позволяет получить ее и с другими вариантами команды. В EXPLAIN EXECUTE время планирования включает в себя время, требуемое для извлечения плана из кэша, и, при необходимости, время, требуемое для повторного планирования.

FORMAT

Установить формат вывода, который может быть TEXT, XML, JSON или YAML. Нетекстовые форматы содержат ту же информацию, что и TEXT, но больше подходят для программного анализа. По умолчанию этот параметр имеет значение TEXT.

логическое_значение

Определяет, должен ли выбранный параметр быть включен или выключен. Для включения параметра можно написать: TRUE, ON или 1, а для выключения — FALSE, OFF или 0. Кроме того, логическое_значение можно опустить; в этом случае предполагается TRUE.

оператор

Любой оператор SELECT, INSERT, UPDATE, DELETE, VALUES, EXECUTE, DECLARE, CREATE TABLE AS или CREATE MATERIALIZED VIEW AS, план выполнения которого вы хотите видеть.

Выводимая информация

Результатом выполнения этой команды является текстовое описание выбранного для оператора плана, при необходимости дополненное статистикой выполнения. Предоставленная информация описывается в разделе Использование EXPLAIN.

Примечания

Чтобы планировщик запросов QHB мог принимать обоснованные решения при оптимизации запросов, данные в pg_statistic должны быть актуальными для всех таблиц, используемых в запросе. Обычно это автоматически обеспечивает Процесс «Автовакуум». Но если в содержимом таблицы недавно произошли существенные изменения, может потребоваться вручную выполнить ANALYZE, не дожидаясь, пока до них доберется автоочистка.

Чтобы измерить в плане выполнения затраты времени на выполнение каждого узла, текущая реализация EXPLAIN ANALYZE добавляет в выполнение запроса затраты на профилирование. В результате выполнение запроса с EXPLAIN ANALYZE иногда может занять значительно больше времени, чем обычное выполнение запроса. Объем затрат зависит от характера запроса, а также от используемой платформы. Наихудший случай возникает в узлах плана, которые сами по себе требуют очень мало времени на выполнение, и в операционных системах, где получение текущего времени является относительно длительной операцией.

Примеры

Выведение плана простого запроса для таблицы, содержащей единственный столбец integer и 10 000 строк:

EXPLAIN SELECT * FROM foo;

                       QUERY PLAN
---------------------------------------------------------
 Seq Scan on foo  (cost=0.00..155.00 rows=10000 width=4)
(1 row)

План того же запроса, но выведенный в формате JSON:

EXPLAIN (FORMAT JSON) SELECT * FROM foo;
           QUERY PLAN
--------------------------------
 [                             +
   {                           +
     "Plan": {                 +
       "Node Type": "Seq Scan",+
       "Relation Name": "foo", +
       "Alias": "foo",         +
       "Startup Cost": 0.00,   +
       "Total Cost": 155.00,   +
       "Plan Rows": 10000,     +
       "Plan Width": 4         +
     }                         +
   }                           +
 ]
(1 row)

Если в таблице есть индекс, а в запросе содержит индексируемое условие WHERE, EXPLAIN может показать другой план:

EXPLAIN SELECT * FROM foo WHERE i = 4;

                         QUERY PLAN
--------------------------------------------------------------
 Index Scan using fi on foo  (cost=0.00..5.98 rows=1 width=4)
   Index Cond: (i = 4)
(2 rows)

План того же запроса, но в формате YAML:

EXPLAIN (FORMAT YAML) SELECT * FROM foo WHERE i='4';
          QUERY PLAN
-------------------------------
 - Plan:                      +
     Node Type: "Index Scan"  +
     Scan Direction: "Forward"+
     Index Name: "fi"         +
     Relation Name: "foo"     +
     Alias: "foo"             +
     Startup Cost: 0.00       +
     Total Cost: 5.98         +
     Plan Rows: 1             +
     Plan Width: 4            +
     Index Cond: "(i = 4)"
(1 row)

Вывод в формате XML оставлен в качестве упражнения для читателя.

Тот же план, но без вывода оценок стоимости:

EXPLAIN (COSTS FALSE) SELECT * FROM foo WHERE i = 4;

        QUERY PLAN
----------------------------
 Index Scan using fi on foo
   Index Cond: (i = 4)
(2 rows)

Пример плана для запроса с агрегатной функцией:

EXPLAIN SELECT sum(i) FROM foo WHERE i < 10;

                             QUERY PLAN
---------------------------------------------------------------------
 Aggregate  (cost=23.93..23.93 rows=1 width=4)
   ->  Index Scan using fi on foo  (cost=0.00..23.92 rows=6 width=4)
         Index Cond: (i < 10)
(3 rows)

Пример использования EXPLAIN EXECUTE для отображения плана выполнения подготовленного запроса:

PREPARE query(int, int) AS SELECT sum(bar) FROM test
    WHERE id > $1 AND id < $2
    GROUP BY foo;

EXPLAIN ANALYZE EXECUTE query(100, 200);

                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=9.54..9.54 rows=1 width=8) (actual time=0.156..0.161 rows=11 loops=1)
   Group Key: foo
   ->  Index Scan using test_pkey on test  (cost=0.29..9.29 rows=50 width=8) (actual time=0.039..0.091 rows=99 loops=1)
         Index Cond: ((id > $1) AND (id < $2))
 Planning time: 0.197 ms
 Execution time: 0.225 ms
(6 rows)

Конечно, показанные здесь конкретные цифры зависят от фактического содержания соответствующих таблиц. Также обратите внимание, что числа и даже выбранная стратегия запроса могут отличаться в разных выпусках QHB из-за улучшений планировщика. Кроме того, команда ANALYZE использует случайную выборку для оценки статистики данных, поэтому оценки затрат могут измениться после нового запуска ANALYZE, даже если фактическое распределение данных в таблице не изменилось.

Совместимость

В стандарте SQL команда EXPLAIN не определена.

См. также

ANALYZE

FETCH

FETCH — получить результат запроса с помощью курсора

Синтаксис

FETCH [ направление [ FROM | IN ] ] имя_курсора

где направлением может быть:

    NEXT
    PRIOR
    FIRST
    LAST
    ABSOLUTE число
    RELATIVE число
    число
    ALL
    FORWARD
    FORWARD число
    FORWARD ALL
    BACKWARD
    BACKWARD число
    BACKWARD ALL

Описание

Команда FETCH извлекает строки с помощью ранее созданного курсора.

Курсор связан с определенным положением, что и использует FETCH. Курсор может находиться перед первой строкой результата запроса, на любой конкретной строке результата или после последней строки результата. При создании курсор располагается перед первой строкой. После выборки нескольких строк курсор будет располагаться на последней полученной строке. Если FETCH выполняется до конца доступных строк, то курсор остается в позиции после последней строки, если выборка выполняется назад, то перед первой строкой. Команды FETCH ALL и FETCH BACKWARD ALL всегда будут оставлять курсор после последней строки или перед первой строкой соответственно.

Формы NEXT, PRIOR, FIRST, LAST, ABSOLUTE и RELATIVE извлекают одну строку после соответствующего перемещения курсора. Если такой строки нет, то возвращается пустой результат, и курсор остается в позиции перед первой строкой или после последней строки, в зависимости от ситуации.

Формы с FORWARD и BACKWARD извлекают указанное число строк, сдвигаясь, соответственно, вперед или назад; в результате курсор оказывается на последней возвращенной строке (или перед/после всех строк, если число превышает количество доступных строк).

Формы RELATIVE 0, FORWARD 0 и BACKWARD 0 запрашивают выборку текущей строки без перемещения курсора, то есть повторно извлекают строку, которая была извлечена последней. Это произойдет успешно, если курсор не будет расположен перед первой строкой или после последней строки; в этом случае строка не возвращается.

Примечание
На этой странице описывается использование курсоров на уровне команд SQL. Если вы пытаетесь использовать курсоры внутри функции PL/pgSQL, правила будут отличаться — см. подраздел Курсоры.

Параметры

направление

Параметр направление определяет направление движения и количество извлекаемых строк. Может принимать одно из следующих значений:

NEXT
Извлечь следующую строку. Это действие по умолчанию, если направление опущено.

PRIOR
Извлечь предыдущую строку.

FIRST
Извлечь первую строку запроса (аналогично ABSOLUTE 1).

LAST
Извлечь последнюю строку запроса (аналогично ABSOLUTE -1).

ABSOLUTE число
Извлечь строку под номером число с начала либо под номером abs(число) с конца, если число отрицательно. Если число выходит за границы диапазона строк, курсор размещается перед первой или после последней строки; в частности, с ABSOLUTE 0 курсор оказывается перед первой строкой.

RELATIVE число
Извлечь строку под номером число, считая со следующей вперед, либо под номером abs(число), считая с предыдущей назад, если число отрицательно. RELATIVE 0 повторно извлекает текущую строку, если таковая имеется.

число
Извлечь следующее число строк (аналогично FORWARD число).

ALL
Извлечь все оставшиеся строки (аналогично FORWARD ALL).

FORWARD
Извлечь следующую строку (аналогично NEXT).

FORWARD число
Извлечь следующее число строк. FORWARD 0 повторно извлекает текущую строку.

FORWARD ALL
Извлечь все оставшиеся строки.

BACKWARD
Извлечь предыдущую строку (аналогично PRIOR).

BACKWARD число
Извлечь предыдущее число строк (сканируя в обратном направлении). BACKWARD 0 повторно извлекает текущую строку.

BACKWARD ALL
Извлечь все предыдущие строки (сканируя в обратном направлении).

число

Здесь число — это целочисленная константа, возможно, со знаком, определяющая положение или количество выбираемых строк. Для вариантов FORWARD и BACKWARD указание отрицательного числа равнозначно смене направления FORWARD на BACKWARD и наоборот.

имя_курсора

Имя открытого курсора.

Выводимая информация

При успешном выполнении FETCH возвращает метку команды вида

FETCH число

Где число — количество извлеченных строк (возможно, ноль). Обратите внимание, что в psql метка команды на самом деле не выдается, так как psql отображает вместо этого извлеченные строки.

Примечания

Если пользователь намеревается использовать варианты FETCH, отличные от FETCH NEXT или FETCH FORWARD с положительным числом, курсор следует объявить с параметром SCROLL. Для простых запросов QHB позволит выполнять обратное извлечение из курсоров, объявленных без SCROLL, но на такое поведение лучше не полагаться. Если курсор объявлен с параметром NO SCROLL, обратное извлечение не допускается.

Извлечение с ABSOLUTE нисколько не быстрее, чем переход к нужной строке с относительным перемещением: нижележащая реализация в любом случае должна пройти все промежуточные строки. Отрицательные абсолютные извлечения еще хуже: запрос должен быть прочитан до конца, чтобы найти последнюю строку, а затем оттуда вернуться к указанной строке. Тем не менее перемотка к началу запроса (как в случае с FETCH ABSOLUTE 0) идет быстро.

Для определения курсора применяется команда DECLARE, а для изменения его позиции без считывания данных — команда MOVE.

Примеры

Следующий пример демонстрирует перемещение курсора в таблице:

BEGIN WORK;

-- Установка курсора:
DECLARE liahona SCROLL CURSOR FOR SELECT * FROM films;

-- Извлечение первых 5 строк в курсоре liahona:
FETCH FORWARD 5 FROM liahona;

 code  |          title          | did | date_prod  |   kind   |  len
-------+-------------------------+-----+------------+----------+-------
 BL101 | The Third Man           | 101 | 1949-12-23 | Drama    | 01:44
 BL102 | The African Queen       | 101 | 1951-08-11 | Romantic | 01:43
 JL201 | Une Femme est une Femme | 102 | 1961-03-12 | Romantic | 01:25
 P_301 | Vertigo                 | 103 | 1958-11-14 | Action   | 02:08
 P_302 | Becket                  | 103 | 1964-02-03 | Drama    | 02:28

-- Извлечение предыдущей строки:
FETCH PRIOR FROM liahona;

 code  |  title  | did | date_prod  |  kind  |  len
-------+---------+-----+------------+--------+-------
 P_301 | Vertigo | 103 | 1958-11-14 | Action | 02:08

-- Закрытие курсора и завершение транзакции:
CLOSE liahona;
COMMIT WORK;

Совместимость

Стандарт SQL определяет команду FETCH для использования только для встраиваемого SQL. Вариант FETCH, описанный здесь, возвращает данные, как если бы это был результат SELECT, а не помещает их в переменные хоста. В остальном FETCH имеет полную прямую совместимость со стандартом SQL.

Формы FETCH с указанием FORWARD и BACKWARD, а также формы FETCH число и FETCH ALL (в которых FORWARD неявно подразумевается) являются расширениями QHB.

В стандарте SQL перед именем курсора допускается только указание FROM; возможность указать IN или опустить оба указания является расширением.

См. также

CLOSE, DECLARE, MOVE

GRANT

GRANT — определить права доступа

Синтаксис

GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
    [, ...] | ALL [ PRIVILEGES ] }
    ON { [ TABLE ] имя_таблицы [, ...]
         | ALL TABLES IN SCHEMA имя_схемы [, ...] }
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { { SELECT | INSERT | UPDATE | REFERENCES } ( имя_столбца [, ...] )
    [, ...] | ALL [ PRIVILEGES ] ( имя_столбца [, ...] ) }
    ON [ TABLE ] имя_таблицы [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { { USAGE | SELECT | UPDATE }
    [, ...] | ALL [ PRIVILEGES ] }
    ON { SEQUENCE имя_последовательности [, ...]
         | ALL SEQUENCES IN SCHEMA имя_схемы [, ...] }
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { { CREATE | CONNECT | TEMPORARY | TEMP } [, ...] | ALL [ PRIVILEGES ] }
    ON DATABASE имя_базы_данных [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { USAGE | ALL [ PRIVILEGES ] }
    ON DOMAIN имя_домена [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { USAGE | ALL [ PRIVILEGES ] }
    ON FOREIGN DATA WRAPPER имя_обертки_сторонних_данных [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { USAGE | ALL [ PRIVILEGES ] }
    ON FOREIGN SERVER имя_сервера [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { EXECUTE | ALL [ PRIVILEGES ] }
    ON { { FUNCTION | PROCEDURE | ROUTINE } имя_подпрограммы [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] [, ...]
         | ALL { FUNCTIONS | PROCEDURES | ROUTINES } IN SCHEMA имя_схемы [, ...] }
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { USAGE | ALL [ PRIVILEGES ] }
    ON LANGUAGE имя_языка [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }
    ON LARGE OBJECT oid_большого_объекта [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] }
    ON SCHEMA имя_схемы [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { CREATE | ALL [ PRIVILEGES ] }
    ON TABLESPACE имя_табличного_пространства [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT { USAGE | ALL [ PRIVILEGES ] }
    ON TYPE имя_типа [, ...]
    TO описание_роли [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY описание_роли ]

GRANT имя_роли [, ...] TO описание_роли [, ...]
    [ WITH ADMIN OPTION ]
    [ GRANTED BY описание_роли ]

где описанием_роли может быть:

    [ GROUP ] имя_роли
  | PUBLIC
  | CURRENT_ROLE
  | CURRENT_USER
  | SESSION_USER

Описание

Команда GRANT имеет два основных варианта: один, предоставляющий права на объекты базы данных (таблицу, столбец, представление, стороннюю таблицу, последовательность, базу данных, обертку сторонних данных, сторонний сервер, функцию, процедуру, процедурный язык, схему или табличное пространство), и второй, предоставляющий членство в роли. Эти варианты во многом похожи, но имеют достаточно отличий, чтобы быть описанными отдельно.

GRANT для объектов баз данных

Этот вариант команды GRANT предоставляет определенные права на объект базы данных для одной или нескольких ролей. Эти права добавляются к уже предоставленным, если таковые имеются.

Ключевое слово PUBLIC показывает, что права должны быть предоставлены всем ролям, включая те, что могут быть созданы позже. PUBLIC может рассматриваться как неявно определенная группа, которая всегда включает все роли. Любая конкретная роль будет иметь сумму прав, предоставленных непосредственно ей, прав, предоставленных любой роли, членом которой она в настоящее время является, и прав, предоставленных PUBLIC.

Если указано WITH GRANT OPTION, получатель права может в свою очередь предоставить его другим лицам. Без этого указания получатель не может этого сделать. Группе PUBLIC право передачи права предоставить нельзя.

Если указано GRANTED BY, то заданным праводателем должен быть текущий пользователь. В настоящее время это предложение присутствует в такой форме только для совместимости со стандартом SQL.

Нет необходимости предоставлять права владельцу объекта (как правило, пользователю, который его создал), так как владелец имеет все права по умолчанию. (Однако в целях обеспечения безопасности владелец может отказаться от некоторых своих прав.)

Право удалить объект или каким-либо образом изменить его определение в качестве предоставляемого права не рассматривается; оно присуще владельцу и не может быть предоставлено или отозвано. (Однако схожий эффект можно получить, предоставив или отозвав членство в роли, которой принадлежит объект; см. ниже.) Также владелец неявно имеет возможность распоряжаться всеми правами для своего объекта.

Возможные права:

SELECT

INSERT

UPDATE

DELETE

TRUNCATE

REFERENCES

TRIGGER

CREATE

CONNECT

TEMPORARY

EXECUTE

USAGE

Особые типы прав, определенные в разделе Права:

TEMP
Альтернативное написание для TEMPORARY.

ALL PRIVILEGES
Предоставление всех прав, доступных для типа объекта. В QHB ключевое слово PRIVILEGES является необязательным, хотя оно и требуется в стандарте SQL.

Синтаксис FUNCTION работает для простых, агрегатных и оконных функций, но не для процедур; для последних предназначен синтаксис PROCEDURE. В качестве альтернативы используйте ROUTINE, чтобы ссылаться на функцию, агрегатную функцию, оконную функцию или процедуру независимо от ее точного типа.

Кроме того, существует возможность предоставления прав на все объекты одного типа в рамках одной или нескольких схем. В настоящее время эта возможность поддерживается только для таблиц, последовательностей, функций и процедур. ALL TABLES также воздействует на представления и сторонние таблицы, аналогично команде GRANT для конкретного объекта. ALL FUNCTIONS также воздействует на агрегатные и оконные функции, но не на процедуры — опять же аналогично GRANT для конкретного объекта. Чтобы включить сюда процедуры, воспользуйтесь формой ALL ROUTINES.

GRANT для ролей

Этот вариант команды GRANT предоставляет членство в роли для одной или нескольких других ролей. Членство в роли имеет важное значение, поскольку оно передает предоставленные роли права каждому из ее членов.

Если указано WITH ADMIN OPTION, этот член роли может, в свою очередь, предоставлять членство в роли другим пользователям, а также отменять членство в роли. Без режима администратора обычные пользователи не могут этого сделать. Роль не имеет права WITH ADMIN OPTION для самой себя, но может предоставить или отозвать членство в самой себе из сеанса базы данных, в котором пользователь сеанса соответствует этой роли. Суперпользователи баз данных могут предоставлять или отменять членство в любой роли кому угодно. Роли с правом CREATEROLE могут предоставлять или отменять членство в любой роли, которая не является суперпользователем.

Если указано GRANTED BY, предоставление права записывается как совершенное конкретной ролью. Этой возможностью могут пользоваться только суперпользователи баз данных, за исключением тех случаев, когда в этом указании называется та же роль, что выполняет команду.

В отличие от прав, членство в роли не может быть предоставлено группе PUBLIC. Обратите также внимание, что эта форма команды не допускает избыточное слово GROUP в описании_роли.

Примечания

Для отмены прав воспользуйтесь командой REVOKE.

В QHB понятия пользователей и групп были объединены в единый вид сущности, называемый ролью. Поэтому больше нет необходимости использовать ключевое слово GROUP, чтобы определить, является ли участник пользователем или группой. Слово GROUP в этой команде по-прежнему допустимо, но лишено смысловой нагрузки.

Пользователь может выполнить команды SELECT, INSERT и подобные им со столбцом таблицы, если обладает этим правом для конкретного столбца или для всей таблицы. Предоставление права на уровне таблицы, а затем отмена права для одного из столбцов этой таблицы не даст желаемого эффекта: операция с правами на уровне столбцов не затронет право на уровне таблицы.

Если пользователь, не являющийся владельцем объекта, попытается назначить право на этот объект (с помощью GRANT), то команда завершится ошибкой, если у этого пользователя нет никаких прав на объект. Если же пользователь имеет некоторые права, команда будет выполнена, но предоставит только те права, которые даны ему с правом передачи. Формы GRANT ALL PRIVILEGES выдадут предупреждающее сообщение, если у пользователя вообще нет никаких прав передачи, тогда как другие формы выдадут предупреждение, если пользователь не имеет прав распоряжаться конкретными правами, указанными в команде. (В принципе, эти утверждения применимы и к владельцу объекта, но поскольку владелец всегда рассматривается как обладатель всех прав, такие ситуации невозможны.)

Следует отметить, что суперпользователи баз данных могут обращаться ко всем объектам, независимо от настроек прав на объект. Это сравнимо с правами пользователя root в системе Unix. Как и в случае с root, под суперпользователем работать неразумно, за исключением тех случаев, когда это абсолютно необходимо.

Если суперпользователь решает выполнить команду GRANT или REVOKE, эта команда выполняется так, как если бы она выполнялась владельцем затрагиваемого объекта. В частности, права, предоставленные с помощью такой команды, будут выглядеть так, будто их предоставил владелец объекта. (Членство в роли будет предоставлено словно бы от имени самой этой роли.)

Кроме того, GRANT и REVOKE могут выполняться ролью, которая не является владельцем затрагиваемого объекта, но является членом роли, которая владеет объектом, или роли, которая обладает правами доступа WITH GRANT OPTION для этого объекта. В этом случае права будут записаны как предоставленные ролью, которая фактически владеет объектом или владеет правами WITH GRANT OPTION. Например, если таблица t1 принадлежит роли g1, членом которой является u1, то u1 может предоставлять права на t1 роли u2, но эти права будут казаться предоставленными непосредственно ролью g1. Любой другой член роли g1 сможет позже отозвать их.

Если роль, выполняющая команду GRANT, получила необходимые права косвенно путем членства в нескольких ролях, то не определено, какая именно роль будет записана в качестве предоставившей право. В таких случаях лучше всего использовать команду SET ROLE, чтобы переключиться на конкретную роль, которую вы хотите назначить в качестве выполняющей GRANT.

Предоставление прав на таблицу не означает автоматического расширения прав на любые используемые этой таблицей последовательности, включая те из них, что связаны со столбцами SERIAL. Разрешения на последовательности должны быть установлены отдельно.

Дополнительную информацию о конкретных типах прав, а также о том, как просмотреть права на объекты, см. в разделе Права.

Примеры

Разрешить всем пользователям добавлять записи в таблицу films:

GRANT INSERT ON films TO PUBLIC;

Предоставить пользователю manuel все права на представление kinds:

GRANT ALL PRIVILEGES ON kinds TO manuel;

Учтите, что если эту команду выполнит суперпользователь или владелец представления kinds, она действительно предоставит все права, но если ее выполнит обычный пользователь, она предоставит только те права, которые даны этому пользователю с правом передачи.

Предоставление членства в роли admins пользователя joe:

GRANT admins TO joe;

Совместимость

Согласно стандарту SQL, слово PRIVILEGES в указании ALL PRIVILEGES является обязательным. Стандарт SQL не поддерживает установку в одной команде прав на более чем один объект.

QHB позволяет владельцу объекта отозвать свои обычные права: например, владелец таблицы может сделать ее доступной ему же только для чтения, отозвав свои собственные права INSERT, UPDATE, DELETE и TRUNCATE. В соответствии со стандартом SQL, это невозможно. Причина заключается в том, что QHB рассматривает права владельца как предоставленные им самому себе, поэтому владелец также может и отозвать их. В стандарте SQL права владельца предоставляются предполагаемой сущностью «_SYSTEM». Так как владелец объекта отличается от «_SYSTEM», лишить себя этих прав он не может.

В соответствии со стандартом SQL, право с правом передачи можно дать группе PUBLIC; QHB поддерживает его только при предоставлении ролям права передачи прав.

Стандарт SQL позволяет указывать в параметре GRANTED BY только значения CURRENT_USER или CURRENT_ROLE. Остальные варианты являются расширениями QHB.

Стандарт SQL предусматривает распространение права USAGE на другие виды объектов: наборы символов, правила сортировки, преобразования.

В стандарте SQL последовательности имеют лишь право USAGE, которое контролирует использование выражения NEXT VALUE FOR, равнозначное функции nextval в QHB. Права SELECT и UPDATE для последовательностей являются расширениями QHB. Применение права USAGE на последовательности к функции currval также относится к расширениям QHB (как и сама функция).

Права на базы данных, табличные пространства, схемы и языки являются расширениями QHB.

См. также

REVOKE, ALTER DEFAULT PRIVILEGES

IMPORT FOREIGN SCHEMA

IMPORT FOREIGN SCHEMA — импортировать определения таблиц со стороннего сервера

Синтаксис

IMPORT FOREIGN SCHEMA удаленная_схема
    [ { LIMIT TO | EXCEPT } ( имя_таблицы [, ...] ) ]
    FROM SERVER имя_сервера
    INTO локальная_схема
    [ OPTIONS ( параметр 'значение' [, ... ] ) ]

Описание

Команда IMPORT FOREIGN SCHEMA создает сторонние таблицы, которые представляют таблицы, существующие на стороннем сервере. Новые сторонние таблицы будут принадлежать пользователю, выполнившему эту команду, и создаются с корректными определениями столбцов и параметрами, соответствующими удаленным таблицам.

По умолчанию импортируются все таблицы и представления, существующие в определенной схеме на стороннем сервере. При желании список таблиц можно ограничить указанным подмножеством или исключить определенные таблицы. Все новые сторонние таблицы создаются в целевой схеме, которая уже должна существовать.

Чтобы использовать IMPORT FOREIGN SCHEMA, необходимо иметь право USAGE на стороннем сервере, а также право CREATE в целевой схеме.

Параметры

удаленная_схема

Удаленная схема, предназначенная для импорта из нее. Что именно представляет собой удаленная схема, зависит от применяемой обертки сторонних данных.

LIMIT TO ( имя_таблицы [, ...] )

Импортировать только сторонние таблицы, соответствующие одному из указанных имен таблиц. Другие таблицы, существующие в сторонней схеме, будут игнорироваться.

EXCEPT ( имя_таблицы [, ...] )

Исключить указанные сторонние таблицы из импорта. Все существующие в сторонней схеме таблицы, за исключением перечисленных здесь, будут импортированы.

имя_сервера

Сторонний сервер, предназначенный для импорта из него.

локальная_схема

Схема, в которой будут созданы импортируемые сторонние таблицы.

OPTIONS ( параметр 'значение' [, ...] )

Параметры, которые будут использоваться во время импорта. Допустимые имена и значения параметров являются специфичными для каждой обертки сторонних данных.

Примеры

Импорт определений таблиц из удаленной схемы foreign_films на сервере film_server с созданием сторонних таблиц в локальной схеме films:

IMPORT FOREIGN SCHEMA foreign_films
    FROM SERVER film_server INTO films;

Та же операция, но импортируются только таблицы actors и directors (если они существуют):

IMPORT FOREIGN SCHEMA foreign_films LIMIT TO (actors, directors)
    FROM SERVER film_server INTO films;

Совместимость

Команда IMPORT FOREIGN SCHEMA соответствует стандарту SQL за исключением предложения OPTIONS, являющегося расширением QHB.

См. также

CREATE FOREIGN TABLE, CREATE SERVER

INSERT

INSERT — создать новые строки в таблице

Синтаксис

[ WITH [ RECURSIVE ] запрос_WITH [, ...] ]
INSERT INTO имя_таблицы [ AS псевдоним ] [ ( имя_столбца [, ...] ) ]
    [ OVERRIDING { SYSTEM | USER } VALUE ]
    { DEFAULT VALUES | VALUES ( { выражение | DEFAULT } [, ...] ) [, ...] | запрос }
    [ ON CONFLICT [ цель_конфликта ] действие_при_конфликте ]
    [ RETURNING * | выражение_результата [ [ AS ] имя_результата ] [, ...] ]

где целью_конфликта может быть:

    ( { имя_столбца_индекса | ( выражение_индекса ) } [ COLLATE правило_сортировки ] [ класс_операторов ] [, ...] ) [ WHERE предикат_индекса ]
    ON CONSTRAINT имя_ограничения

и действием_при_конфликте может быть:

    DO NOTHING
    DO UPDATE SET { имя_столбца = { выражение | DEFAULT } |
                    ( имя_столбца [, ...] ) = [ ROW ] ( { выражение | DEFAULT } [, ...] ) |
                    ( имя_столбца [, ...] ) = ( подзапрос_SELECT )
                  } [, ...]
              [ WHERE условие ]

Описание

Команда INSERT добавляет новые строки в таблицу. Можно добавить одну и более строк, заданных выражениями значений, или ноль и более строк, полученных в результате запроса.

Имена целевых столбцов можно перечислять в любом порядке. Если список имен столбцов вообще не задан, то по умолчанию используются все столбцы таблицы в их объявленном порядке; либо первые N из имен столбцов, если в предложении VALUES или запросе предоставляется только N столбцов. Значения, получаемые от предложения VALUES или запроса, связываются с явным или неявным списком столбцов слева направо.

Каждый столбец, не присутствующий в явном или неявном списке столбцов, будет заполнен значением по умолчанию (если оно объявлено) или NULL (если значения по умолчанию нет).

Если выражение для любого столбца имеет неправильный тип данных, будет предпринята попытка автоматического преобразования типа.

Команда INSERT с таблицами без уникальных индексов не будет блокироваться параллельными операциями. Таблицы с уникальными индексами могут блокироваться, если в параллельных сеансах выполняются действия, которые блокируют или изменяют строки, соответствующие добавляемым значениям уникального индекса; подробнее это описывается в разделе Проверки уникальности индекса. Для указания альтернативного действия, заменяющего выдачу ошибки при нарушении ограничения уникальности или исключающего ограничения, можно использовать предложение ON CONFLICT. (См. Предложение ON CONFLICT ниже).

С необязательным предложением RETURNING команда INSERT вычислит и возвратит значения каждой строки, фактически добавленной (или измененной, если было использовано предложение ON CONFLICT DO UPDATE). Это в первую очередь полезно для получения значений, присвоенных по умолчанию, таких как последовательный номер записи. Однако в этом предложении можно задать любое выражение со столбцами таблицы. Список RETURNING идентичен синтаксису списка результатов SELECT. Будут возвращены только те строки, которые были успешно добавлены или изменены. Например, если строка была заблокирована, но не изменена из-за того, что условие в предложении ON CONFLICT DO UPDATE ... WHERE не было удовлетворено, строка не будет возвращена.

Чтобы добавлять строки в таблицу, необходимо иметь для нее право INSERT. Если присутствует предложение ON CONFLICT DO UPDATE, для такой таблицы также потребуется право UPDATE.

Если указан список столбцов, достаточно иметь право INSERT только для перечисленных столбцов. По аналогии с предложением ON CONFLICT DO UPDATE требуется право UPDATE только для перечисленных обновляемых столбцов. Однако помимо этого предложение ON CONFLICT DO UPDATE требует права SELECT для всех столбцов, значения которых считываются в выражениях ON CONFLICT DO UPDATE или в условии.

Для использования предложения RETURNING требуется право SELECT для всех упомянутых в RETURNING столбцов. Если для добавления строк используется запрос, то, разумеется, нужно иметь право SELECT для всех перечисленных в данном запросе таблиц или столбцов.

Параметры

Добавление

В этом разделе описываются параметры, которые могут использоваться только при добавлении новых строк. Параметры, используемые исключительно с предложением ON CONFLICT, описываются отдельно.

запрос_WITH

Предложение WITH позволяет указать один или несколько подзапросов, на которые можно ссылаться по имени в запросе INSERT. Дополнительную информацию см. в разделе Запросы WITH и на справочной странице команды SELECT.

Сам заданный запрос (оператор SELECT) также может содержать предложение WITH. В этом случае в запросе можно обращаться к обоим наборам запрос_WITH, но у второго будет приоритет, так как он вложен ближе.

имя_таблицы

Имя существующей таблицы (может быть дополнено схемой).

псевдоним

Альтернативное имя для имени_таблицы. При указании псевдонима тот полностью скрывает фактическое имя таблицы. Это особенно полезно, когда в предложении ON CONFLICT DO UPDATE фигурирует таблица с именем excluded, поскольку в противном случае это имя будет присвоено специальной таблице, представляющей строки, которые предназначены для добавления.

имя_столбца

Имя столбца в таблице с именем имя_таблицы. При необходимости имя столбца может быть дополнено именем вложенного поля или индексом в массиве. (Когда данные добавляются только в некоторые поля столбца составного типа, другие поля заполняются значением NULL.) Обращаясь к столбцу в предложении ON CONFLICT DO UPDATE, не следует включать в описание целевого столбца имя таблицы. Например, запись INSERT INTO table_name ... ON CONFLICT DO UPDATE SET table_name.col = 1 недопустима (это соответствует общему поведению команды UPDATE ).

OVERRIDING SYSTEM VALUE

При указании этого предложения все значения, предоставляемые для столбцов идентификации, будут переопределять значения, генерируемые последовательностью по умолчанию.

Для столбца идентификации, определенного как GENERATED ALWAYS, добавление явного значения (кроме DEFAULT) является ошибкой, если не указано OVERRIDING SYSTEM VALUE или OVERRIDING USER VALUE. (Для столбца идентификации, определенного как GENERATED BY DEFAULT, OVERRIDING SYSTEM VALUE является обычным поведением, поэтому его указание ни на что не влияет, но QHB допускает его в качестве расширения.)

OVERRIDING USER VALUE

При указании этого предложения игнорируются любые значения, предоставляемые для столбцов идентификации, и применяются значения, генерируемые последовательностью по умолчанию.

Это предложение полезно, например, при копировании значений между таблицами. Команда INSERT INTO tbl2 OVERRIDING USER VALUE SELECT \* FROM tbl1 скопирует из tbl1 все столбцы, которые не являются столбцами идентификации в tbl2, тогда как значения для столбцов идентификации в tbl2 будут генерироваться последовательностями, связанными с tbl2.

DEFAULT VALUES

Все столбцы будут заполнены значениями по умолчанию, как при для явном указании DEFAULT для каждого столбца. (Предложение OVERRIDING в этой форме не допускается.)

выражение

Выражение или значение для присвоения соответствующему столбцу.

DEFAULT

Соответствующий столбец будет заполнен его значением по умолчанию. Столбец идентификации получит новое значение, сгенерированное связанной последовательностью. Для генерируемого столбца указание этого параметра допустимо, но он всего лишь описывает обычное поведение при вычислении значения столбца из генерирующего его выражения.

запрос

Запрос (оператор SELECT), который выдает строки для добавления в таблицу. Описание его синтаксиса см. на справочной странице команды SELECT.

выражение_результата

Выражение, вычисляемое и возвращаемое командой INSERT после каждой строки, которая добавляется или изменяется. В этом выражении можно использовать любые имена столбцов таблицы с именем имя_таблицы. Чтобы вернуть все столбцы добавленных или измененных строк, нужно написать *.

имя_результата

Имя, используемое для возвращаемого столбца.

Предложение ON CONFLICT

Необязательное предложение ON CONFLICT задает альтернативное действие, заменяющее выдачу ошибки при нарушении уникальности или исключающего ограничения. Для каждой отдельной предложенной для добавления строки либо продолжается добавление, либо, если нарушается решающее ограничение или индекс, указанный как цель_конфликта, выполняется альтернативное действие_при_конфликте. Вариант ON CONFLICT DO NOTHING в качестве альтернативного действия просто отменяет добавление строки. Вариант ON CONFLICT DO UPDATE изменяет существующую строку, которая конфликтует со строкой, предложенной для добавления.

Задаваемая цель_конфликта может выбирать уникальный индекс. В этом случае она состоит из одного или несколько столбцов имя_столбца_индекса) и/или выражений выражение_индекса, а также необязательного предиката_индекса. В качестве решающих индексов выводятся (выбираются) все уникальные индексы в таблице имя_таблицы, которые, без учета порядка столбцов, содержат именно те столбцы/выражения, что определяют цель_конфликта. Если указывается предикат_индекса, он должен, в качестве дополнительного требования выбора, удовлетворять решающим индексам. Обратите внимание: это означает, что не частичный уникальный индекс (уникальный индекс без предиката) будет выбран (и, следовательно, будет использован в ON CONFLICT), если он удовлетворяет всем остальным доступным критериям. Если попытка выбора неудачна, выдается ошибка.

ON CONFLICT DO UPDATE гарантирует атомарный результат команды INSERT или UPDATE; при отсутствии внешней ошибки даже при высокой параллельной активности гарантируется один из этих двух результатов. Эта операция также известна как UPSERT — «UPDATE или INSERT».

цель_конфликта

Определяет, какие именно конфликты в ON CONFLICT будут решаться альтернативным действием, устанавливая решающие индексы. Это указание позволяет выполнять выбор уникального индекса либо явно задает имя ограничения. Для ON CONFLICT DO NOTHING необязательно указывать цель_конфликта; в этом случае игнорироваться будут все конфликты с любыми ограничениями (и уникальными индексами). Для ON CONFLICT DO UPDATE цель_конфликта нужно указывать обязательно.

действие_при_конфликте

Параметр действие_при_конфликте задает альтернативное действие для ON CONFLICT. Это может быть предложение DO NOTHING (не делать ничего) или DO UPDATE (произвести изменение), где указываются точные детали операции UPDATE, выполняемой в случае конфликта. Предложения SET и WHERE в ON CONFLICT DO UPDATE могут ссылаться на существующую строку с использованием имени (или псевдонима) таблицы, а также на строки, предложенные для добавления с помощью специальной таблицы excluded. Для чтения столбцов excluded необходимо иметь право SELECT для соответствующих столбцов в целевой таблице.

Обратите внимание, что эффекты действий всех триггеров BEFORE INSERT уровня строк отражаются в значениях excluded, так как эти эффекты могут привести к исключению строки из добавления.

имя_столбца_индекса

Имя столбца в таблице имя_таблицы. Используется для выбора решающих индексов. Задается в формате CREATE INDEX. Чтобы запрос выполнился, для столбца имя_столбца_индекса требуется право SELECT.

выражение_индекса

Подобно указанию имя_столбца_индекса, но используется для выбора индекса по выражениям со столбцами таблицы имя_таблицы, обозначенными в определениях индексов (а не по простым столбцам). Задается в формате CREATE INDEX. Для всех столбцов, задействованных в выражении_индекса, требуется право SELECT.

правило_сортировки

При указании устанавливает, что соответствующий столбец имя_столбца_индекса или выражение_индекса должен использовать определенный порядок сортировки для сопоставления во время выбора индекса. Обычно этот параметр опускается, так как правила сортировки в большинстве случаев не влияют на то, произойдет ли нарушение ограничений. Задается в формате CREATE INDEX.

класс_операторов

При указании устанавливает, что соответствующий столбец имя_столбца_индекса или выражение_индекса должен использовать определенный класс операторов для сопоставления во время выбора индекса. Обычно этот параметр опускается, поскольку семантика равенства все равно часто равнозначна в разных классах операторов типа, либо поскольку достаточно полагаться на то, что заданные уникальные индексы имеют адекватное определение равенства. Задается в формате CREATE INDEX.

предикат_индекса

Используется для разрешения выбора частичных уникальных индексов. Могут быть выбраны любые индексы, удовлетворяющие этому предикату (причем в действительности им необязательно быть частичными индексами). Задается в формате CREATE INDEX. Для всех столбцов, задействованных в предикате_индекса, требуется право SELECT.

имя_ограничения

Явно указывает решающее ограничение по имени, заменяя неявный выбор ограничения или индекса.

условие

Выражение, которое выдает значение типа boolean. Будут обновлены только те строки, для которых это выражение выдает true, хотя при выборе операции ON CONFLICT DO UPDATE все строки будут заблокированы. Обратите внимание, что условие вычисляется последним, после определения конфликта в качестве кандидата на обновление.
.

Обратите внимание, что ограничения исключения не могут быть решающими в ON CONFLICT DO UPDATE. Во всех случаях в качестве решающих поддерживаются только неоткладываемые (NOT DEFERRABLE) ограничения и уникальные индексы.

Команда INSERT с предложением ON CONFLICT DO UPDATE является «детерминированной». Это означает, что команде не разрешено влиять на одну существующую строку более одного раза; в случае такой ситуации возникнет ошибка нарушения мощности множества. Строки, предлагаемые для добавления, не должны дублировать друг друга с точки зрения атрибутов, обусловленных решающим индексом или ограничением.

Обратите внимание, что в настоящее время это не работает для предложения ON CONFLICT DO UPDATE команды INSERT, применяемой к партиционированной таблице с целью изменения ключа разбиения в конфликтующей строке образом, требующим чтобы эта строка была перенесена в новую партицию.

Совет
Часто предпочтительнее вместо непосредственного указания ограничения в виде ON CONFLICT ON CONSTRAINT имя_ограничения использовать выбор уникального индекса. Выбор продолжит работать правильно, когда нижележащий индекс будет заменен другим более или менее равнозначным индексом методом наложения, например, при использовании CREATE UNIQUE INDEX ... CONCURRENTLY с последующим удалением заменяемого индекса.

Выводимая информация

После успешного завершения команда INSERT возвращает метку команды в виде

INSERT oid число

Здесь число является количеством добавленных или измененных строк. Поле oid всегда содержит 0 (раньше это был OID, назначенный добавленной строке, если число равнялось 1 и целевая таблица была объявлена указанием WITH OIDS, и 0 в противном случае, но создание таблицы WITH OIDS больше не поддерживается).

Если команда INSERT содержит предложение RETURNING, результат будет похож на результат команды SELECT (с теми же столбцами и значениями, что содержатся в списке RETURNING), полученный для строк, добавленных или измененных этой командой.

Примечания

Если указанная таблица является партиционированной, то каждая строка направляется в соответствующую партицию и добавляется в нее. Если указанная таблица является партицией и какая-либо из входных строк нарушает ограничение этой партиции, выдается ошибка.

Примеры

Добавление одной строки в таблицу films:

INSERT INTO films VALUES
    ('UA502', 'Bananas', 105, '1971-07-13', 'Comedy', '82 minutes');

В этом примере столбец len опускается и, как следствие, получает значение по умолчанию:

INSERT INTO films (code, title, did, date_prod, kind)
    VALUES ('T_601', 'Yojimbo', 106, '1961-06-16', 'Drama');

В этом примере для столбца с датой используется предложение DEFAULT, а не указание значения:

INSERT INTO films VALUES
    ('UA502', 'Bananas', 105, DEFAULT, 'Comedy', '82 minutes');
INSERT INTO films (code, title, did, date_prod, kind)
    VALUES ('T_601', 'Yojimbo', 106, DEFAULT, 'Drama');

Добавление строки, полностью состоящей из значений по умолчанию:

INSERT INTO films DEFAULT VALUES;

Добавление нескольких строк с использованием многострочного синтаксиса VALUES:

INSERT INTO films (code, title, did, date_prod, kind) VALUES
    ('B6717', 'Tampopo', 110, '1985-02-10', 'Comedy'),
    ('HG120', 'The Dinner Game', 140, DEFAULT, 'Comedy');

В этом примере в таблицу films добавляются некоторые строки из таблицы tmp_films, имеющей ту же структуру столбцов, что и films:

INSERT INTO films SELECT * FROM tmp_films WHERE date_prod < '2004-05-07';

Этот пример демонстрирует добавление данных в столбцы с типом массива:

-- Создание пустого поля 3x3 для игры в крестики-нолики
INSERT INTO tictactoe (game, board[1:3][1:3])
    VALUES (1, '{{" "," "," "},{" "," "," "},{" "," "," "}}');
-- На самом деле индексы в предыдущем примере не нужны
INSERT INTO tictactoe (game, board)
    VALUES (2, '{{X," "," "},{" ",O," "},{" ",X," "}}');

Добавление одной строки в таблицу distributors и получение последовательного номера, сгенерированного благодаря указанию DEFAULT:

INSERT INTO distributors (did, dname) VALUES (DEFAULT, 'XYZ Widgets')
   RETURNING did;

Увеличение счетчика продаж для продавца, занимающегося компанией Acme Corporation, и сохранение всей измененной строки вместе с текущим временем в таблице журнала:

WITH upd AS (
  UPDATE employees SET sales_count = sales_count + 1 WHERE id =
    (SELECT sales_person FROM accounts WHERE name = 'Acme Corporation')
    RETURNING *
)
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;

Добавление дистрибьюторов или изменение данных о них должным образом. Предполагается, что в таблице определен уникальный индекс, ограничивающий значения в столбце did. Обратите внимание, что для обращения к значениям, изначально предлагаемым для добавления, используется специальная таблица excluded:

INSERT INTO distributors (did, dname)
    VALUES (5, 'Gizmo Transglobal'), (6, 'Associated Computing, Inc')
    ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname;

Добавление дистрибьютора или бездействие для строк, предложенных для добавления, если уже есть существующая исключающая строка (строка, содержащая конфликтующие значения в столбце или столбцах после срабатывания триггеров перед добавлением строки). В данном примере предполагается, что определен уникальный индекс, ограничивающий значения в столбце did:

INSERT INTO distributors (did, dname) VALUES (7, 'Redline GmbH')
    ON CONFLICT (did) DO NOTHING;

Добавление дистрибьюторов или изменение данных о них должным образом. В данном примере предполагается, что в таблице определен уникальный индекс, ограничивающий значения в столбце did. Предложение WHERE позволяет ограничить набор фактически изменяемых строк (однако любая существующая строка, не подлежащая изменению, все равно будет заблокирована):

-- Не менять данные существующих дистрибьюторов в зависимости от почтового индекса
INSERT INTO distributors AS d (did, dname) VALUES (8, 'Anvil Distribution')
    ON CONFLICT (did) DO UPDATE
    SET dname = EXCLUDED.dname || ' (formerly ' || d.dname || ')'
    WHERE d.zipcode <> '21201';

-- Указать имя ограничения непосредственно в операторе (связанный индекс
-- применяется для принятия решения о выполнении действия DO NOTHING)
INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
    ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING;

Добавление дистрибьютора, если возможно; в противном случае бездействие (DO NOTHING). В данном примере предполагается, что в таблице определен уникальный индекс, ограничивающий значения в столбце did по подмножеству строк, в котором логический столбец is_active вычисляется в true:

-- Этот оператор может выбрать частичный уникальный индекс по "did"
-- с предикатом "WHERE is_active", а может, просто использовать
-- обычное ограничение уникальности по столбцу "did"
INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
    ON CONFLICT (did) WHERE is_active DO NOTHING;

Совместимость

Команда INSERT соответствует стандарту SQL за исключением того, что предложение RETURNING является расширением QHB, как и возможность использовать WITH с INSERT, а также возможность указать альтернативное действие с помощью ON CONFLICT. Кроме того, ситуация, при которой список имен столбцов опускается, но не все столбцы получают значения из предложения VALUES или запроса, стандартом не допускается.

Стандарт SQL определяет, что предложение OVERRIDING SYSTEM VALUE можно указать только в том случае, если существует столбец идентификации, для которого всегда генерируется значение. QHB разрешает это предложение в любом случае и игнорирует его, если оно не применимо.

Возможные ограничения по использованию предложения запрос описаны на справочной странице команды SELECT.

LISTEN

LISTEN — перехватывать уведомления

Синтаксис

LISTEN канал

Описание

Команда LISTEN регистрирует текущий сеанс в качестве адресата на канале уведомлений с именем канал. Если текущий сеанс уже зарегистрирован как адресат для этого канала уведомлений, ничего не происходит.

Всякий раз, когда вызывается команда NOTIFY канал (в этом сеансе или в другом, подключенном к той же базе данных), все сеансы, которые в настоящий момент настроены на этот канал уведомлений, получают уведомление и, в свою очередь, передают его своему подключенному клиентскому приложению.

Можно отменить регистрацию сеанса для данного канала уведомлений с помощью команды UNLISTEN. Регистрация прослушивания канала автоматически очищается по окончании сеанса.

Метод, который клиентское приложение должно использовать для обнаружения событий уведомления, зависит от того, какой программный интерфейс приложения QHB оно использует. С библиотекой libpq приложение выполняет команду LISTEN как обычную команду SQL, а затем должно периодически вызывать функцию PQnotifies, чтобы узнать, были ли получены какие-либо уведомления. Другие интерфейсы, например libpgtcl, предоставляют более высокоуровневые методы для обработки событий уведомлений; более того, с libpgtcl программисту приложения даже не нужно запускать LISTEN или UNLISTEN напрямую. Дополнительную информацию см. в документации по используемому интерфейсу.

Параметры

канал

Наименование канала уведомления (любой идентификатор).

Примечания

Команда LISTEN вступает в силу при фиксации транзакции. Если LISTEN или UNLISTEN выполняется в транзакции, которая позже откатывается, набор прослушиваемых каналов уведомлений остается неизменным.

Транзакция, которая выполнила LISTEN, не может быть подготовлена к двухфазной фиксации.

При первой настройке сеанса на перехват уведомлений существует условие гонки: если параллельно фиксирующиеся транзакции посылают уведомления о событии, какое из них получит свежеподписавшийся сеанс? Ответ: сеанс получит все события, зафиксированные после момента фиксации транзакции, регистрирующей подписку. Но эти уведомления будут отражать состояния базы данных с некоторым запозданием по сравнению с тем, которое эта транзакция могла бы наблюдать в своих запросах. Это приводит к следующему правилу использования LISTEN: сначала выполнить (и зафиксировать!) эту команду, затем в новой транзакции проверить состояние базы данных согласно требованиям логики приложения, затем воспользоваться уведомлениями для выявления последующих изменений состояния базы данных. Первые несколько уведомлений могут относиться к изменениям, которые уже наблюдались при исходной проверке базы данных, но обычно в этом нет вреда.

На справочной странице команды NOTIFY можно найти более подробный разбор использования команд LISTEN and NOTIFY.

Примеры

Настройка и выполнение последовательности ожидания/приема уведомления в psql:

LISTEN virtual;
NOTIFY virtual;
Asynchronous notification "virtual" received from server process with PID 8448.

Совместимость

В стандарте SQL нет команды LISTEN.

См. также

NOTIFY, UNLISTEN

LOAD

LOAD — загрузить файл разделяемой библиотеки

Синтаксис

LOAD 'имя_файла'

Описание

Команда LOAD загружает файл разделяемой библиотеки в адресное пространство сервера QHB. Если файл уже загружен, команда ничего не делает. Файлы разделяемой библиотеки, содержащие функции на C/RUST, автоматически загружаются при первом вызове одной из их функций. Поэтому явно выполнять LOAD обычно требуется только для загрузки библиотек, которые изменяют поведение сервера, внедряя свои обработчики, а не предоставляют набор функций.

Имя файла библиотеки обычно задается как простое имя файла, которое ищется в пути поиска библиотек сервера (задается параметром dynamic_library_path). Кроме того, оно может быть задано в качестве полного имени пути. В любом случае стандартное расширение имени файла разделяемой библиотеки платформы можно опустить. Дополнительную информацию см. в подразделе Динамическая загрузка.

Не суперпользователи могут применять LOAD только для загрузки файлов библиотек, расположенных в $libdir/plugins/ — указываемое имя_файла должно начинаться именно с этой строки. (Ответственность за установку только «безопасных» библиотек лежит на администраторе базы данных.)

Совместимость

Команда LOAD является расширением QHB.

См. также

CREATE FUNCTION

LOCK

LOCK — заблокировать таблицу

Синтаксис

LOCK [ TABLE ] [ ONLY ] имя [ * ] [, ...][ IN  режим_блокировки ] [ NOWAIT ]

где режимом_блокировки может быть:

    ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE
    | SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE

Описание

Команда LOCK TABLE получает блокировку уровня таблицы, при необходимости ожидая освобождения всех конфликтующих блокировок. Если указано NOWAIT, LOCK TABLE не ожидает освобождения желаемой блокировки: если ее нельзя получить немедленно, команда прерывается и выдается ошибка. После получения блокировка удерживается до конца текущей транзакции. (Команды UNLOCK TABLE не существует; блокировки всегда освобождаются в конце транзакции.)

Когда блокируется представление, все отношения, находящиеся в запросе определения представления, также рекурсивно блокируются с тем же режимом блокировки.

При автоматическом получении блокировок для команд, ссылающихся на таблицы, QHB всегда использует наименее ограничительный режим блокировки. Команда LOCK TABLE предназначена для случаев, когда может потребоваться более строгая блокировка. Например, предположим, что приложение выполняет транзакцию на уровне изоляции READ COMMITTED и должно обеспечить неизменность данных в таблице на протяжении всей транзакции. Для этого можно перед запросом получить для таблицы блокировку в режиме SHARE. Это предотвратит параллельные изменения данных и обеспечит стабильное представление зафиксированных данных при последующих чтениях таблицы, потому что режим блокировки SHARE конфликтует с запрашиваемой при записи блокировкой ROW EXCLUSIVE, а команда LOCK TABLE имя IN SHARE MODE будет ждать, пока все параллельные транзакции с блокировкой ROW EXCLUSIVE не будут зафиксированы или отменены. Таким образом, в момент получения такой блокировки не останется никаких открытых незафиксированных операций записи; более того, до снятия блокировки никто не сможет даже начать записывать.

Для достижения аналогичного эффекта при выполнении транзакции на уровнях изоляции REPEATABLE READ или SERIALIZABLE нужно выполнить команду LOCK TABLE перед выполнением первой SELECT или оператора, изменяющего данные. Представление данных для транзакции уровня REPEATABLE READ или SERIALIZABLE будет заморожено в тот момент, когда начнет выполняться этот запрос. Если команда LOCK TABLE выполняется в транзакции позже, она также предотвратит параллельную запись — но не гарантирует, что транзакция прочитает последние зафиксированные значения.

Если транзакция такого рода будет изменять данные в таблице, следует использовать вместо SHARE режим блокировки SHARE ROW EXCLUSIVE. Это гарантирует, что в один момент времени будет выполняться только одна транзакция такого типа. Без этого ограничения возможна взаимная блокировка: обе транзакции могут получить блокировки SHARE, после чего не смогут получить блокировку ROW EXCLUSIVE, чтобы действительно выполнить изменения. (Обратите внимание, что собственные блокировки транзакции никогда не конфликтуют, поэтому транзакция может получить блокировку ROW EXCLUSIVE, когда владеет блокировкой SHARE — но не тогда, когда блокировку SHARE удерживает кто-то другой.) Чтобы избежать взаимоблокировок, убедитесь, что все транзакции запрашивают блокировки одних и тех же объектов в одном и том же порядке, и если для одного объекта запрашиваются блокировки с разными режимами, то транзакции всегда должны сначала запрашивать наиболее строгую блокировку.

Дополнительную информацию о режимах и стратегиях блокировки можно найти в разделе Явная блокировка.

Параметры

имя

Имя блокируемой существующей таблицы (может быть дополнено схемой). Если перед именем таблицы указывается ONLY, то блокируется только эта таблица. Если ONLY не указано, то блокируются таблица и все ее потомки (если таковые имеются). После имени таблицы можно добавить необязательное указание *, чтобы явно обозначить, что включены и все дочерние таблицы.

Команда LOCK TABLE a, b; равнозначна команде LOCK TABLE a; LOCK TABLE b;. Таблицы блокируются одна за другой в порядке, указанном в команде LOCK TABLE.

режим_блокировки

Режим блокировки определяет, с какими блокировками эта блокировка конфликтует. Режимы блокировки описаны в разделе Явная блокировка.

Если режим блокировки не указан, то применяется самый строгий режим ACCESS EXCLUSIVE.

NOWAIT

Указывает, что LOCK TABLE не должна ожидать освобождения конфликтующих блокировок: если указанные блокировки не могут быть получены немедленно, без ожидания, транзакция прерывается.

Примечания

Команда LOCK TABLE ... IN ACCESS SHARE MODE требует права SELECT для целевой таблицы. Команда LOCK TABLE ... IN ROW EXCUSIVE MODE требует прав INSERT, UPDATE, DELETE или TRUNCATE для целевой таблицы. Все другие формы LOCK требуют для целевой таблицы прав UPDATE, DELETE или TRUNCATE.

Пользователь, выполняющий блокировку представления, должен иметь соответствующие права для этого представления. Кроме того, владелец представления должен иметь соответствующие права для нижележащих базовых отношений, но пользователь, выполняющий блокировку, не нуждается в каких-либо правах для них.

LOCK TABLE бесполезна вне блока транзакции: блокировка будет удерживаться только до завершения этого оператора. Поэтому если LOCK используется вне блока транзакции, QHB сообщает об ошибке. Для определения блока транзакции воспользуйтесь командами BEGIN и COMMIT (или ROLLBACK).

LOCK TABLE имеет дело только с блокировками уровня таблицы, и поэтому имена режимов, включающие ROW, не вполне корректны. Эти имена режимов обычно следует рассматривать как указывающие на намерение пользователя получить блокировки на уровне строк в заблокированной таблице. Кроме того, ROW EXCLUSIVE — это разделяемая блокировка таблицы. Имейте в виду, что для LOCK TABLE все режимы блокировки имеют одинаковую семантику, отличаясь только правилами, определяющими, какие режимы конфликтуют друг с другом. Информацию о том, как получить фактическую блокировку уровня строк, см. в подразделе Блокировки на уровне строк, а также в параграфе Предложение блокировки на справочной странице команды SELECT.

Примеры

Получение блокировки SHARE для первичного ключа таблицы при выполнении операций добавления в таблицу внешнего ключа:

BEGIN WORK;
LOCK TABLE films IN SHARE MODE;
SELECT id FROM films
    WHERE name = 'Star Wars: Episode I - The Phantom Menace';
-- Если запись не будет возвращена, выполнить ROLLBACK
INSERT INTO films_user_comments VALUES
    (id, 'GREAT! I was waiting for it for so long!');
COMMIT WORK;

Установка блокировки SHARE ROW EXCLUSIVE в таблице первичного ключа перед выполнением операции удаления:

BEGIN WORK;
LOCK TABLE films IN SHARE ROW EXCLUSIVE MODE;
DELETE FROM films_user_comments WHERE id IN
    (SELECT id FROM films WHERE rating < 5);
DELETE FROM films WHERE rating < 5;
COMMIT WORK;

Совместимость

В стандарте SQL нет команды LOCK TABLE; вместо этого для указания уровней изоляции в транзакциях в нем используется команда SET TRANSACTION. QHB поддерживает и этот вариант; дополнительную информацию см. на справочной странице команды SET TRANSACTION.

За исключением режимов блокировки ACCESS SHARE, ACCESS EXCLUSIVE и SHARE UPDATE EXCLUSIVE, режимы блокировки и синтаксис команды LOCK TABLE в QHB совместимы с имеющимися в Oracle.

MOVE

MOVE — переместить курсор

Синтаксис

MOVE [ направление [ FROM | IN ] ] имя_курсора

где направлением может быть:

    NEXT
    PRIOR
    FIRST
    LAST
    ABSOLUTE число
    RELATIVE число
    число
    ALL
    FORWARD
    FORWARD число
    FORWARD ALL
    BACKWARD
    BACKWARD число
    BACKWARD ALL

Описание

Команда MOVE перемещает курсор без извлечения каких-либо данных. MOVE работает точно так же, как команда FETCH, за исключением того, что она только позиционирует курсор и не возвращает строки.

Параметры команды MOVE идентичны таковым команды FETCH; подробную информацию об их синтаксисе и использовании см. на справочной странице команды FETCH.

Выводимая информация

После успешного завершения команда MOVE возвращает метку команды в виде

MOVE число

Где число — это количество строк, которое вернула бы команда FETCH с теми же параметрами (возможно, ноль).

Примеры

BEGIN WORK;
DECLARE liahona CURSOR FOR SELECT * FROM films;

-- Пропустить первые 5 строк:
MOVE FORWARD 5 IN liahona;
MOVE 5

-- Извлечь 6-ю строку из курсора liahona:
FETCH 1 FROM liahona;
 code  | title  | did | date_prod  |  kind  |  len
-------+--------+-----+------------+--------+-------
 P_303 | 48 Hrs | 103 | 1982-10-22 | Action | 01:37
(1 row)

-- Закрыть курсор liahona и завершить транзакцию:
CLOSE liahona;
COMMIT WORK;

Совместимость

В стандарте SQL нет команды MOVE.

См. также

CLOSE, DECLARE, FETCH

NOTIFY

NOTIFY — сгенерировать уведомление

Синтаксис

NOTIFY канал [ , полезная_информация ]

Описание

Команда NOTIFY отправляет событие уведомления вместе с необязательной строкой «полезная информация» каждому клиентскому приложению, которое ранее выполнило команду LISTEN канал для указанного имени канала в текущей базе данных. Уведомления видимы для всех пользователей.

NOTIFY предоставляет простой механизм межпроцессной связи для набора процессов, обращающихся к одной и той же базе данных QHB. Строка полезной информации может быть отправлена вместе с уведомлением, а при использовании таблиц в базе данных для передачи дополнительных данных от уведомителя к адресату(ам) можно создать механизмы более высокого уровня для передачи структурированных данных.

Информация, передаваемая клиенту для события уведомления, включает имя канала уведомления, PID серверного процесса сеанса уведомления и строку полезной информации, которая остается пустой, если не была задана.

Выбор подходящих имен каналов и их смысл остаются на усмотрение проектировщика базы данных. Обычно имя канала совпадает с именем некоторой таблицы в базе данных, и событие уведомления, по существу, означает: «я изменил эту таблицу, взгляните на нее, чтобы увидеть, что нового». Но командами NOTIFY и LISTEN подобная ассоциация не навязывается. Например, проектировщик базы данных может использовать несколько различных имен каналов для передачи сигналов о различных видах изменений в одной таблице. Кроме того, строка полезной информации может использоваться для дифференциации различных случаев.

Когда NOTIFY используется для сигнализации о появлении изменений в определенной таблице, полезной методикой программирования является помещение NOTIFY в триггер оператора, который запускают изменения в таблице. Таким образом, уведомление происходит автоматически при изменении таблицы, и программист приложения не может случайно забыть это сделать.

NOTIFY взаимодействует с транзакциями SQL несколькими важными способами. Во-первых, если NOTIFY выполняется внутри транзакции, события уведомления не доставляются, пока транзакция не зафиксируется. Это целесообразно, так как при прерывании транзакции действие всех команд внутри нее, включая NOTIFY, аннулируется. Но это может сбить с толку, если пользователь ожидает, что события уведомления будут доставляться немедленно. Во-вторых, если перехватывающий сеанс получает сигнал уведомления, находясь в транзакции, это событие уведомления не будет доставлено его подключенному клиенту сразу после завершения (фиксации или прерывания) транзакции. Опять же, смысл в том, что если уведомление было доставлено в рамках транзакции, которая позже была прервана, кто-нибудь может захотеть как-то отменить его — но сервер не может «отозвать» уведомление, которое уже отправлено клиенту. Поэтому события уведомлений доставляются только между транзакциями. Подводя итог вышесказанному, приложениям, использующим NOTIFY для сигнализации в режиме реального времени, следует по возможности максимально сокращать продолжительность своих транзакций.

Если из одной транзакции несколько раз сигнализируется одно и то же имя канала с одинаковыми строками полезной информации, сервер баз данных может принять решение о доставке только одного уведомления. С другой стороны, уведомления с различными строками полезной информации всегда будут доставляться по отдельности. Аналогично никогда не будут объединяться и уведомления от различных транзакций. За исключением удаления более поздних вхождений повторяющихся уведомлений, NOTIFY гарантирует, что уведомления от одной и той же транзакции будут доставлены в том же порядке, в каком были отправлены. Также гарантируется, что сообщения от различных транзакций доставляются в том же порядке, в котором эти транзакции были зафиксированы.

Часто бывает, что клиент, выполнивший NOTIFY, ожидает уведомления на том же канале. В этом случае он получит свое же событие уведомления, как и любой другой прослушивающий сеанс. В зависимости от логики приложения, это может привести к бесполезной работе, например чтению таблицы базы данных для поиска тех же обновлений, которые только что были записаны в этом сеансе. Подобной лишней работы можно избежать, отследив, не совпадает ли PID серверного процесса уведомляющего сеанса (предоставленный в сообщении о событии уведомления) с собственным PID сеанса (доступным из libpq). Если они одинаковы, значит, это вернувшееся событие уведомления о ваших собственных действиях, и его можно игнорировать.

Параметры

канал

Название оповещаемого канала уведомлений (любой идентификатор).

полезная_информация

Строка «полезной информации», передаваемая вместе с уведомлением. Она должна задаваться простым строковым литералом. В стандартной конфигурации ее длина должна быть меньше 8000 байт. (Если требуется передать двоичные данные или большой объем информации, лучше поместить их в таблицу базы данных и передать ключ этой записи.)

Примечания

Существует очередь, содержащая уведомления, которые были отправлены, но еще не обработаны всеми прослушивающими сеансами. Если эта очередь переполняется, транзакции, вызывающие NOTIFY, не смогут выполнить фиксацию. Очередь довольно большая (8 ГБ в стандартной установке), так что ее должно хватить для почти каждого случая использования. Однако если в сеансе выполняется LISTEN, а затем происходит очень длительная транзакция, очередь не очищается. Как только эта очередь заполняется наполовину, в файл журнала упреждающей записи записываются предупреждения, где указывается, какой сеанс препятствует очистке очереди. В этом случае следует добиться завершения текущей транзакции в указанном сеансе, чтобы очередь была очищена.

Функция pg_notification_queue_usage возвращает долю очереди, которая в настоящее время занята ожидающими уведомлениями. Дополнительную информацию см. в разделе Системные информационные функции и операторы.

Транзакцию, которая выполнила NOTIFY, нельзя подготовить к двухфазной фиксации.

pg_notify

Чтобы отправить уведомление, также можно использовать функцию pg_notify(text, text). Эта функция принимает имя канала в качестве первого аргумента, а полезную информацию — в качестве второго. Эта функция намного проще в использовании, чем команда NOTIFY, если нужно работать с непостоянными именами каналов и содержимым сообщений.

Примеры

Конфигурирование и выполнение последовательности ожидания/получения уведомления от psql:

LISTEN virtual;
NOTIFY virtual;
Asynchronous notification "virtual" received from server process with PID 8448.
NOTIFY virtual, 'This is the payload';
Asynchronous notification "virtual" with payload "This is the payload" received
from server process with PID 8448.

LISTEN foo;
SELECT pg_notify('fo' || 'o', 'pay' || 'load');
Asynchronous notification "foo" with payload "payload" received from server
process with PID 14728.

Совместимость

В стандарте SQL нет команды NOTIFY.

См. также

LISTEN, UNLISTEN

PREPARE

PREPARE — подготовить оператор для выполнения

Синтаксис

PREPARE имя [ ( тип_данных [, ...] ) ] AS оператор

Описание

Команда PREPARE создает подготовленный оператор. Подготовленный оператор — это объект на стороне сервера, который можно использовать для оптимизации производительности. При выполнении оператора с командой PREPARE он разбирается, анализируется и перезаписывается. При последующем вызове команды EXECUTE подготовленный оператор планируется и выполняется. Это разделение труда предотвращает повторный синтаксический анализ, при этом позволяя выбрать наилучший план выполнения в зависимости от определенных значений предоставляемых параметров.

Подготовленные операторы могут принимать параметры: значения, которые подставляются в оператор при его выполнении. При создании подготовленного оператора к этим параметрам можно обращаться по порядковому номеру, используя запись $1, $2 и т. д. При необходимости можно указать соответствующий список типов данных параметров. Если тип данных параметра не указан или объявлен как unknown (неизвестный), он выводится из контекста, в котором произошло первое обращение к данному параметру (если это возможно). При выполнении оператора задайте в команде EXECUTE фактические значения этих параметров. Дополнительную информацию об этом см. на справочной странице команды EXECUTE.

Подготовленные операторы существуют только в рамках текущего сеанса базы данных. По окончании сеанса система забывает этот подготовленный оператор, поэтому перед повторным использованием его необходимо воссоздать. Это также означает, что один подготовленный оператор не может использоваться одновременно несколькими клиентами базы данных; тем не менее каждый клиент может создать свой собственный подготовленный оператор для использования. Подготовленные операторы можно очистить вручную с помощью команды DEALLOCATE.

Подготовленные операторы потенциально обладают наибольшим преимуществом производительности, когда один сеанс используется для выполнения большого количества схожих операторов. Разница в производительности будет особенно существенной, если операторы сложны для планирования или перезаписи, например если запрос включает соединение многих таблиц или требует применения нескольких правил. Если оператор относительно прост в планировании и переписывании, но относительно затратен в выполнении, преимущество производительности подготовленных операторов будет менее заметным.

Параметры

имя

Произвольное имя, данное этому конкретному подготовленному оператору. Оно должно быть уникальным в рамках одного сеанса и впоследствии используется для выполнения или освобождения ранее подготовленного оператора.

тип_данных

Тип данных параметра для подготовленного оператора. Если тип данных конкретного параметра не задан или указан как unknown, он будет выводиться из контекста, в котором произошло первое обращение к этому параметру. Для обращения к параметрам в самом подготовленном операторе используйте запись $1, $2 и т. д.

оператор

Любой оператор SELECT, INSERT, UPDATE, DELETE или VALUES.

Примечания

Подготовленный оператор может быть выполнен либо с общим планом, либо со специализированным планом. Общий план является одинаковым для всех выполнений, тогда как специализированный создается для конкретного выполнения с использованием значений параметров, заданных в этом вызове. Использование общего плана позволяет избежать затрат на планирование, но в некоторых ситуациях выполнение специализированного плана будет гораздо более эффективным, поскольку планировщик может воспользоваться знанием значений параметров. (Конечно, если у подготовленного оператора нет параметров, это неактуально, и всегда используется общий план.)

По умолчанию (то есть когда plan_cache_mode установлен в auto), сервер автоматически выберет, какой план использовать для подготовленного оператора с параметрами: общий или специализированный. Текущее правило для этого состоит в том, что первые пять выполнений осуществляются со специализированными планами, и рассчитывается средняя расчетная стоимость этих планов. Затем создается общий план, и его расчетная стоимость сравнивается со средней стоимостью специализированного плана. Последующие выполнения используют общий план, если его стоимость по сравнению со средней стоимостью специализированного плана не настолько велика, чтобы делать повторное перепланирование.

Эту логику можно переопределить, заставляя сервер использовать либо только общие, либо специализированные планы, установив для параметра plan_cache_mode значение force_generic_plan или force_custom_plan соответственно. Этот параметр полезен в первую очередь тогда, когда оценка общего плана по какой-то причине занижена, и он выбирается, даже если его фактическая стоимость намного больше, чем у специализированного плана.

Чтобы узнать план запроса, который QHB выбирает для подготовленного оператора, используйте команду EXPLAIN, например:

EXPLAIN EXECUTE имя(значения_параметров);

Если используется общий план, он будет содержать символы параметров $n, в то время как специализированный план будет содержать подставленные в него фактические значения параметров.

Дополнительную информацию о планировании запросов и статистике, собираемой QHB для этой цели, см. в документации по команде ANALYZE.

Хотя основной целью подготовленных операторов является предотвращение повторного синтаксического анализа и планирования оператора, QHB будет принудительно заново анализировать и планировать выполнение оператора всякий раз, когда задействованные в этом операторе объекты базы данных подвергаются изменениям определения (DDL) с момента предыдущего использования подготовленного оператора. Кроме того, если значение search_path изменяется от одного использования к другому, оператор будет повторно проанализирован с новым search_path. С этими правилами использование подготовленного оператора по сути почти не отличается от повторяющегося выполнения одного и того же текста запроса, но с повышением производительности, если определения объектов не меняются (особенно если оптимальный план из раза в раз остается неизменным). Примером случая, когда семантическая равнозначность оказывается не идеальна, служит то, что если оператор обращается к таблице по неполному имени, а затем новая таблица с таким же именем создается в схеме, стоящей в пути search_path раньше, автоматический повторный анализ запроса не происходит, так как ни один используемый в операторе объект не изменился. Однако если какое-то другое изменение вызывает повторный анализ, при последующем выполнении запрос обратится к новой таблице.

Получить список всех доступных в сеансе подготовленных операторов можно, обратившись к системному представлению pg_prepared_statements.

Примеры

Создание подготовленного оператора для команды INSERT и последующее его выполнение:

PREPARE fooplan (int, text, bool, numeric) AS
    INSERT INTO foo VALUES($1, $2, $3, $4);
EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00);

Создание подготовленного оператора для команды SELECT и последующее его выполнение:

PREPARE usrrptplan (int) AS
    SELECT * FROM users u, logs l WHERE u.usrid=$1 AND u.usrid=l.usrid
    AND l.date = $2;
EXECUTE usrrptplan(1, current_date);

В этом примере тип данных второго параметра не указывается, поэтому он выводится из контекста, в котором используется $2.

Совместимость

Стандарт SQL включает команду PREPARE, но она предназначена только для использования во встраиваемом SQL. Также эта версия команды PREPARE использует несколько иной синтаксис.

См. также

DEALLOCATE, EXECUTE

PREPARE TRANSACTION

PREPARE TRANSACTION — подготовить текущую транзакцию для двухфазной фиксации

Синтаксис

PREPARE TRANSACTION id_транзакции

Описание

Команда PREPARE TRANSACTION подготавливает текущую транзакцию к двухфазной фиксации. После выполнения этой команды транзакция лишается связи с текущим сеансом; вместо этого ее состояние полностью сохраняется на диске, и она с очень большой вероятностью будет успешно зафиксирована, даже если сбой базы данных произойдет до запроса на фиксацию.

После подготовки транзакцию можно затем зафиксировать или откатить с помощью COMMIT PREPARED или ROLLBACK PREPARED соответственно. Эти команды можно вызвать из любого сеанса, а не только из того, в котором выполнялась исходная транзакция.

С точки зрения сеанса, выполняющего команду, PREPARE TRANSACTION не отличается от команды ROLLBACK: после ее выполнения нет активной текущей транзакции, и результаты этой подготовленной транзакции больше не видны. (Они снова станут видимыми, если транзакция фиксируется.)

Если команда PREPARE TRANSACTION по какой-либо причине завершается неудачно, то она действует как ROLLBACK: текущая транзакция отменяется.

Параметры

id_транзакции

Произвольный идентификатор, который позже определяет эту транзакцию для COMMIT PREPARED или ROLLBACK PREPARED. Идентификатор должен быть записан как строковый литерал и иметь длину менее 200 байт. Он не должен совпадать с идентификатором, используемым для любой текущей подготовленной транзакции.

Примечания

Команда PREPARE TRANSACTION не предназначена для использования в приложениях или интерактивных сеансах. Ее цель — позволить внешнему менеджеру транзакций выполнять атомарные глобальные транзакции, охватывающие несколько баз данных или других транзакционных ресурсов. Если вы не разрабатываете собственный менеджер транзакций, использовать PREPARE TRANSACTION не рекомендуется.

Эта команда должна применяться внутри блока транзакции. Чтобы его начать, воспользуйтесь командой BEGIN.

В настоящее время команда PREPARE не способна подготовить транзакцию, в которой выполнялись какие-либо операции с временными таблицами или временным пространством имен сеанса, создавались какие-либо курсоры WITH HOLD или выполнялись команды LISTEN, UNLISTEN или NOTIFY. Эти функциональные возможности слишком тесно связаны с текущим сеансом, чтобы быть полезными в транзакции, подлежащей подготовке.

Если транзакция изменила какие-либо параметры времени выполнения командой SET (без параметра LOCAL), их значения сохраняются после PREPARE TRANSACTION и не будут затронуты последующим выполнением команды COMMIT PREPARED или ROLLBACK PREPARED. То есть в этом отношении PREPARE TRANSACTION действует скорее как COMMIT, чем как ROLLBACK.

Все доступные в настоящее время подготовленные транзакции перечислены в системном представлении pg_prepared_xacts.

ВНИМАНИЕ!
Оставлять транзакции в подготовленном состоянии на длительное время неразумно. Это будет влиять на способность команды VACUUM высвобождать пространство и в крайних случаях может привести к отключению базы данных для предотвращения зацикливания идентификаторов транзакций (см. подраздел Предотвращение ошибок зацикливания идентификаторов транзакций). Также имейте в виду, что транзакция продолжает удерживать все свои блокировки. Предполагаемое использование этой особенности заключается в том, что подготовленная транзакция обычно фиксируется или откатывается, как только внешний менеджер транзакций убеждается, что другие базы данных тоже готовы к фиксации.

Если вы не настроили внешний менеджер транзакций для отслеживания подготовленных транзакций и обеспечения их своевременного закрытия, лучше всего выключить возможность подготовки транзакций, установив в параметре max_prepared_transactions значение ноль. Это предотвратит случайное создание подготовленных транзакций, которые затем могут оказаться забыты и в конечном итоге вызвать проблемы.

Примеры

Подготовка текущей транзакции для двухфазной фиксации, при этом ей назначается идентификатор foobar:

PREPARE TRANSACTION 'foobar';

Совместимость

Команда PREPARE TRANSACTION является расширением QHB. Она предназначена для использования внешними системами управления транзакциями, некоторые из которых включены в стандарты (например X/Open XA), но сторона SQL этих систем не стандартизирована.

См. также

COMMIT PREPARED, ROLLBACK PREPARED

REASSIGN OWNED

REASSIGN OWNED — сменить владельца объектов базы данных, принадлежащих роли базы данных

Синтаксис

REASSIGN OWNED BY { старая_роль | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...]
               TO { новая_роль | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

Описание

Команда REASSIGN OWNED дает системе указание сменить владельца объектов базы данных, принадлежащих одной из старых_ролей, на новую_роль.

Параметры

старая_роль

Имя роли. Права собственности на все объекты в текущей базе данных и на все разделяемые объекты (базы данных, табличные пространства), принадлежащие этой роли, будут переназначены на новую_роль.

новая_роль

Имя роли, которая будет сделана новым владельцем затронутых объектов.

Примечания

Команда REASSIGN OWNED часто используется для подготовки к удалению одной или нескольких ролей. Поскольку REASSIGN OWNED не влияет на объекты в других базах данных, обычно необходимо выполнить эту команду в каждой базе данных, которая содержит объекты, принадлежащие удаляемой роли.

Для выполнения команды REASSIGN OWNED требуется членство как в исходной(ых), так и в целевой ролях.

Альтернативой является команда DROP OWNED, которая просто удаляет все объекты базы данных, принадлежащие одной или нескольким ролям.

Команда REASSIGN OWNED не влияет ни на какие права, предоставленные старым_ролям на объекты, которые им не принадлежат. Кроме того, она не влияет на права по умолчанию, созданные командой ALTER DEFAULT PRIVILEGES. Отозвать эти права можно, воспользовавшись командой DROP OWNED.

Дополнительную информацию см. в разделе Удаление ролей.

Совместимость

Команда REASSIGN OWNED является расширением QHB.

См. также

DROP OWNED, DROP ROLE, ALTER DATABASE

REFRESH MATERIALIZED VIEW

REFRESH MATERIALIZED VIEW — заменить содержимое материализованного представления

Синтаксис

REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] имя
    [ WITH [ NO ] DATA ]

Описание

Команда REFRESH MATERIALIZED VIEW полностью заменяет содержимое материализованного представления. Для выполнения этой команды нужно быть владельцем соответствующего материализованного представления. Старое содержимое удаляется. Если указывается WITH DATA (или по умолчанию), то выполняется дополнительный запрос, предоставляющий новые данные, а материализованное представление остается в сканируемом состоянии. Если указывается WITH NO DATA, то новые данные не генерируются, а материализованное представление остается в несканируемом состоянии.

Параметры WITH NO DATA и CONCURRENTLY нельзя задавать одновременно.

Параметры

CONCURRENTLY

Обновить материализованное представление, не блокируя параллельные выборки из него. Без этого параметра обновление, воздействующее на значительное количество строк, в большинстве случаев будет использовать меньше ресурсов и завершится быстрее, но может блокировать другие подключения, пытающиеся читать из этого материализованного представления. Этот режим может быть быстрее, если затрагивается небольшое количество строк.

Данный параметр допустим только в том случае, если в материализованном представлении имеется хотя бы один индекс UNIQUE, который использует только имена столбцов и включает все строки, то есть он не должен быть индексом выражения или включать предложение WHERE.

Этот параметр нельзя использовать, если материализованное представление еще не заполнено.

Даже с этим параметром в один момент времени допускается только одно обновление (REFRESH) материализованного представления.

имя

Имя обновляемого материализованного представления (может быть дополнено схемой).

Примечания

Если в запросе, определяющем материализованное представление, присутствует предложение ORDER BY, исходное содержимое этого материализованного представления будет упорядочено заданным образом, но команда REFRESH MATERIALIZED VIEW не гарантирует сохранение этого порядка.

Примеры

Эта команда заменит содержимое материализованного представления order_summary, используя запрос из определения материализованного представления, и оставит его в сканируемом состоянии:

REFRESH MATERIALIZED VIEW order_summary;

Эта команда освободит пространство, связанное с материализованным представлением annual_statistics_basis, и оставит это представление в несканируемом состоянии:

REFRESH MATERIALIZED VIEW annual_statistics_basis WITH NO DATA;

Совместимость

Команда REFRESH MATERIALIZED VIEW является расширением QHB.

См. также

CREATE MATERIALIZED VIEW, ALTER MATERIALIZED VIEW, DROP MATERIALIZED VIEW

REINDEX

REINDEX — перестроить индексы

Синтаксис

RREINDEX [ ( параметр [, ...] ) ] { INDEX | TABLE | SCHEMA | DATABASE | SYSTEM } [ CONCURRENTLY ] имя

где параметром может быть:

    CONCURRENTLY [ логическое_значение ]
    TABLESPACE новое_табличное_пространство
    VERBOSE [ логическое_значение ]

Описание

Команда REINDEX перестраивает индекс, используя данные, хранящиеся в таблице индекса, и заменяя его старую копию. Существует несколько сценариев, в которых можно использовать команду REINDEX:

Индекс был поврежден и больше не содержит корректных данных. Хотя в теории этого никогда не должно произойти, на практике индексы могут быть повреждены из-за ошибок программного обеспечения или аппаратных сбоев. В таких случаях REINDEX предоставляет метод восстановления.

Индекс стал «замусоренным», то есть содержит много пустых или почти пустых страниц. Это может произойти с индексами B-деревьями в QHB при определенных необычных схемах доступа. REINDEX предоставляет способ сократить пространство, занимаемое индексом, записывая новую версию этого индекса без неиспользуемых страниц. Дополнительную информацию см. в разделе Регулярная переиндексация.

Вы изменили для индекса параметр хранения (например, коэффициент заполнения) и хотите убедиться, что это изменение вступило в силу.

Если построение индекса завершается неудачно в режиме CONCURRENTLY, этот индекс остается «нерабочим». Такие индексы бесполезны, но с помощью REINDEX их можно легко перестроить. Обратите внимание, что перестроить нерабочий индекс в неблокирующем режиме способна только команда REINDEX INDEX.

Параметры

INDEX

Пересоздает указанный индекс. Эту форму REINDEX нельзя выполнить в блоке транзакции при использовании с партиционированным индексом.

TABLE

Пересоздает все индексы указанной таблицы. Если таблица имеет дополнительную таблицу «TOAST», то она также будет переиндексирована. Эту форму REINDEX нельзя выполнить в блоке транзакции при использовании с партиционированной таблицей.

SCHEMA

Пересоздает все индексы указанной схемы. Если таблица этой схемы имеет дополнительную таблицу «TOAST», то она тоже будет переиндексирована. Также обрабатываются индексы в разделяемых системных каталогах. Эту форму REINDEX нельзя выполнить внутри блока транзакции.

DATABASE

Пересоздает все индексы в текущей базе данных. Также обрабатываются индексы в разделяемых системных каталогах. Эту форму REINDEX нельзя выполнить внутри блока транзакции.

SYSTEM

Пересоздает все индексы в системных каталогах в текущей базе данных. Сюда входят индексы в разделяемых системных каталогах. Индексы в пользовательских таблицах не обрабатываются. Эту форму REINDEX нельзя выполнить внутри блока транзакции.

имя

Имя конкретного индекса, таблицы или базы данных, подлежащих переиндексации. Имена индексов и таблиц могут быть дополнены схемой. В настоящее время REINDEX DATABASE и REINDEX SYSTEM могут переиндексировать только текущую базу данных, поэтому их параметр должен соответствовать имени текущей базы данных.

CONCURRENTLY

Если используется этот параметр, QHB перестроит индекс, не устанавливая никаких блокировок, которые предотвращают параллельные добавления, изменения или удаления в таблице; тогда как стандартное перестроение индекса блокирует запись (но не чтение) в таблице вплоть до своего завершения. Существует несколько особенностей, которые следует учитывать при использовании этого параметра, — см. параграф Неблокирующее перестроение индексов ниже.

Для временных таблиц REINDEX всегда выполняется в неблокирующем режиме, поскольку никакие другие сеансы не могут к ним обратиться, а этот способ переиндексации менее затратен.

TABLESPACE

Указывает, что индексы будут перестроены в новом табличном пространстве.

VERBOSE

Выводит отчет о ходе выполнения по мере переиндексации каждого индекса.

логическое_значение

Определяет, должен ли выбранный параметр быть включен или выключен. Для включения параметра можно написать TRUE, ON или 1, а для выключения — FALSE, OFF или 0. Кроме того, логическое_значение можно опустить, и в этом случае подразумевается TRUE.

новое_табличное_пространство

Табличное пространство, где будут перестроены индексы.

Примечания

При подозрении на повреждение индекса в пользовательской таблице можно просто перестроить этот индекс или все индексы в этой таблице, используя REINDEX INDEX или REINDEX TABLE.

Все становится сложнее, если нужно восстановить поврежденный индекс в системной таблице. В этом случае важно, чтобы система сама не использовала подозрительные индексы. (В действительности при таком сценарии можно обнаружить, что серверные процессы отказывают сразу в момент запуска вследствие зависимости от поврежденных индексов.) Для безопасного восстановления сервер следует запустить с параметром -P, который не позволяет ему использовать индексы при поиске в системных каталогах.

Один из способов сделать это — выключить сервер и запустить QHB в однопользовательском режиме с параметром -P в командной строке. Затем можно выполнить REINDEX DATABASE, REINDEX SYSTEM, REINDEX TABLE или REINDEX INDEX, в зависимости от того, что вы хотите восстановить. Если сомневаетесь, используйте команду REINDEX SYSTEM, чтобы выбрать восстановление всех системных индексов в базе данных. После этого завершите однопользовательский сеанс сервера и перезапустите сервер в обычном режиме. Дополнительную информацию о том, как взаимодействовать с сервером в однопользовательском интерфейсе, см. в разделе Процесс qhb.

Как вариант, можно запустить обычный сеанс сервера, но добавить в параметры командной строки -P. В разных клиентах метод может различаться, но во всех клиентах на базе libpq можно установить для переменной среды PGOPTIONS значение -P до запуска клиента. Обратите внимание, что хотя этот метод не требует блокировки других клиентов, все же было бы разумно запретить другим пользователям подключаться к поврежденной базе данных до завершения восстановления.

Действие REINDEX подобно удалению и пересозданию индекса в том, что содержимое индекса перестраивается с нуля. Тем не менее блокировки при этом устанавливаются другие. Команда REINDEX блокирует запись, но не чтение из родительской таблицы индекса. Кроме того, она устанавливает на обрабатываемый индекс блокировку ACCESS EXCLUSIVE, которая заблокирует операции чтения, пытающиеся задействовать этот индекс. DROP INDEX, напротив, мгновенно устанавливает блокировку ACCESS EXCLUSIVE на родительской таблице, блокируя как запись, так и чтение. Последующая команда CREATE INDEX блокирует запись, но не чтение; поскольку индекс отсутствует, при чтении не будет попыток обращения к нему, а это значит, что не будет никакой блокировки, но чтение может превратиться в затратное последовательное сканирование.

Для переиндексации одного индекса или таблицы нужно быть владельцем этого индекса или таблицы. Для переиндексации схемы или базы данных нужно быть владельцем этой схемы или базы данных. Обратите внимание, что из-за этого в ряде случаев обычные пользователи могут перестраивать индексы таблиц, принадлежащих другим пользователям. Однако из этих правил есть исключение: когда команду REINDEX DATABASE, REINDEX SCHEMA или REINDEX SYSTEM выполняет обычный пользователь, индексы разделяемых каталогов будут пропущены, если только этот пользователь не владеет данным каталогом (как правило, это не так). Конечно, суперпользователи всегда могут переиндексировать все что угодно.

Переиндексация партиционированных таблиц или партиционированных индексов поддерживается командами REINDEX INDEX или REINDEX TABLE соответственно. Каждая партиция в заданном партиционированном отношении переиндексируется в отдельной транзакции. При работе с партиционированными таблицей или индексом эти команды нельзя выполнять внутри блока транзакции.

При использовании предложения TABLESPACE с REINDEX для партиционированных индекса или таблицы ссылки на табличное пространство изменяются только в листовых партициях. Поскольку партиционированные индексы не изменяются, для них рекомендуется отдельно выполнить ALTER TABLE ONLY, чтобы все присоединяемые новые партиции наследовали новое табличное пространство. При сбое эта команда может не перенести все индексы в новое табличное пространство. Повторный запуск этой команды перестроит все листовые партиции и переместит ранее необработанные индексы в новое табличное пространство.

Если с предложением TABLESPACE указывается SCHEMA, DATABASE или SYSTEM, системные отношения пропускаются, и будет сгенерировано единственное предупреждение (WARNING) об этом. Индексы по таблицам TOAST перестраиваются, но не перемещаются в новое табличное пространство.

Неблокирующее перестроение индексов

Перестроение индекса может помешать обычной работе базы данных. Обычно QHB блокирует от записи таблицу, индекс которой перестраивается, и выполняет всю операцию перестройки индекса за одно сканирование этой таблицы. Другие транзакции по-прежнему могут читать эту таблицу, но при попытке добавления, изменения или удаления строк в ней они будут блокироваться до завершения перестройки индекса. Это может иметь серьезные последствия, если система является действующей производственной базой данных. Индексирование очень больших таблиц может занять много часов, и даже у таблиц меньшего размера перестройка индекса может заблокировать запись в таблицы на неприемлемо долгий для производственной системы срок.

QHB поддерживает перестройку индексов с минимальной блокировкой записи. Этот метод вызывается указанием параметра CONCURRENTLY команды REINDEX. При использовании этого параметра QHB должна выполнить два сканирования таблицы для каждого индекса, который необходимо перестроить, и дождаться завершения всех существующих транзакций, которые потенциально могут использовать этот индекс. В целом, данный метод требует большего количества действий, чем стандартное перестроение индекса, и весь процесс занимает значительно больше времени, поскольку нужно дождаться незавершенных транзакций, которые могут изменить индекс. Однако поскольку этот метод позволяет продолжить обычные операции во время перестройки индекса, он полезен для перестроения индексов в производственной среде. Разумеется, дополнительная загрузка процессора, памяти и ввода/вывода, вызванная перестроением индекса, может замедлить другие операции.

При неблокирующей переиндексации выполняются следующие шаги. Каждый шаг выполняется в отдельной транзакции. Если необходимо перестроить несколько индексов, то каждый шаг по очереди выполняется для всех индексов, и только потом начинается следующий шаг.

В каталог pg_index добавляется новое определение временного индекса. Это определение будет использоваться взамен старого индекса. Чтобы предотвратить любые изменения в схеме во время обработки, на уровне сеанса выполняется блокировка SHARE UPDATE EXCLUSIVE переиндексируемых индексов, а также связанных с ними таблиц.

Первый проход по построению индекса выполняется для каждого нового индекса. После построения индекса его флаг pg_index.indisready переключается в «true», чтобы этот индекс был готов к добавлениям, тем самым делая его видимым для других сеансов сразу после окончания построившей его транзакции. Этот шаг выполняется в отдельной транзакции для каждого индекса.

Затем выполняется второй проход для внесения в индекс кортежей, которые были добавлены во время выполнения первого прохода. Этот шаг также выполняется в отдельной транзакции для каждого индекса.

Все ограничения, ссылающиеся на этот индекс, меняются и начинают ссылаться на определение нового индекса; кроме того, изменяются имена индексов. В этот момент флаг pg_index.indisvalid переключается в «true» для нового индекса и в «false» для старого, и производится сброс кэша, в результате чего все сеансы, которые ссылались на старый индекс, становятся нерабочими.

Для старых индексов флаг pg_index.indisready переключается в «false», чтобы предотвратить добавления новых кортежей, как только завершатся текущие запросы, которые могут ссылаться на старый индекс.

Старые индексы удаляются. Блокировки SHARE UPDATE EXCLUSIVE уровня сеанса, установленные для индексов и таблиц, снимаются.

Если при перестроении индексов возникает проблема, например нарушение уникальности в уникальном индексе, команда REINDEX завершится ошибкой, но оставит после себя «нерабочий» новый индекс в дополнение к уже существующему. Этот индекс будет игнорироваться запросами, поскольку может быть неполным; однако он все равно будет обновляться при изменении данных, что повлечет дополнительные издержки. Команда psql \d сообщит о таком индексе как об INVALID (нерабочем):

qhb=# \d tab
       Table "public.tab"
 Column |  Type   | Modifiers
--------+---------+-----------
 col    | integer |
Indexes:
    "idx" btree (col)
    "idx_ccnew" btree (col) INVALID

Если индекс, отмеченный как INVALID, имеет имя с суффиксом ccnew, то он соответствует временному индексу, созданному во время совмещенной операции, и рекомендуемый метод восстановления состоит в удалении такого индекса командой DROP INDEX и повторной попытке выполнить REINDEX CONCURRENTLY. Если нерабочий индекс имеет суффикс ccold, то он соответствует исходному индексу, который удалить нельзя; рекомендуемый метод восстановления состоит в простом удалении нерабочего индекса, раз уж перестроение индекса прошло успешно.

Обычные построения индексов позволяют одновременно выполняться другим построениям индексов в той же таблице, но неблокирующее построение индексов в таблице может выполняться только одно за раз. В обоих случаях никакие другие изменения схемы таблицы в это время не разрешаются. Другое отличие состоит в том, что обычные команды REINDEX TABLE или REINDEX INDEX могут выполняться в блоке транзакции, а REINDEX CONCURRENTLY — нет.

Как и любая длительная транзакция, REINDEX для таблицы может влиять на то, какие кортежи может удалять параллельная команда VACUUM в любой другой таблице.

REINDEX SYSTEM не поддерживает указание CONCURRENTLY, так как системные каталоги нельзя переиндексировать в неблокирующем режиме.

Более того, индексы, связанные с ограничениями-исключениями, тоже нельзя переиндексировать в неблокирующем режиме. Если явно указать имя такого индекса в этой команде, выдается ошибка. Если таблица или база данных с такими индексами переиндексируются в неблокирующем режиме, эти индексы будут пропущены. (Такие индексы можно переиндексировать без параметра CONCURRENTLY.)

Каждый обслуживающий процесс, запускающий REINDEX, будет записывать ход ее выполнения в представлении pg_stat_progress_create_index. Более подробную информацию см. в подразделе Отчет о ходе выполнения CREATE INDEX.

Примеры

Перестроение одного индекса:

REINDEX INDEX my_index;

Перестроение всех индексов таблицы my_table:

REINDEX TABLE my_table;

Перестроение всех индексов в определенной базе данных исходя из предположения, что целостность системных индексов под сомнением:

$ export PGOPTIONS="-P"
$ psql broken_db
...
broken_db=> REINDEX DATABASE broken_db;
broken_db=> \q

Перестроение индексов таблицы без блокировки одновременных операций чтения и записи в затрагиваемых этим процессом отношениях:

REINDEX TABLE CONCURRENTLY my_broken_table;

Совместимость

В стандарте SQL нет команды REINDEX.

См. также

CREATE INDEX, DROP INDEX, reindexdb, Отчет о ходе выполнения CREATE INDEX

RELEASE SAVEPOINT

RELEASE SAVEPOINT — уничтожить ранее определенную точку сохранения

Синтаксис

RELEASE [ SAVEPOINT ] имя_точки_сохранения

Описание

Команда RELEASE SAVEPOINT уничтожает точку сохранения, ранее определенную в текущей транзакции.

Уничтожение точки сохранения делает ее неприменимой в качестве точки отката, но никаких других видимых пользователю проявлений эта команда не имеет. Она не отменяет действие команд, выполненных после установки точки сохранения. (Для этого предназначена команда ROLLBACK TO SAVEPOINT.) Уничтожение ставшей ненужной точки сохранения позволяет системе восстановить некоторые ресурсы еще до завершения транзакции.

Команда RELEASE SAVEPOINT также уничтожает все точки сохранения, которые были установлены после именованной.

Параметры

имя_точки_сохранения

Имя уничтожаемой точки сохранения.

Примечания

Указание имени точки сохранения, которая ранее не была определена, является ошибкой.

Невозможно освободить точку сохранения, когда транзакция находится в прерванном состоянии.

Если несколько точек сохранения имеют одинаковое имя, освобождается только та, которая была определена последней и еще не была освобождена. Повторные команды будут последовательно освобождать более старые точки сохранения.

Примеры

Установка и последующее уничтожение точки сохранения:

BEGIN;
    INSERT INTO table1 VALUES (3);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (4);
    RELEASE SAVEPOINT my_savepoint;
COMMIT;

Данная транзакция добавит значения 3 и 4.

Совместимость

Эта команда соответствует стандарту SQL. Стандарт указывает, что ключевое слово SAVEPOINT является обязательным, но QHB позволяет его опустить.

См. также

BEGIN, COMMIT, ROLLBACK, ROLLBACK TO SAVEPOINT, SAVEPOINT

RESET

RESET — восстановить значение параметра времени выполнения до значения по умолчанию

Синтаксис

RESET параметр_конфигурации
RESET ALL

Описание

Команда RESET восстанавливает параметры времени выполнения до их значений по умолчанию. RESET — это альтернативное написание команды

SET параметр_конфигурации TO DEFAULT

Дополнительную информацию см. на справочной странице команды SET.

Значение по умолчанию определяется как значение, которое имел бы параметр, если бы для него в текущем сеансе не выполнялась команда SET. Фактическим источником этого значения может быть скомпилированное значение по умолчанию, файл конфигурации, параметры командной строки или значения по умолчанию для каждой базы данных или каждого пользователя. Это немного отличается от определения его как «значения, которое параметр имел при запуске сеанса», потому что если это значение пришло из файла конфигурации, оно будет сброшено к тому, что указано в файле конфигурации сейчас. Дополнительную информацию см. в главе Конфигурация сервера.

Транзакционное поведение команды RESET совпадает с командой SET: ее действия будут отменены откатом транзакции.

Параметры

параметр_конфигурации

Имя настраиваемого параметра времени выполнения. Доступные параметры описаны в главе Конфигурация сервера и на справочной странице команды SET.

ALL

Сбрасывает все настраиваемые параметры времени выполнения до значений по умолчанию.

Примеры

Установить переменную конфигурации timezone в значение по умолчанию:

RESET timezone;

Совместимость

Команда RESET является расширением QHB.

См. также

SET, SHOW

REVOKE

REVOKE — отозвать права доступа

Синтаксис

REVOKE [ GRANT OPTION FOR ]
    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
    [, ...] | ALL [ PRIVILEGES ] }
    ON { [ TABLE ] имя_таблицы [, ...]
         | ALL TABLES IN SCHEMA имя_схемы [, ...] }
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { { SELECT | INSERT | UPDATE | REFERENCES } ( имя_столбца [, ...] )
    [, ...] | ALL [ PRIVILEGES ] ( имя_столбца [, ...] ) }
    ON [ TABLE ] имя_таблицы [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { { USAGE | SELECT | UPDATE }
    [, ...] | ALL [ PRIVILEGES ] }
    ON { SEQUENCE имя_последовательности [, ...]
         | ALL SEQUENCES IN SCHEMA имя_схемы [, ...] }
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { { CREATE | CONNECT | TEMPORARY | TEMP } [, ...] | ALL [ PRIVILEGES ] }
    ON DATABASE имя_базы_данных [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { USAGE | ALL [ PRIVILEGES ] }
    ON DOMAIN имя_домена [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { USAGE | ALL [ PRIVILEGES ] }
    ON FOREIGN DATA WRAPPER имя_обертки_сторонних_данных [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { USAGE | ALL [ PRIVILEGES ] }
    ON FOREIGN SERVER имя_сервера [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { EXECUTE | ALL [ PRIVILEGES ] }
    ON { { FUNCTION | PROCEDURE | ROUTINE } имя_функции [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] [, ...]
         | ALL { FUNCTIONS | PROCEDURES | ROUTINES } IN SCHEMA имя_схемы [, ...] }
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { USAGE | ALL [ PRIVILEGES ] }
    ON LANGUAGE имя_языка [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }
    ON LARGE OBJECT oid_большого_объекта [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] }
    ON SCHEMA имя_схемы [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { CREATE | ALL [ PRIVILEGES ] }
    ON TABLESPACE имя_табличного_пространства [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ GRANT OPTION FOR ]
    { USAGE | ALL [ PRIVILEGES ] }
    ON TYPE имя_типа [, ...]
    FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

REVOKE [ ADMIN OPTION FOR ]
    имя_роли [, ...] FROM описание_роли [, ...]
    [ GRANTED BY описание_роли ]
    [ CASCADE | RESTRICT ]

где описанием_роли может быть:

    [ GROUP ] имя_роли
  | PUBLIC
  | CURRENT_ROLE
  | CURRENT_USER
  | SESSION_USER

Описание

Команда REVOKE отзывает ранее предоставленные права у одной или нескольких ролей. Ключевое слово PUBLIC обозначает неявно определенную группу всех ролей.

Определение типов прав см. на справочной странице команды GRANT.

Обратите внимание, что любая конкретная роль будет иметь сумму прав, предоставленных непосредственно ей, прав, предоставленных любой роли, членом которой она в настоящее время является, и прав, предоставленных группе PUBLIC. Таким образом, например, отзыв у роли PUBLIC права SELECT не обязательно означает, что все роли потеряют права SELECT для данного объекта: те, кто получил это право непосредственно или через другую роль, все равно будут иметь его. Аналогично удаление у пользователя права SELECT может не повлиять на его возможность пользоваться этим правом, если оно выдано группе PUBLIC или другой роли, членом которой является этот пользователь.

Если указано GRANT OPTION FOR, отзывается только возможность передачи права, а не само это право. В противном случае будут отменены и право, и возможность передачи.

Если пользователь имеет право с возможностью передачи и предоставил его другим пользователям, то такие права называются зависимыми. Если при отзыве у первого пользователя самого права или возможности его передачи указано CASCADE, то зависимые права (если таковые существуют) тоже отзываются; в противном случае операция отзыва завершается ошибкой. Этот рекурсивный отзыв прав влияет только на права, предоставленные через цепочку пользователей, которая отслеживается до пользователя, являющегося субъектом этой команды REVOKE. Таким образом, затронутые пользователи в итоге могут сохранить это право, если оно было также получено через других пользователей.

При отзыве прав на таблицу соответствующие права для столбца (если таковые имеются) также автоматически отзываются для каждого столбца таблицы. С другой стороны, если роли были предоставлены права на таблицу, то отзыв тех же прав для отдельных столбцов не будет иметь никакого эффекта.

При отзыве членства в роли указание GRANT OPTION меняется на ADMIN OPTION, но поведение очень похоже. Эта форма команды также принимает указание GRANTED BY, но в настоящее время оно игнорируется (за исключением проверки на существование указанной в нем роли). Также обратите внимание, что эта форма команды не принимает пропускаемое слово GROUP в описании_роли.

Примечания

Пользователь может отозвать только те права, которые предоставил непосредственно он сам. Если, например, пользователь A предоставил право с возможностью передачи пользователю B, а пользователь B в свою очередь предоставил его пользователю C, то пользователь A не может отозвать право непосредственно у пользователя C. Вместо этого пользователь A может отозвать возможность передачи у пользователя B и применить параметр CASCADE, чтобы это право в свою очередь было отозвано у пользователя C. Другой пример: если и A, и B предоставили одно и то же право C, A может отозвать только то право, которое предоставил он сам, но не В, поэтому в итоге C все равно будет иметь это право.

Если команду REVOKE пытается выполнить не владелец указанного объекта, она завершится ошибкой, если у пользователя нет никаких прав на этот объект. Если же у пользователя есть некоторые права, команда выполнится, но отменит только те права, для которых у этого пользователя есть возможность передачи. Если же у пользователя нет возможности передачи прав, то формы REVOKE ALL PRIVILEGES выдадут предупреждающее сообщение, тогда как другие формы выдадут предупреждение, если пользователь не имеет возможности распоряжаться именно теми правами, которые указаны в команде. (В принципе, эти утверждения применимы и к владельцу объекта, но поскольку всегда считается, что ему разрешено передавать все права, такие ситуации невозможны.)

Если команду GRANT или REVOKE запускает суперпользователь, она выполняется так, как если бы была запущена владельцем затронутого объекта. Поскольку все права в конечном счете исходят от владельца объекта (возможно, косвенно через цепочки или через возможность передачи права), суперпользователь может отозвать все права, но это может потребовать использования CASCADE, как уже говорилось выше.

Кроме того, команду REVOKE может выполнить роль, которая не является владельцем затронутого объекта, но является членом роли, которой принадлежит объект, или членом роли, обладающей правами WITH GRANT OPTION для этого объекта. В этом случае команда выполняется так, как если бы ее задала содержащая роль, которая фактически владеет объектом или имеет права WITH GRANT OPTION. Например, если таблица t1 принадлежит роли g1, членом которой является роль u1, то u1 может отзывать права на t1, которые записаны как предоставленные ролью g1. Сюда входят как права, данные ролью u1, так и предоставленные другими членами роли g1.

Если роль, выполняющая команду REVOKE, получила права косвенно через несколько путей членства в ролях, то не определено, какая именно роль будет выбрана для выполнения команды. В таких случаях рекомендуется использовать команду SET ROLE, чтобы переключиться на конкретную роль, от имени которой вы хотите выполнить команду REVOKE. Невыполнение этого требования может привести к отзыву совсем не тех прав, которые были запланированы, или вообще ничего не отменить.

Дополнительную информацию о конкретных типах прав, а также о том, как проверять права объектов, см. в разделе Права.

Примеры

Отзыв у группы public права добавлять данные в таблицу films:

REVOKE INSERT ON films FROM PUBLIC;

Отзыв у пользователя manuel всех прав для представления kinds:

REVOKE ALL PRIVILEGES ON kinds FROM manuel;

Обратите внимание, что на самом деле это означает «Отзыв всех прав, которые были выданы мной».

Отзыв членства в роли admins у пользователя joe:

REVOKE admins FROM joe;

Совместимость

Примечания по совместимости команды GRANT аналогично применимы и для команды REVOKE. В соответствии со стандартом требуется ключевое слово RESTRICT или CASCADE, но QHB предполагает RESTRICT по умолчанию.

См. также

GRANT, ALTER DEFAULT PRIVILEGES

ROLLBACK

ROLLBACK — прервать текущую транзакцию

Синтаксис

ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]

Описание

Команда ROLLBACK откатывает текущую транзакцию и приводит к отмене всех изменений, произведенных этой транзакцией.

Параметры

WORK
TRANSACTION

Необязательные ключевые слова. они не имеют никакого эффекта.

AND CHAIN

Если указывается AND CHAIN, то сразу после окончания текущей транзакции начинается новая с теми же характеристиками (см. раздел SET TRANSACTION), что и только что завершившаяся. В противном случае новая транзакция не начинается.

Примечания

Для успешного завершения и фиксации транзакции воспользуйтесь командой COMMIT.

При выполнении ROLLBACK вне блока транзакции будет выдано предупреждение и больше ничего не произойдет. Выполнение же ROLLBACK AND CHAIN вне блока транзакции вызывает ошибку.

Примеры

Отмена всех изменений:

ROLLBACK;

Совместимость

Команда ROLLBACK соответствует стандарту SQL. Форма ROLLBACK TRANSACTION является расширением QHB.

См. также

BEGIN, COMMIT, ROLLBACK TO SAVEPOINT

ROLLBACK PREPARED

ROLLBACK PREPARED — отменить транзакцию, которая ранее была подготовлена для двухфазной фиксации

Синтаксис

ROLLBACK PREPARED id_транзакции

Описание

Команда ROLLBACK PREPARED откатывает транзакцию, которая находится в подготовленном состоянии.

Параметры

id_транзакции

Идентификатор транзакции, которую нужно откатить.

Примечания

Чтобы откатить подготовленную транзакцию, нужно быть тем же пользователем, который выполнял транзакцию изначально, либо суперпользователем. Но при этом не обязательно работать в том же сеансе, где выполнялась эта транзакция.

Эту команду нельзя выполнить внутри блока транзакции. Подготовленная транзакция откатывается немедленно.

Все доступные в настоящее время подготовленные транзакции перечислены в системном представлении pg_prepared_xacts.

Примеры

Откат транзакции с идентификатором foobar:

ROLLBACK PREPARED 'foobar';

Совместимость

Команда ROLLBACK PREPARED является расширением QHB. Она предназначена для использования внешними системами управления транзакциями, некоторые из которых охватываются стандартами (например X/Open XA), но сторона SQL этих систем не стандартизирована.

См. также

PREPARE TRANSACTION, COMMIT PREPARED

ROLLBACK TO SAVEPOINT

ROLLBACK TO SAVEPOINT — откатить к точке сохранения

Синтаксис

ROLLBACK [ WORK | TRANSACTION ] TO [ SAVEPOINT ] имя_точки_сохранения

Описание

Откат всех команд, выполненных после установки точки сохранения. Эта точка сохранения остается действительной, и впоследствии при необходимости можно снова к ней откатиться.

Команда ROLLBACK TO SAVEPOINT неявно уничтожает все точки сохранения, которые были установлены после указанной.

Параметры

имя_точки_сохранения

Точка сохранения, к которой нужно откатиться.

Примечания

Чтобы уничтожить точку сохранения, не отменяя действия команд, выполненных после ее установки, используйте команду RELEASE SAVEPOINT.

Указание имени точки сохранения, которая не была установлена, является ошибкой.

Курсоры относятся к точкам сохранения несколько нетранзакционно. Любой курсор, открытый внутри точки сохранения, при ее откате будет закрыт. Если команда FETCH или MOVE перемещает ранее открытый курсор внутри точки сохранения, которая затем откатывается, то этот курсор остается в том положении, в котором оказался после выполнения FETCH (т. е. движение курсора, вызванное FETCH, не откатывается). Также при откате не отменяется и закрытие курсора. Однако другие побочные эффекты, вызванные запросом курсора (например побочные эффекты вызываемых запросом изменчивых функций) откатываются, если они происходят после точки сохранения, которая впоследствии откатывается. Курсор, выполнение которого приводит к прерыванию транзакции, переводится в невыполняемое состояние, поэтому, хотя транзакцию можно восстановить с помощью ROLLBACK TO SAVEPOINT, такой курсор больше использовать нельзя.

Примеры

Отмена действия команд, выполненных после установки точки сохранения my_savepoint:

ROLLBACK TO SAVEPOINT my_savepoint;

Откат точки сохранения не отражается на положении курсора:

BEGIN;

DECLARE foo CURSOR FOR SELECT 1 UNION SELECT 2;

SAVEPOINT foo;

FETCH 1 FROM foo;
 ?column?
----------
        1

ROLLBACK TO SAVEPOINT foo;

FETCH 1 FROM foo;
 ?column?
----------
        2

COMMIT;

Совместимость

В стандарте SQL указано, что ключевое слово SAVEPOINT является обязательным, но QHB и Oracle позволяют его опустить. Стандарт SQL допускает в качестве пропускаемого слова после ROLLBACK только WORK, но не TRANSACTION. Кроме того, в стандарте SQL есть необязательное предложение AND [ NO ] CHAIN, которое в настоящее время не поддерживается QHB. В остальном эта команда соответствует стандарту SQL.

См. также

BEGIN, COMMIT, RELEASE SAVEPOINT, ROLLBACK, SAVEPOINT

SAVEPOINT

SAVEPOINT — определить новую точку сохранения в текущей транзакции

Синтаксис

SAVEPOINT имя_точки_сохранения

Описание

Команда SAVEPOINT устанавливает новую точку сохранения в текущей транзакции.

Точка сохранения — это специальная метка внутри транзакции, которая позволяет откатить действие всех команд, выполненных после того, как она была установлена, восстанавливая транзакцию до того состояния, каким оно было на момент установки этой точки сохранения.

Параметры

имя_точки_сохранения

Имя, присваиваемое новой точке сохранения. Если точки сохранения с таким именем уже существуют, к ним нельзя будет обратиться, пока не будут освобождены более поздние точки сохранения с тем же именем.

Примечания

Для отката к точке сохранения воспользуйтесь командой ROLLBACK TO SAVEPOINT. Для уничтожения точки сохранения с сохранением изменений, произведенных командами после того, как она была установлена, воспользуйтесь командой RELEASE SAVEPOINT.

Точки сохранения можно установить только внутри блока транзакции. В транзакции может быть определено несколько точек сохранения.

Примеры

Установка точки сохранения с последующей отменой действий всех команд, выполненных после ее установки:

BEGIN;
  INSERT INTO table1 VALUES (1);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (2);
    ROLLBACK TO SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (3);
COMMIT;

Вышеуказанная транзакция добавит значения 1 и 3, но не 2.

Установка и последующее уничтожение точки сохранения:

BEGIN;
    INSERT INTO table1 VALUES (3);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (4);
    RELEASE SAVEPOINT my_savepoint;
COMMIT;

Вышеуказанная транзакция добавит значения 3 и 4.

Использование одного имени для всех точек сохранения:

BEGIN;
    INSERT INTO table1 VALUES (1);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (2);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (3);

    -- откат ко второй точке сохранения
    ROLLBACK TO SAVEPOINT my_savepoint;
    SELECT * FROM table1;               -- выводит строки 1 и 2

    -- освобождение второй точки сохранения
    RELEASE SAVEPOINT my_savepoint;

    -- откат к первой точке сохранения
    ROLLBACK TO SAVEPOINT my_savepoint;
    SELECT * FROM table1;               -- выводит только строку 1
COMMIT;

Вышеуказанная транзакция показывает, что сначала откатилась строка 3 а затем строка 2.

Совместимость

Стандарт SQL требует, чтобы точка сохранения уничтожалась автоматически при установке другой точки сохранения с тем же именем. В QHB сохраняется и старая точка сохранения, хотя при откате или освобождении будет использоваться только самая последняя точка. (Освобождение новой точки сохранения с помощью RELEASE SAVEPOINT приведет к тому, что старая точка сохранения снова станет доступной для команд ROLLBACK TO SAVEPOINT и RELEASE SAVEPOINT.) В остальном команда SAVEPOINT полностью соответствует стандарту SQL.

См. также

BEGIN, COMMIT, RELEASE SAVEPOINT, ROLLBACK, ROLLBACK TO SAVEPOINT

SECURITY LABEL

SECURITY LABEL — определить или изменить примененную к объекту метку безопасности

Синтаксис

SECURITY LABEL [ FOR провайдер ] ON
{
  TABLE имя_объекта |
  COLUMN имя_таблицы.имя_столбца |
  AGGREGATE имя_агрегата ( сигнатура_агрегата ) |
  DATABASE имя_объекта |
  DOMAIN имя_объекта |
  EVENT TRIGGER имя_объекта |
  FOREIGN TABLE имя_объекта
  FUNCTION имя_функции [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  LARGE OBJECT oid_большого_объекта |
  MATERIALIZED VIEW имя_объекта |
  [ PROCEDURAL ] LANGUAGE имя_объекта |
  PROCEDURE имя_процедуры [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  PUBLICATION имя_объекта |
  ROLE имя_объекта |
  ROUTINE имя_подпрограммы [ ( [ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [, ...] ] ) ] |
  SCHEMA имя_объекта |
  SEQUENCE имя_объекта |
  SUBSCRIPTION имя_объекта |
  TABLESPACE имя_объекта |
  TYPE имя_объекта |
  VIEW имя_объекта
} IS 'метка'

где сигнатура_агрегата это:

* |
[ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] |
[ [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ] ] ORDER BY [ режим_аргумента ] [ имя_аргумента ] тип_аргумента [ , ... ]

Описание

Команда SECURITY LABEL применяет метку безопасности к объекту базы данных. С объектом базы данных можно связать произвольное количество меток безопасности, по одной на каждого провайдера. Провайдеры меток — это загружаемые модули, которые регистрируются с помощью функции register_label_provider.

Примечание
register_label_provider не является функцией SQL; ее можно вызвать только из кода C/RUST, загруженного на сервер.

Провайдер меток определяет, является ли данная метка допустимой и разрешено ли присвоить эту метку данному объекту. Смысл заданной метки тоже определяет провайдер меток. QHB не накладывает никаких ограничений на то, как провайдер меток должен интерпретировать метки безопасности; она просто предоставляет механизм их хранения. На практике это средство предназначено для обеспечения интеграции с системами обязательного контроля доступа на основе меток (mandatory access control, MAC), например SELinux. Такие системы принимают все решения по управлению доступом на основе меток объектов, а не на традиционных концепциях избирательного управления доступом (discretionary access control, DAC), таких как пользователи и группы.

Параметры

имя_объекта
имя_таблицы.имя_столбца
имя_агрегата
имя_функции
имя_процедуры
имя_подпрограммы

Имя маркируемого объекта. Имена объектов, располагающихся в схемах (таблицы, функции и т. д.) могут быть дополнены схемой.

провайдер

Имя провайдера, с которым должна быть связана эта метка. Именованный провайдер должен быть загружен и быть готов к предложенной операции маркировки. Если загружен только один провайдер, имя провайдера для краткости можно опустить.

режим_аргумента

Режим аргумента функции, процедуры или агрегата: IN (входной), OUT (выходной), INOUT (входной-выходной) или VARIADIC (переменный). Если этот параметр опущен, значение по умолчанию равно IN. Обратите внимание, что SECURITY LABEL на самом деле не обращает никакого внимания на аргументы OUT, так как для определения идентичности функции необходимы только входные аргументы. Поэтому достаточно перечислить аргументы IN, INOUT и VARIADIC.

имя_аргумента

Имя аргумента функции, процедуры или агрегата. Обратите внимание, что SECURITY LABEL на самом деле не обращает никакого внимания на имена аргументов, поскольку для определения идентичности функции необходимы только типы данных аргументов.

тип_аргумента

Тип данных аргумента функции, процедуры или агрегата.

oid_большого_объекта

OID большого объекта.

PROCEDURAL

Пропускаемое слово.

метка

Новая метка безопасности, записанная как строковый литерал, либо NULL для удаления метки безопасности.

Примеры

Следующий пример показывает, как можно изменить метку безопасности для таблицы.

SECURITY LABEL FOR selinux ON TABLE mytable IS 'system_u:object_r:sepgsql_table_t:s0';

Совместимость

В стандарте SQL нет команды SECURITY LABEL.

См. также

sepgsql

SELECT

SELECT, TABLE, WITH — выбрать строки из таблицы или представления

Синтаксис

[ WITH [ RECURSIVE ] запрос_WITH [, ...] ]
SELECT [ ALL | DISTINCT [ ON ( выражение [, ...] ) ] ]
    [ * | выражение [ [ AS ] имя_вывода ] [, ...] ]
    [ FROM элемент_FROM [, ...] ]
    [ WHERE условие ]
    [ GROUP BY элемент_группирования [, ...] ]
    [ HAVING условие ]
    [ WINDOW имя_окна AS ( определение_окна ) [, ...] ]
    [ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] выборка ]
    [ ORDER BY выражение [ ASC | DESC | USING оператор ] [ NULLS { FIRST | LAST } ] [, ...] ]
    [ LIMIT { число | ALL } ]
    [ OFFSET начало [ ROW | ROWS ] ]
    [ FETCH { FIRST | NEXT } [ число ] { ROW | ROWS } { ONLY | WITH TIES } ]
    [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF имя_таблицы [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ]

где элементом_FROM может быть:

    [ ONLY ] имя_таблицы [ * ] [ [ AS ] псевдоним [ ( псевдоним_столбца [, ...] ) ] ]
                [ TABLESAMPLE метод_выборки ( аргумент [, ...] ) [ REPEATABLE ( затравка ) ] ]
    [ LATERAL ] ( выборка ) [ AS ] псевдоним [ ( псевдоним_столбца [, ...] ) ]
    имя_запроса_WITH [ [ AS ] псевдоним [ ( псевдоним_столбца [, ...] ) ] ]
    [ LATERAL ] имя_функции ( [ аргумент [, ...] ] )
                [ WITH ORDINALITY ] [ [ AS ] псевдоним [ ( псевдоним_столбца [, ...] ) ] ]
    [ LATERAL ] имя_функции ( [ аргумент [, ...] ] ) [ AS ] псевдоним ( определение_столбца [, ...] )
    [ LATERAL ] имя_функции ( [ аргумент [, ...] ] ) AS ( определение_столбца [, ...] )
    [ LATERAL ] ROWS FROM( имя_функции ( [ аргумент [, ...] ] ) [ AS ( определение_столбца [, ...] ) ] [, ...] )
                [ WITH ORDINALITY ] [ [ AS ] псевдоним [ ( псевдоним_столбца [, ...] ) ] ]
    элемент_FROM тип_соединения элемент_FROM { ON условие_соединения | USING ( столбец_соединения [, ...] ) [ AS псевдоним_использования_соединения ] }
    элемент_FROM NATURAL тип_соединения элемент_FROM
    элемент_FROM CROSS JOIN элемент_FROM

и элементом_группирования может быть:

    ( )
    выражение
    ( выражение [, ...] )
    ROLLUP ( { выражение | ( выражение [, ...] ) } [, ...] )
    CUBE ( { выражение | ( выражение [, ...] ) } [, ...] )
    GROUPING SETS ( элемент_группирования [, ...] )

и запрос_WITH:

    имя_запроса_WITH [ ( имя_столбца [, ...] ) ] AS [ [ NOT ] MATERIALIZED ] ( select | values | insert | update | delete )
        [ SEARCH { BREADTH | DEPTH } FIRST BY имя_столбца [, ...] SET имя_столбца_последовательности_поиска ]
        [ CYCLE имя_столбца [, ...] SET имя_столбца_пометки_цикла [ TO значение_пометки_цикла DEFAULT пометка_цикла_по_умолчанию ] USING имя_столбца_пути_цикла ]

TABLE [ ONLY ] имя_таблицы [ * ]

Описание

Команда SELECT выбирает строки из нуля и более таблиц. В целом обработка при помощи SELECT выглядит следующим образом:

Вычисляются все запросы в списке WITH. Они по сути служат временными таблицами, к которым можно обращаться в списке FROM. Запрос WITH, на который в FROM обращаются более одного раза, вычисляется только один раз, если иное не указано в параметре NOT MATERIALIZED. (См. параграф Предложение WITH ниже.)

Вычисляются все элементы в списке FROM. (Каждый элемент в списке FROM — это реальная или виртуальная таблица.) Если в списке указано более одного элемента, они объединяются перекрестным соединением. (См. параграф Предложение FROM ниже.)

Если указано предложение WHERE, все строки, которые не удовлетворяют этому условию, исключаются из результата. (См. параграф Предложение WHERE ниже.)

Если указано предложение GROUP BY или вызываются агрегатные функции, то выходные строки объединяется в группы, соответствующие одному или нескольким значениям, и вычисляются результаты агрегатных функций. Если присутствует предложение HAVING, оно исключает группы, не удовлетворяющие данному условию. (См. параграфы Предложение GROUP BY и Предложение HAVING ниже.)

Вычисляются фактические выходные строки по заданным в SELECT выходным выражениям для каждой выбранной строки или группы строк. (См. параграф Список SELECT ниже.)

Команда SELECT DISTINCT удаляет из результата повторяющиеся строки. Команда SELECT DISTINCT ON удаляет строки, соответствующие всем заданным выражениям. Команда SELECT ALL (по умолчанию) возвращает все строки-кандидаты, включая дубликаты. (См. параграф Предложение DISTINCT ниже.)

С операторами UNION, INTERSECT и EXCEPT выходные данные нескольких команд SELECT объединяются в один результирующий набор. Оператор UNION возвращает все строки, находящиеся в одном или обоих результирующих наборах. Оператор INTERSECT возвращает все строки, которые точно находятся в обоих результирующих наборах. Оператор EXCEPT возвращает строки, которые находятся в первом результирующем наборе, но не во втором. Во всех трех случаях повторяющиеся строки исключаются, если только явно не указано ALL. Можно добавить пропускаемое слово DISTINCT, чтобы явно указать, что требуется устранение повторяющихся строк. Обратите внимание, что здесь DISTINCT — это поведение по умолчанию, хотя в самой команде SELECT по умолчанию подразумевается ALL. (См. параграфы Предложение UNION, Предложение INTERSECT и Предложение EXCEPT ниже.)

Если указано предложение ORDER BY, возвращаемые строки сортируются в указанном порядке. Если ORDER BY не задается, строки возвращаются в любом порядке, который по мнению системы быстрее всего создать. (См. параграф Предложение ORDER BY ниже.)

Если указано предложение LIMIT (или FETCH FIRST) или OFFSET, команда SELECT возвращает только подмножество результирующих строк. (См. параграф Предложение LIMIT ниже.)

Если указано FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE или FOR KEY SHARE, команда SELECT блокирует выбранные строки от параллельных изменений. (См. параграф Предложение блокировки ниже.)

Для всех столбцов, задействованных в команде SELECT, необходимо иметь право SELECT. Использование блокировок FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE и FOR KEY SHARE требует права UPDATE (по меньшей мере для одного столбца каждой таблицы, выбранной для блокировки таким образом).

Параметры

Предложение WITH

Предложение WITH позволяет указать один или несколько подзапросов, на которые можно ссылаться по имени в основном запросе. Во время выполнения основного запроса эти подзапросы в сущности действуют как временные таблицы или представления. Каждый подзапрос может быть оператором SELECT, TABLE, VALUES, INSERT, UPDATE или DELETE. При указании в WITH операторов, изменяющих данные (INSERT, UPDATE или DELETE), обычно добавляется предложение RETURNING. Именно результат RETURNING, а не нижележащая таблица, изменяемая этим оператором, формирует временную таблицу, которую затем читает основной запрос. Если RETURNING опущено, оператор все равно выполняется, но не выдает никаких выходных данных, поэтому на него нельзя ссылаться как на таблицу в основном запросе.

Имя (без схемы) должно быть указано для каждого запроса WITH. При необходимости можно указать список имен столбцов; если этот список опущен, имена столбцов выводятся из подзапроса.

Если указано RECURSIVE, это позволяет подзапросу SELECT ссылаться самому на себя по имени. Такой подзапрос должен иметь форму

нерекурсивная_часть UNION [ ALL | DISTINCT ] рекурсивная_часть

где рекурсивная_часть должна находиться в правой части от UNION. Для каждого запроса допускается только одна рекурсивная ссылка. Операторы изменения данных не могут быть рекурсивными, но в них можно использовать результаты рекурсивного запроса SELECT. Пример см. в разделе Запросы WITH.

Еще одна особенность RECURSIVE заключается в том, что запросам WITH необязательно быть упорядоченными: один запрос может ссылаться на другой запрос, который находится в списке после него. (Однако циклические ссылки или взаимная рекурсия не реализованы.) Без RECURSIVE запросы WITH могут ссылаться только на запросы WITH того же уровня, предшествующие им в списке WITH.

Когда в предложении WITH содержится несколько запросов, RECURSIVE нужно написать только один раз, сразу после WITH. Этот параметр будет действовать на все запросы в предложении WITH, кроме тех, которые не используют рекурсию или ссылки на последующие запросы.

Необязательное предложение SEARCH вычисляет столбец последовательности поиска, который можно использовать для упорядочивания результатов рекурсивного запроса по принципу «сначала в ширину» или «сначала в глубину». Предоставляемый список имен столбцов задает ключ строки, который будет использоваться для отслеживания посещенных строк. В список результирующих столбцов запроса WITH будет добавлен столбец с именем имя_столбца_последовательности_поиска. По этому столбцу можно провести упорядочивание во внешнем запросе, чтобы получить соответствующий порядок. Примеры см. в подразделе Порядок поиска.

Необязательное предложение CYCLE используется для обнаружения циклов в рекурсивных запросах. Предоставляемый список имен столбцов задает ключ строки, который будет использоваться для отслеживания посещенных строк. В список результирующих столбцов запроса WITH будет добавлен столбец с именем имя_столбца_пометки_цикла. При обнаружении цикла в этом столбце будет установлено значение_пометки_цикла; иначе — пометка_цикла_по_умолчанию. Более того, при обнаружении цикла будет остановлена обработка рекурсивного объединения. значение_пометки_цикла и пометка_цикла_по_умолчанию должны быть константами и должны быть приводимыми к общему типу данных, а у этого типа должен иметься оператор неравенства. (Стандарт SQL требует, чтобы они были логическими константами или символьными строками, но QHB этого не требует.) По умолчанию используются TRUE и FALSE (типа boolean). Кроме того, в список результирующих столбцов запроса WITH будет добавлен столбец с именем имя_столбца_пути_цикла. Этот столбец применяется внутренне для отслеживания посещенных строк. Примеры см. в подразделе Выявление циклов.

Предложения SEARCH и CYCLE работают только с рекурсивными запросами WITH. При этом запрос_WITH должен быть объединением (UNION или UNION ALL) двух команд SELECT (или равнозначных им) без вложенных UNION. При использовании обоих предложений сразу столбец, добавляемый предложением SEARCH, располагается перед столбцами, добавляемыми предложением CYCLE.

Основной запрос и запросы WITH, условно говоря, выполняются одновременно. Это означает, что действие оператора изменения данных в WITH не будет видно из других частей запроса, кроме как при чтении его результата в RETURNING. Если два таких оператора изменения данных пытаются изменить одну и ту же строку, результаты будут неопределенными.

Ключевое свойство запросов WITH состоит в том, что обычно они вычисляются только один раз для всего основного запроса, даже если тот ссылается на них более одного раза. В частности, операторы изменения данных гарантированно выполняются один и только один раз, независимо от того, будет ли их результат или какая-то его часть прочитана основным запросом.

Однако добавив для запроса WITH пометку NOT MATERIALIZED, можно отказаться от этой гарантии. В таком случае запрос WITH можно свернуть в основной запрос так же, как если бы это был простой подзапрос SELECT в предложении FROM основного запроса. Это приводит к дублированию вычислений, если основной запрос ссылается на запрос в WITH более одного раза; но если для каждого такого использования требуется только несколько строк из всего результата запроса WITH, указание NOT MATERIALIZED, позволяющее оптимизировать запросы совместно, в целом, может оказаться выгодным. NOT MATERIALIZED игнорируется в запросе WITH, если тот является рекурсивным или не свободен от побочных эффектов (т. е. когда он не является простым SELECT без изменчивых функций).

По умолчанию запрос WITH без побочных эффектов вкладывается в основной запрос, если в своем предложении FROM тот используется ровно один раз. Это позволяет совместно оптимизировать два уровня запросов в ситуациях, когда это должно быть семантически незаметно. Однако такое сворачивание можно предотвратить, если пометить запрос WITH как MATERIALIZED. Это может быть полезно, например, если предложение WITH используется в качестве преграды для оптимизатора, чтобы предотвратить выбор планировщиком плохого плана.

Дополнительную информацию см. в разделе Запросы WITH.

Предложение FROM

Предложение FROM задает одну или несколько исходных таблиц, служащих источником данных для SELECT. Если указывается несколько источников, результатом является декартово произведение (перекрестное соединение) их всех. Но обычно в запрос добавляются уточняющие условия (через WHERE), чтобы ограничить возвращаемый набор строк небольшим подмножеством декартова произведения.

Предложение FROM может содержать следующие элементы:

имя_таблицы

Имя существующей таблицы или представления (может быть дополнено схемой). Если перед именем таблицы указывается ONLY, сканируется только эта таблица. Если ONLY не указано, сканируются таблица и все ее потомки (если таковые имеются). После имени таблицы можно добавить необязательное указание *, чтобы явно обозначить, что блокировка затрагивает и все дочерние таблицы.

псевдоним

Псевдоним, заменяющий имя для элемента FROM. Используется для краткости или для устранения неоднозначности с замкнутыми соединениями (где одна и та же таблица сканируется несколько раз). Когда задается псевдоним, он полностью скрывает фактическое имя таблицы или функции; например, если задано FROM foo AS f, остальная часть запроса SELECT должна обращаться к этому элементу FROM по имени f, а не foo. Если задан псевдоним таблицы, то также можно записать список псевдонимов для одного или нескольких столбцов таблицы, чтобы предоставить им альтернативные имена.

TABLESAMPLE метод_выборки ( аргумент [, ...] ) [ REPEATABLE ( затравка ) ]

Предложение TABLESAMPLE после имени_таблицы показывает, что для получения подмножества строк в этой таблице должен использоваться заданный метод_выборки. Эта выборка предшествует применению любых других фильтров, например предложений WHERE. Стандартный дистрибутив QHB включает два метода выборки, BERNOULLI и SYSTEM, а другие методы выборки можно установить в базе данных с помощью расширений.

Методы выборки BERNOULLI и SYSTEM принимают единственный аргумент, определяющий, какая часть таблицы должна попасть в выборку в диапазоне от 0% до 100%. Этот аргумент может быть любым выражением со значением типа real. (Другие методы выборки могут принимать большее число аргументов или другие аргументы.) Эти два метода возвращают случайную выборку таблицы, которая будет содержать приблизительно указанный процент строк таблицы. Метод BERNOULLI сканирует всю таблицу и выбирает или игнорирует отдельные строки независимо с заданной вероятностью. Метод SYSTEM делает выборку на уровне блоков, для каждого блока определяя шанс его включения; возвращаются все строки в каждом выбранном блоке. Метод SYSTEM значительно быстрее метода BERNOULLI, когда выбирается небольшой процент строк, но в результате эффектов кучности он может возвращать менее случайную выборку таблицы.

В необязательном предложении REPEATABLE определяется аргумент затравка — число или выражение, используемое для генерации случайных чисел в рамках метода выборки. Значением затравки может быть любое отличное от NULL число с плавающей запятой. Два запроса, в которых указаны одинаковые значения затравки и аргумента, выдадут одну и ту же выборку таблицы при условии неизменности содержимого последней. Но разные значения затравки обычно создают разные выборки. В отсутствие предложения REPEATABLE для каждого запроса выбирается новая случайная выборка, основанная на затравке, сгенерированной системой. Обратите внимание, что некоторые дополнительные методы выборки не принимают предложение REPEATABLE и выдают новые выборки при каждом использовании.

выборка

Предложение FROM может содержать подзапрос SELECT. Это действует так, словно на время выполнения этой команды SELECT его выходные данные были созданы в виде временной таблицы. Обратите внимание, что подзапрос SELECT должен быть заключен в круглые скобки и для него должен быть задан псевдоним. Также здесь можно использовать команду VALUES.

имя_запроса_WITH

На запрос WITH можно ссылаться по имени, как если бы имя запроса было именем таблицы. (На самом деле запрос WITH скрывает любую реальную таблицу с тем же именем для целей основного запроса. При необходимости можно обратиться к реальной таблице с тем же именем, указав имя таблицы, дополненное схемой.) Так же, как и для имени таблицы, для этого имени можно задать псевдоним.

имя_функции

В предложении FROM могут находиться вызовы функций. (Это особенно полезно для функций, возвращающих результирующие множества, но можно использовать любую функцию.) Это действует так, как если бы выходные данные функции были созданы как временная таблица на время выполнения этой команды SELECT. Если результат функции имеет составной тип (включая случай, когда у функции несколько параметров OUT), каждый атрибут становится отдельным столбцом этой неявной таблицы.

Когда к вызову функции добавляется необязательное предложение WITH ORDINALITY, к результирующим столбцам функции добавляется новый столбец типа bigint. В нем нумеруются строки набора результатов функции, начиная с 1. По умолчанию этот столбец называется ordinality.

Так же, как и для таблицы, для функции можно задать псевдоним. Если псевдоним задается, то также можно написать список псевдонимов столбцов, чтобы предоставить альтернативные имена для одного или нескольких атрибутов составного типа, возвращаемого функцией, включая имя столбца нумерации, если он присутствует.

Несколько вызовов функций можно объединить в одном элементе предложения FROM, если заключить их в конструкцию ROWS FROM( ... ). Результатом такого элемента будет конкатенация первых строк из каждой функции, затем вторых строк из каждой функции и т. д. Если какие-то функции выдают меньше строк, чем другие, то отсутствующие данные заменяются значениями NULL, так что общее количество возвращаемых строк всегда такое же, как у функции, которая выдала наибольшее количество строк.

Если функция была определена как возвращающая тип данных record, для нее нужно указать псевдоним или ключевое слово AS, а затем список определений столбцов в форме ( имя_столбца тип_данных [, ... ]). Список определений столбцов должен соответствовать фактическому количеству и типам столбцов, возвращаемых функцией.

Если при использовании синтаксиса ROWS FROM( ... ) одной из функций требуется список определений столбцов, предпочтительно поместить его после вызова функции внутри ROWS FROM( ... ). Список определений столбцов можно поместить после конструкции ROWS FROM( ... ), только если вызывается всего одна функция, а предложение WITH ORDINALITY отсутствует.

Для того чтобы использовать ORDINALITY вместе со списком определений столбцов, необходимо использовать синтаксис ROWS FROM( ... ) и поместить туда этот список.

тип_соединения

Может быть одним из:

[INNER ] JOIN

LEFT [OUTER ] JOIN

RIGHT [OUTER ] JOIN

FULL [OUTER ] JOIN

Для типов соединений INNER и OUTER должно быть указано условие соединения, а именно одно из предложений ON условие_соединения, USING (столбец_соединения[, ... ]) или NATURAL. См. их описания ниже.

Предложение JOIN объединяет в себе два элемента FROM, которые для удобства будем называть «таблицами», хотя на самом деле они могут быть любого типа, допустимого в качестве элемента FROM. При необходимости для определения порядка вложенности используйте круглые скобки. При отсутствии круглых скобок предложения JOIN вкладываются слева направо. В любом случае, JOIN связывает элементы сильнее, чем разделяющие запятые в списке FROM. Все варианты JOIN введены только для удобства записи, поскольку они не делают ничего, что нельзя было бы сделать с простыми FROM и WHERE.

LEFT OUTER JOIN возвращает все строки ограниченного декартова произведения (т. е. все объединенные строки, попадающие под условие соединения) плюс по одной копии для каждой строки в таблице слева, для которой не находится строки в таблице справа, удовлетворяющей условию соединения. Левая строка дополняется до полной ширины объединенной таблицы значениями NULL, вставляемыми в столбцы справа. Обратите внимание, что при принятии решения о том, какие строки двух таблиц соответствуют друг другу, учитывается только условие самого предложения JOIN. И только затем применяются внешние условия.

Напротив, RIGHT OUTER JOIN возвращает все соединенные строки плюс по одной строке для каждой строки справа, не имеющей соответствия слева (дополненной значениями NULL слева). Это предложение введено только для удобства записи, так как его можно преобразовать в LEFT OUTER JOIN, поменяв местами левую и правую таблицы.

FULL OUTER JOIN возвращает все соединенные строки плюс по одной строке для каждой несоответствующей строки слева (дополненной значениями NULL справа), плюс по одной строке для каждой несоответствующей строки справа (дополненной значениями NULL слева).

ON условие_соединения

Аргумент условие_соединения является выражением, возвращающим значение типа boolean (как в предложении WHERE) и указывающим, какие строки при соединении считаются соответствующими.

USING ( столбец_соединения [, ...] ) [ AS псевдоним_использования_соединения ]

Предложение вида USING ( a, b, ... ) представляет собой сокращенную форму записи ON таблица_слева.a = таблица_справа.a AND таблица_слева.b = таблица_справа.b .... Также USING подразумевает, что только один из каждой пары равнозначных столбцов будет включен в результат соединения, а не оба.

Если задается имя псевдоним_использования_соединения, оно предоставляет псевдоним таблицы для столбцов соединения. По этому имени можно обращаться только к столбцам соединения, перечисленным в предложении USING. В отличие от обычного псевдонима, этот не скрывает имена соединенных таблиц от остального запроса. Также в отличие от обычного псевдонима, с ним нельзя написать список псевдонимов столбцов — выходные имена столбцов соединения совпадают с теми, которые находятся в списке USING.

NATURAL

NATURAL является сокращенной формой списка USING, который содержит все столбцы в двух таблицах, имеющие совпадающие имена. Если общие имена столбцов отсутствуют, NATURAL равнозначно ON TRUE.

CROSS JOIN

Указание CROSS JOIN равнозначно INNER JOIN ON (TRUE), то есть никакие строки по условию не удаляются. Оба варианта формируют простое декартово произведение — тот же результат, какой можно получить, перечислив эти две таблицы на верхнем уровне FROM, но только ограниченный условием соединения (если таковое имеется).

LATERAL

Ключевое слово LATERAL может предшествовать подзапросу SELECT в списке FROM. Оно позволяет в этом подзапросе SELECT обращаться к столбцам элементов FROM, которые появляются перед ним в списке FROM. (Без LATERAL каждый подзапрос SELECT обрабатывается независимо и поэтому не может перекрестно ссылаться на другие элементы FROM.)

LATERAL может также предшествовать вызову функции в списке FROM, но в данном случае это будет пропускаемое слово, потому что выражения с функциями в любом случае могут ссылаться на предыдущие элементы списка FROM.

Элемент LATERAL может находиться и в верхнем уровне списка FROM, и в дереве JOIN. В последнем случае он может ссылаться на любые элементы, находящиеся в левой части JOIN, от которого он находится справа.

Когда элемент FROM содержит перекрестные ссылки LATERAL, вычисление выполняется следующим образом: для каждой строки элемента FROM, предоставляющего столбец(цы) с перекрестной ссылкой, или набора строк из нескольких элементов FROM, предоставляющих эти столбцы, элемент LATERAL вычисляется с использованием этой строки или значений набора строк этих столбцов. Результирующие строки, как обычно, объединяются со строками, из которых они были вычислены. Эта процедура повторяется для каждой строки или набора строк из исходной таблицы (или таблиц).

Исходная таблица (или таблицы) столбца должна быть соединена с элементом LATERAL с помощью INNER или LEFT, иначе не образуется однозначно определенного набора строк, из которого можно было бы вычислить все наборы строк для элемента LATERAL. Таким образом, хотя такая конструкция, как X RIGHT JOIN LATERAL Y, синтаксически допустима, фактически Y не может обращаться к X.

Предложение WHERE

Необязательное предложение WHERE имеет общую форму

WHERE условие

где условие — это любое выражение, которое при вычислении выдает результат типа boolean. Любая строка, которая не удовлетворяет этому условию, будет исключена из вывода. Строка удовлетворяет условию, если возвращает true, когда вместо ссылок на переменные подставляются фактические значения строк.

Предложение GROUP BY

Необязательное предложение GROUP BY имеет общую форму

GROUP BY [ ALL | DISTINCT ] элемент_группирования [, ...]

GROUP BY будет собирать в одну строку все выбранные строки, имеющие одинаковые значения для выражений группировки. Таким выражением, используемым внутри элемента_группирования, может быть имя входного столбца, либо имя или порядковый номер выходного столбца (из списка элементов SELECT), либо произвольное выражение, сформированное из значений входных столбцов. В случае неоднозначности имя в GROUP BY будет интерпретироваться как имя входного, а не выходного столбца.

Если в элементах группирования задается GROUPING SETS, ROLLUP или CUBE, то предложение GROUP BY в целом определяет некоторое количество независимых наборов_группирования. Эффект от этого равнозначен объединению подзапросов (с UNION ALL) с отдельными наборами группирования в качестве их предложений GROUP BY. Необязательное предложение DISTINCT удаляет перед обработкой повторяющиеся наборы; оно не превращает UNION ALL в UNION DISTINCT. Дополнительную информацию об обращении с наборами группирования см. в подразделе GROUPING SETS, CUBE и ROLLUP.

Агрегатные функции, если они используются, вычисляются по всем строкам, составляющим каждую группу, выдавая отдельное значение для каждой группы. (Если указаны агрегатные функции, но нет предложения GROUP BY, запрос выполняется так, как если бы в нем была одна группа, содержащая все выбранные строки.) Набор строк, подаваемых в каждую агрегатную функцию, может быть дополнительно отфильтрован путем добавления к вызову агрегатной функции предложения FILTER; дополнительную информацию см. в подразделе Агрегатные выражения. С предложением FILTER на вход агрегатной функции будут поступать только строки, соответствующие фильтру.

Когда имеется предложение GROUP BY или какая-либо агрегатная функция, выражения в списке SELECT не могут обращаться к негруппируемым столбцам, кроме как в агрегатных функциях или если негруппируемый столбец функционально зависит от группируемых столбцов, поскольку в противном случае в негруппируемый столбец вернулось бы более одного возможного значения. Функциональная зависимость появляется, если группируемые столбцы (или их подмножество) являются первичным ключом таблицы, содержащей негруппируемый столбец.

Имейте в виду, что все агрегатные функции вычисляются перед вычислением любых «скалярных» выражений в предложении HAVING или списке SELECT. Это означает, что, например, нельзя использовать выражение CASE, чтобы пропустить вычисление агрегатной функции; см. подраздел Правила вычисления выражений.

В настоящий момент вместе с GROUP BY нельзя указать FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE и FOR KEY SHARE.

Предложение HAVING

Необязательное предложение HAVING имеет общую форму

HAVING условие

где условие задается так же, как и в предложении WHERE.

Предложение HAVING исключает из результата строки групп, не удовлетворяющие условию. HAVING отличается от WHERE: WHERE фильтрует отдельные строки перед применением GROUP BY, тогда как HAVING фильтрует группы строк, созданные с помощью GROUP BY. Каждый столбец, указанный в условии, должен однозначно ссылаться на группируемый столбец, за исключением случаев, когда ссылка находится в агрегатной функции или негруппируемый столбец функционально зависит от группируемых.

Наличие предложения HAVING превращает запрос в группируемый, даже если отсутствует предложение GROUP BY. То же самое происходит, когда запрос содержит агрегатные функции, но нет предложения GROUP BY. Все выбранные строки считаются образующими одну группу, а в списке SELECT и предложении HAVING можно обращаться к столбцам таблицы только из агрегатных функций. Такой запрос будет выдавать одну строку, если результат условия HAVING является true, и ноль строк в противном случае.

В настоящий момент вместе с HAVING нельзя указать FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE и FOR KEY SHARE.

Предложение WINDOW

Необязательное предложение WINDOW имеет общую форму

WINDOW имя_окна AS ( определение_окна ) [, ...]

где имя_окна — это имя, на которое можно ссылаться из предложений OVER или последующих определений окна, а определение_окна — это:

[ имя_существующего_окна ]
[ PARTITION BY выражение [, ...] ]
[ ORDER BY выражение [ ASC | DESC | USING оператор ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ предложение_рамки ]

Если имя_существующего_окна указано, оно должно ссылаться на предшествующую запись в списке WINDOW; новое окно копирует предложение разбиения из этой записи, а также предложение сортировки, если таковое имеется. В этом случае для нового окна нельзя указать свое собственное предложение PARTITION BY, а предложение ORDER BY можно определить, только если его не было у копируемого окна. Новое окно всегда использует свое собственное предложение рамки; в копируемом окне предложение рамки не должно быть указано.

Элементы списка PARTITION BY интерпретируется во многом так же, как элементы предложения GROUP BY, за исключением того, что они всегда являются простыми выражениями и никогда не являются именем или номером выходного столбца. Другое отличие заключается в том, что эти выражения могут содержать вызовы агрегатных функций, которые не разрешены в обычном предложении GROUP BY. Они разрешены здесь, потому что формирование окна происходит после группировки и агрегирования.

Аналогично элементы списка ORDER BY интерпретируется во многом так же, как элементы предложения ORDER BY, за исключением того, что эти выражения всегда воспринимаются как простые выражения и никогда как имя или номер выходного столбца.

Необязательное предложение_рамки определяет рамку окна для оконных функций, которые зависят от рамки (не все они зависят). Рамка окна представляет собой набор связанных строк для каждой строки запроса (называемой текущей строкой). предложением_рамки может быть:

{ RANGE | ROWS | GROUPS } начало_рамки [ исключение_рамки ]
{ RANGE | ROWS | GROUPS } BETWEEN начало_рамки AND конец_рамки [ исключение_рамки ]

где началом_рамки и концом_рамки могут быть:

UNBOUNDED PRECEDING
смещение PRECEDING
CURRENT ROW
смещение FOLLOWING
UNBOUNDED FOLLOWING

и исключением_рамки может быть:

EXCLUDE CURRENT ROW
EXCLUDE GROUP
EXCLUDE TIES
EXCLUDE NO OTHERS

Если конец_рамки опущен, то по умолчанию подразумевается CURRENT ROW. Ограничения заключаются в том, что в качестве начала_рамки нельзя указать как UNBOUNDED FOLLOWING, конец_рамки — как UNBOUNDED PRECEDING, и конец_рамки не может находиться перед началом_рамки в приведенном выше списке параметров начала_рамки и конца_рамки — например, синтаксис RANGE BETWEEN CURRENT ROW AND смещение PRECEDING не допускается.

По умолчанию рамка окна образуется предложением RANGE UNBOUNDED PRECEDING, которое равнозначно RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; оно устанавливает рамку так, чтобы та включала все строки от начала партиции до последней строки, родственной текущей (строка, которая, согласно указанному для окна предложению ORDER BY, считается равнозначной текущей строке; если предложение ORDER BY отсутствует, все строки являются родственными). В целом, UNBOUNDED PRECEDING означает, что рамка начинается с первой строки партиции, и подобным же образом UNBOUNDED FOLLOWING означает, что рамка заканчивается последней строкой партиции независимо от того, какой установлен режим: RANGE, ROWS или GROUPS. В режиме ROWS указание CURRENT ROW означает, что рамка начинается или заканчивается текущей строкой; но в режиме RANGE или GROUPS это означает, что рамка начинается или заканчивается первой или последней строкой, родственной текущей, согласно порядку ORDER BY. Параметры смещение PRECEDING и смещение FOLLOWING различаются по смыслу в зависимости от режима рамки. В режиме ROWS смещение является целым числом, показывающим количество строк, на которое начало или конец рамки смещается вверх или вниз от текущей строки соответственно. В режиме GROUPS смещение является целым числом, показывающим, количество родственных групп, на которое начало или конец рамки смещается вверх или вниз от текущей строки соответственно, где родственная группа — это группа строк, равнозначных согласно предложению ORDER BY данного окна. В режиме RANGE использование параметра смещение требует, чтобы в определении окна присутствовал ровно один столбец ORDER BY. Тогда рамка содержит те строки, где значение упорядочивающего столбца не более чем на смещение меньше (для PRECEDING) или больше (для FOLLOWING) значения упорядочивающего столбца текущей строки. В этих случаях тип данных выражения смещение зависит от типа данных упорядочивающего столбца. Для числовых столбцов это обычно тот же тип, а для столбцов с типом дата/время — тип interval. Во всех этих случаях значение смещения должно быть отличным от NULL и неотрицательным. Кроме того, несмотря на то, что смещение необязательно должно быть простой константой, оно не может содержать переменные, агрегатные или оконные функции.

Параметр исключение_рамки позволяет исключать из рамки строки, окружающие текущую строку, даже если они могли бы быть включены в соответствии с параметрами начала и конца рамки. Предложение EXCLUDE CURRENT ROW исключает из рамки текущую строку. EXCLUDE GROUP исключает из рамки текущую строку и родственные ей согласно порядку сортировки. EXCLUDE TIES исключает из рамки только строки, родственные текущей строке, но не саму текущую строку. EXCLUDE NO OTHERS просто явно указывает поведение по умолчанию: не исключать ни текущую строку, ни родственные ей.

Учтите, что в режиме ROWS могут выдаваться непредсказуемые результаты, если согласно порядку, указанному в ORDER BY, строки сортируются неоднозначно. Режимы RANGE и GROUPS предназначены для обеспечения того, чтобы строки, являющиеся родственными в порядке ORDER BY, обрабатывались одинаково: все строки данной родственной группы попадут в одну рамку или будут исключены из нее.

Назначение предложения WINDOW — управление поведением оконных функций, фигурирующих в параметрах списка SELECT или предложения ORDER BY запроса. Эти функции могут ссылаться на записи предложения WINDOW по имени в своих предложениях OVER. Однако на запись предложения WINDOW не обязательно нигде ссылаться: если она не используется в запросе, то просто игнорируется. Можно использовать оконные функции вообще без предложения WINDOW, так как в вызове оконной функции можно указать ее определение окна непосредственно в предложении OVER. Тем не менее предложение WINDOW позволяет сократить текст запроса, когда одно и то же определение окна требуется для более чем одной оконной функции.

В настоящий момент вместе с WINDOW нельзя указать FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE и FOR KEY SHARE.

Оконные функции подробно описаны в разделе Оконные функции и подразделах Вызовы оконных функций и Обработка оконных функций.

Список SELECT

Список SELECT (между ключевыми словами SELECT и FROM) задает выражения, которые формируют выходные строки команды SELECT. Выражения могут ссылаться на столбцы, вычисленные в предложении FROM (и обычно это делают).

Так же, как и в таблице, каждый выходной столбец SELECT имеет имя. В простом SELECT это имя используется только для обозначения столбца для отображения, но когда SELECT является подзапросом большего запроса, имя рассматривается большим запросом как имя столбца виртуальной таблицы, созданной подзапросом. Чтобы указать имя, используемое для выходного столбца, нужно после выражения столбца написать AS имя_вывода. (Можно опустить AS, но только если желаемое имя вывода не совпадает с ключевыми словами QHB (см. раздел Ключевые слова SQL). Для защиты от возможных будущих добавлений ключевых слов рекомендуется всегда либо писать AS, либо заключать имя вывода в кавычки.) Если не задать имя столбца, QHB выберет его автоматически. Если выражение столбца является простой ссылкой на столбец, то выбранное имя совпадет с именем этого столбца. В более сложных случаях может использоваться имя функции или типа, либо система может сгенерировать имя, например ?column?.

Имя выходного столбца можно использовать для обращения к его значению в предложениях ORDER BY и GROUP BY, но не в предложениях WHERE или HAVING; там следует писать вместо имени само выражение.

Вместо выражения в выходном списке можно написать * в качестве краткого обозначения всех столбцов выбранных строк. Кроме того, можно написать имя_таблицы.* как краткое обозначение столбцов, получаемых конкретно из этой таблицы. В этих случаях невозможно указать новые имена с помощью AS; имена выходных столбцов будут совпадать с именами столбцов таблицы.

Согласно стандарту SQL, выражения в выходном списке должны быть вычислены перед применением DISTINCT, ORDER BY или LIMIT. Безусловно, это необходимо при использовании DISTINCT, так как иначе неясно, какие значения должны выдаваться как уникальные. Однако во многих случаях удобнее, если выходные выражения вычисляются после ORDER BY и LIMIT; особенно если выходной список содержит какие-либо изменчивые или затратные функции. В этом случае порядок вычисления функций является более интуитивным, а для строк, которые не попадут в результат, вычисления производиться не будут. QHB фактически будет вычислять выходные выражения после сортировки и ограничения количества строк, если эти выражения не фигурируют в DISTINCT, ORDER BY или GROUP BY. (В качестве примера, в запросе SELECT f(x) FROM tab ORDER BY 1 функция f(x), несомненно, должна вычисляться перед сортировкой.) Выходные выражения, содержащие функции, возвращающие множества, фактически вычисляются после сортировки и перед ограничением количества строк, так что LIMIT будет отбрасывать строки, выдаваемые функцией, возвращающей множество.

Предложение DISTINCT

Если указывается SELECT DISTINCT, то из результирующего набора исключаются все повторяющиеся строки (сохраняется одна строка из каждой группы дубликатов). SELECT ALL задает обратное: все строки сохраняются; это поведение по умолчанию.

SELECT DISTINCT ON ( выражение [, ...] ) сохраняет только первую строку из каждого набора строк, в котором заданные выражения вычисляются в одинаковые значения. Выражения DISTINCT ON интерпретируются по тех же правилам, что и ORDER BY (см. выше). Обратите внимание, что «первая строка» каждого набора непредсказуема, если для обеспечения того, чтобы желаемая строка появилась первой, не указано ORDER BY. Например:

SELECT DISTINCT ON (location) location, time, report
    FROM weather_reports
    ORDER BY location, time DESC;

извлекает самый последний отчет о погоде для каждого местоположения. Но если бы мы не воспользовались ORDER BY, чтобы принудительно установить нисходящий порядок значений времени для каждого местоположения, мы бы получили для каждого местоположения отчет от непредсказуемого времени.

Выражение (или выражения) DISTINCT ON должно соответствовать самому левому выражению (или выражения) ORDER BY. Предложение ORDER BY обычно содержит дополнительное выражение (или выражения), определяющее желаемый приоритет строк в каждой группе DISTINCT ON.

В настоящий момент вместе с DISTINCT нельзя указать FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE и FOR KEY SHARE.

Предложение UNION

Предложение UNION имеет общую форму:

оператор_SELECT UNION [ ALL | DISTINCT ] оператор_SELECT

Где оператор_SELECT — это любой оператор SELECT без предложений ORDER BY, LIMIT, FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE или FOR KEY SHARE. (ORDER BY и LIMIT можно присоединить к вложенному выражению, если оно заключено в круглые скобки. Без скобок эти предложения будут восприняты для применения к результату UNION, а не к входному выражению в его правой части.)

Оператор UNION вычисляет объединение набора строк, возвращаемых задействованными операторами SELECT. Строка находится в объединении двух результирующих наборов, если находится хотя бы в одном из них. Два оператора SELECT, представляющие прямые операнды UNION, должны выдавать одинаковое количество столбцов, а соответствующие столбцы должны иметь совместимые типы данных.

Результат UNION не будет содержать повторяющихся строк, если не указан параметр ALL. ALL предотвращает устранение дубликатов. (Следовательно, UNION ALL обычно значительно быстрее, чем UNION, поэтому по возможности следует использовать ALL.) Можно написать DISTINCT, чтобы явно указать, что повторяющиеся строки должны быть удалены (это поведение по умолчанию).

Несколько операторов UNION в одном операторе SELECT вычисляются слева направо, если круглыми скобками не определен иной порядок.

В настоящий момент ни для результата с UNION, ни для любого из подзапросов UNION невозможно указать FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE и FOR KEY SHARE.

Предложение INTERSECT

Предложение INTERSECT имеет общую форму:

оператор_SELECT INTERSECT [ ALL | DISTINCT ] оператор_SELECT

Где оператор_SELECT — это любой оператор SELECT без предложения ORDER BY, LIMIT, FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE или FOR KEY SHARE.

Оператор INTERSECT вычисляет пересечение набора строк, возвращаемых задействованными операторами SELECT. Строка находится в пересечении двух результирующих наборов, если присутствует в них обоих.

Результат INTERSECT не будет содержать повторяющихся строк, если не указан параметр ALL. С ALL строка, повторяющаяся m раз в левой таблице и n раз в правой, будет выдана в результирующем наборе min(m,n) раз. Можно написать DISTINCT, чтобы явно указать, что повторяющиеся строки должны быть удалены (это поведение по умолчанию).

Несколько операторов INTERSECT в одном операторе SELECT вычисляются слева направо, если круглыми скобками не определен иной порядок. INTERSECT связывает свои подзапросы сильнее, чем UNION. Иными словами, A UNION B INTERSECT C будет восприниматься как A UNION (B INTERSECT C).

В настоящий момент ни для результата с INTERSECT, ни для любого из подзапросов INTERSECT нельзя указать FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE и FOR KEY SHARE.

Предложение EXCEPT

Предложение EXCEPT имеет общую форму:

оператор_SELECT EXCEPT [ ALL | DISTINCT ] оператор_SELECT

Где оператор_SELECT — это любой оператор SELECT без предложений ORDER BY, LIMIT, FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE или FOR KEY SHARE.

Оператор EXCEPT вычисляет набор строк, которые находятся в результате выполнения левого оператора SELECT, но не в результате выполнения правого.

Результат EXCEPT не будет содержать каких-либо повторяющихся строк, если не указан параметр ALL. С ALL строка, повторяющаяся m раз в левой таблице и n раз в правой, будет выдана в результирующем наборе max(m-n,0) раз. Можно написать DISTINCT, чтобы явно указать, что повторяющиеся строки должны быть удалены (это поведение по умолчанию).

Несколько операторов EXCEPT в одном операторе SELECT вычисляются слева направо, если круглыми скобками не определен иной порядок. EXCEPT связывает свои подзапросы так же сильно, как UNION.

В настоящий момент ни для результата с EXCEPT, ни для любого из подзапросов EXCEPT нельзя указать FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE и FOR KEY SHARE.

Предложение ORDER BY

Необязательное предложение ORDER BY имеет общую форму:

ORDER BY выражение [ ASC | DESC | USING оператор ] [ NULLS { FIRST | LAST } ] [, ...]

Предложение ORDER BY указывает, что результирующие строки запроса должны быть отсортированы в соответствии с указанным выражением (или выражениями). Если значения двух строк равны по самому левому выражению, то они сравниваются по следующему выражению и т. д. Если их значения равны по всем указанным выражениям, строки возвращаются в порядке, определяемом реализацией.

Каждое выражения может быть именем или порядковым номером выходного столбца (из элемента списка SELECT) либо произвольным выражением, сформированным из значений входного столбца.

Порядковый номер означает последовательный номер (нумерация слева направо) позиции выходного столбца. Эта возможность позволяет определить порядок сортировки по столбцу, который не имеет уникального имени. В этом нет абсолютной необходимости, потому что имя выходному столбцу всегда можно назначить с помощью предложения AS.

Также в предложении ORDER BY можно использовать произвольные выражения, в том числе столбцы, которые не отображаются в списке результатов SELECT. Таким образом, следующий запрос вполне корректен:

SELECT name FROM distributors ORDER BY code;

Однако здесь есть ограничение: если предложение ORDER BY применяется к результату предложения UNION, INTERSECT или EXCEPT, в нем можно указывать только имя или номер выходного столбца, но не выражение.

Если выражение ORDER BY — простое имя, соответствующее имени как выходного, так и входного столбца,ORDER BY будет интерпретировать его как имя выходного столбца. Это противоположно выбору, который в такой же ситуации сделает GROUP BY. Данная несогласованность допущена для совместимости со стандартом SQL.

Дополнительно после любого выражения в предложении ORDER BY можно добавить ключевое слово ASC (по возрастанию) или DESC (по убыванию). Если ничего не указано, по умолчанию предполагается ASC. Как вариант, в предложении USING можно указать имя конкретного оператора сортировки. Оператор сортировки должен быть членом «меньше» или «больше» некоторого семейства операторов B-дерева. ASC обычно равнозначно USING <, а DESC — USING >. (Но создатель пользовательского типа данных может точно определить порядок сортировки по умолчанию, который может соответствовать операторам с другими именами.)

Если указывается NULLS LAST, значения NULL при сортировке окажутся после всех значений не NULL; если указывается NULLS FIRST, значения NULL окажутся перед всеми значениями не NULL. Если ни то, ни другое не указано, то при явном или косвенном выборе порядка ASC по умолчанию подразумевается NULLS LAST, и наоборот, при явном или косвенном выборе порядка DESC по умолчанию подразумевается NULLS FIRST (следовательно, по умолчанию считается, что значения NULL больше значений не NULL). С предложением USING порядок NULL по умолчанию зависит от того, является ли заданный оператор оператором «меньше» или «больше».

Обратите внимание, что параметры сортировки применяются только к выражению, за которым они следуют: например, ORDER BY x, y DESC не то же самое, что ORDER BY x DESC, y DESC.

Данные символьных строк сортируются в соответствии с правилом сортировки, установленным для сортируемого столбца. При необходимости это поведение можно переопределить путем включения в выражение предложения COLLATE, например, так: ORDER BY mycolumn COLLATE "en_US". Дополнительную информацию см. в подразделе Применение правил сортировки и разделе Поддержка правил сортировки.

Предложение LIMIT

Предложение LIMIT состоит из двух независимых вложенных предложений:

LIMIT { число | ALL }
OFFSET начало

Параметр число задает максимальное количество возвращаемых строк, тогда как начало задает количество строк, которые необходимо пропустить, прежде чем начать выдавать строки. Если указаны оба значения, сначала строки пропускаются в количестве, заданном значением начало, а потом отсчитывается заданное значением число количество следующих строк, которые будут возвращены.

Если выражение число вычисляется в значение NULL, это предложение обрабатывается как LIMIT ALL, то есть без ограничения числа строк. Если начало вычисляется в значение NULL, оно обрабатывается как OFFSET 0.

SQL:2008 представил другой синтаксис для получения того же результата, который также поддерживает QHB. Это:

OFFSET начало { ROW | ROWS }
FETCH { FIRST | NEXT } [ число ] { ROW | ROWS } { ONLY | WITH TIES }

В этом синтаксисе стандарт требует, чтобы значение начало или число было литеральной константой, параметром или именем переменной; в качестве расширения QHB разрешает использовать другие выражения, но, как правило, они должны быть заключены в круглые скобки, чтобы избежать неоднозначности. Если в предложении FETCH число опускается, то по умолчанию принимается значение 1. Параметр WITH TIES используется для возвращения дополнительных строк, согласно предложению ORDER BY совпадающих с последней позицией набора результатов; в этом случае предложение ORDER BY является обязательным, а SKIP LOCKED — недопустимым. Слова ROW и ROWS, а также FIRST и NEXT являются пропускаемыми и не влияют на действие этих предложений. Согласно стандарту, предложение OFFSET должно предшествовать предложению FETCH, если присутствуют оба; но QHB менее строга и позволяет любой порядок.

При использовании LIMIT имеет смысл использовать и предложение ORDER BY, чтобы результирующие строки выдавались в определенном порядке. В противном случае вы получите непредсказуемое подмножество строк запроса — можно запросить строки с десятой по двадцатую, но в каком порядке? Порядок будет неизвестен, если не указать ORDER BY.

Планировщик запросов учитывает ограничение LIMIT при создании плана запроса, поэтому высока вероятность, что вы получите разные планы (и разный же порядок строк) в зависимости от того, какие значения указаны для LIMIT и OFFSET. Таким образом, различные значения LIMIT/OFFSET при выборе различных подмножеств результата запроса приведут к несогласованным результатам, если не установить предсказуемый порядок сортировки результатов с помощью ORDER BY. Это не ошибка, а неотъемлемое следствие того факта, что SQL не гарантирует вывод результатов запроса в каком-то конкретном порядке, если тот не задан предложением ORDER BY.

Возможно даже, что без предложения ORDER BY, принудительно выбирающего детерминированное подмножество, при повторном выполнении одного и того же запроса с LIMIT будут получены разные подмножества строк таблицы. Это опять же не ошибка; в подобном случае детерминированность результатов просто не гарантируется.

Предложение блокировки

Предложениями блокировки являются операторы FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE и FOR KEY SHARE; они влияют на то, как SELECT блокирует строки по мере их получения из таблицы.

Предложение блокировки имеет общую форму

FOR сила_блокировки [ OF имя_таблицы [, ...] ] [ NOWAIT | SKIP LOCKED ]

где силой_блокировки может быть:

UPDATE
NO KEY UPDATE
SHARE
KEY SHARE

Дополнительную информацию о каждом режиме блокировки на уровне строк см. в подразделе Блокировки на уровне строк.

Чтобы операция не ожидала фиксации других транзакций, используйте указание NOWAIT или SKIP LOCKED. С NOWAIT оператор сообщает об ошибке, а не ждет, если выбранную строку нельзя заблокировать немедленно. С указанием SKIP LOCKED все выбранные строки, которые нельзя немедленно заблокировать, пропускаются. Пропуск заблокированных строк формирует несогласованное представление данных, поэтому этот вариант не подходит для общего применения, но может быть использован для предотвращения конфликта блокировок при обращении множества потребителей к таблице типа очереди. Обратите внимание, что указания NOWAIT и SKIP LOCKED можно применить только к блокировке(ам) уровня строк — необходимая блокировка ROW SHARE уровня таблицы по-прежнему запрашивается обычным способом (см. главу Параллельный контроль). Если нужно запросить блокировку уровня таблицы без ожидания, можно использовать команду LOCK с указанием NOWAIT.

Если в предложении блокировки задаются определенные имена таблиц, то блокируются только строки, поступающие из этих таблиц; все остальные таблицы, указанные в SELECT, считываются как обычно. Предложение блокировки без списка таблиц воздействует на все таблицы, указанные в операторе. Если предложение блокировки применяется к представлению или подзапросу, оно влияет на все таблицы, используемые в этом представлении или подзапросе. Однако эти предложения не применяются к запросам WITH, на которые ссылается основной запрос. Если вы хотите, чтобы блокировка строк происходила в запросе WITH, укажите предложение блокировки непосредственно в этом запросе WITH.

Если необходимо задать различное поведение блокировки для разных таблиц, можно написать несколько предложений блокировки. При этом если одна и та же таблица упоминается (или неявно затрагивается) более чем в одном предложении блокировки, то эта таблица обрабатывается так, как если бы была указана только в самом сильном из них. Аналогично таблица обрабатывается в режиме NOWAIT, если тот указан в любом из затрагивающих ее предложений. В противном случае таблица обрабатывается в режиме SKIP LOCKED, если тот указан в любом из затрагивающих ее предложений.

Предложения блокировки нельзя использовать в контекстах, где возвращаемые строки невозможно четко связать с отдельными строками таблицы; например, они не могут использоваться при агрегировании.

Когда предложение блокировки появляется на верхнем уровне запроса SELECT, блокируются именно те строки, которые возвращаются запросом; в случае запроса соединения блокируются те строки, которые участвуют в возвращаемых строках соединения. Кроме того, строки, удовлетворяющие условиям запроса на момент создания снимка запроса, будут заблокированы, хотя и не будут возвращены, если были изменены с момента снимка и больше не удовлетворяют условиям запроса. Если используется LIMIT, блокировка останавливается, как только возвращается достаточно строк, чтобы удовлетворить заданному пределу (но обратите внимание, что строки, пропускаемые указанием OFFSET, будут блокироваться). Аналогично если предложение блокировки используется в запросе курсора, то заблокируются только те строки, которые были фактически извлечены или пройдены курсором.

Когда предложение блокировки появляется в подзапросе SELECT, блокируются те строки, которые возвращаются во внешний запрос из подзапроса. Этих строк может оказаться меньше, чем можно было бы предположить при проверке только подзапроса, поскольку условия из внешнего запроса могут способствовать оптимизации выполнения подзапроса. Например,

SELECT * FROM (SELECT * FROM mytable FOR UPDATE) ss WHERE col1 = 5;

заблокирует только строки c col1 = 5, хотя в такой записи условие не относится к подзапросу.

В предыдущих версиях не удавалось сохранить блокировку, измененную более поздней точкой сохранения. Например, этому коду:

BEGIN;
SELECT * FROM mytable WHERE key = 1 FOR UPDATE;
SAVEPOINT s;
UPDATE mytable SET ... WHERE key = 1;
ROLLBACK TO s;

не удалось бы сохранить блокировку FOR UPDATE после ROLLBACK TO.

ВНИМАНИЕ!
Возможно, команда SELECT, запущенная с уровнем изоляции транзакции READ COMMITTED и с указанием ORDER BY и предложением блокировки будет возвращать строки не по порядку. Это связано с тем, что ORDER BY применяется первым. Команда сортирует результат, но затем может быть заблокирована при попытке получить блокировку одной или нескольких строк. Как только блокировка с SELECT снимается, некоторые значения сортируемых столбцов могут быть изменены, приводя к нарушению порядка этих строк (хотя они упорядочены с точки зрения исходных значений столбцов). При необходимости эту проблему можно обойти, поместив в подзапрос предложение FOR UPDATE/SHARE, например:

SELECT * FROM (SELECT * FROM mytable FOR UPDATE) SS ORDER BY column1;

Обратите внимание, что этот запрос приведет к блокировке всех строк таблицы mytable, тогда как с FOR UPDATE на верхнем уровне блокировались бы только фактически возвращаемые строки. Это может значительно повлиять на производительность, особенно если ORDER BY объединено с LIMIT или другими ограничениями. Таким образом, этот метод рекомендуется только в том случае, если ожидаются параллельные изменения сортируемых столбцов и требуется тщательно отсортированный результат.

На уровне изоляции транзакции REPEATABLE READ или SERIALIZABLE это привело бы к ошибке сериализации (с SQLSTATE '40001'), поэтому на этих уровнях изоляции нет никакой возможности получения строк не по порядку.

Команда TABLE

Команда

TABLE имя

равнозначна

SELECT * FROM имя

Ее можно использовать как команду верхнего уровня или как вариант более краткого синтаксиса внутри сложных запросов. С командой TABLE можно использовать только предложения WITH, UNION, INTERSECT, EXCEPT, ORDER BY, LIMIT, OFFSET, FETCH и предложение блокировки FOR; предложение WHERE и любые формы агрегирования использовать нельзя.

Примеры

Соединение таблицы films с таблицей distributors:

SELECT f.title, f.did, d.name, f.date_prod, f.kind
    FROM distributors d, films f
    WHERE f.did = d.did

       title       | did |     name     | date_prod  |   kind
-------------------+-----+--------------+------------+----------
 The Third Man     | 101 | British Lion | 1949-12-23 | Drama
 The African Queen | 101 | British Lion | 1951-08-11 | Romantic
 ...

Суммирование значений столбца len (продолжительность) для всех фильмов и группирование результатов по столбцу kind (тип фильма):

SELECT kind, sum(len) AS total FROM films GROUP BY kind;

   kind   | total
----------+-------
 Action   | 07:34
 Comedy   | 02:58
 Drama    | 14:28
 Musical  | 06:42
 Romantic | 04:38

Суммирование значений столбца len для всех фильмов, группирование результатов по столбцу kind и выведение только тех групп, общая продолжительность которых меньше 5 часов:

SELECT kind, sum(len) AS total
    FROM films
    GROUP BY kind
    HAVING sum(len) < interval '5 hours';

   kind   | total
----------+-------
 Comedy   | 02:58
 Romantic | 04:38

Следующие два примера демонстрируют идентичные способы сортировки результатов по содержимому второго столбца (name):

SELECT * FROM distributors ORDER BY name;
SELECT * FROM distributors ORDER BY 2;

 did |       name
-----+------------------
 109 | 20th Century Fox
 110 | Bavaria Atelier
 101 | British Lion
 107 | Columbia
 102 | Jean Luc Godard
 113 | Luso films
 104 | Mosfilm
 103 | Paramount
 106 | Toho
 105 | United Artists
 111 | Walt Disney
 112 | Warner Bros.
 108 | Westward

Следующий пример показывает объединение таблиц distributors и actors, с ограничением результатов значениями, начинающимися с буквы W в каждой таблице. Интерес представляют только неповторяющиеся строки, поэтому ключевое слово ALL опущено.

distributors:               actors:
 did |     name              id |     name
-----+--------------        ----+----------------
 108 | Westward               1 | Woody Allen
 111 | Walt Disney            2 | Warren Beatty
 112 | Warner Bros.           3 | Walter Matthau
 ...                         ...

SELECT distributors.name
    FROM distributors
    WHERE distributors.name LIKE 'W%'
UNION
SELECT actors.name
    FROM actors
    WHERE actors.name LIKE 'W%';

      name
----------------
 Walt Disney
 Walter Matthau
 Warner Bros.
 Warren Beatty
 Westward
 Woody Allen

Этот пример показывает, как использовать функцию в предложении FROM, как со списком определений столбцов, так и без него:

CREATE FUNCTION distributors(int) RETURNS SETOF distributors AS $$
    SELECT * FROM distributors WHERE did = $1;
$$ LANGUAGE SQL;

SELECT * FROM distributors(111);
 did |    name
-----+-------------
 111 | Walt Disney

CREATE FUNCTION distributors_2(int) RETURNS SETOF record AS $$
    SELECT * FROM distributors WHERE did = $1;
$$ LANGUAGE SQL;

SELECT * FROM distributors_2(111) AS (f1 int, f2 text);
 f1  |     f2
-----+-------------
 111 | Walt Disney

Пример функции с добавленным столбцом нумерации:

SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
 unnest | ordinality
--------+----------
 a      |        1
 b      |        2
 c      |        3
 d      |        4
 e      |        5
 f      |        6
(6 rows)

Этот пример показывает, как использовать простое предложение WITH:

WITH t AS (
    SELECT random() as x FROM generate_series(1, 3)
  )
SELECT * FROM t
UNION ALL
SELECT * FROM t

         x
--------------------
  0.534150459803641
  0.520092216785997
 0.0735620250925422
  0.534150459803641
  0.520092216785997
 0.0735620250925422

Обратите внимание, что запрос WITH вычисляется только один раз, поэтому мы получаем два одинаковых набора из трех случайных значений.

В этом примере WITH RECURSIVE применяется для поиска всех подчиненных (непосредственных или косвенных) работодателя Мэри и их уровня косвенности в таблице, показывающей только непосредственных подчиненных:

WITH RECURSIVE employee_recursive(distance, employee_name, manager_name) AS (
    SELECT 1, employee_name, manager_name
    FROM employee
    WHERE manager_name = 'Mary'
  UNION ALL
    SELECT er.distance + 1, e.employee_name, e.manager_name
    FROM employee_recursive er, employee e
    WHERE er.employee_name = e.manager_name
  )
SELECT distance, employee_name FROM employee_recursive;

Обратите внимание, что это типичная форма рекурсивных запросов: начальное условие, последующий UNION, а затем рекурсивная часть запроса. Позаботьтесь о том, чтобы рекурсивная часть запроса в итоге перестала возвращать строки, иначе запрос окажется в бесконечном цикле. (Другие примеры см. в разделе Запросы WITH.)

В этом примере используется LATERAL для применения возвращающей множество функции get_product_names() для каждой строки таблицы manufacturers (производители):

SELECT m.name AS mname, pname
FROM manufacturers m, LATERAL get_product_names(m.id) pname;

Производители, у которых в данный момент нет никаких продуктов, не попадут в результат, так как это внутреннее соединение. Если бы мы захотели включить в результат имена таких производителей, можно сделать так:

SELECT m.name AS mname, pname
FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true;

Совместимость

Разумеется, команда SELECT совместима со стандартом SQL. Но есть некоторые расширения и некоторые отсутствующие функциональные возможности.

Пропуск предложений FROM

QHB позволяет опустить предложение FROM. Это упрощает вычисление результатов простых выражений:

SELECT 2+2;

 ?column?
----------
        4

Некоторые другие базы данных SQL могут сделать это только путем введения фиктивной таблицы с одной строкой, из которой нужно сделать SELECT.

Пустые списки SELECT

Список выходных выражений после SELECT может быть пустым, создавая результирующую таблицу без столбцов. Согласно стандарту SQL это недопустимый синтаксис. QHB допускает его, так как это согласуется с возможностью иметь таблицы без столбцов. Однако при использовании DISTINCT пустой список не допускается.

Пропуск ключевого слова AS

В стандарте SQL необязательное ключевое слово AS перед именем выходного столбца можно опустить, если новое имя столбца является допустимым именем столбца (то есть отличающимся от всех зарезервированных ключевых слов). QHB чуть более строга: AS требуется, если новое имя столбца соответствует любому ключевому слову вообще, независимо от того, зарезервировано оно или нет. Рекомендуется использовать AS или заключать имена выходных столбцов в кавычки, чтобы предотвратить любой возможный конфликт при добавлении ключевых слов в будущем.

В элементах FROM как стандарт SQL, так и QHB позволяют опускать AS перед псевдонимом, который является незарезервированным ключевым словом. Но для имен выходных столбцов это нецелесообразно из-за синтаксической неоднозначности.

ONLY и наследование

Стандарт SQL требует заключать имя таблицы в круглые скобки при написании ONLY, например SELECT * FROM ONLY (tab1), ONLY (tab2) WHERE .... QHB считает эти скобки необязательными.

QHB позволяет писать в конце *, чтобы явно указать что дочерние таблицы включаются в рассмотрение, в отличие от поведения с ONLY. Стандарт SQL этого не допускает.

(Эти соображения применяются ко всем командам SQL, поддерживающим параметр ONLY.)

Ограничения предложения TABLESAMPLE

Предложение TABLESAMPLE в настоящее время принимается только для обычных таблиц и материализованных представлений. Согласно стандарту SQL должна быть возможность применить его к любому элементу FROM.

Вызовы функций в предложении FROM

QHB позволяет записывать вызов функции непосредственно в качестве члена списка FROM. В стандарте SQL было бы необходимо обернуть такой вызов функции в подзапрос SELECT; то есть синтаксис FROM функция(...) псевдоним примерно равнозначен FROM LATERAL (SELECT функция(...)) псевдоним. Обратите внимание, что указание LATERAL считается неявным; это связано с тем, что стандарт требует поведения LATERAL для элемента UNNEST() в предложении FROM. QHB обрабатывает UNNEST() так же, как и другие функции, возвращающие множества.

Доступность пространств имен в GROUP BY и ORDER BY

В стандарте SQL-92 предложение ORDER BY может использовать только имена или номера выходных столбцов, в то время как предложение GROUP BY может использовать только выражения, основанные на именах входных столбцов. QHB расширяет оба этих предложения, позволяя также применять и другие варианты (но если есть неоднозначность, она использует интерпретацию стандарта). Кроме того, QHB позволяет указывать в обоих предложениях произвольные выражения. Обратите внимание, что имена, фигурирующие в выражениях, всегда будут восприниматься как имена входных столбцов, а не выходных.

В SQL:1999 и более поздних стандартах введено немного другое определение, которое не полностью совместимо с SQL-92. В большинстве случаев, однако, QHB будет интерпретировать выражение ORDER BY или GROUP BY так, как требует SQL:1999.

Функциональные зависимости

QHB распознает функциональную зависимость (позволяя пропускать столбцы из GROUP BY) только в том случае, если первичный ключ таблицы включен в список GROUP BY. Стандарт SQL задает дополнительные условия, которые должны быть учтены.

LIMIT и OFFSET

Предложения LIMIT и OFFSET являются специфичным синтаксисом QHB, также используемым в MySQL. Стандарт SQL:2008 для той же цели вводит предложения OFFSET ... FETCH {FIRST|NEXT} ..., рассмотренные ранее в параграфе Предложение LIMIT. Этот синтаксис также используется IBM DB2. (Приложения, написанные для Oracle, часто используют обходной путь, включая автоматически генерируемый столбец rownum (отсутствующий в QHB), чтобы реализовать эффект этих предложений.)

FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE

Хотя в стандарте SQL есть предложение блокировки FOR UPDATE, стандарт позволяет использовать его только как параметр в предложении DECLARE CURSOR. QHB позволяет использовать его в любом запросе SELECT, а также в подзапросах SELECT, но это является расширением. Варианты FOR NO KEY UPDATE, FOR SHARE и FOR KEY SHARE, а также параметры NOWAIT и SKIP LOCKED не отображены в стандарте.

Изменение данных в WITH

QHB позволяет использовать в в качестве запросов WITH команды INSERT, UPDATE и DELETE. Этого нет в стандарте SQL.

Нестандартные предложения

DISTINCT ON ( ... ) является расширением стандарта SQL.

ROWS FROM( ... ) является расширением стандарта SQL.

Параметры MATERIALIZED и NOT MATERIALIZED предложения WITH также являются расширениями стандарта SQL.

SELECT INTO

SELECT INTO — определить новую таблицу из результатов запроса

Синтаксис

[ WITH [ RECURSIVE ] запрос_WITH [, ...] ]
SELECT [ ALL | DISTINCT [ ON ( выражение [, ...] ) ] ]
    * | выражение [ [ AS ] имя_результата ] [, ...]
    INTO [ TEMPORARY | TEMP | UNLOGGED ] [ TABLE ] новая_таблица
    [ FROM элемент_FROM [, ...] ]
    [ WHERE условие ]
    [ GROUP BY выражение [, ...] ]
    [ HAVING условие [, ...] ]
    [ WINDOW имя_окна AS ( определение_окна ) [, ...] ]
    [ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] select ]
    [ ORDER BY выражение [ ASC | DESC | USING оператор ] [ NULLS { FIRST | LAST } ] [, ...] ]
    [ LIMIT { число | ALL } ]
    [ OFFSET начало [ ROW | ROWS ] ]
    [ FETCH { FIRST | NEXT } [ число ] { ROW | ROWS } ONLY ]
    [ FOR { UPDATE | SHARE } [ OF имя_таблицы [, ...] ] [ NOWAIT ] [...] ]

Описание

Команда SELECT INTO создает новую таблицу и заполняет ее данными, вычисленными из запроса. Данные не возвращаются клиенту, как это происходит с обычной командой SELECT. Столбцы новой таблицы имеют имена и типы данных, связанные с выходными столбцами SELECT.

Параметры

TEMPORARY или TEMP

Если этот параметр указывается, таблица создается как временная. Дополнительную информацию см. на справочной странице команды CREATE TABLE.

UNLOGGED

Если этот параметр указывается, таблица создается как нежурналируемая. Дополнительную информацию см. на справочной странице команды CREATE TABLE.

новая_таблица

Имя создаваемой таблицы (может быть дополнено схемой).

Все остальные параметры подробно описаны на справочной странице команды SELECT.

Примечания

Команда SELECT INTO функционально схожа с командой CREATE TABLE AS. Однако рекомендуется использовать синтаксис CREATE TABLE AS, так как форма SELECT INTO недоступна в ECPG или PL/pgSQL, поскольку они по-другому интерпретируют предложение INTO. Кроме того, CREATE TABLE AS, предлагает больший набор функций, нежели SELECT INTO.

В отличие от CREATE TABLE AS, команда SELECT INTO не позволяет указывать некоторые свойства, например, табличный метод доступа с помощью USING метод или табличное пространство таблицы с помощью TABLESPACE имя_табличного_пространства. Поэтому при возникновении такой необходимости лучше использовать команду CREATE TABLE AS. Таким образом, для новой таблицы выбирается метод доступа к таблице по умолчанию. Дополнительную информацию см. в описании параметра default_table_access_method.

Примеры

Создание новой таблицы films_recent, содержащей только последние записи из таблицы films:

SELECT * INTO films_recent FROM films WHERE date_prod >= '2002-01-01';

Совместимость

Стандарт SQL применяет команду SELECT INTO для передачи выбранных значений в скалярные переменные клиентской программы, а не для создания новой таблицы. Именно это применение описано в ECPG и PL/pgSQL (см. главу PL/pgSQL — процедурный язык SQL). В QHB применение SELECT INTO для создания таблиц имеет исторические причины. Некоторые другие реализации SQL тоже используют SELECT INTO таким образом (но большинство реализаций SQL поддерживают и CREATE TABLE AS). Если не учитывать эти соображения совместимости, в новом коде для этой цели лучше всего использовать CREATE TABLE AS.

См. также

CREATE TABLE AS

SET

SET — изменить параметр времени выполнения

Синтаксис

SET [ SESSION | LOCAL ] параметр_конфигурации { TO | = } { значение | 'значение' | DEFAULT }
SET [ SESSION | LOCAL ] TIME ZONE { значение | 'значение' | LOCAL | DEFAULT }

Описание

Команда SET изменяет параметры конфигурации времени выполнения. С помощью SET можно на ходу изменить многие из параметров времени выполнения, перечисленных в главе Конфигурация сервера. (Но некоторые из них требуют для изменения прав суперпользователя, а другие нельзя изменить после запуска сервера или сеанса.) Команда SET влияет только на значение, используемое в текущем сеансе.

Если команда SET (или равнозначная ей SET SESSION) выполняется внутри транзакции, которая позже прерывается, при откате транзакции эффекты команды SET исчезают. Если же окружающая транзакция фиксируется, эффекты сохранятся до конца сеанса, если их не переопределит другая команда SET.

Эффекты от выполнения SET LOCAL сохраняются только до конца текущей транзакции, независимо от того, фиксируется она или нет. Особый случай представляет использование SET с последующей SET LOCAL в пределах одной транзакции: значение SET LOCAL будет видимым до конца транзакции, но после этого (если транзакция фиксируется) начнет действовать значение SET.

Также эффекты SET или SET LOCAL отменяются при откате к точке сохранения, которая была установлена до выполнения команды.

Если SET LOCAL используется в функции с параметром SET, задающим значение той же переменной (см. CREATE FUNCTION), эффекты команды SET LOCAL при выходе из функции исчезают, то есть в любом случае восстанавливается значение, действовавшее при вызове функции. Это позволяет использовать SET LOCAL для динамических или неоднократных изменений параметра внутри функции, при этом благодаря параметру SET имея возможность сохранить и восстановить значение вызывающей стороны. Однако обычная команда SET переопределяет любой параметр SET окружающей функции; ее эффект сохранится, если не произойдет откат.

Параметры

SESSION

Указывает, что команда вступает в силу для текущего сеанса. (Это значение по умолчанию, если не указано SESSION или LOCAL.)

LOCAL

Указывает, что команда вступает в силу только для текущей транзакции. После выполнения COMMIT или ROLLBACK снова вступают в силу установки уровня сеанса. При выполнении такой команды вне блока транзакции будет выдано предупреждение и больше ничего не произойдет.

параметр_конфигурации

Имя устанавливаемого параметра времени выполнения. Доступные параметры описаны в главе Конфигурация сервера и ниже.

значение

Новое значение параметра. Значения можно указывать в виде строковых констант, идентификаторов, чисел или разделенных запятыми списков из них, в зависимости от конкретного параметра. Можно написать DEFAULT, чтобы сбросить параметр к значению по умолчанию (то есть к тому значению, которое он имел бы, если бы в текущем сеансе не выполнялись никакие команды SET).

Помимо параметров конфигурации, описанных в главе Конфигурация сервера, есть несколько параметров, которые можно настроить только с помощью команды SET или которые имеют специальный синтаксис:

SCHEMA

SET SCHEMA 'значение' является синонимом SET search_path TO значение. С помощью этого синтаксиса можно указать только одну схему.

NAMES

SET NAMES значение является синонимом SET client_encoding TO значение.

SEED

Устанавливает внутреннее начальное значение для генератора случайных чисел (функции random). Допустимые значения — это числа с плавающей запятой от -1 до 1 включительно.

Начальное значение также можно установить путем вызова функции setseed:

SELECT setseed(значение);

TIME ZONE

SET TIME ZONE значение является синонимом SET timezone TO значение. Синтаксис SET TIME ZONE позволяет использовать специальный синтаксис для указания часового пояса. Вот примеры допустимых значений:

'PST8PDT'
Часовой пояс города Беркли, штат Калифорния.

'Europe / Rome'
Часовой пояс Италии.

-7
Часовой пояс, сдвинутый от UTC на 7 часов к западу (эквивалент PDT). Положительные значения означают сдвиг от UTC к востоку.

INTERVAL '-08:00' HOUR TO MINUTE
Часовой пояс, сдвинутый от UTC на 8 часов к западу (эквивалент PST).

LOCAL
DEFAULT
Устанавливает в качестве часового пояса местный часовой пояс (т. е. значение по умолчанию серверного параметра timezone).

Значения часового пояса, заданные в виде чисел или интервалов, внутри преобразуются в формат часового пояса POSIX. Например, после SET TIME ZONE -7 команда SHOW TIME ZONE покажет <-07>+07.

Аббревиатуры часовых поясов командой SET не поддерживаются; дополнительную информацию о часовых поясах см. в подразделе Часовые пояса.

Примечания

Равнозначную функциональность предоставляет функция set_config; см. подраздел Функции для системного администрирования. Кроме того, можно обновить командой UPDATE системное представление pg_settings, и это будет аналогом выполнения команды SET.

Примеры

Установка пути поиска схемы:

SET search_path TO my_schema, public;

Установка традиционного стиля даты POSTGRES с преобразованием ввода в «день перед месяцем»:

SET datestyle TO postgres, dmy;

Установка часового пояса для Беркли, штат Калифорния:

SET TIME ZONE 'PST8PDT';

Установка часового пояса Италии:

SET TIME ZONE 'Europe/Rome';

Совместимость

Команда SET TIME ZONE расширяет синтаксис, определенный в стандарте SQL. Стандарт допускает только числовые смещения часовых поясов, тогда как QHB допускает для последних более гибкие спецификации. Все остальные возможности SET являются расширениями QHB.

См. также

RESET, SHOW

SET CONSTRAINTS

SET CONSTRAINTS — установить время проверки ограничений для текущей транзакции

Синтаксис

SET CONSTRAINTS { ALL | имя [, ...] } { DEFERRED | IMMEDIATE }

Описание

Команда SET CONSTRAINTS устанавливает поведение при проверке ограничений в рамках текущей транзакции. Ограничения IMMEDIATE проверяются в конце каждого оператора. Ограничения DEFERRED не проверяются до фиксации транзакции. Каждое ограничение имеет собственный режим IMMEDIATE или DEFERRED.

При создании ограничению выдается одна из трех характеристик: DEFERRABLE INITIALLY DEFERRED (откладываемое изначально отложенное), DEFERRABLE INITIALLY IMMEDIATE (откладываемое изначально немедленное) или NOT DEFERRABLE (неоткладываемое). Третий класс всегда IMMEDIATE (немедленный) и не зависит от команды SET CONSTRAINTS. Первые два класса запускают каждую транзакцию в указанном режиме, но их поведение может быть изменено в рамках транзакции командой SET CONSTRAINTS.

SET CONSTRAINTS со списком имен ограничений изменяет режим только этих ограничений (которые все должны быть откладываемыми). Каждое имя ограничения можно дополнить схемой. Если имя схемы не указано, при поиске первого совпадающего имени используется текущий путь поиска схемы. SET CONSTRAINTS ALL изменяет режим всех откладываемых ограничений.

Когда SET CONSTRAINTS меняет режим ограничения с DEFERRED на IMMEDIATE, новый режим начинает действовать в обратную сторону: все изменения данных, которые были бы проверены в конце транзакции, вместо этого проверяются во время выполнения команды SET CONSTRAINTS. Если какое-либо такое ограничение нарушается, SET CONSTRAINTS выдает ошибку (и режим ограничения не изменяется). Таким образом, SET CONSTRAINTS можно использовать для принудительной проверки ограничений в определенной точке транзакции.

В настоящее время такое поведение распространяется только на ограничения UNIQUE, PRIMARY KEY, REFERENCES (внешний ключ) и EXCLUDE. Ограничения NOT NULL и CHECK всегда проверяются немедленно, в момент, когда строка добавляется или изменяется (не в конце оператора). Ограничения уникальности и исключения, которые не были объявлены как DEFERRABLE, тоже проверяются немедленно.

Запуск триггеров, объявленных как «триггеры ограничений», также контролируется этой установкой — они срабатывают в то время, когда должна произойти проверка связанного ограничения.

Примечания

Поскольку QHB не требует, чтобы имена ограничений были уникальными в схеме (только в своей таблице), возможно существование нескольких соответствующих ограничений для указанного имени ограничения. В этом случае SET CONSTRAINTS будет действовать на все эти ограничения. Для имен без указания схемы действие команды будет распространяться только на первое ограничение, найденное в некоторой схеме в пути поиска, остальные схемы просматриваться не будут.

Эта команда изменяет только поведение ограничений в рамках текущей транзакции. При выполнении вне блока транзакции будет выдано предупреждение и больше ничего не произойдет.

Совместимость

Эта команда соответствует поведению, определенному в стандарте SQL, за исключением того, что в QHB она не применяется к ограничениям NOT NULL и CHECK. Кроме того, QHB проверяет неоткладываемые ограничения уникальности сразу же, а не в конце оператора, как предполагает стандарт.

SET ROLE

SET ROLE — установить идентификатор текущего пользователя для текущего сеанса

Синтаксис

SET [ SESSION | LOCAL ] ROLE имя_роли
SET [ SESSION | LOCAL ] ROLE NONE
RESET ROLE

Описание

Эта команда задает идентификатор текущего пользователя для текущего сеанса SQL как имя_роли. Имя роли можно записать в виде идентификатора или строкового литерала. После SET ROLE проверка прав доступа для команд SQL выполняется так, как если бы сеанс изначально был установлен с этим именем роли.

Заданное имя_роли должно быть ролью, членом которой является пользователь текущего сеанса. (Если пользователь сеанса является суперпользователем, он может выбрать любую роль.)

Модификаторы SESSION и LOCAL действуют так же, как и для обычной команды SET.

SET ROLE NONE устанавливает в качестве идентификатора текущего пользователя идентификатор текущего пользователя сеанса, возвращаемый функцией session_user. RESET ROLE устанавливает в качестве идентификатора текущего пользователя значение, заданное во время подключения параметрами командной строки или командой ALTER ROLE или ALTER DATABASE (если такое значение существует). В ином случае RESET ROLE тоже устанавливает в качестве идентификатора текущего пользователя идентификатор текущего пользователя сеанса. Эти формы может выполнить любой пользователь.

Примечания

С помощью этой команды можно как добавить права, так и ограничить их. Если роль пользователя сеанса имеет атрибут INHERIT, она автоматически получает права всех ролей, на которые может переключиться с помощью SET ROLE; в этом случае SET ROLE по сути удаляет все права, назначенные непосредственно пользователю сеанса и другим ролям, членом которых он является, оставляя только права, доступные для указанной роли. С другой стороны, если роль пользователя сеанса имеет атрибут NOINHERIT, то SET ROLE удаляет права, назначенные непосредственно пользователю сеанса, и вместо этого назначает права, доступные для указанной роли.

В частности, когда суперпользователь переключается через SET ROLE на роль не суперпользователя, он теряет свои права суперпользователя.

SET ROLE оказывает действие, сравнимое с SET SESSION AUTHORIZATION, но проверки прав при этом совсем другие. Кроме того, SET SESSION AUTHORIZATION определяет, какие роли допустимы для последующих команд SET ROLE, тогда как изменение ролей с помощью SET ROLE не изменяет набор ролей, допустимых для последующих SET ROLE.

SET ROLE не обрабатывает переменные сеанса, заданные для данной роли в параметрах команды ALTER ROLE; они устанавливаются только во время входа в систему.

Команду SET ROLE нельзя использовать в функции с характеристикой SECURITY DEFINER.

Примеры

SELECT SESSION_USER, CURRENT_USER;

 session_user | current_user
--------------+--------------
 peter        | peter

SET ROLE 'paul';

SELECT SESSION_USER, CURRENT_USER;

 session_user | current_user
--------------+--------------
 peter        | paul

Совместимость

QHB допускает синтаксис идентификатора («имя_роли»), тогда как стандарт SQL требует, чтобы имя роли было записано как строковый литерал. Стандарт SQL не разрешает выполнять эту команду во время транзакции; в QHB такого ограничения нет, потому что для него нет никаких причин. Модификаторы SESSION и LOCAL, как и синтаксис RESET, являются расширением QHB.

См. также

SET SESSION AUTHORIZATION

SET SESSION AUTHORIZATION

SET SESSION AUTHORIZATION — установить идентификатор пользователя сеанса и идентификатор текущего пользователя для текущего сеанса

Синтаксис

SET [ SESSION | LOCAL ] SESSION AUTHORIZATION имя_пользователя
SET [ SESSION | LOCAL ] SESSION AUTHORIZATION DEFAULT
RESET SESSION AUTHORIZATION

Описание

Эта команда устанавливает идентификатор пользователя сеанса и идентификатор текущего пользователя текущего сеанса SQL как имя_пользователя. Имя пользователя можно записать в виде идентификатора или строкового литерала. Используя эту команду, можно, например, временно стать непривилегированным пользователем, а затем снова переключиться на суперпользователя.

Идентификатор пользователя сеанса изначально задается как имя пользователя (возможно, прошедшего аутентификацию), предоставленное клиентом. Идентификатор текущего пользователя обычно равен идентификатору пользователя сеанса, но может временно измениться в рамках функций SECURITY DEFINER и других схожих механизмов; также его можно изменить командой SET ROLE. Идентификатор текущего пользователя принимается во внимание при проверке разрешений.

Идентификатор пользователя сеанса можно изменить, только если исходный пользователь сеанса (аутентифицированный пользователь) имел право суперпользователя. В противном случае команда принимается, только если в ней указано имя аутентифицированного пользователя.

Модификаторы SESSION и LOCAL действуют так же, как и для обычной команды SET.

Формы DEFAULT и RESET сбрасывают идентификаторы текущего пользователя и пользователя сеанса, так что текущим становится изначально аутентифицированный пользователь. Эти формы может выполнить любой пользователь.

Примечания

Команду SET SESSION AUTHORIZATION нельзя использовать в функции с характеристикой SECURITY DEFINER.

Примеры

SELECT SESSION_USER, CURRENT_USER;

 session_user | current_user
--------------+--------------
 peter        | peter

SET SESSION AUTHORIZATION 'paul';

SELECT SESSION_USER, CURRENT_USER;

 session_user | current_user
--------------+--------------
 paul         | paul

Совместимость

Стандарт SQL позволяет вместо литерала имя_пользователя указывать некоторые другие выражения, но на практике эти варианты не важны. QHB допускает синтаксис идентификаторов («имя_пользователя»), а стандарт SQL — нет. Стандарт SQL не разрешает выполнять эту команду во время транзакции; в QHB этого ограничения нет, потому что для него нет никаких причин. Модификаторы SESSION и LOCAL, как и синтаксис RESET, являются расширением QHB.

Согласно стандарту, права, необходимые для выполнения этой команды, определяются реализацией.

См. также

SET ROLE

SET TRANSACTION

SET TRANSACTION — установить характеристики текущей транзакции

Синтаксис

SET TRANSACTION режим_транзакции [, ...]
SET TRANSACTION SNAPSHOT id_снимка
SET SESSION CHARACTERISTICS AS TRANSACTION режим_транзакции [, ...]

где режимом_транзакции может быть:

    ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
    READ WRITE | READ ONLY
    [ NOT ] DEFERRABLE

Описание

Команда SET TRANSACTION устанавливает характеристики текущей транзакции. На последующие транзакции она не влияет. SET SESSION CHARACTERISTICS устанавливает характеристики транзакции по умолчанию для последующих транзакций сеанса. Эти характеристики по умолчанию можно переопределить для отдельной транзакции командой SET TRANSACTION.

Доступные характеристики транзакции — это уровень изоляции транзакции, режим доступа к транзакции (чтение/запись или только чтение) и режим ее откладываемости. Кроме того, можно выбрать снимок, но только для текущей транзакции, а не для сеанса по умолчанию.

Уровень изоляции транзакции определяет, какие данные транзакция может видеть, когда параллельно выполняются другие транзакции:

READ COMMITTED
Оператор может видеть только строки, зафиксированные до начала его выполнения. Это значение по умолчанию.

REPEATABLE READ
Все операторы текущей транзакции могут видеть только строки, зафиксированные до выполнения в этой транзакции первого запроса или оператора изменения данных.

SERIALIZABLE
Все операторы текущей транзакции могут видеть только строки, зафиксированные до выполнения в этой транзакции первого запроса или оператора изменения данных. Если маршрут операций чтения и записи среди параллельных сериализуемых транзакций создаст ситуацию, невозможную при однопоточном (поочередном) их выполнении, произойдет откат одной из этих транзакций с ошибкой serialization_failure (сбой сериализации).

Стандарт SQL определяет еще один дополнительный уровень, READ UNCOMMITTED. В QHB READ UNCOMMITTED трактуется как READ COMMITTED.

Уровень изоляции транзакции нельзя изменить после выполнения в текущей транзакции первого запроса или оператора изменения данных (SELECT, INSERT, DELETE, UPDATE, FETCH или COPY). Дополнительную информацию об изоляции транзакций и управлении параллельным доступом см. в главе Управление параллельным доступом.

Режим доступа к транзакции определяет, является ли транзакция доступной для чтения/записи или только для чтения. Доступ на чтение/запись — это поведение по умолчанию. Когда транзакция доступна только для чтения, запрещены следующие команды SQL: INSERT, UPDATE, DELETE и COPY FROM (если таблица, в которую будут писать, не является временной таблицей), все команды CREATE, ALTER и DROP, а также COMMENT, GRANT, REVOKE, TRUNCATE; EXPLAIN ANALYZE и EXECUTE запрещаются, если команда, которую они будут выполнять, находится в числе вышеперечисленных. Это высокоуровневое определение режима только для чтения, которое не препятствует записи на диск.

Свойство DEFERRABLE у транзакции не имеет эффекта, если транзакция также не находится в режимах SERIALIZABLE и READ ONLY. Если для транзакции выбраны все эти три свойства, она может быть заблокирована при первой попытке получить свой снимок данных, после чего сможет выполняться без обычных для режима SERIALIZABLE затрат и без какого-либо риска привести к сбою сериализации или быть отмененной из-за него. Этот режим хорошо подходит для длительных построений отчетов или выполнения резервных копий.

Команда SET TRANSACTION SNAPSHOT позволяет запустить новую транзакцию с тем же снимком данных, что имеет существующая транзакция. Ранее существовавшая транзакция должна экспортировать этот снимок с помощью функции pg_export_snapshot (см. подраздел Функции синхронизации снимков). Эта функция возвращает идентификатор снимка, который необходимо передать команде SET TRANSACTION SNAPSHOT, чтобы указать, какой снимок будет импортирован. Идентификатор должен быть записан в виде строкового литерала, например '00000003-0000001B-1'. SET TRANSACTION SNAPSHOT может выполняться только в начале транзакции, до выполнения первого запроса или оператора изменения данных (SELECT, INSERT, DELETE, UPDATE, FETCH или COPY) этой транзакции. Более того, в транзакции уже должен быть установлен уровень изоляции SERIALIZABLE или REPEATABLE READ (иначе снимок будет немедленно удален, поскольку на уровне READ COMMITTED для каждой команды создается новый снимок). Если импортирующая транзакция имеет уровне изоляции SERIALIZABLE, то транзакция, экспортирующая снимок, тоже должна иметь этот уровень изоляции. Кроме того, сериализуемая транзакция в режиме чтение/запись не может импортировать снимок из транзакции в режиме только чтения.

Примечания

При выполнении команды SET TRANSACTION без предварительного выполнения START TRANSACTION или BEGIN будет выдано предупреждение и больше ничего не произойдет.

Можно обойтись без SET TRANSACTION, указав вместо нее желаемый режим_транзакции в командах BEGIN или START TRANSACTION. Но для SET TRANSACTION SNAPSHOT такой возможности не предусмотрено.

Режимы транзакций сеанса по умолчанию также можно задать или просмотреть в параметрах конфигурации default_transaction_isolation, default_transaction_read_only и default_transaction_deferrable. (На самом деле SET SESSION CHARACTERISTICS — это более многословная альтернатива установки этих переменных с помощью команды SET.) Это означает, что значения по умолчанию можно установить в файле конфигурации, с помощью ALTER DATABASE и т. д. Дополнительную информацию см. в главе Конфигурация сервера.

Аналогичным образом режимы текущей транзакции можно установить или просмотреть в параметрах конфигурации transaction_isolation, transaction_read_only и transaction_deferrable. Установка одного из этих параметров действует так же, как указание соответствующего параметра SET TRANSACTION, с теми же ограничениями по условиям применения. Однако эти параметры нельзя установить в файле конфигурации или из любого другого источника, кроме активного SQL.

Примеры

Чтобы начать новую транзакцию с тем же снимком данных, который имеет уже существующая транзакция, его нужно сначала экспортировать из первой транзакции. При этом будет возвращен идентификатор снимка, например:

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT pg_export_snapshot();
 pg_export_snapshot
---------------------
 00000003-0000001B-1
(1 строка)

Затем этот идентификатор нужно в начале новой транзакции передать команде SET TRANSACTION SNAPSHOT:

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION SNAPSHOT '00000003-0000001B-1';

Совместимость

Эти команды определены в стандарте SQL, за исключением режима транзакции DEFERRABLE и формы SET TRANSACTION SNAPSHOT, которые являются расширениями QHB.

В стандарте уровнем изоляции транзакции по умолчанию является SERIALIZABLE. В QHB обычно по умолчанию используется READ COMMITTED, но, как упоминалось выше, его можно изменить.

В стандарте SQL есть еще одна характеристика транзакции, которую нельзя установить с помощью этих команд: размер диагностической области. Эта концепция специфична для встраиваемого SQL и поэтому не реализуется на сервере QHB.

Стандарт SQL требует запятых между последовательными режимами_транзакции, но по историческим причинам QHB позволяет опустить запятые.

SHOW

SHOW — показать значение параметра времени выполнения

Синтаксис

SHOW имя
SHOW ALL

Описание

Команда SHOW отображает текущие значения параметров времени выполнения. Эти переменные можно установить с помощью команды SET, путем редактирования файла конфигурации qhb.conf, через переменную среды PGOPTIONS (при использовании libpq или приложений на основе libpq) или через флаги командной строки при запуске сервера qhb. Дополнительную информацию см. в главе Конфигурация сервера.

Параметры

имя

Имя параметра времени выполнения. Доступные параметры описаны в главе Конфигурация сервера и на справочной странице команды SET. Кроме того, есть несколько параметров, которые могут быть показаны, но не установлены:

SERVER_VERSION
Показывает номер версии сервера.

SERVER_ENCODING
Показывает кодировку набора символов на стороне сервера. В настоящее время этот параметр может быть показан, но не установлен, поскольку кодировка определяется во время создания базы данных.

LC_COLLATE
Показывает значение локали базы данных для правила сортировки (порядка текста). В настоящее время этот параметр может быть показан, но не установлен, поскольку это значение локали определяется во время создания базы данных.

LC_CTYPE
Показывает параметр локали базы данных для классификации символов. В настоящее время этот параметр может быть показан, но не установлен, поскольку это значение локали определяется во время создания базы данных.

IS_SUPERUSER
Возвращает true, если текущая роль имеет права суперпользователя.

ALL
Показать значения всех параметров конфигурации с их описаниями.

Примечания

Ту же информацию выдает функция current_setting; см. подраздел Функции для системного администрирования. Также эту информацию выдает системное представление pg_settings.

Примеры

Просмотр текущего значения параметра DateStyle:

SHOW DateStyle;
 DateStyle
-----------
 ISO, MDY
(1 row)

Просмотр текущего значения параметра geqo:

SHOW geqo;
 geqo
------
 on
(1 row)

Просмотр всех параметров:

SHOW ALL;
            name         | setting |                description
-------------------------+---------+-------------------------------------------------
 allow_system_table_mods | off     | Allows modifications of the structure of ...
    .
    .
    .
 xmloption               | content | Sets whether XML data in implicit parsing ...
 zero_damaged_pages      | off     | Continues processing past damaged page headers.
(196 rows)

Совместимость

Команда SHOW является расширением QHB.

См. также

SET, RESET

START TRANSACTION

START TRANSACTION — начать блок транзакции

Синтаксис

START TRANSACTION [ режим_транзакции [, ...] ]

где режимом_транзакции может быть:

ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
    READ WRITE | READ ONLY
    [ NOT ] DEFERRABLE

Описание

Эта команда начинает новый блок транзакции. Если указан уровень изоляции, режим чтения/записи или режим откладываемости, то новая транзакция имеет те же характеристики, как если бы была выполнена команда SET TRANSACTION. Данная команда равнозначна команде BEGIN.

Параметры

Информацию о значении параметров для данной команды см. в описании SET TRANSACTION.

Совместимость

Согласно стандарту, необязательно выполнять команду START TRANSACTION, чтобы начать блок транзакции: любая команда SQL неявно начинает блок. Поведение QHB можно рассматривать как неявное выполнение COMMIT после каждой команды, которая не следует за START TRANSACTION (или BEGIN), и поэтому его часто называют «автоматической фиксацией». Другие реляционные СУБД тоже могут для удобства предлагать возможность автоматической фиксации.

Значение DEFERRABLE параметра режим_транзакции является языковым расширением QHB.

Стандарт SQL требует разделения последовательных режимов_транзакции запятыми, но по историческим причинам QHB позволяет их опустить.

См. также параграф о совместимости на справочной страницы команды SET TRANSACTION.

См. также

BEGIN, COMMIT, ROLLBACK, SAVEPOINT, SET TRANSACTION

TRUNCATE

TRUNCATE — опустошить таблицу или набор таблиц

Синтаксис

TRUNCATE [ TABLE ] [ ONLY ] имя [ * ] [, ... ]
    [ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ]

Описание

Команда TRUNCATE быстро удаляет все строки из набора таблиц. Она действует так же, как и команда DELETE, выполненная без дополнительных определений для каждой заданной таблицы, но поскольку команда TRUNCATE на самом деле не сканирует таблицы, она быстрее. Более того, она сразу же освобождает место на диске и не требует последующей операции VACUUM. Это наиболее удобно на больших таблицах.

Параметры

имя

Имя опустошаемой таблицы (может быть дополнено схемой). Если перед именем таблицы указывается ONLY, то опустошается только эта таблица. Если ONLY не указано, опустошаются как сама таблица, так и все ее дочерние таблицы (если таковые имеются). После имени таблицы можно добавить необязательное указание *, чтобы явно обозначить, что включены дочерние таблицы.

RESTART IDENTITY

Автоматически перезапустить последовательности, принадлежащие столбцам опустошаемой таблицы (или таблиц).

CONTINUE IDENTITY

Не изменять значения последовательностей. Это поведение по умолчанию.

CASCADE

Автоматически опустошить все таблицы, имеющих ссылки по внешнему ключу на любую из заданных таблиц или на любые таблицы, добавленные в эту группу благодаря указанию CASCADE.

RESTRICT

Отказать в опустошении, если на какую-либо таблицу имеются ссылки по внешнему ключу из таблиц, не перечисленных в команде. Это поведение по умолчанию.

Примечания

Чтобы опустошить таблицу, необходимо иметь право TRUNCATE для этой таблицы.

Команда TRUNCATE запрашивает блокировку ACCESS EXCLUSIVE для каждой таблицы, которую обрабатывает, тем самым блокируя все остальные параллельные операции на таблице. Когда указывается RESTART IDENTITY, все последовательности, которые должны быть перезапущены, также блокируются. Если к таблице требуется параллельный доступ, следует использовать команду DELETE.

TRUNCATE нельзя использовать для таблицы, на которую имеются ссылки по внешним ключам из других таблиц, если только все эти таблицы не опустошаются в той же команде. В таких случаях проверка допустимости операции потребует сканирования таблиц, а весь смысл команды заключается в том, чтобы этого не делать. Для автоматической обработки всех зависимых таблиц можно использовать CASCADE — но будьте очень осторожны при использовании этого указания, иначе можете потерять данные, которые не собирались удалять! В частности, следует отметить, что когда опустошаемая таблица является партицией, родственные партиции остаются нетронутыми, но все таблицы, которые на нее ссылаются, и их партиции подвергаются каскадному опустошению.

TRUNCATE не запустит триггеры ON DELETE, которые могут существовать для таблиц, но запустит триггеры ON TRUNCATE. Если для любой из таблиц определены триггеры ON TRUNCATE, то все триггеры BEFORE TRUNCATE срабатывают до того, как произойдет опустошение, а все триггеры AFTER TRUNCATE срабатывают после опустошения последней таблицы и сброса всех последовательностей. Триггеры будут срабатывать в том же порядке, в каком должны обрабатываться таблицы (сначала те что перечислены в команде, а затем добавленные каскадно).

Команда TRUNCATE не является безопасной с точки зрения MVCC. После опустошения таблица будет выглядеть пустой для параллельных транзакций, если они используют снимок, сделанный до того, как произошло опустошение. Более подробную информацию см. в разделе Ограничения применимости.

Команда TRUNCATE является безопасной для транзакций по отношению к данным в таблицах: если окружающая транзакция не фиксируется, произойдет откат опустошения.

Когда указывается RESTART IDENTITY, подразумеваемые операции ALTER SEQUENCE RESTART также выполняются транзакционно, то есть они откатятся, если окружающая транзакция не фиксируется. Обратите внимание, что если перед откатом транзакции на перезапущенных последовательностях выполняются какие-либо дополнительные операции, действие этих операций тоже откатится, но не их влияние на currval(); то есть после завершения транзакции currval() продолжит отражать последнее значение последовательности, полученное внутри неудачной транзакции, даже если сама последовательность уже может быть несогласованной с ним. Это похоже на обычное поведение currval() после сбоя транзакции.

Команду TRUNCATE можно применять для сторонних таблиц, если это поддерживает обертка сторонних данных. См., к примеру, postgres_fdw.

Примеры

Опустошение таблиц bigtable и fattable:

TRUNCATE bigtable, fattable;

Та же операция и сброс любых связанных генераторов последовательностей:

TRUNCATE bigtable, fattable RESTART IDENTITY;

Опустошение таблицы othertable и каскадная обработка таблиц, которые ссылаются на othertable через ограничения внешнего ключа:

TRUNCATE othertable CASCADE;

Совместимость

Стандарт SQL:2008 включает команду TRUNCATE с синтаксисом TRUNCATE TABLE имя_таблицы. Предложения CONTINUE IDENTITY/RESTART IDENTITY тоже включены в стандарт, но имеют несколько другое, хотя и похожее назначение. Согласно стандарту, поведение этой команды при параллельных операциях отчасти определяется реализацией, поэтому при необходимости вышеуказанные примечания следует рассматривать и сопоставлять с другими реализациями.

См. также

DELETE

UNLISTEN

UNLISTEN — перестать перехватывать уведомления

Синтаксис

UNLISTEN { канал | * }

Описание

Команда UNLISTEN используется для удаления существующей подписки на события NOTIFY. UNLISTEN отменяет любую существующую подписку текущего сеанса QHB на канал уведомлений с именем канал. Специальный знак * отменяет все подписки текущего сеанса.

Справочная страница команды NOTIFY содержит более подробное описание команд LISTEN и NOTIFY.

Параметры

канал

Имя канала уведомления (любой идентификатор).

*

Все текущие подписки на уведомления для этого сеанса отменяются.

Примечания

Вы можете перестать перехватывать уведомления с канала, на который не подписаны; при этом не появится никакого предупреждения или ошибки.

В конце каждого сеанса UNLISTEN * выполняется автоматически.

Транзакцию, которая выполняла UNLISTEN, нельзя подготовить к двухфазной фиксации.

Примеры

Подписка на получение события:

LISTEN virtual;
NOTIFY virtual;
Asynchronous notification "virtual" received from server process with PID 8448.

После того, как UNLISTEN была выполнена, дальнейшие сообщения NOTIFY будут игнорироваться:

UNLISTEN virtual;
NOTIFY virtual;
-- события NOTIFY не приходят

Совместимость

В стандарте SQL нет команды UNLISTEN.

См. также

LISTEN, NOTIFY

UPDATE

UPDATE — изменить строки таблицы

Синтаксис

[ WITH [ RECURSIVE ] запрос_WITH [, ...] ]
UPDATE [ ONLY ] имя_таблицы [ * ] [ [ AS ] псевдоним ]
    SET { имя_столбца = { выражение | DEFAULT } |
          ( имя_столбца [, ...] ) = [ ROW ] ( { выражение | DEFAULT } [, ...] ) |
          ( имя_столбца [, ...] ) = ( подзапрос_SELECT )
        } [, ...]
    [ FROM элемент_FROM [, ...] ]
    [ WHERE условие | WHERE CURRENT OF имя_курсора ]
    [ RETURNING * | выражение_результата [ [ AS ] имя_результата ] [, ...] ]

Описание

Команда UPDATE изменяет значения указанных столбцов во всех строках, удовлетворяющих условию. В предложении SET следует указывать только те столбцы, которые необходимо изменить; столбцы, явно не измененные, сохраняют свои предыдущие значения.

Существует два способа изменить строки в таблице, используя информацию, содержащуюся в других таблицах базы данных: с помощью подзапросов SELECT или указав дополнительные таблицы в предложении FROM. Какой метод является более подходящим, зависит от конкретных обстоятельств.

Необязательное предложение RETURNING указывает, что UPDATE должна вычислить и возвратить значение(я) каждой фактически измененной строки. Можно вычислить любое выражение, использующее столбцы данной таблицы и/или столбцы других таблиц, упомянутых в списке FROM. При этом при вычислении используются новые (после изменения) значения столбцов таблицы. Синтаксис списка RETURNING тот же, что и у списка результатов SELECT.

Нужно иметь право UPDATE для таблицы или, по крайней мере, для перечисленных изменяемых столбцов. Также необходимо иметь право SELECT для всех столбцов, значения которых считываются в выражениях или условии.

Параметры

запрос_WITH

Предложение WITH позволяет указать один или несколько подзапросов, на которые можно ссылаться по имени в запросе UPDATE. Дополнительную информацию см. в разделе Запросы WITH и на справочной странице команды SELECT.

имя_таблицы

Имя изменяемой таблицы (может быть дополнено схемой). Если перед именем таблицы указывается ONLY, соответствующие строки изменяются только в указанной таблице. Если ONLY не указывается, строки также изменятся во всех таблицах, наследовавших от указанной. После имени таблицы можно добавить необязательное указание *, чтобы явно обозначить, что в операцию включены и дочерние таблицы.

псевдоним

Альтернативное имя целевой таблицы. При предоставлении псевдоним полностью скрывает фактическое имя таблицы. Например, в запросе UPDATE foo AS f остальная часть оператора UPDATE должна ссылаться на таблицу по имени f, а не foo.

имя_столбца

Имя столбца в таблице с именем имя_таблицы. При необходимости имя столбца можно дополнить именем вложенного поля или индекса в массиве. Имя таблицы добавлять к имени целевого столбца не нужно — например, синтаксис UPDATE table_name SET table_name.col = 1 является недопустимым.

выражение

Выражение, присваиваемое столбцу. Выражение может использовать предыдущие значения этого и других столбцов в таблице.

DEFAULT

Установить для столбца значение по умолчанию (которое будет равно NULL, если ему не было присвоено никакого конкретного выражения по умолчанию). В столбце идентификации будет установлено новое значение, сгенерированное соответствующей последовательностью. Для генерируемого столбца это указание допускается, но оно просто задает обычное поведение, при котором этот столбец вычисляется из своего генерирующего выражения.

подзапрос_SELECT

Подзапрос SELECT, который создает столько выходных столбцов, сколько их перечислено в предшествующем ему списке столбцов, заключенном в скобки. При выполнении этот подзапрос должен выдавать не более одной строки. Если он выдает одну строку, значения его столбцов назначаются целевым столбцам; если он не возвращает строк, целевым столбцам назначаются значения NULL. Подзапрос может ссылаться на предыдущие значения текущей строки изменяемой таблицы.

элемент_FROM

Табличное выражение, позволяющее столбцам из других таблиц находиться в условии WHERE и в обновленных выражениях. В нем используется тот же синтаксис, что и в предложении FROM команды SELECT. Не повторяйте имя целевой таблицы как элемент_FROM, если не собираетесь определить замкнутое соединение (в этом случае для данного имени в элементе_FROM должен находиться псевдоним).

условие

Выражение, которое возвращает значение типа boolean. Будут изменены только те строки, для которых это выражение возвращает true.

имя_курсора

Имя курсора, который будет использован в условии WHERE CURRENT OF. Будет изменена строка, извлеченная из этого курсора последней. Курсор должен быть негруппирующим запросом к целевой таблице команды UPDATE. Обратите внимание, что WHERE CURRENT OF нельзя указывать вместе с логическим условием. Дополнительную информацию об использовании курсоров с WHERE CURRENT OF см. на справочной странице команды DECLARE.

выражение_результата

Выражение, которое вычисляется и возвращается командой UPDATE после изменения каждой строки. Это выражение может использовать любые имена столбцов таблицы имя_таблицы или таблиц, перечисленных в списке FROM. Чтобы вернуть все столбцы, напишите *.

имя_результата

Имя, используемое для возвращаемого столбца.

Выводимая информация

После успешного завершения команда UPDATE возвращает метку команды в форме

UPDATE число

Где число — это количество обновленных строк, включая совпадающие строки, значения которых не изменились. Обратите внимание, что это количество может быть меньше, чем количество строк, соответствующих условию, если изменения были подавлены триггером BEFORE UPDATE. Если число равно 0, значит, данным запросом не была изменена ни одна строка (это не считается ошибкой).

Если команда UPDATE содержит предложение RETURNING, результат будет похож на тот, что возвращает команда SELECT (содержащий столбцы и значения, определенные в списке RETURNING), вычисленный по строкам, измененным этой командой.

Примечания

Когда присутствует предложение FROM, целевая таблица по сути соединяется с таблицами из списка элемента_FROM, и каждая выходная строка этого соединения представляет операцию изменения для целевой таблицы. При использовании FROM необходимо убедиться, что соединение создает не более одной выходной строки для каждой изменяемой строки. Другими словами, целевая строка должна соединяться максимум с одной строкой из других таблиц. При нарушении данного условия для изменения целевой строки будет использоваться только одна из присоединенных строк, но какая именно, предсказать трудно.

Из-за этой неопределенности безопаснее ссылаться на другие таблицы только в подзапросах, хотя они зачастую труднее читаются и работают медленнее, чем при использовании соединения.

Применительно к партиционированной таблице изменение строки может привести к тому, что она перестанет удовлетворять ограничению содержащей ее партиции. В таком случае если в дереве партиционирования есть какая-либо другая партиция, ограничению которой удовлетворяет эта строка, последняя перемещается в эту партицию. Если такая партиция отсутствует, произойдет ошибка. На самом деле движение строки фактически является операцией DELETE и INSERT.

Существует вероятность того, что одновременное выполнение команд UPDATE или DELETE для перемещаемой строки приведет к ошибке сериализации. Предположим, что сеанс 1 выполняет UPDATE ключа разбиения, и в это же время параллельный сеанс 2, для которого эта строка видна, выполняет операцию UPDATE или DELETE для этой строки. В этом случае UPDATE или DELETE сеанса 2 обнаружит перемещение строки и выдаст ошибку сбоя сериализации (которая всегда возвращается с кодом SQLSTATE '40001'). Если это произойдет, приложения могут попытаться повторить транзакцию. В обычных условиях, когда таблица не партиционирована или нет никакого перемещения строки, сеанс 2 определил бы недавно измененную строку и выполнил бы UPDATE/ DELETE на этой новой версии строки.

Обратите внимание, что хотя строки можно переместить из локальных партиций в партицию сторонней таблицы (при условии, что обертка сторонних данных поддерживает маршрутизацию кортежей), их нельзя переместить из партиции сторонней таблицы в другую партицию.

Примеры

Изменение слова Drama на Dramatic в столбце kind таблицы films:

UPDATE films SET kind = 'Dramatic' WHERE kind = 'Drama';

Корректировка записей температуры и сброс уровня осадков к значению по умолчанию в одной строке таблицы weather:

UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT
  WHERE city = 'San Francisco' AND date = '2003-07-03';

Выполнение той же операции с возвратом измененных записей:

UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT
  WHERE city = 'San Francisco' AND date = '2003-07-03'
  RETURNING temp_lo, temp_hi, prcp;

Такое же изменение с применением альтернативного синтаксиса со списком столбцов:

UPDATE weather SET (temp_lo, temp_hi, prcp) = (temp_lo+1, temp_lo+15, DEFAULT)
  WHERE city = 'San Francisco' AND date = '2003-07-03';

Увеличение счетчика продаж для менеджера, занимающегося компанией Acme Corporation, с применением предложения FROM:

UPDATE employees SET sales_count = sales_count + 1 FROM accounts
  WHERE accounts.name = 'Acme Corporation'
  AND employees.id = accounts.sales_person;

Выполнение той же операции с подзапросом SELECT в предложении WHERE:

UPDATE employees SET sales_count = sales_count + 1 WHERE id =
  (SELECT sales_person FROM accounts WHERE name = 'Acme Corporation');

Изменение имени контакта в таблице счетов, чтобы оно соответствовало имени назначенного на данный момент менеджера по продажам:

UPDATE accounts SET (contact_first_name, contact_last_name) =
    (SELECT first_name, last_name FROM salesmen
     WHERE salesmen.id = accounts.sales_id);

Подобный результат можно получить, применив соединение:

UPDATE accounts SET contact_first_name = first_name,
                    contact_last_name = last_name
  FROM salesmen WHERE salesmen.id = accounts.sales_id;

Однако если salesmen.id — не уникальный ключ, второй запрос может давать непредсказуемые результаты, тогда как первый запрос гарантированно выдаст ошибку, если найдется несколько записей с одним id. Кроме того, если для конкретной записи в accounts.sales_id не найдется соответствий, первый запрос установит в полях имени NULL, а второй вовсе не изменит строку.

Обновление статистики в сводной таблице в соответствии с текущими данными:

UPDATE summary s SET (sum_x, sum_y, avg_x, avg_y) =
    (SELECT sum(x), sum(y), avg(x), avg(y) FROM data d
     WHERE d.group_id = s.group_id);

Попытка добавить новый продукт вместе с количеством. Если такая запись уже существует, то вместо этого изменить количество данного продукта в существующей записи. Чтобы реализовать этот подход, не откатывая всю транзакцию, использовать точки сохранения:

BEGIN;
-- другие операции
SAVEPOINT sp1;
INSERT INTO wines VALUES('Chateau Lafite 2003', '24');
-- Предполагая, что выше возникает ошибка из-за нарушения уникальности ключа,
-- мы выполняем следующие команды:
ROLLBACK TO sp1;
UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
-- Продолжение других операций и в завершение...
COMMIT;

Изменение значения столбца kind таблицы films в строке, на которой в данный момент находится курсор c_films:

UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;

Совместимость

Эта команда соответствует стандарту SQL, за исключением предложений FROM и RETURNING, которые являются расширениями QHB, как и возможность применять WITH с UPDATE.

В некоторых других СУБД также поддерживается параметр FROM, но предполагается, что целевая таблица должна еще раз упоминаться в этом предложении. QHB интерпретирует FROM не так. Будьте осторожны при портировании приложений, которые используют это расширение.

Согласно стандарту, исходным значением для вложенного списка имен целевых столбцов в скобках может быть любое строковое выражение, выдающее строку с правильным количеством столбцов. QHB принимает в качестве этого значения только конструктор строк или подзапрос SELECT. Изменяемое значение отдельного столбца можно указать как DEFAULT в конструкторе строки, но не внутри подзапроса SELECT.

VACUUM

VACUUM — убрать мусор и при необходимости проанализировать базу данных

Синтаксис

VACUUM [ ( параметр [, ...] ) ] [ таблица_и_столбцы [, ...] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ таблица_и_столбцы [, ...] ]

где параметром может быть:

    FULL [ логическое_значение ]
    FREEZE [ логическое_значение ]
    VERBOSE [ логическое_значение ]
    ANALYZE [ логическое_значение ]
    DISABLE_PAGE_SKIPPING [ логическое_значение ]
    SKIP_LOCKED [ логическое_значение ]
    INDEX_CLEANUP { AUTO | ON | OFF }
    PROCESS_TOAST [ логическое_значение ]
    TRUNCATE [ логическое_значение ]
    PARALLEL целое_число

и таблица_и_столбцы это:

    имя_таблицы [ ( имя_столбца [, ...] ) ]

Описание

Команда VACUUM освобождает память, занятую неиспользуемыми кортежами. В обычной работе QHB удаленные или устаревшие из-за изменения кортежи физически не удаляются из своей таблицы, а остаются в ней до тех пор, пока не будет проведена операция VACUUM. Поэтому VACUUM нужно периодически запускать, особенно в часто изменяемых таблицах.

Без списка таблица_и_столбцы команда VACUUM обрабатывает каждую таблицу и материализованное представление в текущей базе данных, на очистку которых текущий пользователь имеет разрешение. При наличии списка VACUUM обрабатывает только заданную таблицу(ы).

VACUUM ANALYZE выполняет VACUUM (очистку), а затем ANALYZE (анализ) для каждой выбранной таблицы. Это удобная комбинированная форма для скриптов регламентного обслуживания. Дополнительную информацию о проведении анализа см. на справочной странице команды ANALYZE.

Обычная VACUUM (без FULL) просто освобождает пространство и делает его доступным для повторного использования. Эта форма команды может работать параллельно с обычным чтением и записью таблицы, так как не получает эксклюзивную блокировку. Однако дополнительное пространство не возвращается в операционную систему (в большинстве случаев); оно просто остается доступным для повторного использования в той же таблице. Также она позволяет задействовать для обработки индексов несколько процессоров. Эта возможность называется параллельной очисткой. Для ее выключения можно воспользоваться параметром PARALLEL и задать в нем ноль параллельных рабочих процессов. VACUUM FULL перезаписывает все содержимое таблицы в новый дисковый файл без дополнительного места, позволяя возвращать неиспользуемое пространство в операционную систему. Эта форма намного медленнее и при обработке требует блокировки ACCESS EXCLUSIVE для каждой таблицы.

Когда список параметров заключен в скобки, эти параметры можно записать в любом порядке. Без скобок параметры следует указывать точно в том порядке, который продемонстрирован выше. Синтаксис без скобок является устаревшим.

Параметры

FULL

Выбирает «полную» очистку, которая может освободить больше места, но занимает гораздо больше времени и ставит на таблицы эксклюзивную блокировку. Кроме того, этот метод требует дополнительного дискового пространства, так как он записывает новую копию таблицы и не освобождает старую копию до завершения операции. Обычно это следует использовать только тогда, когда требуется освободить значительное пространство внутри таблицы.

FREEZE

Выбирает агрессивное «замораживание» кортежей. Указание FREEZE равнозначно выполнению VACUUM с нулевыми значениями параметров vacuum_freeze_min_age и vacuum_freeze_table_age. При перезаписи таблицы агрессивное замораживание выполняется всегда, поэтому при указании FULL этот параметр избыточен.

VERBOSE

Выводит подробный отчет о процедуре очистки для каждой таблицы.

ANALYZE

Обновляет статистику, используемую планировщиком для определения наиболее эффективного способа выполнения запроса.

DISABLE_PAGE_SKIPPING

Обычно VACUUM будет пропускать страницы, основываясь на карте видимости. Страницы, где все кортежи значатся замороженными, всегда можно пропускать, а те, где все кортежи будут видны для всех транзакций, можно обрабатывать, только когда выполняется агрессивная очистка. Кроме того, за тем же исключением, некоторые страницы можно пропускать, чтобы не ждать, когда их закончат использовать другие сеансы. Этот параметр выключает поведение пропуска страниц и предназначен для использования, только когда содержимое карты видимости является подозрительным, а это должно происходить, только если существует аппаратная или программная проблема, вызывающая повреждение базы данных.

SKIP_LOCKED

Указывает, что в начале работы над отношением VACUUM не должен ждать освобождения конфликтующих блокировок: если отношение нельзя немедленно заблокировать без ожидания, оно пропускается. Обратите внимание, что даже при использовании этого параметра VACUUM по-прежнему может блокироваться при открытии индексов отношения. Кроме того, VACUUM ANALYZE может блокироваться при получении выборочных строк из партиций, потомков таблиц и некоторых типов сторонних таблиц. Кроме того, хотя обычно VACUUM обрабатывает все партиции указанных партиционированных таблиц, этот параметр позволит VACUUM пропустить их, если на партиционированной таблице имеется конфликтующая блокировка.

INDEX_CLEANUP

Обычно VACUUM будет пропускать очистку индексов, когда в таблице имеется всего несколько неиспользуемых кортежей. Предполагается, что стоимость обработки всех индексов таблицы значительно превысит выгоду от удаления из индекса этих неиспользуемых кортежей. Этот параметр можно использовать, чтобы заставить VACUUM обрабатывать индексы, содержащие один и более неиспользуемых кортежей. При значении AUTO (по умолчанию) VACUUM может пропускать очистку индексов, когда это применимо. Если INDEX_CLEANUP установлен в значение ON, VACUUM будет консервативно удалять из индексов все неиспользуемые кортежи. Это может быть полезно для обратной совместимости с предыдущими версиями QHB, где это было стандартным поведением.

Кроме того, INDEX_CLEANUP можно установить в значение OFF, чтобы заставить VACUUM всегда пропускать очистку индексов, даже когда в таблице имеется много неиспользуемых кортежей. Это может быть полезно при необходимости выполнить VACUUM как можно быстрее, чтобы избежать надвигающейся угрозы зацикливания идентификаторов транзакций (см. подраздел Предотвращение ошибок зацикливания идентификаторов транзакций). Однако механизм защиты от этого зацикливания, управляемый параметром vacuum_failsafe_age, обычно срабатывает автоматически, поэтому более предпочтителен. Если очистка индекса не проводится регулярно, может пострадать производительность, поскольку при изменениях таблицы индексы будут накапливать неиспользуемые кортежи, а сама таблица будет накапливать неиспользуемые указатели строк, которые нельзя удалить до завершения очистки индекса.

Этот параметр не действует на таблицы, в которых нет индексов, и игнорируется при использовании параметра FULL. Также он не действует на механизм защиты от зацикливания идентификаторов транзакций. При срабатывании он будет пропускать очистку индексов, даже если в INDEX_CLEANUP установлено ON.

PROCESS_TOAST

Указывает, что VACUUM должна попытаться обработать соответствующую таблицу TOAST для каждого отношения, если таковая существует. Обычно это желаемое поведение, которое установлено по умолчанию. Установка для этого параметра значения false может быть полезно, если нужно очистить только основное отношение. Этот параметр требуется при использовании параметра FULL.

TRUNCATE

Указывает, что VACUUM должна попытаться отрезать любые пустые страницы в конце таблицы, чтобы дисковое пространство из-под усеченных страниц могло быть возвращено операционной системе. Обычно это желаемое поведение, которое установлено по умолчанию, если только параметр vacuum_truncate очищаемой таблицы не был установлен в значение false. Установка этого параметра в false может быть полезна, чтобы избежать блокировки ACCESS EXCLUSIVE на таблицы, требующей усечения. Этот параметр игнорируется при использовании параметра FULL.

PARALLEL

Осуществляет фазы очистки и уборки индексов при параллельном выполнении VACUUM, используя целое_число фоновых рабочих процессов (подробную информацию о каждой фазе очистки см. в таблице Фазы VACUUM). Количество рабочих процессов, используемых для этой операции, равно количеству индексов в отношении, поддерживающих параллельную очистку. Это количество ограничено количеством рабочих процессов, заданных параметром PARALLEL (если он указан), которое дополнительно ограничено параметром max_parallel_maintenance_workers. Индекс может участвовать в параллельной очистки только в том случае, если его размер больше, чем min_parallel_index_scan_size. Пожалуйста, учтите, что это не гарантирует, что при выполнении данной операции будет задействовано ровно столько параллельных рабочих процессов, сколько задано целым_числом. Вполне возможно, что в процессе очистки рабочие процессы будут использоваться в меньшем количестве или не будут использоваться вовсе. Каждый индекс может обрабатывать только один рабочий процесс, поэтому параллельные рабочие процессы запускаются, только если в таблице имеется как минимум 2 индекса. Рабочие процессы очистки запускаются до начала каждой фазы и завершаются после ее окончания. В будущих релизах это поведение может измениться. Данный параметр нельзя использовать вместе с FULL.

логическое_значение

Указывает, должен ли выбранный параметр быть включен или выключен. Для включения этого параметра можно написать TRUE, ON или 1, а для выключения — FALSE, OFF или 0. Также логическое_значение можно опустить, и в этом случае предполагается TRUE.

целое_число

Указывает неотрицательное целое значение, передаваемое выбранному параметру.

имя_таблицы

Имя конкретной таблицы или материализованного представления, подлежащих очистке (может быть дополнено схемой). Если указанная таблица является партиционированной, все ее листовые партиции тоже очищаются.

имя_столбца

Имя конкретного столбца для анализа. По умолчанию производится анализ всех столбцов. Если указан список столбцов, также должно быть указано ANALYZE.

Выводимая информация

Когда указывается VERBOSE, команда VACUUM выдает статусные сообщения о том, какая таблица обрабатывается в настоящее время. Также печатаются различные статистические данные о таблицах.

Примечания

Чтобы очистить таблицу, обычно нужно быть ее владельцем или суперпользователем. Однако владельцам баз данных разрешено очищать все таблицы в своих базах данных, за исключением разделяемых каталогов. (Ограничение для разделяемых каталогов означает, что истинную очистку всей базы данных может выполнить только суперпользователь.) VACUUM будет пропускать все таблицы, которые вызывающий пользователь не имеет права очищать.

Команду VACUUM нельзя выполнить внутри блока транзакции.

Для таблиц с индексами GIN VACUUM (в любой форме) также завершает все отложенные добавления индекса, перемещая отложенные записи индекса в соответствующие места в основной структуре индекса GIN. Подробную информацию см. в подразделе Методика быстрого обновления GIN.

Для удаления неиспользуемых строк рекомендуется часто (хотя бы каждую ночь) очищать активные производственные базы данных. После добавления или удаления большого количества строк может быть полезно выполнить для затронутой таблицы команду VACUUM ANALYZE. Это обновит системные каталоги, добавив в них результаты всех последних изменений, а также позволит планировщику запросов QHB принимать лучшие решения при планировании запросов.

Параметр FULL не рекомендуется для рутинного использования, но может быть полезен в особых случаях. Например, если вы удалили или изменили большинство строк в таблице и хотели бы, чтобы таблица физически уменьшилась, стала занимать меньше места на диске и позволила проводить более быстрое сканирование таблицы. Обычно VACUUM FULL сжимает таблицу более эффективно, чем простая команда VACUUM.

Параметр PARALLEL используется только в целях очистки. Если этот параметр указан вместе с ANALYZE, на работу ANALYZE он не влияет.

Команда VACUUM вызывает значительное увеличение трафика ввода/вывода, что может привести к низкой производительности для других активных сеансов. Поэтому иногда рекомендуется использовать функцию задержки очистки на основе стоимости. При параллельной очистке каждый рабочий процесс переходит в спящий режим на время, пропорциональное объему произведенной им работы. Подробную информацию см. в подразделе Задержка очистки по стоимости.

QHB включает в себя установку процесса «Автовакуум», который может автоматизировать регулярную очистку. Дополнительную информацию об автоматической и ручной очистке см. в разделе Регулярная очистка.

Каждый обслуживающий процесс, запускающий VACUUM без параметра FULL будет записывать ход ее выполнения в представлении pg_stat_progress_vacuum. Обслуживающие процессы, запускающие VACUUM FULL будут записывать ход ее выполнения в представлении pg_stat_progress_cluster. Подробную информацию см. в подразделах Отчет о ходе выполнения VACUUM и Отчет о ходе выполнения CLUSTER.

Примеры

Очистка единственной таблицу onek, анализ ее для оптимизатора и выведение подробного отчета об операции очистки:

VACUUM (VERBOSE, ANALYZE) onek;

Совместимость

В стандарте SQL нет команды VACUUM.

См. также

vacuumdb, Задержка очистки по стоимости, Процесс «Автовакуум», Отчет о ходе выполнения VACUUM, Отчет о ходе выполнения CLUSTER

VALUES

VALUES — вычислить набор строк

Синтаксис

VALUES ( выражение [, ...] ) [, ...]
    [ ORDER BY выражение_сортировки [ ASC | DESC | USING оператор ] [, ...] ]
    [ LIMIT { число | ALL } ]
    [ OFFSET начало [ ROW | ROWS ] ]
    [ FETCH { FIRST | NEXT } [ число ] { ROW | ROWS } ONLY ]

Описание

Команда VALUES вычисляет значение строки или набор значений строк, заданных выражениями значений. Чаще всего она используется для формирования «таблицы констант» в рамках более крупной команды, но может быть использована и самостоятельно.

Если указано более одной строки, то все строки должны иметь одинаковое количество элементов. Типы данных результирующих столбцов таблицы определяются путем объединения явных и неявных типов выражений, находящихся в этом столбце, с использованием тех же правил, что и для UNION (см. раздел UNION, CASE и связанные конструкции).

В составе более крупных команд синтаксис допускает использование VALUES везде, где разрешена SELECT. Так как грамматически она воспринимается как SELECT, с командой VALUES можно использовать предложения ORDER BY, LIMIT (или равнозначное FETCH FIRST) и OFFSET.

Параметры

выражение

Константа или выражение, подлежащее вычислению и добавлению в указанное место в результирующей таблице (наборе строк). В списке VALUES, находящемся на верхнем уровне INSERT, выражение можно заменить на DEFAULT, указывая, что следует добавить значение по умолчанию целевого столбца. DEFAULT нельзя использовать, когда VALUES употребляется в других контекстах.

выражение_сортировки

Выражение или целочисленная константа, указывающая, как сортировать результирующие строки. Это выражение может ссылаться на столбцы результата VALUES по именам column1, column2 и т. д. Дополнительную информацию см. в описании предложения ORDER BY команды SELECT.

оператор

Оператор сортировки. Дополнительную информацию см. в описании предложения ORDER BY команды SELECT.

число

Максимальное количество возвращаемых строк. Дополнительную информацию см. в описании предложения LIMIT команды SELECT.

начало

Количество строк, которые необходимо пропустить перед тем, как начать возвращать строки. Дополнительную информацию см. в описании предложения LIMIT команды SELECT.

Примечания

Следует избегать списков VALUES с очень большим количеством строк, так как можно столкнуться с нехваткой памяти или низкой производительностью. Применение VALUES в команде INSERT является особым случаем (поскольку нужные типы столбцов известны из целевой таблицы INSERT, и их не надо определять, сканируя весь список VALUES), поэтому может обрабатывать более объемные списки, чем это практично в других контекстах.

Примеры

Простая команда VALUES:

VALUES (1, 'one'), (2, 'two'), (3, 'three');

Эта команда вернет таблицу из двух столбцов и трех строк. По сути она равнозначна запросу:

SELECT 1 AS column1, 'one' AS column2
UNION ALL
SELECT 2, 'two'
UNION ALL
SELECT 3, 'three';

Более часто VALUES используют в составе другой команды SQL. Чаще всего она применяется в INSERT:

INSERT INTO films (code, title, did, date_prod, kind)
    VALUES ('T_601', 'Yojimbo', 106, '1961-06-16', 'Drama');

В контексте INSERT список VALUES может содержать слово DEFAULT, указывающее, что здесь вместо указания некоторого значения должно использоваться значение столбца по умолчанию:

INSERT INTO films VALUES
    ('UA502', 'Bananas', 105, DEFAULT, 'Comedy', '82 minutes'),
    ('T_601', 'Yojimbo', 106, DEFAULT, 'Drama', DEFAULT);

Также VALUES можно применять там, где есть возможность написать подзапрос SELECT, например в предложении FROM:

SELECT f.*
  FROM films f, (VALUES('MGM', 'Horror'), ('UA', 'Sci-Fi')) AS t (studio, kind)
  WHERE f.studio = t.studio AND f.kind = t.kind;

UPDATE employees SET salary = salary * v.increase
  FROM (VALUES(1, 200000, 1.2), (2, 400000, 1.4)) AS v (depno, target, increase)
  WHERE employees.depno = v.depno AND employees.sales >= v.target;

Обратите внимание, что когда VALUES используется в предложении FROM, требуется предложение AS, как и для SELECT. При этом имена всех столбцов указывать в AS необязательно, но рекомендуется. (В QHB именами столбцов по умолчанию являются column1, column2 и т. д., но в других СУБД эти имена могут быть другими.)

Когда VALUES используется в команде INSERT, значения автоматически будут приведены к типу данных соответствующего целевого столбца. Когда же она используется в других контекстах, может потребоваться указать верный тип данных. Если все записи представлены строковыми константами в кавычках, достаточно привести к нужному типу значения в первой строке, чтобы задать тип для всех строк:

SELECT * FROM machines
WHERE ip_address IN (VALUES('192.168.0.1'::inet), ('192.168.0.10'), ('192.168.1.43'));

Совет
Для простых проверок на включение (IN) лучше полагаться на форму IN со списком скаляров, чем писать запрос VALUES, как показано выше. Метод со списком скаляров короче и зачастую более эффективен.

Совместимость

Команда VALUES соответствует стандарту SQL. LIMIT и OFFSET являются расширениями QHB; см. также справочную страницу команды SELECT.

См. также

INSERT, SELECT

Поддержка SQL-стандарта

Содержание

Коды ошибок

Поддержка даты и времени

Ключевые слова

Соответствие стандарту SQL

Ключевые слова SQL

В Таблице 1 перечислены все синтаксические единицы, которые являются ключевыми словами в стандарте SQL и в QHB. Исходную информацию можно найти в подразделе Идентификаторы и ключевые слова (для экономии места включены только две последние версии стандарта SQL и SQL-92 для исторического сравнения. Различия между ними и другими промежуточными версиями стандарта невелики).

В SQL есть разница между зарезервированными и незарезервированными ключевыми словами. Согласно стандарту, по-настоящему ключевыми словами являются только зарезервированные слова; они не могут быть идентификаторами. Незарезервированные ключевые слова имеют особое значение только в определенных контекстах и могут использоваться как идентификаторы в других. Большинство незарезервированных ключевых слов на самом деле являются именами встроенных таблиц и функций, определенных в SQL. По сути концепция незарезервированных ключевых слов существует только для того, чтобы показать, что в отдельных контекстах слово имеет некоторое предопределенное значение.

В QHB синтаксический анализатор сталкивается с дополнительными трудностями. Существует несколько различных классов синтаксических единиц, начиная с тех, что никогда не могут использоваться как идентификаторы, и заканчивая теми, что для синтаксического анализатора абсолютно ничем не отличаются от обычных идентификаторов. (Последнее обычно характерно для функций, определенных в SQL.) Даже зарезервированные ключевые слова не полностью зарезервированы в QHB, а могут использоваться в качестве меток столбцов (например, можно написать SELECT 55 AS CHECK, хотя CHECK и является зарезервированным ключевым словом).

В Таблице 1, в столбце QHB, пометка «не зарезервировано» дается тем ключевым словам, которые явно известны синтаксическому анализатору, но допускаются в качестве имен столбцов или таблиц. Некоторые ключевые слова, которые не могут использоваться в качестве имен функций или типов данных, но в остальном не отличаются от незарезервированных, помечены соответствующим образом. (Большинство этих слов представляют собой встроенные функции или типы данных со специальным синтаксисом. Такие функции или типы все еще доступны, но пользователь уже не может их переопределить.) Меткой «зарезервировано» обозначаются те синтаксические единицы, которые не допускаются в качестве имен столбцов или таблиц. Некоторые зарезервированные ключевые слова могут быть именами функций или типов данных; это также показано в таблице. Если такой пометки нет, зарезервированное ключевое слово допускается только в качестве имени метки столбца. Пустая запись в этом столбце означает, что QHB воспринимает это слово как обычный идентификатор.

Кроме того, хотя большинство ключевых слов можно использовать как «открытые» метки столбцов, не добавляя перед ними AS (как описано в подразделе Метки столбцов), существует несколько ключевых слов, перед которыми нужно добавлять AS во избежание неопределенности. В таблице они помечены как «нужен AS».

Вообще, при получении нетипичных ошибок во время анализа команд, содержащих в качестве идентификатора любое из перечисленных ключевых слов, для решения проблемы стоит попробовать заключить идентификатор в кавычки.

Перед изучением Таблицы 1 важно понять, что отсутствие ключевого слова в перечне зарезервированных слов в QHB не означает, что функциональность, связанная с этим словом, не реализована. И наоборот, присутствие ключевого слова не указывает на наличие соответствующей функциональности.

Таблица 1. Ключевые слова SQL

Ключевое слово | QHB | SQL: 2016 | SQL: 2011 | SQL-92
A |  | не зарезервировано | не зарезервировано |
ABORT | не зарезервировано |  |  |
ABS |  | зарезервировано | зарезервировано |
ABSENT |  | не зарезервировано | не зарезервировано |
ABSOLUTE | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
ACCESS | не зарезервировано |  |  |
ACCORDING |  | не зарезервировано | не зарезервировано |
ACOS |  | зарезервировано |  |
ACTION | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
ADA |  | не зарезервировано | не зарезервировано | не зарезервировано
ADD | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
ADMIN | не зарезервировано | не зарезервировано | не зарезервировано |
AFTER | не зарезервировано | не зарезервировано | не зарезервировано |
AGGREGATE | не зарезервировано |  |  |
ALL | зарезервировано | зарезервировано | зарезервировано | зарезервировано
ALLOCATE |  | зарезервировано | зарезервировано | зарезервировано
ALSO | не зарезервировано |  |  |
ALTER | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
ALWAYS | не зарезервировано | не зарезервировано | не зарезервировано |
ANALYSE | зарезервировано |  |  |
ANALYZE | зарезервировано |  |  |
AND | зарезервировано | зарезервировано | зарезервировано | зарезервировано
ANY | зарезервировано | зарезервировано | зарезервировано | зарезервировано
ARE |  | зарезервировано | зарезервировано | зарезервировано
ARRAY | зарезервировано, нужен AS | зарезервировано | зарезервировано |
ARRAY_AGG |  | зарезервировано | зарезервировано |
ARRAY_MAX_CARDINALITY |  | зарезервировано | зарезервировано |
AS | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
ASC | зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
ASENSITIVE | не зарезервировано | зарезервировано | зарезервировано |
ASIN |  | зарезервировано |  |
ASSERTION | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
ASSIGNMENT | не зарезервировано | не зарезервировано | не зарезервировано |
ASYMMETRIC | зарезервировано | зарезервировано | зарезервировано |
AT | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
ATAN |  | зарезервировано |  |
ATOMIC | не зарезервировано | зарезервировано | зарезервировано |
ATTACH | не зарезервировано |  |  |
ATTRIBUTE | не зарезервировано | не зарезервировано | не зарезервировано |
ATTRIBUTES |  | не зарезервировано | не зарезервировано |
AUTHORIZATION | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
AVG |  | зарезервировано | зарезервировано | зарезервировано
BACKWARD | не зарезервировано |  |  |
BASE64 |  | не зарезервировано | не зарезервировано |
BEFORE | не зарезервировано | не зарезервировано | не зарезервировано |
BEGIN | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
BEGIN_FRAME |  | зарезервировано | зарезервировано |
BEGIN_PARTITION |  | зарезервировано | зарезервировано |
BERNOULLI |  | не зарезервировано | не зарезервировано |
BETWEEN | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
BIGINT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
BINARY | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано |
BIT | не зарезервировано (не может быть функцией или типом) |  |  | зарезервировано
BIT_LENGTH |  |  |  | зарезервировано
BLOB |  | зарезервировано | зарезервировано |
BLOCKED |  | не зарезервировано | не зарезервировано |
BOM |  | не зарезервировано | не зарезервировано |
BOOLEAN | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
BOTH | зарезервировано | зарезервировано | зарезервировано | зарезервировано
BREADTH | не зарезервировано | не зарезервировано | не зарезервировано |
BY | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
C |  | не зарезервировано | не зарезервировано | не зарезервировано
CACHE | не зарезервировано |  |  |
CALL | не зарезервировано | зарезервировано | зарезервировано |
CALLED | не зарезервировано | зарезервировано | зарезервировано |
CARDINALITY |  | зарезервировано | зарезервировано |
CASCADE | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
CASCADED | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
CASE | зарезервировано | зарезервировано | зарезервировано | зарезервировано
CAST | зарезервировано | зарезервировано | зарезервировано | зарезервировано
CATALOG | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
CATALOG_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
CEIL |  | зарезервировано | зарезервировано |
CEILING |  | зарезервировано | зарезервировано |
CHAIN | не зарезервировано | не зарезервировано | не зарезервировано |
CHAINING |  | не зарезервировано |  |
CHAR | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
CHARACTER | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
CHARACTERISTICS | не зарезервировано | не зарезервировано | не зарезервировано |
CHARACTERS |  | не зарезервировано | не зарезервировано |
CHARACTER_LENGTH |  | зарезервировано | зарезервировано | зарезервировано
CHARACTER_SET_CATALOG |  | не зарезервировано | не зарезервировано | не зарезервировано
CHARACTER_SET_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
CHARACTER_SET_SCHEMA |  | не зарезервировано | не зарезервировано | не зарезервировано
CHAR_LENGTH |  | зарезервировано | зарезервировано | зарезервировано
CHECK | зарезервировано | зарезервировано | зарезервировано | зарезервировано
CHECKPOINT | не зарезервировано |  |  |
CLASS | не зарезервировано |  |  |
CLASSIFIER |  | зарезервировано |  |
CLASS_ORIGIN |  | не зарезервировано | не зарезервировано | не зарезервировано
CLOB |  | зарезервировано | зарезервировано |
CLOSE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
CLUSTER | не зарезервировано |  |  |
COALESCE | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
COBOL |  | не зарезервировано | не зарезервировано | не зарезервировано
COLLATE | зарезервировано | зарезервировано | зарезервировано | зарезервировано
COLLATION | зарезервировано (может быть функцией или типом) | не зарезервировано | не зарезервировано | зарезервировано
COLLATION_CATALOG |  | не зарезервировано | не зарезервировано | не зарезервировано
COLLATION_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
COLLATION_SCHEMA |  | не зарезервировано | не зарезервировано | не зарезервировано
COLLECT |  | зарезервировано | зарезервировано |
COLUMN | зарезервировано | зарезервировано | зарезервировано | зарезервировано
COLUMNS | не зарезервировано | не зарезервировано | не зарезервировано |
COLUMN_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
COMMAND_FUNCTION |  | не зарезервировано | не зарезервировано | не зарезервировано
COMMAND_FUNCTION_CODE |  | не зарезервировано | не зарезервировано |
COMMENT | не зарезервировано |  |  |
COMMENTS | не зарезервировано |  |  |
COMMIT | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
COMMITTED | не зарезервировано | не зарезервировано | не зарезервировано | не зарезервировано
CONCURRENTLY | зарезервировано (может быть функцией или типом) |  |  |
CONDITION |  | зарезервировано | зарезервировано |
CONDITIONAL |  | не зарезервировано |  |
CONDITION_NUMBER |  | не зарезервировано | не зарезервировано | не зарезервировано
CONFIGURATION | не зарезервировано |  |  |
CONFLICT | не зарезервировано |  |  |
CONNECT |  | зарезервировано | зарезервировано | зарезервировано
CONNECTION | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
CONNECTION_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
CONSTRAINT | зарезервировано | зарезервировано | зарезервировано | зарезервировано
CONSTRAINTS | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
CONSTRAINT_CATALOG |  | не зарезервировано | не зарезервировано | не зарезервировано
CONSTRAINT_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
CONSTRAINT_SCHEMA |  | не зарезервировано | не зарезервировано | не зарезервировано
CONSTRUCTOR |  | не зарезервировано | не зарезервировано |
CONTAINS |  | зарезервировано | зарезервировано |
CONTENT | не зарезервировано | не зарезервировано | не зарезервировано |
CONTINUE | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
CONTROL |  | не зарезервировано | не зарезервировано |
CONVERSION | не зарезервировано |  |  |
CONVERT |  | зарезервировано | зарезервировано | зарезервировано
COPY | не зарезервировано | зарезервировано |  |
CORR |  | зарезервировано | зарезервировано |
CORRESPONDING |  | зарезервировано | зарезервировано | зарезервировано
COS |  | зарезервировано |  |
COSH |  | зарезервировано |  |
COST | не зарезервировано |  |  |
COUNT |  | зарезервировано | зарезервировано | зарезервировано
COVAR_POP |  | зарезервировано | зарезервировано |
COVAR_SAMP |  | зарезервировано | зарезервировано |
CREATE | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
CROSS | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
CSV | не зарезервировано |  |  |
CUBE | не зарезервировано | зарезервировано | зарезервировано |
CUME_DIST |  | зарезервировано | зарезервировано |
CURRENT | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
CURRENT_CATALOG | зарезервировано | зарезервировано | зарезервировано |
CURRENT_DATE | зарезервировано | зарезервировано | зарезервировано | зарезервировано
CURRENT_DEFAULT_TRANSFORM_GROUP |  | зарезервировано | зарезервировано |
CURRENT_PATH |  | зарезервировано | зарезервировано |
CURRENT_ROLE | зарезервировано | зарезервировано | зарезервировано |
CURRENT_ROW |  | зарезервировано | зарезервировано |
CURRENT_SCHEMA | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано |
CURRENT_TIME | зарезервировано | зарезервировано | зарезервировано | зарезервировано
CURRENT_TIMESTAMP | зарезервировано | зарезервировано | зарезервировано | зарезервировано
CURRENT_TRANSFORM_GROUP_FOR_TYPE |  | зарезервировано | зарезервировано |
CURRENT_USER | зарезервировано | зарезервировано | зарезервировано | зарезервировано
CURSOR | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
CURSOR_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
CYCLE | не зарезервировано | зарезервировано | зарезервировано |
DATA | не зарезервировано | не зарезервировано | не зарезервировано | не зарезервировано
DATABASE | не зарезервировано |  |  |
DATALINK |  | зарезервировано | зарезервировано |
DATE |  | зарезервировано | зарезервировано | зарезервировано
DATETIME_INTERVAL_CODE |  | не зарезервировано | не зарезервировано | не зарезервировано
DATETIME_INTERVAL_PRECISION |  | не зарезервировано | не зарезервировано | не зарезервировано
DAY | не зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
DB |  | не зарезервировано | не зарезервировано |
DEALLOCATE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
DEC | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
DECFLOAT |  | зарезервировано |  |
DECIMAL | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
DECLARE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
DEFAULT | зарезервировано | зарезервировано | зарезервировано | зарезервировано
DEFAULTS | не зарезервировано | не зарезервировано | не зарезервировано |
DEFERRABLE | зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
DEFERRED | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
DEFINE |  | зарезервировано |  |
DEFINED |  | не зарезервировано | не зарезервировано |
DEFINER | не зарезервировано | не зарезервировано | не зарезервировано |
DEGREE |  | не зарезервировано | не зарезервировано |
DELETE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
DELIMITER | не зарезервировано |  |  |
DELIMITERS | не зарезервировано |  |  |
DENSE_RANK |  | зарезервировано | зарезервировано |
DEPENDS | не зарезервировано |  |  |
DEPTH | не зарезервировано | не зарезервировано | не зарезервировано |
DEREF |  | зарезервировано | зарезервировано |
DERIVED |  | не зарезервировано | не зарезервировано |
DESC | зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
DESCRIBE |  | зарезервировано | зарезервировано | зарезервировано
DESCRIPTOR |  | не зарезервировано | не зарезервировано | зарезервировано
DETACH | не зарезервировано |  |  |
DETERMINISTIC |  | зарезервировано | зарезервировано |
DIAGNOSTICS |  | не зарезервировано | не зарезервировано | зарезервировано
DICTIONARY | не зарезервировано |  |  |
DISABLE | не зарезервировано |  |  |
DISCARD | не зарезервировано |  |  |
DISCONNECT |  | зарезервировано | зарезервировано | зарезервировано
DISPATCH |  | не зарезервировано | не зарезервировано |
DISTINCT | зарезервировано | зарезервировано | зарезервировано | зарезервировано
DLNEWCOPY |  | зарезервировано | зарезервировано |
DLPREVIOUSCOPY |  | зарезервировано | зарезервировано |
DLURLCOMPLETE |  | зарезервировано | зарезервировано |
DLURLCOMPLETEONLY |  | зарезервировано | зарезервировано |
DLURLCOMPLETEWRITE |  | зарезервировано | зарезервировано |
DLURLPATH |  | зарезервировано | зарезервировано |
DLURLPATHONLY |  | зарезервировано | зарезервировано |
DLURLPATHWRITE |  | зарезервировано | зарезервировано |
DLURLSCHEME |  | зарезервировано | зарезервировано |
DLURLSERVER |  | зарезервировано | зарезервировано |
DLVALUE |  | зарезервировано | зарезервировано |
DO | зарезервировано |  |  |
DOCUMENT | не зарезервировано | не зарезервировано | не зарезервировано |
DOMAIN | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
DOUBLE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
DROP | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
DYNAMIC |  | зарезервировано | зарезервировано |
DYNAMIC_FUNCTION |  | не зарезервировано | не зарезервировано | не зарезервировано
DYNAMIC_FUNCTION_CODE |  | не зарезервировано | не зарезервировано |
EACH | не зарезервировано | зарезервировано | зарезервировано |
ELEMENT |  | зарезервировано | зарезервировано |
ELSE | зарезервировано | зарезервировано | зарезервировано | зарезервировано
EMPTY |  | зарезервировано | не зарезервировано |
ENABLE | не зарезервировано |  |  |
ENCODING | не зарезервировано | не зарезервировано | не зарезервировано |
ENCRYPTED | не зарезервировано |  |  |
END | зарезервировано | зарезервировано | зарезервировано | зарезервировано
END-EXEC |  | зарезервировано | зарезервировано | зарезервировано
END_FRAME |  | зарезервировано | зарезервировано |
END_PARTITION |  | зарезервировано | зарезервировано |
ENFORCED |  | не зарезервировано | не зарезервировано |
ENUM | не зарезервировано |  |  |
EQUALS |  | зарезервировано | зарезервировано |
ERROR |  | не зарезервировано |  |
ESCAPE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
EVENT | не зарезервировано |  |  |
EVERY |  | зарезервировано | зарезервировано |
EXCEPT | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
EXCEPTION |  |  |  | зарезервировано
EXCLUDE | не зарезервировано | не зарезервировано | не зарезервировано |
EXCLUDING | не зарезервировано | не зарезервировано | не зарезервировано |
EXCLUSIVE | не зарезервировано |  |  |
EXEC |  | зарезервировано | зарезервировано | зарезервировано
EXECUTE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
EXISTS | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
EXP |  | зарезервировано | зарезервировано |
EXPLAIN | не зарезервировано |  |  |
EXPRESSION | не зарезервировано | не зарезервировано | не зарезервировано |
EXTENSION | не зарезервировано |  |  |
EXTERNAL | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
EXTRACT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
FALSE | зарезервировано | зарезервировано | зарезервировано | зарезервировано
FAMILY | не зарезервировано |  |  |
FETCH | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
FILE |  | не зарезервировано | не зарезервировано |
FILTER | не зарезервировано, нужен AS | зарезервировано | зарезервировано |
FINAL |  | не зарезервировано | не зарезервировано |
FINALIZE | не зарезервировано |  |  |
FINISH |  | не зарезервировано |  |
FIRST | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
FIRST_VALUE |  | зарезервировано | зарезервировано |
FLAG |  | не зарезервировано | не зарезервировано |
FLOAT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
FLOOR |  | зарезервировано | зарезервировано |
FOLLOWING | не зарезервировано | не зарезервировано | не зарезервировано |
FOR | зарезервировано | зарезервировано | зарезервировано | зарезервировано
FORCE | не зарезервировано |  |  |
FOREIGN | зарезервировано | зарезервировано | зарезервировано | зарезервировано
FORMAT |  | не зарезервировано |  |
FORTRAN |  | не зарезервировано | не зарезервировано | не зарезервировано
FORWARD | не зарезервировано |  |  |
FOUND |  | не зарезервировано | не зарезервировано | зарезервировано
FRAME_ROW |  | зарезервировано | зарезервировано |
FREE |  | зарезервировано | зарезервировано |
FREEZE | зарезервировано (может быть функцией или типом) |  |  |
FROM | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
FS |  | не зарезервировано | не зарезервировано |
FULFILL |  | не зарезервировано |  |
FULL | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
FUNCTION | не зарезервировано | зарезервировано | зарезервировано |
FUNCTIONS | не зарезервировано |  |  |
FUSION |  | зарезервировано | зарезервировано |
G |  | не зарезервировано | не зарезервировано |
GENERAL |  | не зарезервировано | не зарезервировано |
GENERATED | не зарезервировано | не зарезервировано | не зарезервировано |
GET |  | зарезервировано | зарезервировано | зарезервировано
GLOBAL | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
GO |  | не зарезервировано | не зарезервировано | зарезервировано
GOTO |  | не зарезервировано | не зарезервировано | зарезервировано
GRANT | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
GRANTED | не зарезервировано | не зарезервировано | не зарезервировано |
GREATEST | не зарезервировано (не может быть функцией или типом) |  |  |
GROUP | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
GROUPING | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
GROUPS | не зарезервировано | зарезервировано | зарезервировано |
HANDLER | не зарезервировано |  |  |
HAVING | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
HEADER | не зарезервировано |  |  |
HEX |  | не зарезервировано | не зарезервировано |
HIERARCHY |  | не зарезервировано | не зарезервировано |
HOLD | не зарезервировано | зарезервировано | зарезервировано |
HOUR | не зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
ID |  | не зарезервировано | не зарезервировано |
IDENTITY | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
IF | не зарезервировано |  |  |
IGNORE |  | не зарезервировано | не зарезервировано |
ILIKE | зарезервировано (может быть функцией или типом) |  |  |
IMMEDIATE | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
IMMEDIATELY |  | не зарезервировано | не зарезервировано |
IMMUTABLE | не зарезервировано |  |  |
IMPLEMENTATION |  | не зарезервировано | не зарезервировано |
IMPLICIT | не зарезервировано |  |  |
IMPORT | не зарезервировано | зарезервировано | зарезервировано |
IN | зарезервировано | зарезервировано | зарезервировано | зарезервировано
INCLUDE | не зарезервировано |  |  |
INCLUDING | не зарезервировано | не зарезервировано | не зарезервировано |
INCREMENT | не зарезервировано | не зарезервировано | не зарезервировано |
INDENT |  | не зарезервировано | не зарезервировано |
INDEX | не зарезервировано |  |  |
INDEXES | не зарезервировано |  |  |
INDICATOR |  | зарезервировано | зарезервировано | зарезервировано
INHERIT | не зарезервировано |  |  |
INHERITS | не зарезервировано |  |  |
INITIAL |  | зарезервировано |  |
INITIALLY | зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
INLINE | не зарезервировано |  |  |
INNER | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
INOUT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
INPUT | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
INSENSITIVE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
INSERT | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
INSTANCE |  | не зарезервировано | не зарезервировано |
INSTANTIABLE |  | не зарезервировано | не зарезервировано |
INSTEAD | не зарезервировано | не зарезервировано | не зарезервировано |
INT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
INTEGER | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
INTEGRITY |  | не зарезервировано | не зарезервировано |
INTERSECT | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
INTERSECTION |  | зарезервировано | зарезервировано |
INTERVAL | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
INTO | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
INVOKER | не зарезервировано | не зарезервировано | не зарезервировано |
IS | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
ISNULL | зарезервировано (может быть функцией или типом), нужен AS |  |  |
ISOLATION | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
JOIN | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
JSON |  | не зарезервировано |  |
JSON_ARRAY |  | зарезервировано |  |
JSON_ARRAYAGG |  | зарезервировано |  |
JSON_EXISTS |  | зарезервировано |  |
JSON_OBJECT |  | зарезервировано |  |
JSON_OBJECTAGG |  | зарезервировано |  |
JSON_QUERY |  | зарезервировано |  |
JSON_TABLE |  | зарезервировано |  |
JSON_TABLE_PRIMITIVE |  | зарезервировано |  |
JSON_VALUE |  | зарезервировано |  |
K |  | не зарезервировано | не зарезервировано |
KEEP |  | не зарезервировано |  |
KEY | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
KEYS |  | не зарезервировано |  |
KEY_MEMBER |  | не зарезервировано | не зарезервировано |
KEY_TYPE |  | не зарезервировано | не зарезервировано |
LABEL | не зарезервировано |  |  |
LAG |  | зарезервировано | зарезервировано |
LANGUAGE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
LARGE | не зарезервировано | зарезервировано | зарезервировано |
LAST | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
LAST_VALUE |  | зарезервировано | зарезервировано |
LATERAL | зарезервировано | зарезервировано | зарезервировано |
LEAD |  | зарезервировано | зарезервировано |
LEADING | зарезервировано | зарезервировано | зарезервировано | зарезервировано
LEAKPROOF | не зарезервировано |  |  |
LEAST | не зарезервировано (не может быть функцией или типом) |  |  |
LEFT | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
LENGTH |  | не зарезервировано | не зарезервировано | не зарезервировано
LEVEL | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
LIBRARY |  | не зарезервировано | не зарезервировано |
LIKE | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
LIKE_REGEX |  | зарезервировано | зарезервировано |
LIMIT | зарезервировано, нужен AS | не зарезервировано | не зарезервировано |
LINK |  | не зарезервировано | не зарезервировано |
LISTAGG |  | зарезервировано |  |
LISTEN | не зарезервировано |  |  |
LN |  | зарезервировано | зарезервировано |
LOAD | не зарезервировано |  |  |
LOCAL | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
LOCALTIME | зарезервировано | зарезервировано | зарезервировано |
LOCALTIMESTAMP | зарезервировано | зарезервировано | зарезервировано |
LOCATION | не зарезервировано | не зарезервировано | не зарезервировано |
LOCATOR |  | не зарезервировано | не зарезервировано |
LOCK | не зарезервировано |  |  |
LOCKED | не зарезервировано |  |  |
LOG |  | зарезервировано |  |
LOG10 |  | зарезервировано |  |
LOGGED | не зарезервировано |  |  |
LOWER |  | зарезервировано | зарезервировано | зарезервировано
M |  | не зарезервировано | не зарезервировано |
MAP |  | не зарезервировано | не зарезервировано |
MAPPING | не зарезервировано | не зарезервировано | не зарезервировано |
MATCH | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
MATCHED |  | не зарезервировано | не зарезервировано |
MATCHES |  | зарезервировано |  |
MATCH_NUMBER |  | зарезервировано |  |
MATCH_RECOGNIZE |  | зарезервировано |  |
MATERIALIZED | не зарезервировано |  |  |
MAX |  | зарезервировано | зарезервировано | зарезервировано
MAXVALUE | не зарезервировано | не зарезервировано | не зарезервировано |
MEASURES |  | зарезервировано |  |
MEMBER |  | зарезервировано | зарезервировано |
MERGE |  | зарезервировано | зарезервировано |
MESSAGE_LENGTH |  | не зарезервировано | не зарезервировано | не зарезервировано
MESSAGE_OCTET_LENGTH |  | не зарезервировано | не зарезервировано | не зарезервировано
MESSAGE_TEXT |  | не зарезервировано | не зарезервировано | не зарезервировано
METHOD | не зарезервировано | зарезервировано | зарезервировано |
MIN |  | зарезервировано | зарезервировано | зарезервировано
MINUTE | не зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
MINVALUE | не зарезервировано | не зарезервировано | не зарезервировано |
MOD |  | зарезервировано | зарезервировано |
MODE | не зарезервировано |  |  |
MODIFIES |  | зарезервировано | зарезервировано |
MODULE |  | зарезервировано | зарезервировано | зарезервировано
MONTH | не зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
MORE |  | не зарезервировано | не зарезервировано | не зарезервировано
MOVE | не зарезервировано |  |  |
MULTISET |  | зарезервировано | зарезервировано |
MUMPS |  | не зарезервировано | не зарезервировано | не зарезервировано
NAME | не зарезервировано | не зарезервировано | не зарезервировано | не зарезервировано
NAMES | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
NAMESPACE |  | не зарезервировано | не зарезервировано |
NATIONAL | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
NATURAL | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
NCHAR | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
NCLOB |  | зарезервировано | зарезервировано |
NESTED |  | не зарезервировано |  |
NESTING |  | не зарезервировано | не зарезервировано |
NEW | не зарезервировано | зарезервировано | зарезервировано |
NEXT | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
NFC | не зарезервировано | не зарезервировано | не зарезервировано |
NFD | не зарезервировано | не зарезервировано | не зарезервировано |
NFKC | не зарезервировано | не зарезервировано | не зарезервировано |
NFKD | не зарезервировано | не зарезервировано | не зарезервировано |
NIL |  | не зарезервировано | не зарезервировано |
NO | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
NONE | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
NORMALIZE | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
NORMALIZED | не зарезервировано | не зарезервировано | не зарезервировано |
NOT | зарезервировано | зарезервировано | зарезервировано | зарезервировано
NOTHING | не зарезервировано |  |  |
NOTIFY | не зарезервировано |  |  |
NOTNULL | зарезервировано (может быть функцией или типом), нужен AS |  |  |
NOWAIT | не зарезервировано |  |  |
NTH_VALUE |  | зарезервировано | зарезервировано |
NTILE |  | зарезервировано | зарезервировано |
NULL | зарезервировано | зарезервировано | зарезервировано | зарезервировано
NULLABLE |  | не зарезервировано | не зарезервировано | не зарезервировано
NULLIF | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
NULLS | не зарезервировано | не зарезервировано | не зарезервировано |
NUMBER |  | не зарезервировано | не зарезервировано | не зарезервировано
NUMERIC | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
OBJECT | не зарезервировано | не зарезервировано | не зарезервировано |
OCCURRENCES_REGEX |  | зарезервировано | зарезервировано |
OCTETS |  | не зарезервировано | не зарезервировано |
OCTET_LENGTH |  | зарезервировано | зарезервировано | зарезервировано
OF | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
OFF | не зарезервировано | не зарезервировано | не зарезервировано |
OFFSET | зарезервировано, нужен AS | зарезервировано | зарезервировано |
OIDS | не зарезервировано |  |  |
OLD | не зарезервировано | зарезервировано | зарезервировано |
OMIT |  | зарезервировано |  |
ON | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
ONE |  | зарезервировано |  |
ONLY | зарезервировано | зарезервировано | зарезервировано | зарезервировано
OPEN |  | зарезервировано | зарезервировано | зарезервировано
OPERATOR | не зарезервировано |  |  |
OPTION | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
OPTIONS | не зарезервировано | не зарезервировано | не зарезервировано |
OR | зарезервировано | зарезервировано | зарезервировано | зарезервировано
ORDER | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
ORDERING |  | не зарезервировано | не зарезервировано |
ORDINALITY | не зарезервировано | не зарезервировано | не зарезервировано |
OTHERS | не зарезервировано | не зарезервировано | не зарезервировано |
OUT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
OUTER | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
OUTPUT |  | не зарезервировано | не зарезервировано | зарезервировано
OVER | не зарезервировано, нужен AS | зарезервировано | зарезервировано |
OVERFLOW |  | не зарезервировано |  |
OVERLAPS | зарезервировано (может быть функцией или типом), нужен AS | зарезервировано | зарезервировано | зарезервировано
OVERLAY | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
OVERRIDING | не зарезервировано | не зарезервировано | не зарезервировано |
OWNED | не зарезервировано |  |  |
OWNER | не зарезервировано |  |  |
P |  | не зарезервировано | не зарезервировано |
PAD |  | не зарезервировано | не зарезервировано | зарезервировано
PARALLEL | не зарезервировано |  |  |
PARAMETER |  | зарезервировано | зарезервировано |
PARAMETER_MODE |  | не зарезервировано | не зарезервировано |
PARAMETER_NAME |  | не зарезервировано | не зарезервировано |
PARAMETER_ORDINAL_POSITION |  | не зарезервировано | не зарезервировано |
PARAMETER_SPECIFIC_CATALOG |  | не зарезервировано | не зарезервировано |
PARAMETER_SPECIFIC_NAME |  | не зарезервировано | не зарезервировано |
PARAMETER_SPECIFIC_SCHEMA |  | не зарезервировано | не зарезервировано |
PARSER | не зарезервировано |  |  |
PARTIAL | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
PARTITION | не зарезервировано | зарезервировано | зарезервировано |
PASCAL |  | не зарезервировано | не зарезервировано | не зарезервировано
PASS |  | не зарезервировано |  |
PASSING | не зарезервировано | не зарезервировано | не зарезервировано |
PASSTHROUGH |  | не зарезервировано | не зарезервировано |
PASSWORD | не зарезервировано |  |  |
PAST |  | не зарезервировано |  |
PATH |  | не зарезервировано | не зарезервировано |
PATTERN |  | зарезервировано |  |
PER |  | зарезервировано |  |
PERCENT |  | зарезервировано | зарезервировано |
PERCENTILE_CONT |  | зарезервировано | зарезервировано |
PERCENTILE_DISC |  | зарезервировано | зарезервировано |
PERCENT_RANK |  | зарезервировано | зарезервировано |
PERIOD |  | зарезервировано | зарезервировано |
PERMISSION |  | не зарезервировано | не зарезервировано |
PERMUTE |  | зарезервировано |  |
PLACING | зарезервировано | не зарезервировано | не зарезервировано |
PLAN |  | не зарезервировано |  |
PLANS | не зарезервировано |  |  |
PLI |  | не зарезервировано | не зарезервировано | не зарезервировано
POLICY | не зарезервировано |  |  |
PORTION |  | зарезервировано | зарезервировано |
POSITION | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
POSITION_REGEX |  | зарезервировано | зарезервировано |
POWER |  | зарезервировано | зарезервировано |
PRECEDES |  | зарезервировано | зарезервировано |
PRECEDING | не зарезервировано | не зарезервировано | не зарезервировано |
PRECISION | не зарезервировано (не может быть функцией или типом), нужен AS | зарезервировано | зарезервировано | зарезервировано
PREPARE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
PREPARED | не зарезервировано |  |  |
PRESERVE | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
PRIMARY | зарезервировано | зарезервировано | зарезервировано | зарезервировано
PRIOR | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
PRIVATE |  | не зарезервировано |  |
PRIVILEGES | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
PROCEDURAL | не зарезервировано |  |  |
PROCEDURE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
PROCEDURES | не зарезервировано |  |  |
PROGRAM | не зарезервировано |  |  |
PRUNE |  | не зарезервировано |  |
PTF |  | зарезервировано |  |
PUBLIC |  | не зарезервировано | не зарезервировано | зарезервировано
PUBLICATION | не зарезервировано |  |  |
QUOTE | не зарезервировано |  |  |
QUOTES |  | не зарезервировано |  |
RANGE | не зарезервировано | зарезервировано | зарезервировано |
RANK |  | зарезервировано | зарезервировано |
READ | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
READS |  | зарезервировано | зарезервировано |
REAL | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
REASSIGN | не зарезервировано |  |  |
RECHECK | не зарезервировано |  |  |
RECOVERY |  | не зарезервировано | не зарезервировано |
RECURSIVE | не зарезервировано | зарезервировано | зарезервировано |
REF | не зарезервировано | зарезервировано | зарезервировано |
REFERENCES | зарезервировано | зарезервировано | зарезервировано | зарезервировано
REFERENCING | не зарезервировано | зарезервировано | зарезервировано |
REFRESH | не зарезервировано |  |  |
REGR_AVGX |  | зарезервировано | зарезервировано |
REGR_AVGY |  | зарезервировано | зарезервировано |
REGR_COUNT |  | зарезервировано | зарезервировано |
REGR_INTERCEPT |  | зарезервировано | зарезервировано |
REGR_R2 |  | зарезервировано | зарезервировано |
REGR_SLOPE |  | зарезервировано | зарезервировано |
REGR_SXX |  | зарезервировано | зарезервировано |
REGR_SXY |  | зарезервировано | зарезервировано |
REGR_SYY |  | зарезервировано | зарезервировано |
REINDEX | не зарезервировано |  |  |
RELATIVE | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
RELEASE | не зарезервировано | зарезервировано | зарезервировано |
RENAME | не зарезервировано |  |  |
REPEATABLE | не зарезервировано | не зарезервировано | не зарезервировано | не зарезервировано
REPLACE | не зарезервировано |  |  |
REPLICA | не зарезервировано |  |  |
REQUIRING |  | не зарезервировано | не зарезервировано |
RESET | не зарезервировано |  |  |
RESPECT |  | не зарезервировано | не зарезервировано |
RESTART | не зарезервировано | не зарезервировано | не зарезервировано |
RESTORE |  | не зарезервировано | не зарезервировано |
RESTRICT | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
RESULT |  | зарезервировано | зарезервировано |
RETURN | не зарезервировано | зарезервировано | зарезервировано |
RETURNED_CARDINALITY |  | не зарезервировано | не зарезервировано |
RETURNED_LENGTH |  | не зарезервировано | не зарезервировано | не зарезервировано
RETURNED_OCTET_LENGTH |  | не зарезервировано | не зарезервировано | не зарезервировано
RETURNED_SQLSTATE |  | не зарезервировано | не зарезервировано | не зарезервировано
RETURNING | зарезервировано, нужен AS | не зарезервировано | не зарезервировано |
RETURNS | не зарезервировано | зарезервировано | зарезервировано |
REVOKE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
RIGHT | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
ROLE | не зарезервировано | не зарезервировано | не зарезервировано |
ROLLBACK | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
ROLLUP | не зарезервировано | зарезервировано | зарезервировано |
ROUTINE | не зарезервировано | не зарезервировано | не зарезервировано |
ROUTINES | не зарезервировано |  |  |
ROUTINE_CATALOG |  | не зарезервировано | не зарезервировано |
ROUTINE_NAME |  | не зарезервировано | не зарезервировано |
ROUTINE_SCHEMA |  | не зарезервировано | не зарезервировано |
ROW | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
ROWS | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
ROW_COUNT |  | не зарезервировано | не зарезервировано | не зарезервировано
ROW_NUMBER |  | зарезервировано | зарезервировано |
RULE | не зарезервировано |  |  |
RUNNING |  | зарезервировано |  |
SAVEPOINT | не зарезервировано | зарезервировано | зарезервировано |
SCALAR |  | не зарезервировано |  |
SCALE |  | не зарезервировано | не зарезервировано | не зарезервировано
SCHEMA | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
SCHEMAS | не зарезервировано |  |  |
SCHEMA_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
SCOPE |  | зарезервировано | зарезервировано |
SCOPE_CATALOG |  | не зарезервировано | не зарезервировано |
SCOPE_NAME |  | не зарезервировано | не зарезервировано |
SCOPE_SCHEMA |  | не зарезервировано | не зарезервировано |
SCROLL | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
SEARCH | не зарезервировано | зарезервировано | зарезервировано |
SECOND | не зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
SECTION |  | не зарезервировано | не зарезервировано | зарезервировано
SECURITY | не зарезервировано | не зарезервировано | не зарезервировано |
SEEK |  | зарезервировано |  |
SELECT | зарезервировано | зарезервировано | зарезервировано | зарезервировано
SELECTIVE |  | не зарезервировано | не зарезервировано |
SELF |  | не зарезервировано | не зарезервировано |
SENSITIVE |  | зарезервировано | зарезервировано |
SEQUENCE | не зарезервировано | не зарезервировано | не зарезервировано |
SEQUENCES | не зарезервировано |  |  |
SERIALIZABLE | не зарезервировано | не зарезервировано | не зарезервировано | не зарезервировано
SERVER | не зарезервировано | не зарезервировано | не зарезервировано |
SERVER_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
SESSION | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
SESSION_USER | зарезервировано | зарезервировано | зарезервировано | зарезервировано
SET | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
SETOF | не зарезервировано (не может быть функцией или типом) |  |  |
SETS | не зарезервировано | не зарезервировано | не зарезервировано |
SHARE | не зарезервировано |  |  |
SHOW | не зарезервировано | зарезервировано |  |
SIMILAR | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано |
SIMPLE | не зарезервировано | не зарезервировано | не зарезервировано |
SIN |  | зарезервировано |  |
SINH |  | зарезервировано |  |
SIZE |  | не зарезервировано | не зарезервировано | зарезервировано
SKIP | не зарезервировано | зарезервировано |  |
SMALLINT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
SNAPSHOT | не зарезервировано |  |  |
SOME | зарезервировано | зарезервировано | зарезервировано | зарезервировано
SOURCE |  | не зарезервировано | не зарезервировано |
SPACE |  | не зарезервировано | не зарезервировано | зарезервировано
SPECIFIC |  | зарезервировано | зарезервировано |
SPECIFICTYPE |  | зарезервировано | зарезервировано |
SPECIFIC_NAME |  | не зарезервировано | не зарезервировано |
SQL | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
SQLCODE |  |  |  | зарезервировано
SQLERROR |  |  |  | зарезервировано
SQLEXCEPTION |  | зарезервировано | зарезервировано |
SQLSTATE |  | зарезервировано | зарезервировано | зарезервировано
SQLWARNING |  | зарезервировано | зарезервировано |
SQRT |  | зарезервировано | зарезервировано |
STABLE | не зарезервировано |  |  |
STANDALONE | не зарезервировано | не зарезервировано | не зарезервировано |
START | не зарезервировано | зарезервировано | зарезервировано |
STATE |  | не зарезервировано | не зарезервировано |
STATEMENT | не зарезервировано | не зарезервировано | не зарезервировано |
STATIC |  | зарезервировано | зарезервировано |
STATISTICS | не зарезервировано |  |  |
STDDEV_POP |  | зарезервировано | зарезервировано |
STDDEV_SAMP |  | зарезервировано | зарезервировано |
STDIN | не зарезервировано |  |  |
STDOUT | не зарезервировано |  |  |
STORAGE | не зарезервировано |  |  |
STORED | не зарезервировано |  |  |
STRICT | не зарезервировано |  |  |
STRING |  | не зарезервировано |  |
STRIP | не зарезервировано | не зарезервировано | не зарезервировано |
STRUCTURE |  | не зарезервировано | не зарезервировано |
STYLE |  | не зарезервировано | не зарезервировано |
SUBCLASS_ORIGIN |  | не зарезервировано | не зарезервировано | не зарезервировано
SUBMULTISET |  | зарезервировано | зарезервировано |
SUBSCRIPTION | не зарезервировано |  |  |
SUBSET |  | зарезервировано |  |
SUBSTRING | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
SUBSTRING_REGEX |  | зарезервировано | зарезервировано |
SUCCEEDS |  | зарезервировано | зарезервировано |
SUM |  | зарезервировано | зарезервировано | зарезервировано
SUPPORT | не зарезервировано |  |  |
SYMMETRIC | зарезервировано | зарезервировано | зарезервировано |
SYSID | не зарезервировано |  |  |
SYSTEM | не зарезервировано | зарезервировано | зарезервировано |
SYSTEM_TIME |  | зарезервировано | зарезервировано |
SYSTEM_USER |  | зарезервировано | зарезервировано | зарезервировано
T |  | не зарезервировано | не зарезервировано |
TABLE | зарезервировано | зарезервировано | зарезервировано | зарезервировано
TABLES | не зарезервировано |  |  |
TABLESAMPLE | зарезервировано (может быть функцией или типом) | зарезервировано | зарезервировано |
TABLESPACE | не зарезервировано |  |  |
TABLE_NAME |  | не зарезервировано | не зарезервировано | не зарезервировано
TAN |  | зарезервировано |  |
TANH |  | зарезервировано |  |
TEMP | не зарезервировано |  |  |
TEMPLATE | не зарезервировано |  |  |
TEMPORARY | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
TEXT | не зарезервировано |  |  |
THEN | зарезервировано | зарезервировано | зарезервировано | зарезервировано
THROUGH |  | не зарезервировано |  |
TIES | не зарезервировано | не зарезервировано | не зарезервировано |
TIME | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
TIMESTAMP | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
TIMEZONE_HOUR |  | зарезервировано | зарезервировано | зарезервировано
TIMEZONE_MINUTE |  | зарезервировано | зарезервировано | зарезервировано
TO | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
TOKEN |  | не зарезервировано | не зарезервировано |
TOP_LEVEL_COUNT |  | не зарезервировано | не зарезервировано |
TRAILING | зарезервировано | зарезервировано | зарезервировано | зарезервировано
TRANSACTION | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
TRANSACTIONS_COMMITTED |  | не зарезервировано | не зарезервировано |
TRANSACTIONS_ROLLED_BACK |  | не зарезервировано | не зарезервировано |
TRANSACTION_ACTIVE |  | не зарезервировано | не зарезервировано |
TRANSFORM | не зарезервировано | не зарезервировано | не зарезервировано |
TRANSFORMS |  | не зарезервировано | не зарезервировано |
TRANSLATE |  | зарезервировано | зарезервировано | зарезервировано
TRANSLATE_REGEX |  | зарезервировано | зарезервировано |
TRANSLATION |  | зарезервировано | зарезервировано | зарезервировано
TREAT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
TRIGGER | не зарезервировано | зарезервировано | зарезервировано |
TRIGGER_CATALOG |  | не зарезервировано | не зарезервировано |
TRIGGER_NAME |  | не зарезервировано | не зарезервировано |
TRIGGER_SCHEMA |  | не зарезервировано | не зарезервировано |
TRIM | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
TRIM_ARRAY |  | зарезервировано | зарезервировано |
TRUE | зарезервировано | зарезервировано | зарезервировано | зарезервировано
TRUNCATE | не зарезервировано | зарезервировано | зарезервировано |
TRUSTED | не зарезервировано |  |  |
TYPE | не зарезервировано | не зарезервировано | не зарезервировано | не зарезервировано
TYPES | не зарезервировано |  |  |
UESCAPE | не зарезервировано | зарезервировано | зарезервировано |
UNBOUNDED | не зарезервировано | не зарезервировано | не зарезервировано |
UNCOMMITTED | не зарезервировано | не зарезервировано | не зарезервировано | не зарезервировано
UNCONDITIONAL |  | не зарезервировано |  |
UNDER |  | не зарезервировано | не зарезервировано |
UNENCRYPTED | не зарезервировано |  |  |
UNION | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
UNIQUE | зарезервировано | зарезервировано | зарезервировано | зарезервировано
UNKNOWN | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
UNLINK |  | не зарезервировано | не зарезервировано |
UNLISTEN | не зарезервировано |  |  |
UNLOGGED | не зарезервировано |  |  |
UNMATCHED |  | зарезервировано |  |
UNNAMED |  | не зарезервировано | не зарезервировано | не зарезервировано
UNNEST |  | зарезервировано | зарезервировано |
UNTIL | не зарезервировано |  |  |
UNTYPED |  | не зарезервировано | не зарезервировано |
UPDATE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
UPPER |  | зарезервировано | зарезервировано | зарезервировано
URI |  | не зарезервировано | не зарезервировано |
USAGE |  | не зарезервировано | не зарезервировано | зарезервировано
USER | зарезервировано | зарезервировано | зарезервировано | зарезервировано
USER_DEFINED_TYPE_CATALOG |  | не зарезервировано | не зарезервировано |
USER_DEFINED_TYPE_CODE |  | не зарезервировано | не зарезервировано |
USER_DEFINED_TYPE_NAME |  | не зарезервировано | не зарезервировано |
USER_DEFINED_TYPE_SCHEMA |  | не зарезервировано | не зарезервировано |
USING | зарезервировано | зарезервировано | зарезервировано | зарезервировано
UTF16 |  | не зарезервировано |  |
UTF32 |  | не зарезервировано |  |
UTF8 |  | не зарезервировано |  |
VACUUM | не зарезервировано |  |  |
VALID | не зарезервировано | не зарезервировано | не зарезервировано |
VALIDATE | не зарезервировано |  |  |
VALIDATOR | не зарезервировано |  |  |
VALUE | не зарезервировано | зарезервировано | зарезервировано | зарезервировано
VALUES | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
VALUE_OF |  | зарезервировано | зарезервировано |
VARBINARY |  | зарезервировано | зарезервировано |
VARCHAR | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано | зарезервировано
VARIADIC | зарезервировано |  |  |
VARYING | не зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
VAR_POP |  | зарезервировано | зарезервировано |
VAR_SAMP |  | зарезервировано | зарезервировано |
VERBOSE | зарезервировано (может быть функцией или типом) |  |  |
VERSION | не зарезервировано | не зарезервировано | не зарезервировано |
VERSIONING |  | зарезервировано | зарезервировано |
VIEW | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
VIEWS | не зарезервировано |  |  |
VOLATILE | не зарезервировано |  |  |
WHEN | зарезервировано | зарезервировано | зарезервировано | зарезервировано
WHENEVER |  | зарезервировано | зарезервировано | зарезервировано
WHERE | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
WHITESPACE | не зарезервировано | не зарезервировано | не зарезервировано |
WIDTH_BUCKET |  | зарезервировано | зарезервировано |
WINDOW | зарезервировано, нужен AS | зарезервировано | зарезервировано |
WITH | зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
WITHIN | не зарезервировано, нужен AS | зарезервировано | зарезервировано |
WITHOUT | не зарезервировано, нужен AS | зарезервировано | зарезервировано |
WORK | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
WRAPPER | не зарезервировано | не зарезервировано | не зарезервировано |
WRITE | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано
XML | не зарезервировано | зарезервировано | зарезервировано |
XMLAGG |  | зарезервировано | зарезервировано |
XMLATTRIBUTES | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLBINARY |  | зарезервировано | зарезервировано |
XMLCAST |  | зарезервировано | зарезервировано |
XMLCOMMENT |  | зарезервировано | зарезервировано |
XMLCONCAT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLDECLARATION |  | не зарезервировано | не зарезервировано |
XMLDOCUMENT |  | зарезервировано | зарезервировано |
XMLELEMENT | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLEXISTS | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLFOREST | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLITERATE |  | зарезервировано | зарезервировано |
XMLNAMESPACES | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLPARSE | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLPI | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLQUERY |  | зарезервировано | зарезервировано |
XMLROOT | не зарезервировано (не может быть функцией или типом) |  |  |
XMLSCHEMA |  | не зарезервировано | не зарезервировано |
XMLSERIALIZE | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLTABLE | не зарезервировано (не может быть функцией или типом) | зарезервировано | зарезервировано |
XMLTEXT |  | зарезервировано | зарезервировано |
XMLVALIDATE |  | зарезервировано | зарезервировано |
YEAR | не зарезервировано, нужен AS | зарезервировано | зарезервировано | зарезервировано
YES | не зарезервировано | не зарезервировано | не зарезервировано |
ZONE | не зарезервировано | не зарезервировано | не зарезервировано | зарезервировано

Коды ошибок QHB

Всем сообщениям, которые выдает сервер QHB, назначены пятисимвольные коды ошибок, соответствующие кодам «SQLSTATE», описанным в стандарте SQL. Приложения, которые должны знать, какое условие ошибки имело место, обычно должны проверять код ошибки, а не обращаться к текстовому сообщению о ней. Коды ошибок, скорее всего, не изменятся от выпуска к выпуску QHB, а кроме того, они не меняются при локализации сообщений об ошибках. Обратите внимание, что не все коды ошибок, которые выдает QHB, определены стандартом SQL; некоторые дополнительные коды ошибок для условий, не описанных стандартом, были добавлены независимо или позаимствованы из других баз данных.

Согласно стандарту, первые два символа кода ошибки обозначают класс ошибок, а последние три символа обозначают определенное условие в этом классе. Благодаря этому, приложение, не знающее значение определенного кода ошибки, все равно может понять, что делать, по классу ошибки.

В Таблице 1 перечислены все коды ошибок, определенные в QHB. (Некоторые из них в настоящее время не используются, хотя и определены в стандарте SQL.) Также показаны классы ошибок. Для каждого класса ошибок имеется «стандартный» код ошибки с последними тремя символами 000. Этот код выдается только для тех условий ошибок, которые относятся к некоторому классу, но не имеют более определенного кода.

Символ, указанный во втором столбце, — имя условия в PL/pgSQL. Имена условий могут записываться в верхнем или нижнем регистре. (Обратите внимание, что PL/pgSQL, в отличие от ошибок, не распознает предупреждения, то есть классы 00, 01 и 02.)

Для некоторых типов ошибок сервер сообщает имя объекта базы данных (таблицу, столбец таблицы, тип данных или ограничение), связанного с ошибкой; например, имя уникального ограничения, вызвавшего ошибку unique_violation. Такие имена передаются в отдельных полях сообщения об ошибке, чтобы приложениям не пришлось извлекать их из возможно локализованного текста сообщения для человека. Пока что полностью охвачены этой особенностью только ошибки класса SQLSTATE 23 (нарушения ограничений целостности), но в будущем, скорее всего, будут охвачены и другие классы.

Таблица 1. Коды ошибок QHB

Код ошибки | Имя условия
Класс 00 — Успешное завершение |
00000 | successful_completion
Класс 01 — Предупреждение |
01000 | warning
0100C | dynamic_result_sets_returned
01008 | implicit_zero_bit_padding
01003 | null_value_eliminated_in_set_function
01007 | privilege_not_granted
01006 | privilege_not_revoked
01004 | string_data_right_truncation
01P01 | deprecated_feature
Класс 02 — Нет данных (это также класс предупреждений согласно стандарту SQL) |
02000 | no_data
02001 | no_additional_dynamic_result_sets_returned
Класс 03 — Оператор SQL еще не завершен |
03000 | sql_statement_not_yet_complete
Класс 08 — Исключение, связанное с подключением |
08000 | connection_exception
08003 | connection_does_not_exist
08006 | connection_failure
08001 | sqlclient_unable_to_establish_sqlconnection
08004 | sqlserver_rejected_establishment_of_sqlconnection
08007 | transaction_resolution_unknown
08P01 | protocol_violation
Класс 09 — Исключение с действием триггера |
09000 | triggered_action_exception
Класс 0A — Неподдерживаемая функциональность |
0A000 | feature_not_supported
Класс 0B — Неверное начало транзакции |
0B000 | invalid_transaction_initiation
Класс 0F — Исключение с указателем на данные |
0F000 | locator_exception
0F001 | invalid_locator_specification
Класс 0L — Неверный праводатель |
0L000 | invalid_grantor
0LP01 | invalid_grant_operation
Класс 0P — Неверное указание роли |
0P000 | invalid_role_specification
Класс 0Z — Исключение диагностики |
0Z000 | diagnostics_exception
0Z002 | stacked_diagnostics_accessed_without_active_handler
Класс 20 — Case не найден |
20000 | case_not_found
Класс 21 — Нарушение количества |
21000 | cardinality_violation
Класс 22 — Исключение в данных |
22000 | data_exception
2202E | array_subscript_error
22021 | character_not_in_repertoire
22008 | datetime_field_overflow
22012 | division_by_zero
22005 | error_in_assignment
2200B | escape_character_conflict
22022 | indicator_overflow
22015 | interval_field_overflow
2201E | invalid_argument_for_logarithm
22014 | invalid_argument_for_ntile_function
22016 | invalid_argument_for_nth_value_function
2201F | invalid_argument_for_power_function
2201G | invalid_argument_for_width_bucket_function
22018 | invalid_character_value_for_cast
22007 | invalid_datetime_format
22019 | invalid_escape_character
2200D | invalid_escape_octet
22025 | invalid_escape_sequence
22P06 | nonstandard_use_of_escape_character
22010 | invalid_indicator_parameter_value
22023 | invalid_parameter_value
22013 | invalid_preceding_or_following_size
2201B | invalid_regular_expression
2201W | invalid_row_count_in_limit_clause
2201X | invalid_row_count_in_result_offset_clause
2202H | invalid_tablesample_argument
2202G | invalid_tablesample_repeat
22009 | invalid_time_zone_displacement_value
2200C | invalid_use_of_escape_character
2200G | most_specific_type_mismatch
22004 | null_value_not_allowed
22002 | null_value_no_indicator_parameter
22003 | numeric_value_out_of_range
2200H | sequence_generator_limit_exceeded
22026 | string_data_length_mismatch
22001 | string_data_right_truncation
22011 | substring_error
22027 | trim_error
22024 | unterminated_c_string
2200F | zero_length_character_string
22P01 | floating_point_exception
22P02 | invalid_text_representation
22P03 | invalid_binary_representation
22P04 | bad_copy_file_format
22P05 | untranslatable_character
2200L | not_an_xml_document
2200M | invalid_xml_document
2200N | invalid_xml_content
2200S | invalid_xml_comment
2200T | invalid_xml_processing_instruction
22030 | duplicate_json_object_key_value
22032 | invalid_json_text
22033 | invalid_sql_json_subscript
22034 | more_than_one_sql_json_item
22035 | no_sql_json_item
22036 | non_numeric_sql_json_item
22037 | non_unique_keys_in_a_json_object
22038 | singleton_sql_json_item_required
22039 | sql_json_array_not_found
2203A | sql_json_member_not_found
2203B | sql_json_number_not_found
2203C | sql_json_object_not_found
2203D | too_many_json_array_elements
2203E | too_many_json_object_members
2203F | sql_json_scalar_required
Класс 23 — Нарушение ограничения целостности |
23000 | integrity_constraint_violation
23001 | restrict_violation
23502 | not_null_violation
23503 | foreign_key_violation
23505 | unique_violation
23514 | check_violation
23P01 | exclusion_violation
Класс 24 — Неверное состояние курсора |
24000 | invalid_cursor_state
Класс 25 — Неверное состояние транзакции |
25000 | invalid_transaction_state
25001 | active_sql_transaction
25002 | branch_transaction_already_active
25008 | held_cursor_requires_same_isolation_level
25003 | inappropriate_access_mode_for_branch_transaction
25004 | inappropriate_isolation_level_for_branch_transaction
25005 | no_active_sql_transaction_for_branch_transaction
25006 | read_only_sql_transaction
25007 | schema_and_data_statement_mixing_not_supported
25P01 | no_active_sql_transaction
25P02 | in_failed_sql_transaction
25P03 | idle_in_transaction_session_timeout
Класс 26 — Неверное имя оператора SQL |
26000 | invalid_sql_statement_name
Класс 27 — Нарушение при изменении данных в триггере |
27000 | triggered_data_change_violation
Класс 28 — Неверное указание авторизации |
28000 | invalid_authorization_specification
28P01 | invalid_password
Класс 2B — Зависимые описания прав все еще существуют |
2B000 | dependent_privilege_descriptors_still_exist
2BP01 | dependent_objects_still_exist
Класс 2D — Неверное завершение транзакции |
2D000 | invalid_transaction_termination
Класс 2F — Исключение в подпрограмме SQL |
2F000 | sql_routine_exception
2F005 | function_executed_no_return_statement
2F002 | modifying_sql_data_not_permitted
2F003 | prohibited_sql_statement_attempted
2F004 | reading_sql_data_not_permitted
Класс 34 — Неверное имя курсора |
34000 | invalid_cursor_name
Класс 38 — Исключение во внешней подпрограмме |
38000 | external_routine_exception
38001 | containing_sql_not_permitted
38002 | modifying_sql_data_not_permitted
38003 | prohibited_sql_statement_attempted
38004 | reading_sql_data_not_permitted
Класс 39 — Исключение при вызове внешней подпрограммы |
39000 | external_routine_invocation_exception
39001 | invalid_sqlstate_returned
39004 | null_value_not_allowed
39P01 | trigger_protocol_violated
39P02 | srf_protocol_violated
39P03 | event_trigger_protocol_violated
Класс 3B — Исключение точки сохранения |
3B000 | savepoint_exception
3B001 | invalid_savepoint_specification
Класс 3D — Неверное имя каталога |
3D000 | invalid_catalog_name
Класс 3F — Неверное имя схемы |
3F000 | invalid_schema_name
Класс 40 — Откат транзакции |
40000 | transaction_rollback
40002 | transaction_integrity_constraint_violation
40001 | serialization_failure
40003 | statement_completion_unknown
40P01 | deadlock_detected
Класс 42 — Ошибка синтаксиса или нарушение правила доступа |
42000 | syntax_error_or_access_rule_violation
42601 | syntax_error
42501 | insufficient_privilege
42846 | cannot_coerce
42803 | grouping_error
42P20 | windowing_error
42P19 | invalid_recursion
42830 | invalid_foreign_key
42602 | invalid_name
42622 | name_too_long
42939 | reserved_name
42804 | datatype_mismatch
42P18 | indeterminate_datatype
42P21 | collation_mismatch
42P22 | indeterminate_collation
42809 | wrong_object_type
428C9 | generated_always
42703 | undefined_column
42883 | undefined_function
42P01 | undefined_table
42P02 | undefined_parameter
42704 | undefined_object
42701 | duplicate_column
42P03 | duplicate_cursor
42P04 | duplicate_database
42723 | duplicate_function
42P05 | duplicate_prepared_statement
42P06 | duplicate_schema
42P07 | duplicate_table
42712 | duplicate_alias
42710 | duplicate_object
42702 | ambiguous_column
42725 | ambiguous_function
42P08 | ambiguous_parameter
42P09 | ambiguous_alias
42P10 | invalid_column_reference
42611 | invalid_column_definition
42P11 | invalid_cursor_definition
42P12 | invalid_database_definition
42P13 | invalid_function_definition
42P14 | invalid_prepared_statement_definition
42P15 | invalid_schema_definition
42P16 | invalid_table_definition
42P17 | invalid_object_definition
Класс 44 — Нарушение WITH CHECK OPTION |
44000 | with_check_option_violation
Класс 53 — Нехватка ресурсов |
53000 | insufficient_resources
53100 | disk_full
53200 | out_of_memory
53300 | too_many_connections
53400 | configuration_limit_exceeded
Класс 54 — Превышение ограничения программы |
54000 | program_limit_exceeded
54001 | statement_too_complex
54011 | too_many_columns
54023 | too_many_arguments
Класс 55 — Объект не в требуемом состоянии |
55000 | object_not_in_prerequisite_state
55006 | object_in_use
55P02 | cant_change_runtime_param
55P03 | lock_not_available
55P04 | unsafe_new_enum_value_usage
Класс 57 — Вмешательство оператора |
57000 | operator_intervention
57014 | query_canceled
57P01 | admin_shutdown
57P02 | crash_shutdown
57P03 | cannot_connect_now
57P04 | database_dropped
Класс 58 — Ошибка системы (ошибка, внешняя по отношению к PostgreSQL) |
58000 | system_error
58030 | io_error
58P01 | undefined_file
58P02 | duplicate_file
Класс 72 — Ошибка снимка |
72000 | snapshot_too_old
Класс F0 — Ошибка файла конфигурации |
F0000 | config_file_error
F0001 | lock_file_exists
Класс HV — Ошибка обертки сторонних данных (SQL/MED) |
HV000 | fdw_error
HV005 | fdw_column_name_not_found
HV002 | fdw_dynamic_parameter_value_needed
HV010 | fdw_function_sequence_error
HV021 | fdw_inconsistent_descriptor_information
HV024 | fdw_invalid_attribute_value
HV007 | fdw_invalid_column_name
HV008 | fdw_invalid_column_number
HV004 | fdw_invalid_data_type
HV006 | fdw_invalid_data_type_descriptors
HV091 | fdw_invalid_descriptor_field_identifier
HV00B | fdw_invalid_handle
HV00C | fdw_invalid_option_index
HV00D | fdw_invalid_option_name
HV090 | fdw_invalid_string_length_or_buffer_length
HV00A | fdw_invalid_string_format
HV009 | fdw_invalid_use_of_null_pointer
HV014 | fdw_too_many_handles
HV001 | fdw_out_of_memory
HV00P | fdw_no_schemas
HV00J | fdw_option_name_not_found
HV00K | fdw_reply_handle
HV00Q | fdw_schema_not_found
HV00R | fdw_table_not_found
HV00L | fdw_unable_to_create_execution
HV00M | fdw_unable_to_create_reply
HV00N | fdw_unable_to_establish_connection
Класс P0 — Ошибка PL/pgSQL |
P0000 | plpgsql_error
P0001 | raise_exception
P0002 | no_data_found
P0003 | too_many_rows
P0004 | assert_failure
Класс XX — Внутренняя ошибка |
XX000 | internal_error
XX001 | data_corrupted
XX002 | index_corrupted

Поддержка даты и времени

QHB использует внутренний эвристический синтаксический анализатор для поддержки всех вводимых значений даты и времени. Даты и время вводятся в виде строк и разбиваются на отдельные поля с предварительным определением того, какого рода информация может быть в конкретном поле. Каждое поле интерпретируется и получает числовое значение, игнорируется или отклоняется. Синтаксический анализатор содержит внутренние таблицы поиска для всех текстовых полей, включая месяцы, дни недели и часовые пояса.

Это приложение включает информацию о содержимом этих справочных таблиц и описывает шаги, используемые синтаксическим анализатором для распознавания даты и времени.

Интерпретация ввода даты и времени

Строки ввода даты/времени распознаются по следующей схеме.

Разбить входную строку на синтаксические единицы и классифицировать каждую единицу как строку, время, часовой пояс или число.

Если числовая синтаксическая единица содержит двоеточие (:), то это строка времени. Включаются все последующие цифры и двоеточия.

Если числовая синтаксическая единица содержит тире (-), слэш (/) или две или более точек (.), то это строка даты, которая может содержать название месяца. Если единица даты уже встречалась, то она интерпретируется как название часового пояса (например, America/New_York).

Если синтаксическая единица состоит только из чисел, то это либо отдельное поле, либо объединенная дата ISO 8601 (например, 19990113 для 13 января 1999 года) или время (например, 141516 для 14:15:16).

Если синтаксическая единица начинается с плюса (+) или минуса (-), то это либо числовой часовой пояс, либо специальное поле.

Если синтаксическая единица представляет собой буквенную строку, сопоставить ее с возможными строками:

Проверить, соответствует ли синтаксическая единица какой-либо известной аббревиатуре часового пояса. Эти сокращения предоставляются файлом конфигурации, описанным в подразделе Файлы конфигурации даты/времени.

Если соответствие не найдено, проверить по внутренней таблице, не совпадает ли синтаксическая единица со специальной строкой (например, today), днем недели (например, Thursday), месяцем (например, January) или пропускаемым словом (например, at, on).

Если соответствие все же не найдено, вывести ошибку.

Когда синтаксическая единица является числом или числовым полем:

Если получено восемь или шесть цифр и если никакие другие поля даты не были ранее прочитаны, интерпретировать их как «объединенную дату» (например 19990118 или 990118). Интерпретация такой даты — ГГГГММДД или ГГММДД.

Если синтаксическая единица состоит из трех цифр и год уже был прочитан, интерпретировать ее как день года.

Если это четыре или шесть цифр и год уже был прочитан, интерпретировать их как время (ЧЧММ или ЧЧММСС).

Если это три или более цифр и поля даты еще не были найдены, интерпретировать их как год (это приводит к установке порядка гг-мм-дд для оставшихся полей даты).

В противном случае предполагается, что порядок полей даты будет следовать настройке DateStyle: мм-дд-гг, дд-мм-гг или гг-мм-дд. Если поле месяца или дня окажется за пределами диапазона, выдать ошибку.

Если был указан год до н. э. (BC), отнять год и добавить единицу для внутреннего хранения. (В григорианском календаре нет нулевого года, поэтому 1 год до н. э. становится нулевым.)

Если год до н. э. не был указан и поле года было двухзначным, исправить запись года на четырехзначную. Если поле меньше 70, добавить 2000, в противном случае добавить 1900.

Совет
Годы с 1 по 99 н. э. по григорианскому календарю можно ввести, используя четырехзначное число с ведущими нулями (например, 0099 — это 99 год н. э.).

Обработка недопустимых или неоднозначных временных меток

Обычно если строка даты/времени является синтаксически допустимой, но содержит поля со значениями вне диапазона, выдается ошибка. Например, при введении даты 31 февраля строка будет отклонена.

При переходе на летнее время допустимая на первый взгляд строка даты/времени может отображать несуществующую или неоднозначную метку времени. В таких случаях строка не отклоняется; неоднозначность решается путем определения того, какое смещение UTC применить. Например, предположим, что для параметра TimeZone установлено значение America/New_York, и рассмотрим следующий пример:

=> SELECT ’2018-03-11 02:30’::timestamptz;
timestamptz
------------------------
2018-03-11 03:30:00-04
(1 row)

Поскольку в этом часовом поясе в этот день стрелки часов переводились вперед, по гражданскому времени момента 2:30 не существовало; часы перескочили вперед с 2:00 по восточному стандартному времени (EST) на 3:00 по летнему восточному времени (EDT). QHB воспринимает данное время, как если бы оно было стандартным временем (часовой пояс UTC-5), которое затем преобразовалось в 3:30 EDT (часовой пояс UTC-4).

И, наоборот, рассмотрим поведение во время перевода времени назад:

=> SELECT ’2018-11-04 02:30’::timestamptz;
timestamptz
------------------------
2018-11-04 02:30:00-05
(1 row)

В этот день возможны две интерпретации времени 2:30; было 2:30 в часовом поясе EDT, а затем, через час после возврата к стандартному времени, наступило 2:30 в часовом поясе EST. Опять же, QHB воспринимает данное время, как если бы оно было стандартным (часовой пояс UTC-5). Можно форсировать ситуацию, указав часовой пояс летнего времени:

=> SELECT ’2018-11-04 02:30 EDT’::timestamptz;
timestamptz
------------------------
2018-11-04 01:30:00-05
(1 row)

Точное правило, применяемое в таких случаях, состоит в том, что недопустимая временная метка, которая предположительно попадает в момент перевода часов вперед на летнее время, привязывается к смещению UTC, действовавшему в этом часовом поясе непосредственно перед переходом, а неоднозначная временная метка, которая может попасть в оба интервала при переводе часов назад, привязывается к смещению UTC, действовавшему сразу после перехода. В большинстве часовых поясов это равносильно утверждению, что «в случае сомнений предпочтительна интерпретация со стандартным временем».

В любом случае смещение от UTC, связанное с меткой времени, можно задать явно, написав числовое смещение или аббревиатуру часового пояса, соответствующую фиксированному смещению от UTC. Вышеуказанное правило применяется только тогда, когда необходимо рассчитать смещение от UTC для часового пояса, в котором оно меняется.

Ключевые слова даты и времени

В Таблице 1 перечислены синтаксические единицы, которые распознаются как названия месяцев.

Таблица 1. Названия месяцев

Месяц | Аббревиатуры
January | Jan
February | Feb
March | Mar
April | Apr
May |
June | Jun
July | Jul
August | Aug
September | Sep, Sept
October | Oct
November | Nov
December | Dec

В Таблице 2 показаны синтаксические единицы, которые распознаются как названия дней недели.

Таблица 2. Названия дней недели

День | Аббревиатуры
Sunday | Sun
Monday | Mon
Tuesday | Tue, Tues
Wednesday | Wed, Weds
Thursday | Thu, Thur, Thurs
Friday | Fri
Saturday | Sat

В Таблице 3 показаны синтаксические единицы, которые выполняют различные функции модификаторов.

Таблица 3. Модификаторы поля даты/времени

Идентификатор | Описание
AM | Время до 12:00
AT | Игнорируется
JULIAN , JD , J | Следующее поле — юлианская дата
ON | Игнорируется
PM | Время 12:00 и более позднее
T | Следующее поле — время

Файлы конфигурации даты/времени

Поскольку аббревиатуры часовых поясов недостаточно стандартизированы, QHB предоставляет средства для самостоятельного определения набора аббревиатур, принимаемых сервером. Параметр времени выполнения timezone_abbreviations определяет активный набор аббревиатур. Хотя этот параметр может изменить любой пользователь базы данных, возможные значения для него контролируются администратором базы данных и на самом деле являются именами файлов конфигурации, хранящихся в .../share/timezonesets/ каталога установки. Добавляя или изменяя файлы в этом каталоге, администратор может назначить локальную политику для аббревиатур часовых поясов.

В timezone_abbreviations можно установить любое имя файла, найденное в .../share/timezonesets/, если то полностью состоит из букв. (Запрет на использование небуквенных символов в timezone_abbreviations препятствует чтению файлов, находящихся вне заданного каталога, а также чтению резервных файлов редактора и других посторонних файлов.)

Файл аббревиатур часовых поясов может содержать пустые строки и комментарии, начинающиеся с #. Строки без комментариев должны иметь один из следующих форматов:

аббревиатура_пояса смещение
аббревиатура_пояса смещение D
аббревиатура_пояса имя_часового_пояса
@INCLUDE имя_файла
@OVERRIDE

аббревиатура_пояса просто задает определяемую аббревиатуру. смещение — это целое число, задающее эквивалентное смещение от UTC в секундах: положительное — к востоку от Гринвича, а отрицательное — к западу. Например, -18000 означало бы пять часов к западу от Гринвича или североамериканское восточное стандартное время. D указывает, что название зоны представляет местное летнее, а не стандартное время.

Как вариант, можно задать имя_часового_пояса, ссылающееся на имя пояса, определенное в базе данных часовых поясов IANA. Определение пояса позволяет узнать, используется или использовалась ли аббревиатура в этом пояса, и если да, то выбирается соответствующее значение, то есть значение, действовавшее в указанный момент времени, или действовавшее непосредственно перед ним, если текущее не было определено, или самое старое значение, если оно начало действовать только после этого момент. Такое поведение важно для работы с аббревиатурами, значение которых менялось в ходе истории. Также разрешается определять аббревиатуру через имя часового пояса, в котором этой аббревиатуры нет; в таком случае использование аббревиатуры равнозначно написанию просо имени часового пояса.

Совет
Простое целочисленное смещение предпочтительнее использовать при определении аббревиатуры, смещение которой от UTC никогда не менялось, поскольку обрабатывать такие аббревиатуры гораздо легче, чем те, что требуют обращения к определению часового пояса.

Синтаксис @INCLUDE позволяет включать другой файл из каталога .../share/timezonesets/. Включение может быть вложенным до ограниченной глубины.

Синтаксис @OVERRIDE указывает, что последующие записи в файле могут переопределять предыдущие (как правило, это записи, полученные из включенных файлов). Без этого указания конфликтующие определения аббревиатуры одного и того же часового пояса считаются ошибкой.

При установке без изменений файл Default содержит все не конфликтующие аббревиатуры часовых поясов для большей части мира. Дополнительные файлы Australia и India предоставляются для данных регионов: эти файлы сначала включают файл Default, а затем добавляют или изменяют аббревиатуры по мере необходимости.

В справочных целях стандартная установка также содержит файлы Africa.txt, America.txt и т. д., содержащие информацию о каждой используемой, согласно базе данных часовых поясов IANA, аббревиатуре часового пояса. Определения имен часовых поясов, находящихся в этих файлах, можно копировать и вставлять в пользовательский файл конфигурации по мере необходимости. Обратите внимание, что эти файлы нельзя напрямую указывать как значение параметра timezone_abbreviations, поскольку их имена содержат точку.

Примечание
Если при чтении набора аббревиатур часовых поясов возникает ошибка, новое значение не применяется и сохраняется старый набор. Если ошибка возникает при запуске базы данных, происходит сбой запуска.

ВНИМАНИЕ!
Аббревиатуры часовых поясов, определенные в файле конфигурации, переопределяют не относящиеся к часовым поясам значения, встроенные в QHB. Например, файл конфигурации Australia определяет SAT (для южно-австралийского стандартного времени, South Australian Standard Time). Когда этот файл активен, SAT не будет распознаваться как сокращение слова «суббота».

ВНИМАНИЕ!
Если вы изменяете файлы в .../share/timezonesets/, резервное копирование остается на ваше усмотрение — обычный дамп базы данных не будет включать этот каталог.

Указание часовых поясов в стиле POSIX

QHB может принимать указания часовых поясов, записанные в соответствии с правилами стандарта POSIX для переменной среды TZ. Указания часовых поясов в стиле POSIX не подходят для решения вопросов, связанных со сложной историей часовых поясов в реальном мире, но иногда их использование бывает оправданным.

Указание часового пояса в стиле POSIX имеет следующую форму:

STD смещение [ DST [ смещение_летнего_времени ] [ , правило ] ]

(Для удобства восприятия между полями добавлены пробелы, но на практике их использовать не следует.) В указании имеются следующие поля:

STD — аббревиатура, используемая для стандартного часового пояса.

смещение — смещение стандартного времени часового пояса от UTC.

DST — аббревиатура часового пояса при переходе на летнее время. Если это поле и следующие за ним опущены, для часового пояса используется фиксированное смещение UTC без учета перехода на летнее время.

смещение_летнего_времени — смещение часового пояса от UTC при переходе на летнее время. Обычно это поле опускается, поскольку по умолчанию такое смещение на один час меньше стандартного смещения, что, как правило, и происходит на практике.

правило определяет, когда вступает в силу переход на летнее время; более подробно это описывается ниже.

В таком синтаксисе аббревиатура часового пояса может представлять собой строку букв, например EST, или произвольную строку, заключенную в угловые скобки, например <UTC-05>. Обратите внимание, что показанные здесь аббревиатуры часовых поясов используются только для вывода и только в некоторых выходных форматах метки времени. Определения аббревиатур, распознаваемых во входных значениях меток времени, описаны в подразделе Файлы конфигурации даты/времени.

В полях смещений задается разница с UTC в часах и, при необходимости, в минутах и секундах. Они записываются в формате чч[:мм[:сс]] и могут иметь спереди знак (+ or -). Положительный знак используется для часовых поясов к западу от Гринвича. (Обратите внимание, что это противоположно соглашению о знаках ISO-8601, применяемому в других областях QHB.) Элемент чч может состоять из одной или двух цифр; мм и сс (если они задаются) должны состоять из двух цифр.

Поле, задающее правило перехода на летнее время, имеет следующий формат:

дата_перехода [ / время_перехода ] , дата_возврата [ / время_возврата ]

(Как и ранее, на практике пробелов здесь быть не должно.) Поля дата_перехода и время_перехода определяют момент перехода на летнее время, а поля дата_возврата и время_возврата определяют момент возврата к стандартному времени. (В некоторых случаях, в частности, в Южном полушарии, первая дата в календаре может идти после второй.) Поля даты задаются в одном из следующих форматов:

n
Простое целое число, обозначающее день года, с нумерацией от нуля до 364, или до 365 в високосном году.

Jn
В этой форме n обозначает номер от 1 до 365, а 29 февраля не считается, даже когда присутствует. (Таким образом, смены часового пояса, происходящие 29 февраля, таким способом задать нельзя. Однако после февраля дни имеют одинаковые номера, независимо от того, високосный это год или нет, так что для записи фиксированных дат перевода часов эта форма более полезна, чем простое целое.)

Mm.n.d
Эта форма задает переход, который всегда происходит в один и тот же месяц и день недели. m обозначает номер месяца от 1 до 12. n обозначает номер вхождения в этом месяце дня недели, заданного полем d. Значение n задается числом от 1 до 4 либо числом 5, означающим последнее вхождение этого дня недели в этом месяце (фактически оно может быть четвертым или пятым). Значение d задается числом от 0 до 6, где 0 обозначает воскресенье. Например, запись M3.2.0 означает «второе воскресенье марта».

Примечание
Для описания многих распространенных правил перехода на летнее время формата M вполне достаточно. Но примите к сведению, что ни один из этих вариантов не позволяет отразить изменения в этих правилах, поэтому на практике для правильной интерпретации меток времени, относящихся к прошлому, необходимы исторические данные, хранящиеся в привязке к именованным часовым поясам (в базе данных часовых поясов IANA).

Поля времени в правиле перехода имеют тот же формат, что и описанные ранее поля смещения, за исключением того, что они не могут содержать знака. Они определяют текущее местное время, в которое производится перевод часов. Если эти поля опущены, по умолчанию их значением считается 02:00:00.

Если аббревиатура для летнего времени задается, но правило перехода опускается, в качестве альтернативы используется правило M3.2.0,M11.1.0, соответствующее порядку, принятому в США в 2020 г. (то есть часы переводятся вперед во второе воскресенье марта, а назад — в первое воскресенье ноября, в 2:00 по действующему времени). Обратите внимание, что это правило не дает правильные даты перехода в США ранее 2007 г.

Например, запись CET-1CEST,M3.5.0,M10.5.0/3 описывает действующий в 2020 г. порядок перевода часов в Париже. В этом указании говорится, что стандартное время имеет аббревиатуру CET и смещено на час вперед (к востоку) от UTC; летнее время имеет аббревиатуру CEST и смещено (неявно) на два часа вперед от UTC; летнее время начинается в последнее воскресенье марта в 2:00 CET и заканчивается в последнее воскресенье октября в 3:00 CEST.

Названия четырех часовых поясов EST5EDT, CST6CDT, MST7MDT и PST8PDT выглядят как указания часовых поясов в стиле POSIX. Однако в действительности они воспринимаются как именованные часовые пояса, поскольку (по историческим причинам) в базе данных часовых поясов IANA есть файлы с такими именами. Практическое следствие этого состоит в том, что для этих имен часовых поясов будут выдаваться исторические сведения о действовавших правилах перехода на летнее время в США, тогда как обычное указание в стиле POSIX этого не сделает.

Не стоит забывать, что ошибиться при указании часового пояса в стиле POSIX очень легко, поскольку допустимость аббревиатуры не проверяется. К примеру, команда SET TIMEZONE TO FOOBAR0 выполнится успешно, и по сути система будет использовать довольно своеобразную аббревиатуру для UTC.

История единиц измерения времени

Стандарт SQL гласит, что «В определении "литерала даты и времени" "значения даты и времени" ограничены естественными правилами для дат и времени согласно григорианскому календарю». Следуя примеру стандарта SQL, QHB считает даты исключительно в григорианском календаре, в том числе те годы, когда этот календарь не использовался. Это правило известно как пролептический григорианский календарь.

Юлианский календарь был введен Юлием Цезарем в 45 году до н. э. Он широко использовался в западном мире вплоть до 1582 года, когда страны начали переходить на григорианский календарь. В юлианском календаре тропический год равен примерно 365 1/4 дням = 365,25 дням. В результате каждые 128 лет накапливается приблизительно 1 лишний день.

Накапливающаяся календарная погрешность побудила Папу Григория XIII реформировать календарь в соответствии с предписаниями Трентского Собора. В григорианском календаре тропический год равен примерно 365 + 97/400 дням = 365,2425 дням. Таким образом, в григорианском календаре погрешность в один день тропического года накапливается примерно за 3300 лет.

Приблизительное число 365 + 97/400 достигается за счет того, что каждые 97 лет из 400 становятся високосными с помощью следующих правил:

Каждый год, кратный 4, является високосным.
Однако каждый год, кратный 100, не является високосным.
Однако каждый год, кратный 400, все-таки является високосным.

То есть 1700, 1800, 1900, 2100 и 2200 не являются високосными годами. Но 1600, 2000 и 2400 — високосные годы. А вот в старом юлианском календаре високосными являются все годы, кратные 4.

Папская булла, изданная в феврале 1582 года, постановила, что октябрь 1582 года должен быть на 10 дней короче, чтобы 15 октября следовало сразу за 4 октября. Этому предписанию последовали Италия, Польша, Португалия и Испания. Вскоре к ним присоединились и другие католические страны, но протестантские страны вводили эти изменения неохотно, а страны греческой православной церкви не проводили реформу до начала 20-го века. В 1752 году реформа была проведена в Великобритании и ее доминионах (включая территорию нынешних США). Таким образом, за 2 сентября 1752 года последовало 14 сентября 1752 года. Вот почему в системах Unix, имеющих программу cal, выводится следующее:

$ cal 9 1752
September 1752
S M Tu W Th F S
1 2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Но, конечно, этот календарь действителен только для Великобритании и доминионов, а в других местах он не действует. Поскольку отслеживание реальных календарей, которые использовались в разных местах в разное время, сопряжено с трудностями и неясностями, QHB вместо этого применяет правила григорианского календаря ко всем датам, хотя этот метод и не является исторически достоверным.

Различные календари были разработаны в разных частях света, многие из которых появились до григорианской системы. Например, появление китайского календаря относится к 14 веку до н. э. Легенда гласит, что император Хуанди изобрел этот календарь в 2637 году до н. э. В Китайской Народной Республике григорианский календарь используется в гражданских целях. Китайский календарь используется для определения праздничных дат.

Юлианские даты

Система юлианских дат — это способ исчисления периодов времени. Он не имеет отношения к юлианскому календарю, хотя схожее название может сбить с толку. Система юлианских дат была изобретена французским ученым Жозефом Юстусом Скалигером (1540-

и, вероятно, получила свое название в честь его отца, итальянского ученого Юлия Цезаря Скалигера (1484-1558).

В системе юлианских дат каждый день имеет порядковый номер, начинающийся с JD 0 (который иногда называют номером юлианской даты). JD 0 соответствует 1 января 4713 г. до н. э. по юлианскому календарю или 24 ноября 4714 г. до н. э. по григорианскому календарю. Исчисление по юлианским датам чаще всего используется астрономами для маркировки своих ночных наблюдений, и поэтому день длится с полудня до полудня UTC, а не с полуночи до полуночи: JD 0 (первый юлианский день) обозначает 24 часа с полудня UTC 24 ноября 4714 г. до н. э. до полудня UTC 25 ноября 4714 г. до н. э.

Хотя QHB поддерживает юлианскую дату для записи входных и выходных дат (а также использует юлианские даты для некоторых внутренних вычислений даты и времени), он не придерживается концепции с отсчетом суток от полудня. QHB считает юлианский день длящимся от полуночи до полуночи, как и обычный день.

Однако при необходимости эта формулировка позволяет получить астрономическое определение, произведя вычисления в часовом поясе UTC+12. Например,

=> SELECT extract(julian from '2021-06-23 7:00:00-04'::timestamptz at time zone 'UTC+12');
     date_part
--------------------
 2459388.9583333335
(1 row)
=> SELECT extract(julian from '2021-06-23 8:00:00-04'::timestamptz at time zone 'UTC+12');
 date_part
-----------
   2459389
(1 row)
=> SELECT extract(julian from date '2021-06-23');
 date_part
-----------
   2459389
(1 row)

Соответствие стандарту SQL

В этом разделе в общих чертах описывается, в какой степени QHB соответствует текущему стандарту SQL. Следующая информация не является полноценной декларацией соответствия, но при этом представляет основные темы достаточно подробно, чтобы быть полезной и целесообразной для пользователей.

Официально стандарт SQL называется ISO/IEC 9075 «Database Language SQL» (Язык баз данных SQL). Время от времени выпускается обновленная версия стандарта; последнее обновление было сделано в 2016 году. Эта версия стандарта получила название ISO/IEC 9075:2016, или просто SQL:2016. Ранее были выпущены версии SQL:2011, SQL:2008, SQL:2006, SQL:2003, SQL:1999 и SQL-92. Каждая версия заменяет предыдущую, поэтому заявления о соответствии более ранним версиям не имеют официальной ценности. Разработка QHB нацелена на соответствие последней официальной версии стандарта, если оно не противоречит традиционной функциональности или здравому смыслу. Поддерживается большая часть функциональности, требуемой стандартом SQL, хотя иногда с немного отличающимся синтаксисом или функциями. Можно ожидать, что со временем степень соответствия стандарту будет постепенно возрастать.

SQL-92 определил три уровня функционального соответствия стандарту: начальный (Entry), промежуточный (Intermediate) и полный (Full). Большинство СУБД, заявлявших о соответствии стандарту SQL, соответствовали ему только на начальном уровне, поскольку общий набор функций на промежуточном и полном уровнях либо был слишком обширным, либо конфликтовал с устаревшими моделями поведения.

Начиная с SQL:1999, вместо трех бесполезных пространных уровней, вычлененных в SQL-92, в стандарте SQL определен широкий ряд отдельных функциональных возможностей. Большое его подмножество представляет собой «Основную» функциональность, которую должны обеспечивать все реализации, совместимые с SQL. Поддержка остальных возможностей не являются обязательной.

Версии стандарта, начиная с SQL:2003, также разбиты на несколько частей. Каждая часть имеет краткое название. Обратите внимание, что нумерация этих частей непоследовательная.

ISO/IEC 9075-1 Структура (SQL/Framework)

ISO/IEC 9075-2 Основа (SQL/Foundation)

ISO/IEC 9075-3 Интерфейс уровня вызовов (SQL/CLI)

ISO/IEC 9075-4 Модули постоянного хранения (SQL/PSM)

ISO/IEC 9075-9 Управление внешними данными (SQL/MED)

ISO/IEC 9075-10 Привязки объектных языков (SQL/OLB)

ISO/IEC 9075-11 Схемы информации и определений (SQL/Schemata)

ISO/IEC 9075-13 Программы и типы, использующие язык Java (SQL/JRT)

ISO/IEC 9075-14 Спецификации, связанные с XML (SQL/XML)

ISO/IEC 9075-15 Многомерные массивы (SQL/MDA)

Ядро QHB реализует части 1, 2, 9, 11 и 14. Часть 3 реализуется драйвером ODBC, а часть 13 — плагином PL/Java, но точное соответствие этих компонентов стандарту на данный момент не проверено. Части 4, 10 и 15 в QHB в настоящее время не реализованы.

QHB поддерживает почти всю основную функциональность SQL:2016. Из 177 обязательных возможностей, необходимых для полного соответствия «Основной» функциональности, QHB обеспечивает соответствие как минимум для 170. Кроме того, она поддерживает длинный список дополнительных возможностей. Следует отметить, что на момент написания этой документации ни одна из существующих версий СУБД не претендовала на полное соответствие «Основной» функциональности SQL:2016.

В следующих двух разделах мы предоставляем список функциональных возможностей, которые поддерживает QHB, а также список возможностей, определенных в SQL:2016, которые в QHB еще не поддерживаются. Оба списка носят приблизительный характер: функциональность, указанная как поддерживаемая, может отличаться от стандарта в незначительных деталях, а неподдерживаемая функциональность на самом деле может быть по большей части реализована. Наиболее точная информация о том, что работает, а что нет, содержится в основной документации.

Примечание
Код функциональности, содержащей дефис, обозначает подчиненную функциональность. Поэтому если какая-то подчиненная функциональность не поддерживается, основная функциональность тоже указывается как неподдерживаемая, даже если поддерживаются некоторые другие подчиненные функциональные возможности.

Поддерживаемая функциональность

Идентификатор | Основа? | Описание | Комментарий
B012 |  | Встроенный C |
B021 |  | Непосредственный SQL |
E011 | Основа | Числовые типы данных |
E011-01 | Основа | Типы данных INTEGER и SMALLINT |
E011-02 | Основа | Типы данных REAL, DOUBLE PRECISION и FLOAT |
E011-03 | Основа | Типы данных DECIMAL и NUMERIC |
E011-04 | Основа | Арифметические операторы |
E011-05 | Основа | Числовое сравнение |
E011-06 | Основа | Неявное приведение среди числовых типов данных |
E021 | Основа | Символьные типы данных |
E021-01 | Основа | Тип данных CHARACTER |
E021-02 | Основа | Тип данных CHARACTER VARYING |
E021-03 | Основа | Символьные литералы |
E021-04 | Основа | Функция CHARACTER_LENGTH | убирает завершающие пробелы из значений CHARACTER перед подсчетом
E021-05 | Основа | Функция OCTET_LENGTH |
E021-06 | Основа | Функция SUBSTRING |
E021-07 | Основа | Конкатенация символов |
E021-08 | Основа | Функции UPPER и LOWER |
E021-09 | Основа | Функция TRIM |
E021-10 | Основа | Неявные приведения среди типов символьных строк |
E021-11 | Основа | Функция POSITION |
E021-12 | Основа | Сравнение символов |
E031 | Основа | Идентификаторы |
E031-01 | Основа | Идентификаторы с разделителями |
E031-02 | Основа | Идентификаторы в нижнем регистре |
E031-03 | Основа | Завершающее подчеркивание |
E051 | Основа | Базовая спецификация запросов |
E051-01 | Основа | SELECT DISTINCT |
E051-02 | Основа | Предложение GROUP BY |
E051-04 | Основа | GROUP BY может содержать столбцы не из <списка выборки> |
E051-05 | Основа | Элементы списка выборки можно переименовывать |
E051-06 | Основа | Предложение HAVING |
E051-07 | Основа | Уточняющий символ * в списке выборки |
E051-08 | Основа | Корреляционные имена в предложении FROM |
E051-09 | Основа | Переименование столбцов в предложении FROM |
E061 | Основа | Базовые предикаты и условия поиска |
E061-01 | Основа | Предикат сравнения |
E061-02 | Основа | Предикат BETWEEN |
E061-03 | Основа | Предикат IN со списком значений |
E061-04 | Основа | Предикат LIKE |
E061-05 | Основа | Предложение ESCAPE в предикате LIKE |
E061-06 | Основа | Предикат NULL |
E061-07 | Основа | Предикаты количественного сравнения |
E061-08 | Основа | Предикат EXISTS |
E061-09 | Основа | Подзапросы в предикате сравнения |
E061-11 | Основа | Подзапросы в предикате IN |
E061-12 | Основа | Подзапросы в предикате количественного сравнения |
E061-13 | Основа | Коррелирующие подзапросы |
E061-14 | Основа | Условие поиска |
E071 | Основа | Базовые выражения запросов |
E071-01 | Основа | Табличный оператор UNION DISTINCT |
E071-02 | Основа | Табличный оператор UNION ALL |
E071-03 | Основа | Табличный оператор EXCEPT DISTINCT |
E071-05 | Основа | Столбцам, объединенным с помощью табличных операторов, необязательно иметь одинаковый тип данных. |
E071-06 | Основа | Табличные операторы в подзапросах |
E081 | Основа | Основные права доступа |
E081-01 | Основа | Право на SELECT |
E081-02 | Основа | Право на DELETE |
E081-03 | Основа | Право на INSERT на уровне таблицы |
E081-04 | Основа | Право на UPDATE на уровне таблицы |
E081-05 | Основа | Право на UPDATE на уровне столбца |
E081-06 | Основа | Право REFERENCES на уровне таблицы |
E081-07 | Основа | Право REFERENCES на уровне столбца |
E081-08 | Основа | Предложение WITH GRANT OPTION |
E081-09 | Основа | Право USAGE |
E081-10 | Основа | Право на EXECUTE |
E091 | Основа | Функции множеств |
E091-01 | Основа | AVG |
E091-02 | Основа | COUNT |
E091-03 | Основа | MAX |
E091-04 | Основа | MIN |
E091-05 | Основа | SUM |
E091-06 | Основа | Квантификатор ALL |
E091-07 | Основа | Квантификатор DISTINCT |
E101 | Основа | Базовые манипуляции с данными |
E101-01 | Основа | Оператор INSERT |
E101-03 | Основа | Оператор UPDATE с критерием отбора |
E101-04 | Основа | Оператор DELETE с критерием отбора |
E111 | Основа | Оператор SELECT, возвращающий одну строку |
E121 | Основа | Базовая поддержка курсоров |
E121-01 | Основа | DECLARE CURSOR |
E121-02 | Основа | Столбцы ORDER BY, которым необязательно быть в списке выборки |
E121-03 | Основа | Выражения значений в предложении ORDER BY |
E121-04 | Основа | Оператор OPEN |
E121-06 | Основа | Оператор UPDATE с позиционированием |
E121-07 | Основа | Оператор DELETE с позиционированием |
E121-08 | Основа | Оператор CLOSE |
E121-10 | Основа | Оператор FETCH с неявным NEXT |
E121-17 | Основа | Курсоры WITH HOLD |
E131 | Основа | Поддержка значения NULL (NULL вместо значений) |
E141 | Основа | Базовые ограничения целостности |
E141-01 | Основа | Ограничения NOT NULL |
E141-02 | Основа | Ограничения UNIQUE столбцов NOT NULL |
E141-03 | Основа | Ограничения PRIMARY KEY |
E141-04 | Основа | Основное ограничение FOREIGN KEY со значением по умолчанию NO ACTION (без действия) как для операции удаления со ссылками, так и для операций изменения со ссылками |
E141-06 | Основа | Ограничения CHECK |
E141-07 | Основа | Значения столбцов по умолчанию |
E141-08 | Основа | NOT NULL распространяется на PRIMARY KEY |
E141-10 | Основа | Имена во внешнем ключе можно указывать в любом порядке |
E151 | Основа | Поддержка транзакций |
E151-01 | Основа | Оператор COMMIT |
E151-02 | Основа | Оператор ROLLBACK |
E152 | Основа | Базовый оператор SET TRANSACTION |
E152-01 | Основа | Оператор SET TRANSACTION: предложение ISOLATION LEVEL SERIALIZABLE |
E152-02 | Основа | Оператор SET TRANSACTION: предложения READ ONLY и READ WRITE |
E153 | Основа | Обновляемые запросы с подзапросами |
E161 | Основа | Комментарии SQL, начинающиеся с двух минусов |
E171 | Основа | Поддержка SQLSTATE |
E182 | Основа | Привязки для базовых языков |
F021 | Основа | Базовая информационная схема |
F021-01 | Основа | Представление COLUMNS |
F021-02 | Основа | Представление TABLES |
F021-03 | Основа | Представление VIEWS |
F021-04 | Основа | Представление TABLE_CONSTRAINTS |
F021-05 | Основа | Представление REFERENTIAL_CONSTRAINTS |
F021-06 | Основа | Представление CHECK_CONSTRAINTS |
F031 | Основа | Базовые манипуляции со схемой |
F031-01 | Основа | Оператор CREATE TABLE для создания хранимых базовых таблиц |
F031-02 | Основа | Оператор CREATE VIEW |
F031-03 | Основа | Оператор GRANT |
F031-04 | Основа | Оператор ALTER TABLE: предложение ADD COLUMN |
F031-13 | Основа | Оператор DROP TABLE: предложение RESTRICT |
F031-16 | Основа | Оператор DROP VIEW: предложение RESTRICT |
F031-19 | Основа | Оператор REVOKE: предложение RESTRICT |
F032 |  | Каскадное удаление (CASCADE) |
F033 |  | Оператор ALTER TABLE: предложение DROP COLUMN |
F034 |  | Расширенный оператор REVOKE |
F034-01 |  | Оператор REVOKE, выполняемый не владельцем объекта схемы |
F034-02 |  | Оператор REVOKE: предложение GRANT OPTION FOR |
F034-03 |  | Оператор REVOKE для отзыва права, данного субъекту с указанием WITH GRANT OPTION |
F041 | Основа | Базовое соединение таблиц |
F041-01 | Основа | Внутреннее соединение (но не обязательно с ключевым словом INNER) |
F041-02 | Основа | Ключевое слово INNER |
F041-03 | Основа | LEFT OUTER JOIN |
F041-04 | Основа | RIGHT OUTER JOIN |
F041-05 | Основа | Внешние соединения могут быть вложенными |
F041-07 | Основа | Внутренняя таблица с левой или правой стороны внешнего соединения также может использоваться во внутреннем соединении |
F041-08 | Основа | Поддерживаются все операторы сравнения (а не только =) |
F051 | Основа | Базовая поддержка даты и времени |
F051-01 | Основа | Тип данных DATE (включая поддержку литерала DATE) |
F051-02 | Основа | Тип данных TIME (включая поддержку литерала TIME) с точностью до доли секунды как минимум с 0 знаков после запятой |
F051-03 | Основа | Тип данных TIMESTAMP (включая поддержку литерала TIMESTAMP) с точностью до доли секунды как минимум с 0 и 6 знаками после запятой |
F051-04 | Основа | Предикат сравнения для типов данных DATE, TIME и TIMESTAMP |
F051-05 | Основа | Явное приведение (CAST) между типами даты/времени и типами символьных строк |
F051-06 | Основа | CURRENT_DATE |
F051-07 | Основа | LOCALTIME |
F051-08 | Основа | LOCALTIMESTAMP |
F052 |  | Арифметика с интервалами и датами/временем |
F053 |  | Предикат OVERLAPS |
F081 | Основа | UNION и EXCEPT в представлениях |
F111 |  | Уровни изоляции, отличные от SERIALIZABLE |
F111-01 |  | Уровень изоляции READ UNCOMMITTED |
F111-02 |  | Уровень изоляции READ COMMITTED |
F111-03 |  | Уровень изоляции REPEATABLE READ |
F131 | Основа | Операции группировки |
F131-01 | Основа | Предложения WHERE, GROUP BY и HAVING, поддерживаемые в запросах со сгруппированными представлениями |
F131-02 | Основа | Поддержка нескольких таблиц в запросах со сгруппированными представлениями |
F131-03 | Основа | Поддержка функций множеств в запросах со сгруппированными представлениями |
F131-04 | Основа | Подзапросы с предложениями GROUP BY и HAVING и сгруппированными представлениями |
F131-05 | Основа | SELECT, возвращающий одну строку, с предложениями GROUP BY и HAVING и сгруппированными представлениями |
F171 |  | Несколько схем для одного пользователя |
F181 | Основа | Поддержка множества модулей |
F191 |  | Действия при удалении со ссылками |
F200 |  | Оператор TRUNCATE TABLE |
F201 | Основа | Функция CAST |
F202 |  | TRUNCATE TABLE: возможность перезапуска столбца идентификаторов |
F221 | Основа | Явные значения по умолчанию |
F222 |  | Оператор INSERT: предложение DEFAULT VALUES |
F231 |  | Таблицы прав |
F231-01 |  | Представление TABLE_PRIVILEGES |
F231-02 |  | Представление COLUMN_PRIVILEGES |
F231-03 |  | Представление USAGE_PRIVILEGES |
F251 |  | Поддержка доменов |
F261 | Основа | Выражение CASE |
F261-01 | Основа | Простой оператор CASE |
F261-02 | Основа | Оператор CASE с критериями отбора |
F261-03 | Основа | NULLIF |
F261-04 | Основа | COALESCE |
F262 |  | Расширенное выражение CASE |
F271 |  | Составные символьные литералы |
F281 |  | Улучшенный оператор LIKE |
F302 |  | Табличный оператор INTERSECT |
F302-01 |  | Табличный оператор INTERSECT DISTINCT |
F302-02 |  | Табличный оператор INTERSECT ALL |
F304 |  | Табличный оператор EXCEPT ALL |
F311 | Основа | Оператор определения схемы |
F311-01 | Основа | CREATE SCHEMA |
F311-02 | Основа | CREATE TABLE для хранимых базовых таблиц |
F311-03 | Основа | CREATE VIEW |
F311-04 | Основа | CREATE VIEW: WITH CHECK OPTION |
F311-05 | Основа | Оператор GRANT |
F321 |  | Авторизация пользователей |
F341 |  | Таблицы используемости |
F361 |  | Поддержка подпрограмм |
F381 |  | Расширенные манипуляции со схемой |
F381-01 |  | Оператор ALTER TABLE: предложение ALTER COLUMN |
F381-02 |  | Оператор ALTER TABLE: предложение ADD CONSTRAINT |
F381-03 |  | Оператор ALTER TABLE: предложение DROP CONSTRAINT |
F382 |  | Изменение типа данных столбца |
F383 |  | Предложение, устанавливающее NOT NULL для столбца |
F384 |  | Предложение, удаляющее свойство столбца идентификаторов |
F385 |  | Предложение, удаляющее выражение генерирования столбца |
F386 |  | Предложение, устанавливающее генерирование значений столбца идентификаторов |
F391 |  | Длинные идентификаторы |
F392 |  | Управляющие символы Unicode в идентификаторах |
F393 |  | Управляющие символы Unicode в литералах |
F394 |  | Необязательное указание нормальной формы |
F401 |  | Расширенное соединение таблиц |
F401-01 |  | NATURAL JOIN |
F401-02 |  | FULL OUTER JOIN |
F401-04 |  | CROSS JOIN |
F402 |  | Соединения по именам столбцов для больших объектов, массивов и мультимножеств |
F404 |  | Диапазонная переменная для имен общих столбцов |
F411 |  | Указание часового пояса | отличия в интерпретации литералов
F421 |  | Национальные символы |
F431 |  | Прокручиваемые курсоры только для чтения |
F431-01 |  | FETCH с явным NEXT |
F431-02 |  | FETCH FIRST |
F431-03 |  | FETCH LAST |
F431-04 |  | FETCH PRIOR |
F431-05 |  | FETCH ABSOLUTE |
F431-06 |  | FETCH RELATIVE |
F441 |  | Расширенная поддержка функций множеств |
F442 |  | Смешанные ссылки на столбцы в функциях множеств |
F471 | Основа | Скалярные значения подзапросов |
F481 | Основа | Расширенный предикат NULL |
F491 |  | Управление ограничениями |
F501 | Основа | Представления функционалов и соответствия |
F501-01 | Основа | Представление SQL_FEATURES |
F501-02 | Основа | Представление SQL_SIZING |
F502 |  | Таблицы расширенной документации |
F531 |  | Временные таблицы |
F555 |  | Дополнительная точность в секундах |
F561 |  | Полные выражения значений |
F571 |  | Проверки значений истинности |
F591 |  | Производные таблицы |
F611 |  | Типы данных для индикаторов |
F641 |  | Конструкторы строк и таблиц |
F651 |  | Спецификаторы имен каталогов |
F661 |  | Простые таблицы |
F672 |  | Ретроспективные проверочные ограничения |
F690 |  | Поддержка правил сортировки | но без поддержки наборов символов
F692 |  | Расширенная поддержка правил сортировки |
F701 |  | Действия при обновлении со ссылками |
F711 |  | ALTER для домена |
F731 |  | Права на INSERT для столбцов |
F751 |  | Усовершенствования CHECK для представлений |
F761 |  | Управление сеансом |
F762 |  | CURRENT_CATALOG |
F763 |  | CURRENT_SCHEMA |
F771 |  | Управление соединением |
F781 |  | Самоссылающиеся операции |
F791 |  | Нечувствительные курсоры |
F801 |  | Полные функции множеств |
F850 |  | <Предложение order by> на верхнем уровне в <выражении запроса> |
F851 |  | <Предложение order by> в подзапросах |
F852 |  | <Предложение order by> на верхнем уровне в представлениях |
F855 |  | Вложенное <предложение order by> в <выражении запроса> |
F856 |  | Вложенное <предложение fetch first> в <предложении запроса> |
F857 |  | <Предложение fetch first> на верхнем уровне в <выражении запроса> |
F858 |  | <Предложение fetch first> в подзапросах |
F859 |  | <Предложение fetch first> на верхнем уровне в представлениях |
F860 |  | <Указание числа строк> в <предложении fetch first> |
F861 |  | <Предложение offset для результата> на верхнем уровне в <выражении запроса> |
F862 |  | <Предложение offset для результата> в подзапросах |
F863 |  | Вложенное <предложение offset для результата> в <выражении запроса> |
F864 |  | <Предложение offset для результата> на верхнем уровне в представлениях |
F865 |  | <Указание числа строк> с <предложением offset для результата> |
F867 |  | Предложение FETCH FIRST: параметр WITH TIES |
S071 |  | Пути SQL при разрешении имен функций и типов |
S092 |  | Массивы пользовательских типов |
S095 |  | Конструкторы массива из запроса |
S096 |  | Необязательное указание границ массива |
S098 |  | ARRAY_AGG |
S111 |  | ONLY в выражениях запросов |
S201 |  | Вызываемые из SQL подпрограммы для массивов |
S201-01 |  | Параметры-массивы |
S201-02 |  | Массивы в качестве типа результата функций |
S211 |  | Пользовательские функции приведений |
S301 |  | Расширенный UNNEST |
S404 |  | TRIM_ARRAY |
T031 |  | Тип данных BOOLEAN |
T071 |  | Тип данных BIGINT |
T121 |  | WITH (без RECURSIVE) в выражении запроса |
T122 |  | WITH (без RECURSIVE) в подзапросе |
T131 |  | Рекурсивный запрос |
T132 |  | Рекурсивный запрос в подзапросе |
T133 |  | Расширенные значения для меток циклов | Разрабатываемый SQL:202x
T141 |  | Предикат SIMILAR |
T151 |  | Предикат DISTINCT |
T152 |  | Предикат DISTINCT с отрицанием |
T171 |  | Предложение LIKE в определении таблицы |
T172 |  | Предложение подзапроса AS в определении таблицы |
T173 |  | Расширенное предложение LIKE в определении таблицы |
T174 |  | Столбцы идентификаторов |
T177 |  | Поддержка генератора последовательностей: возможность простого перезапуска |
T178 |  | Столбцы идентификаторов: возможность простого перезапуска |
T191 |  | Действие RESTRICT при нарушении ссылок |
T201 |  | Сравнимые типы данных для ссылочных ограничений |
T211-01 |  | Триггеры, активируемые при UPDATE, INSERT или DELETE в одной базовой таблице |
T211-02 |  | Триггеры BEFORE |
T211-03 |  | Триггеры AFTER |
T211-04 |  | Триггеры FOR EACH ROW |
T211-05 |  | Возможность задать условие поиска, которое должно быть истинным перед вызовом триггера |
T211-07 |  | Право TRIGGER |
T212 |  | Расширенные возможности триггеров |
T213 |  | Триггеры INSTEAD OF |
T241 |  | Оператор START TRANSACTION |
T261 |  | Сцепленные транзакции |
T271 |  | Точки сохранения |
T281 |  | Право SELECT на уровне столбцов |
T285 |  | Расширенные имена производных столбцов |
T312 |  | Функция OVERLAY |
T321-01 | Основа | Пользовательские функции без перегрузки |
T321-02 | Основа | Пользовательские хранимые процедуры без перегрузки |
T321-03 | Основа | Вызов функций |
T321-04 | Основа | Оператор CALL |
T321-06 | Основа | Представление ROUTINES |
T321-07 | Основа | Представление PARAMETERS |
T323 |  | Явная настройка безопасности внешних подпрограмм |
T325 |  | Уточненные ссылки на параметры SQL |
T331 |  | Базовые роли |
T332 |  | Расширенные роли |
T341 |  | Перегрузка вызываемых из SQL функций и процедур |
T351 |  | Скобочные комментарии SQL (комментарии /*...*/) |
T431 |  | Расширенные возможности группирования |
T432 |  | Вложения и конкатенация GROUPING SETS |
T433 |  | Многоаргументная функция GROUPING |
T434 |  | GROUP BY DISTINCT |
T441 |  | Функции ABS и MOD |
T461 |  | Симметричный предикат BETWEEN |
T491 |  | Производная таблица LATERAL |
T501 |  | Расширенный предикат EXISTS |
T521 |  | Именованные аргументы в операторе CALL |
T523 |  | Значения по умолчанию для параметров INOUT процедур, вызываемых из SQL |
T524 |  | Именованные аргументы при вызовах подпрограмм без применения оператора CALL |
T525 |  | Значения по умолчанию для параметров функций, вызываемых из SQL |
T551 |  | Необязательные ключевые слова для синтаксиса по умолчанию |
T581 |  | Функция подстроки регулярного выражения |
T591 |  | Ограничения UNIQUE для столбцов, принимающих NULL |
T611 |  | Элементарные операции OLAP |
T612 |  | Расширенные операции OLAP |
T613 |  | Формирование выборка |
T614 |  | Функция NTILE |
T615 |  | Функции LEAD и LAG |
T617 |  | Функции FIRST_VALUE и LAST_VALUE |
T620 |  | Предложение WINDOW: параметр GROUPS |
T621 |  | Расширенные числовые функции |
T622 |  | Тригонометрические функции |
T623 |  | Общие логарифмические функции |
T624 |  | Стандартные логарифмические функции |
T631 | Основа | Предикат IN с одним элементом списка |
T651 |  | Операторы SQL-схемы в подпрограммах SQL |
T653 |  | Операторы SQL-схемы во внешних подпрограммах |
T655 |  | Циклически зависимые подпрограммы |
T831 |  | Язык путей SQL/JSON: строгий режим |
T832 |  | Язык путей SQL/JSON: метод элементов |
T833 |  | Язык путей SQL/JSON: множественные индексы |
T834 |  | Язык путей SQL/JSON: метод доступа к членам по символу подстановки (*) |
T835 |  | Язык путей SQL/JSON: выражения фильтров |
T836 |  | Язык путей SQL/JSON: предикат «начинается с» |
T837 |  | Язык путей SQL/JSON: предикат regex_like |
X010 |  | Тип XML |
X011 |  | Массивы типа XML |
X014 |  | Атрибуты типа XML |
X016 |  | Хранимые значения XML |
X020 |  | XMLConcat |
X031 |  | XMLElement |
X032 |  | XMLForest |
X034 |  | XMLAgg |
X035 |  | XMLAgg: параметр ORDER BY |
X036 |  | XMLComment |
X037 |  | XMLPI |
X040 |  | Базовое отображение таблиц |
X041 |  | Базовое отображение таблиц: отсутствуют значения NULL |
X042 |  | Базовое отображение таблиц: NULL в виде nil |
X043 |  | Базовое отображение таблиц: таблица в виде леса элементов |
X044 |  | Базовое отображение таблицы: таблица в виде элемента |
X045 |  | Базовое отображение таблиц: с целевым пространством имен |
X046 |  | Базовое отображение таблиц: отображение данных |
X047 |  | Базовое отображение таблиц: отображение метаданных |
X048 |  | Базовое отображение таблиц: кодирование двоичных строк в base64 |
X049 |  | Базовое отображение таблиц: кодирование двоичных строк в шестнадцатеричном виде |
X050 |  | Расширенное отображение таблиц |
X051 |  | Расширенное отображение таблиц: отсутствуют значения NULL |
X052 |  | Расширенное отображение таблицы: NULL в виде nil |
X053 |  | Расширенное отображение таблиц: таблица в виде леса элементов |
X054 |  | Расширенное отображение таблицы: таблица в виде элемента |
X055 |  | Расширенное отображение таблиц: с целевым пространством имен |
X056 |  | Расширенное отображение таблиц: отображение данных |
X057 |  | Расширенное отображение таблиц: отображение метаданных |
X058 |  | Расширенное отображение таблиц: кодирование двоичных строк в base64 |
X059 |  | Расширенное отображение таблиц: кодирование двоичных строк в шестнадцатеричном виде |
X060 |  | XMLParse: ввод символьных строк и параметр CONTENT |
X061 |  | XMLParse: ввод символьных строк и параметр DOCUMENT |
X070 |  | XMLSerialize: сериализация символьных строк и параметр CONTENT |
X071 |  | XMLSerialize: сериализация символьных строк и параметр DOCUMENT |
X072 |  | XMLSerialize: сериализация символьных строк |
X090 |  | Предикат XML-документа |
X120 |  | Параметры XML в подпрограммах SQL |
X121 |  | Параметры XML во внешних подпрограммах |
X221 |  | Механизм передачи XML BY VALUE |
X301 |  | XMLTable: выбор списка производных столбцов |
X302 |  | XMLTable: выбор столбца нумерации |
X303 |  | XMLTable: выбор столбца по умолчанию |
X304 |  | XMLTable: передача элемента контекста | требуется XML DOCUMENT
X400 |  | Сопоставление имен и идентификаторов |
X410 |  | Изменение типа данных столбца: поддержка типа XML |

Неподдерживаемая функциональность

Следующие возможности, определенные в SQL:2016, не реализованы в этой версии QHB. В некоторых случаях доступна равнозначная им функциональность.

Идентификатор | Основа? | Описание | Комментарий
B011 |  | Встроенный язык Ada |
B013 |  | Встроенный язык COBOL |
B014 |  | Встроенный язык Fortran |
B015 |  | Встроенный язык MUMPS |
B016 |  | Встроенный язык Pascal |
B017 |  | Встроенный язык PL/I |
B031 |  | Базовый динамический SQL |
B032 |  | Расширенный динамический SQL |
B032-01 |  | <Оператор describe input> |
B033 |  | Нетипизированные аргументы функции, вызываемой из SQL |
B034 |  | Динамическое указание атрибутов курсора |
B035 |  | Нерасширенные имена дескрипторов |
B041 |  | Расширения встроенных объявлений исключений SQL |
B051 |  | Расширенные права для выполнения |
B111 |  | Язык модулей — Ada |
B112 |  | Язык модулей — C |
B113 |  | Язык модулей — COBOL |
B114 |  | Язык модулей — Fortran |
B115 |  | Язык модулей — MUMPS |
B116 |  | Язык модулей — Pascal |
B117 |  | Язык модулей — PL/I |
B121 |  | Язык подпрограмм — Ada |
B122 |  | Язык подпрограмм — C |
B123 |  | Язык подпрограмм — COBOL |
B124 |  | Язык подпрограмм — Fortran |
B125 |  | Язык подпрограмм — MUMPS |
B126 |  | Язык подпрограмм — Pascal |
B127 |  | Язык подпрограмм — PL/I |
B128 |  | Язык подпрограмм — SQL |
B200 |  | Полиморфные табличные функции (PTF) |
B201 |  | Более одного обобщенного табличного параметра PTF |
B202 |  | Соразделение PTF |
B203 |  | Более одного указания соразделения |
B204 |  | PRUNE WHEN EMPTY |
B205 |  | Сквозное использование столбцов |
B206 |  | Параметры дескриптора PTF |
B207 |  | Векторное произведение партиционирований |
B208 |  | Интерфейс компонентных процедур PTF |
B209 |  | Расширенные имена PTF |
B211 |  | Язык модулей — Ada: поддержка VARCHAR и NUMERIC |
B221 |  | Язык подпрограмм — Ada: поддержка VARCHAR и NUMERIC |
F054 |  | TIMESTAMP в списке приоритетов типа DATE |
F121 |  | Базовое управление диагностикой |
F121-01 |  | Оператор GET DIAGNOSTICS |
F121-02 |  | Оператор SET TRANSACTION: предложение DIAGNOSTICS SIZE |
F122 |  | Расширенное управление диагностикой |
F123 |  | Вся диагностика |
F263 |  | Разделенные запятыми предикаты в простом выражении CASE |
F291 |  | Предикат UNIQUE |
F301 |  | CORRESPONDING в выражениях запросов |
F312 |  | Оператор MERGE | возможная альтернатива — INSERT ... ON CONFLICT DO UPDATE
F313 |  | Расширенный оператор MERGE |
F314 |  | Оператор MERGE с ветвью DELETE |
F403 |  | Партиционированные соединенные таблицы |
F451 |  | Определение набора символов |
F461 |  | Именованные наборы символов |
F492 |  | Необязательное указание соблюдения ограничений таблицы |
F521 |  | Утверждения |
F671 |  | Подзапросы в CHECK | намеренно опущен
F673 |  | Вызовы подпрограммы, читающей SQL-данные, в условиях CHECK |
F693 |  | Порядок сопоставления символов для SQL-сеансов и клиентских модулей |
F695 |  | Поддержка перекодировки |
F696 |  | Дополнительная документация по перекодировке |
F721 |  | Откладываемые ограничения | только сторонние и уникальные ключи
F741 |  | Типы ссылочных совпадений MATCH | пока без частичного совпадения
F812 | Основа | Базовое флагирование |
F813 |  | Расширенное флагирование |
F821 |  | Ссылки на локальные таблицы |
F831 |  | Полное изменение курсора |
F831-01 |  | Изменяемые прокручиваемые курсоры |
F831-02 |  | Обновляемые упорядоченные курсоры |
F841 |  | Предикат LIKE_REGEX |
F842 |  | Функция OCCURRENCES_REGEX |
F843 |  | Функция POSITION_REGEX |
F844 |  | Функция SUBSTRING_REGEX |
F845 |  | Функция TRANSLATE_REGEX |
F846 |  | Поддержка октетов в операторах регулярных выражений |
F847 |  | Неконстантные регулярные выражения |
F866 |  | Предложение FETCH FIRST: параметр PERCENT |
R010 |  | Распознавание шаблона строк: предложение FROM |
R020 |  | Распознавание шаблона строк: предложение WINDOW |
R030 |  | Распознавание шаблона строк: полная поддержка агрегатов |
S011 | Основа | Различные типы данных |
S011-01 | Основа | Представление USER_DEFINED_TYPES |
S023 |  | Базовые структурированные типы |
S024 |  | Расширенные структурированные типы |
S025 |  | Окончательные структурированные типы |
S026 |  | Самоссылающиеся структурированные типы |
S027 |  | Создание метода по заданному имени метода |
S028 |  | Допускающий перестановку список параметров UDT |
S041 |  | Базовые ссылочные типы |
S043 |  | Расширенные ссылочные типы |
S051 |  | Создание таблицы из типа | частично поддерживается
S081 |  | Подтаблицы |
S091 |  | Базовая поддержка массивов | частично поддерживается
S091-01 |  | Массивы встроенных типов данных |
S091-02 |  | Массивы отдельных типов |
S091-03 |  | Выражения с массивами |
S094 |  | Массивы ссылочных типов |
S097 |  | Присвоение значения элементу массива |
S151 |  | Предикат типа |
S161 |  | Обработка подтипов |
S162 |  | Обработка подтипов для ссылочных типов |
S202 |  | Вызываемые из SQL подпрограммы для мультимножеств |
S231 |  | Указатели на структурные типы |
S232 |  | Указатели на массивы |
S233 |  | Указатели на мультимножества |
S241 |  | Функции преобразований |
S242 |  | Оператор изменения преобразования |
S251 |  | Упорядочивания, определяемые пользователем |
S261 |  | Метод SPECIFICTYPE |
S271 |  | Базовая поддержка мультимножеств |
S272 |  | Мультимножества пользовательских типов |
S274 |  | Мультимножества ссылочных типов |
S275 |  | Расширенная поддержка мультимножеств |
S281 |  | Типы вложенных коллекций |
S291 |  | Ограничение уникальности для всей строки |
S401 |  | Различные типы на основе типов массивов |
S402 |  | Различные типы на основе отдельных типов |
S403 |  | ARRAY_MAX_CARDINALITY |
T011 |  | Метка времени в информационной схеме |
T021 |  | Типы данных BINARY и VARBINARY |
T022 |  | Расширенная поддержка типов данных BINARY и VARBINARY |
T023 |  | Составные двоичные литералы |
T024 |  | Пробелы в двоичных литералах |
T041 |  | Базовая поддержка типов данных LOB |
T041-01 |  | Тип данных BLOB |
T041-02 |  | Тип данных CLOB |
T041-03 |  | Функции POSITION, LENGTH, LOWER, TRIM, UPPER и SUBSTRING для типов данных LOB |
T041-04 |  | Конкатенация типов данных LOB |
T041-05 |  | Указатель на LOB: неудерживаемый |
T042 |  | Расширенная поддержка типов данных LOB |
T043 |  | Множитель T |
T044 |  | Множитель P |
T051 |  | Типы строк |
T053 |  | Явные псевдонимы для ссылки на все поля |
T061 |  | Поддержка UCS |
T076 |  | Тип данных DECFLOAT |
T101 |  | Расширенное определение допустимости значений NULL |
T111 |  | Изменяемые соединения, объединения и столбцы |
T175 |  | Генерируемые столбцы | в основном поддерживается
T176 |  | Поддержка генератора последовательностей | поддерживается за исключением NEXT VALUE FOR
T180 |  | Системно версионируемые таблицы |
T181 |  | Таблицы с периодом времени применения |
T211 |  | Базовые возможности триггеров |
T211-06 |  | Поддержка правил времени выполнения для взаимодействия триггеров и ограничений |
T211-08 |  | Несколько триггеров для одного события выполняются в том порядке, в каком они были созданы в каталоге | намеренно опущено
T231 |  | Чувствительные курсоры |
T251 |  | Оператор SET TRANSACTION: параметр LOCAL |
T272 |  | Расширенное управление точками сохранения |
T301 |  | Функциональные зависимости | частично поддерживается
T321 | Основа | Базовые подпрограммы, вызываемые из SQL |
T321-05 | Основа | Оператор RETURN |
T322 | PSM | Объявляемые атрибуты типа данных |
T324 |  | Явная настройка безопасности подпрограмм SQL |
T326 |  | Табличные функции |
T471 |  | Наборы результатов в качестве возвращаемого значения |
T472 |  | DESCRIBE CURSOR |
T495 |  | Совмещенное изменение и извлечение данных | другой синтаксис
T502 |  | Предикаты периодов |
T511 |  | Счетчики транзакций |
T522 |  | Значения по умолчанию для параметров IN процедур, вызываемых из SQL | поддерживаются, за исключением ключевого слова DEFAULT в вызове
T561 |  | Удерживаемые указатели |
T571 |  | Возвращающие массивы внешние функции, вызываемые из SQL |
T572 |  | Возвращающие мультимножества внешние функции, вызываемые из SQL |
T601 |  | Ссылки на локальные курсоры |
T616 |  | Варианты обработки NULL для функций LEAD и LAG |
T618 |  | Функция NTH_VALUE | функция существует, но некоторые параметры отсутствуют
T619 |  | Вложенные оконные функции |
T625 |  | LISTAGG |
T641 |  | Присвоение значения нескольким столбцам | поддерживаются только некоторые варианты синтаксиса
T652 |  | Операторы динамического SQL в подпрограммах SQL |
T653 |  | Операторы модификации схемы SQL во внешних подпрограммах |
T654 |  | Операторы динамического SQL во внешних подпрограммах |
T811 |  | Базовые функции-конструкторы SQL/JSON |
T812 |  | SQL/JSON: JSON_OBJECTAGG |
T813 |  | SQL/JSON: JSON_ARRAYAGG с ORDER BY |
T814 |  | Двоеточие в JSON_OBJECT или JSON_OBJECTAGG |
T821 |  | Базовые операторы запросов SQL/JSON |
T822 |  | SQL/JSON: предикат IS JSON WITH UNIQUE KEYS |
T823 |  | SQL/JSON: предложение PASSING |
T824 |  | JSON_TABLE: специфичное предложение PLAN |
T825 |  | SQL/JSON: предложения ON EMPTY и ON ERROR |
T826 |  | Выражение общего значения в предложениях ON ERROR или ON EMPTY |
T827 |  | JSON_TABLE: одноуровневые предложения NESTED COLUMNS |
T828 |  | JSON_QUERY |
T829 |  | JSON_QUERY: варианты обертки массива |
T830 |  | Требование уникальных ключей в функциях-конструкторах SQL/JSON |
T838 |  | JSON_TABLE: предложение PLAN DEFAULT |
T839 |  | Форматируемое приведение даты/времени из символьных строк и обратно |
M001 |  | Передача данных |
M002 |  | Передача данных через SQL/CLI |
M003 |  | Передача данных через встроенный SQL |
M004 |  | Поддержка сторонних данных | частично поддерживается
M005 |  | Поддержка сторонних схем |
M006 |  | Подпрограмма GetSQLString |
M007 |  | TransmitRequest |
M009 |  | Подпрограммы GetOpts и GetStatistics |
M010 |  | Поддержка обертки сторонних данных | другой API
M011 |  | Передача данных через Ada |
M012 |  | Передача данных через C |
M013 |  | Передача данных через COBOL |
M014 |  | Передача данных через Fortran |
M015 |  | Передача данных через MUMPS |
M016 |  | Передача данных через Pascal |
M017 |  | Передача данных через PL/I |
M018 |  | Подпрограммы интерфейса обертки сторонних данных на языке Ada |
M019 |  | Подпрограммы интерфейса обертки сторонних данных на языке C | другой API
M020 |  | Подпрограммы интерфейса обертки сторонних данных на языке COBOL |
M021 |  | Подпрограммы интерфейса обертки сторонних данных на языке Fortran |
M022 |  | Подпрограммы интерфейса обертки сторонних данных на языке MUMPS |
M023 |  | Подпрограммы интерфейса обертки сторонних данных на языке Pascal |
M024 |  | Подпрограммы интерфейса обертки сторонних данных на языке PL/I |
M030 |  | Поддержка сторонних данных SQL-сервера |
M031 |  | Общие подпрограммы обертки сторонних данных |
X012 |  | Мультимножества типа XML |
X013 |  | Различные типы на основе типа XML |
X015 |  | Поля типа XML |
X025 |  | XMLCAST |
X030 |  | XMLDocument |
X038 |  | XMLText |
X065 |  | XMLParse: ввод BLOB и вариант CONTENT |
X066 |  | XMLParse: ввод BLOB и вариант DOCUMENT |
X068 |  | XMLSerialize: BOM |
X069 |  | XMLSerialize: INDENT |
X073 |  | XMLSerialize: сериализация BLOB и вариант CONTENT |
X074 |  | XMLSerialize: сериализация BLOB и вариант DOCUMENT |
X075 |  | XMLSerialize: сериализация BLOB |
X076 |  | XMLSerialize: VERSION |
X077 |  | XMLSerialize: явное указание ENCODING |
X078 |  | XMLSerialize: явное объявление XML |
X080 |  | Пространства имен при публикации XML |
X081 |  | Объявления пространств имен XML на уровне запроса |
X082 |  | Объявления пространств имен XML в DML |
X083 |  | Объявления пространств имен XML в DDL |
X084 |  | Объявления пространств имен XML в составных операторах |
X085 |  | Предопределенные префиксы пространств имен |
X086 |  | Объявления пространств имен XML в XMLTable |
X091 |  | Предикат содержимого XML |
X096 |  | XMLExists | Только XPath 1.0
X100 |  | Поддержка базового языка для XML: вариант CONTENT |
X101 |  | Поддержка базового языка для XML: вариант DOCUMENT |
X110 |  | Поддержка базового языка для XML: сопоставление VARCHAR |
X111 |  | Поддержка базового языка для XML: сопоставление CLOB |
X112 |  | Поддержка базового языка для XML: сопоставление BLOB |
X113 |  | Поддержка базового языка для XML: параметр STRIP WHITESPACE |
X114 |  | Поддержка базового языка для XML: параметр PRESERVE WHITESPACE |
X131 |  | Предложение XMLBINARY на уровне запроса |
X132 |  | Предложение XMLBINARY в DML |
X133 |  | Предложение XMLBINARY в DDL |
X134 |  | Предложение XMLBINARY в составных операторах |
X135 |  | Предложение XMLBINARY в подзапросах |
X141 |  | Предикат IS VALID: управляемый данными |
X142 |  | Предикат IS VALID: предложение ACCORDING TO |
X143 |  | Предикат IS VALID: предложение ELEMENT |
X144 |  | Предикат IS VALID: расположение схемы |
X145 |  | Предикат IS VALID вне проверочных ограничений |
X151 |  | Предикат IS VALID с вариантом DOCUMENT |
X152 |  | Предикат IS VALID с вариантом CONTENT |
X153 |  | Предикат IS VALID с вариантом SEQUENCE |
X155 |  | Предикат IS VALID: NAMESPACE без предложения ELEMENT |
X157 |  | Предикат IS VALID: NO NAMESPACE с предложением ELEMENT |
X160 |  | Базовая информационная схема для зарегистрированных XML-схем |
X161 |  | Расширенная информационная схема для зарегистрированных XML-схем |
X170 |  | Варианты обработки NULL с XML |
X171 |  | Вариант NIL ON NO CONTENT |
X181 |  | Тип XML(DOCUMENT (UNTYPED)) |
X182 |  | Тип XML(DOCUMENT(ANY)) |
X190 |  | Тип XML(SEQUENCE) |
X191 |  | Тип XML(DOCUMENT(XMLSCHEMA)) |
X192 |  | Тип XML(CONTENT(XMLSCHEMA)) |
X200 |  | XMLQuery |
X201 |  | XMLQuery: RETURNING CONTENT |
X202 |  | XMLQuery: RETURNING SEQUENCE |
X203 |  | XMLQuery: передача элемента контекста |
X204 |  | XMLQuery: инициализация переменной XQuery |
X205 |  | XMLQuery: указание EMPTY ON EMPTY |
X206 |  | XMLQuery: указание NULL ON EMPTY |
X211 |  | Поддержка XML 1.1 |
X222 |  | Механизм передачи XML BY REF | анализатор принимает BY REF, но игнорирует его; передача всегда идет по BY VALUE
X231 |  | Тип XML(CONTENT (UNTYPED)) |
X232 |  | Тип XML(CONTENT (ANY)) |
X241 |  | RETURNING CONTENT при публикации XML |
X242 |  | RETURNING SEQUENCE при публикации XML |
X251 |  | Хранимые значения XML типа XML(DOCUMENT (UNTYPED)) |
X252 |  | Хранимые значения XML типа XML(DOCUMENT (ANY)) |
X253 |  | Хранимые значения XML типа XML(CONTENT (UNTYPED)) |
X254 |  | Хранимые значения XML типа XML(CONTENT (ANY)) |
X255 |  | Хранимые значения XML типа XML(SEQUENCE) |
X256 |  | Хранимые значения XML типа XML(DOCUMENT (XMLSCHEMA)) |
X257 |  | Хранимые значения XML типа XML(CONTENT (XMLSCHEMA)) |
X260 |  | Тип XML: предложение ELEMENT |
X261 |  | Тип XML: NAMESPACE без предложения ELEMENT |
X263 |  | Тип XML: NO NAMESPACE с предложением ELEMENT |
X264 |  | Тип XML: расположение схемы |
X271 |  | XMLValidate: управляемый данными |
X272 |  | XMLValidate: предложение ACCORDING TO |
X273 |  | XMLValidate: предложение ELEMENT |
X274 |  | XMLValidate: расположение схемы |
X281 |  | XMLValidate с вариантом DOCUMENT |
X282 |  | XMLValidate с вариантом CONTENT |
X283 |  | XMLValidate с вариантом SEQUENCE |
X284 |  | XMLValidate: NAMESPACE без предложения ELEMENT |
X286 |  | XMLValidate: NO NAMESPACE с предложением ELEMENT |
X300 |  | XMLTable | Только XPath 1.0
X305 |  | XMLTable: инициализация переменной XQuery |

Ограничения XML и соответствие SQL/XML

В SQL:2006 в спецификации ISO/IEC 9075-14 (SQL/XML), посвященные XML, были внесены существенные изменения. Реализация типа данных XML и связанных с ним функций в QHB во большей степени соответствует более ранней редакции от 2003 года, с некоторыми заимствованиями из последующих редакций. В частности:

Тогда как в текущем стандарте предусмотрено семейство типов данных XML, содержащих «документы» или «содержимое» в нетипизированных вариантах или вариантах с типами XML Schema, а также тип XML(SEQUENCE), содержащий произвольные фрагменты содержимого XML, в QHB есть только один тип xml, который может содержать «документ» или «содержимое». Эквивалента определенному в стандарте типу «последовательность» в QHB не существует.

QHB предоставляет две функции, появившиеся в SQL:2006, но вместо языка XML Query, указанного для них в стандарте, в этих вариантах используется язык XPath 1.0.

В этом разделе описаны некоторые проистекающие из этого различия, с которыми вы можете столкнуться.

Запросы ограничены XPath 1.0

Специфичные для QHB функции xpath() и xpath_exists() выполняют запросы к документам XML на языке XPath. QHB также предоставляет стандартные функции XMLEXISTS и XMLTABLE, поддерживающие только XPath, хотя официально они должны поддерживать язык XQuery. Для всех этих функций QHB использует библиотеку libxml2, которая поддерживает только XPath 1.0.

Существует тесная связь между языком XQuery и XPath версии 2.0 и новее: любое выражение, которое синтаксически допустимо и успешно выполняется, выдает в обоих языках одинаковый результат (с небольшим исключением для выражений, содержащих ссылки на числовые символы или предварительно определенные сущности, которые XQuery заменяет соответствующими символами, а XPath оставляет в исходном виде). Но между этими языками и XPath 1.0 такой связи нет; он появился раньше и во многих отношениях отличается от них.

Следует иметь в виду две категории ограничений: ограничение языка XQuery до XPath для функций, указанных в стандарте SQL, и ограничение XPath до версии 1.0 как для стандартных, так и для специфичных функций QHB.

Ограничение языка XQuery относительно XPath

В число возможностей XQuery, отсутствующих у XPath, входят:

Выражения XQuery могут не только возвращать все возможные значения XPath, но и конструировать новые узлы XML. XPath может создавать и возвращать значения атомарных типов (числа, строки и т. д.), но возвращать способен только те узлы XML, которые уже присутствовали в документах, поступивших на вход выражения.

В XQuery есть управляющие конструкции для итерации, сортировки и группировки.

XQuery позволяет объявлять и использовать локальные функции.

В последних версиях XPath начинает появляться функциональность, пересекающаяся с функциональностью XQuery (например функциональные конструкции for-each и sort, анонимные функции и функция parse-xml, создающая узел из строки), но до XPath 3.0 они были недоступны.

Ограничение XPath до версии 1.0

Разработчикам, знакомым с XQuery и XPath 2.0 или новее, приходится сталкиваться с рядом отличий XPath 1.0:

Фундаментальный тип выражения XQuery/XPath, тип sequence, который может содержать узлы XML, атомарные значения или и то, и другое, в XPath 1.0 отсутствует. В 1.0 выражение может создавать только множество узлов (содержащих ноль или более узлов XML) или одно атомарное значение.

В отличие от последовательности XQuery/XPath, которая может содержать любые требуемые элементы в любом желаемом порядке, во множестве узлов XPath 1.0 нет гарантированного порядка и оно, как и любое множество, не допускает несколько появлений одного и того же элемента.

Примечание
По-видимому, библиотека libxml2 всегда возвращает в QHB множества узлов с внутренними членами в том же относительном порядке, в каком они идут во входном документе. В ее документации это поведение не указано, а выражение XPath 1.0 не может им управлять.

Тогда как XQuery/XPath предоставляет все типы, определенные в стандарте XML Schema, а также множество операторов и функций для этих типов, XPath 1.0 поддерживает только множества узлов и три атомарных типа: boolean, double и string.

В XPath 1.0 отсутствует условный оператор. Выражение XQuery/XPath вида if ( hat ) then hat/@size else "no hat" не имеет эквивалента в XPath 1.0.

В XPath 1.0 отсутствует оператор сравнения строк с упорядочиванием. Условия "cat" < "dog" и "cat" > "dog" оба являются ложными, поскольку представляют собой числовые сравнения двух значений NaN. Условия же = и !=, напротив, сравнивают строки в виде строк.

XPath 1.0 размывает разницу между сравнениями значений и общими сравнениями, как они определены в XQuery/XPath. Сравнения sale/@hatsize = 7 и sale/@customer = "alice" по сути оба являются количественными сравнениями, и их результатом будет истина, если существует элемент sale с заданным значением атрибута, но sale/@taxable = false() — это сравнение значений с фактическим логическим значением всего множества узлов. Его результат будет истиной, только если ни у одного элемента sale вообще не будет атрибута taxable.

В модели данных XQuery/XPath узел документа может иметь либо форму документа (т. е. содержать ровно один элемент верхнего уровня, снаружи которого допускаются только комментарии и инструкции по обработке), либо форму содержимого (с ослабленными ограничениями). В XPath 1.0 ему соответствует корневой узел, который может иметь только форму документа. Это одна из причин, по которой значение типа xml, передаваемое в качестве элемента контекста любой функции QHB на базе XPath, должно быть в форме документа.

Кроме различий, отмеченных здесь, есть и другие. В языках XQuery и XPath 2.0 и новее существует режим совместимости с XPath 1.0, а имеющийся в документации W3C список изменений библиотеки функций и языковых изменений, применяемых в этом режиме, предоставляет более полное (но все же не исчерпывающее) описание различий. Режим совместимости не обеспечивает полную идентичность XPath 1.0 и более поздних языков.

Сопоставления типов данных и значений между SQL и XML

В SQL:2006 и более поздних версиях точно определены преобразования между стандартными типами данных SQL и типами стандарта XML Schema в обе стороны. Однако эти правила выражаются в типах и семантике XQuery/XPath и не могут быть напрямую применены к другой модели данных, характерной для XPath 1.0.

Когда QHB сопоставляет значения данных SQL с XML (как в функции xmlelement) или XML с SQL (как в выходных столбцах xmltable), за исключением нескольких специально обрабатываемых случаев, QHB просто предполагает, что строковая форма XPath 1.0, содержащая данные типа XML, будет допустима в качестве формы ввода в виде текста в типе данных SQL, и наоборот. Это правило подкупает своей простотой, и при этом результаты для многих типов данных именно такие, какими, согласно стандарту, должны быть у преобразований.

В тех случаях, когда требуется взаимодействие с другими системами, для некоторых типов данных может возникнуть необходимость в явном использовании функций форматирования типов данных (например, описанных в разделе Функции для форматирования типов данных) для получения сопоставлений, в точности соответствующих стандарту.

Непреднамеренные ограничения реализации

В этом разделе рассматриваются ограничения, которые присущи не самой библиотеке libxml2, а текущей реализации в QHB.

Поддерживается только механизм передачи параметров по значению (BY VALUE)

В стандарте SQL определены два механизма передачи параметров, которые применяются при передаче аргумента XML из SQL в функцию XML или получении результата: BY REF, в котором конкретное значение XML сохраняет идентификатор узла, и BY VALUE, в котором передается содержимое XML, но идентификатор узла теряется. Задать механизм можно перед списком параметров, в качестве механизма по умолчанию для всех параметров, или после каждого параметра, тем самым переопределив выбор по умолчанию.

Чтобы проиллюстрировать разницу, рассмотрим следующие два запроса. Если x является значением XML, в среде SQL:2006 они выдадут значения true и false соответственно:

SELECT XMLQUERY(’$a is $b’ PASSING BY REF x AS a, x AS b NULL ON EMPTY);

SELECT XMLQUERY(’$a is $b’ PASSING BY VALUE x AS a, x AS b NULL ON EMPTY);

QHB примет указания BY VALUE и BY REF в конструкции XMLEXISTS или XMLTABLE, но проигнорирует их. Тип данных xml содержит сериализованное представление символьной строки, поэтому идентификатор узла, который нужно сохранять, отсутствует, и фактически передача всегда производится по значению (BY VALUE).

Невозможно передать в запросы именованные параметры

Функции на основе XPath могут принимать один параметр, служащий элементом контекста для выражения XPath, но не поддерживают передачу дополнительных значений, которые могли бы использоваться в выражении как именованные параметры.

Отсутствие типа XML(SEQUENCE)

Тип данных xml в QHB может содержать значение только в форме документа (DOCUMENT) или содержимого (CONTENT). Элемент контекста выражения XQuery/XPath должен быть одиночным узлом XML или атомарным значением, но XPath 1.0 дополнительно ограничивает его только узлом XML и не имеет типа узла, содержащего CONTENT. В результате единственной формой значения XML, которую в качестве элемента контекста XPath может предоставить QHB, является корректно сформированный документ (DOCUMENT).

Приложения

Расширения

Расширения QHB

Специализированные и экспериментальные модули PostgreSQL

Клиентские приложения

Сторонние расширения

Поддерживаемые платформы

Поддержка решений 1С

Особенности использования QHB

Отличия QHB от PostgreSQL

Ограничения QHB

Страница загрузки

Краткие инструкции

Пошаговая инструкция по начальной загрузке, установке и запуску

Краткая инструкция по обновлению

Краткая инструкция по миграции кластера баз данных в QHB

Запуск и остановка сервера с помощью сервиса systemd

Пример настройки синхронной реплики

Пример настройки протоколирования

Установка защищенной базы данных QHB

Работа с модулями

Инструкция по установке и использованию QCP

Работа сервера метрик в режиме разделенного коллектора и агрегатора

Работа с 1С

Инструкция по установке QHB под 1С

Конфигурирование QHB под 1С

Резервное копирование QHB под 1С

Сокращения

Глоссарий

Поддержка цветового оформления

Лицензирование

Контроль целостности

Профили безопасности

Замечания к релизу

Расширения

Содержание

Расширения QHB

Rbytea

Qbim

Uref

Qbayes

Part_Import

qhb_audit

Специализированные и экспериментальные модули PostgreSQL

adminpack

amcheck

auth_delay

auto_explain

bloom

btree_gin

btree_gist

citext

cube

dblink

dict_int

dict_xsyn

earthdistance

file_fdw

fuzzystrmatch

hstore

intagg

intarray

isn

lo

ltree

old_snapshot

pageinspect

passwordcheck

pg_buffercache

pgcrypto

pg_freespacemap

pg_prewarm

pgrowlocks

pg_stat_statements

pgstattuple

pg_surgery

pg_trgm

pg_visibility

postgres_fdw

seg

sepgsql

spi

sslinfo

tablefunc

tcn

test_decoding

tsm_system_rows

tsm_system_time

unaccent

uuid-ossp

xml2

Клиентские приложения

oid2name

vacuumlo

Сторонние расширения

PostGIS

Patroni

mysql_fdw

Orafce

pg_hint_plan

pg_repack

pg_store_plans

pgtt

plpgsql_check

sr_plan

В данном разделе содержится информация о модулях, которые можно найти в каталоге share/extension дистрибутива QHB. В их число входят средства портирования, утилиты анализа и подключаемые функции, не включенные в состав основной системы QHB, в основном потому что они предназначены для ограниченной аудитории или чересчур экспериментальны, чтобы быть частью основного дерева кода. Тем не менее это не отменяет их полезность.

В этом приложении рассматриваются расширения и другие подключаемые серверные модули, находящиеся в пакете qhb-contrib, который необходимо установить в соответствии с описанием по установке.

Многие модули предоставляют новые пользовательские функции, операторы и типы. Чтобы использовать один из этих модулей после установки его кода, вы должны зарегистрировать новые объекты SQL в СУБД. Для этого нужно выполнить команду CREATE EXTENSION. В чистой базе данных можно просто выполнить

CREATE EXTENSION имя_модуля;

Эта команда регистрирует новые объекты SQL только в текущей базе данных, поэтому ее необходимо выполнять в каждой базе данных, в которой вам требуется функциональность этого модуля. Как вариант, эту команду можно запустить в базе-шаблоне template1, чтобы нужное расширение копировалось во все создаваемые впоследствии базы данных по умолчанию.

Для всех этих модулей CREATE EXTENSION должен выполнять суперпользователь, если только модуль не рассматривается как «доверенный» — тогда его может устанавливать любой пользователь, имеющий право CREATE в текущей базе данных. В разделах, где описываются модули, отмечено, какие из них являются доверенными. В целом, доверенными модулями считаются те, которые не предоставляют доступ к функциональности за рамками базы данных.

Многие модули позволяют устанавливать свои объекты в схему по выбору. Для этого нужно добавить SCHEMA имя_схемы в команду CREATE EXTENSION. По умолчанию объекты устанавливаются в текущую схему для создаваемых объектов, которой, в свою очередь, по умолчанию становится public.

Однако обратите внимание, что некоторые из этих модулей не являются «расширениями» в этом смысле, а загружаются на сервер по-другому, например, через параметр shared_preload_libraries. Более подробную информацию можно найти в документации каждого модуля.

Расширения QHB

В данном разделе представлены расширения, специально разработанные для QHB.

Rbytea — внешнее хранение двоичных данных

Тип данных rbytea

Установка расширения

Функции для работы с типом rbytea

Фоновый процесс очистки устаревших копий

Особенности функционирования расширения rbytea

Rbytea в кластере и при использовании шифрования

Обновление расширения с предыдущих версий

Параметры конфигурации расширения

Qbim — реализует битемпоральную модель данных

Предпосылки

Модель данных

Реализация модели

Пример использования Qbim для тестового объекта

Пример использования Qbim со связанными объектами

Uref — обобщенный ссылочный тип

Структура типа uref

Методы для работы с типом uref

Индексирование полей типа uref

Qbayes — методы статистического анализа на основе условных вероятностей Байеса

Назначение расширения

Алгоритмы Байесовского предсказания

Методы расширения

Ограничения и параметры

Пример использования

Замечания, примеры и варианты работы с расширением Qbayes

Part_Import — частичный импорт файлов данных из «внешней» базы данных

Описание

partial_import

table_script

Тестовые сценарии

qhb_audit — аудит действий в СУБД

Настройка

Параметры

Формат CSV файла

Формат сообщений через REST API

Отслеживаемые события

Rbytea

Rbytea — внешнее хранение двоичных данных.

Тип данных rbytea

Тип данных rbytea предназначен для хранения двоичных данных. Он аналогичен типу bytea, с той лишь разницей, что сами данные хранятся не в табличном пространстве базы, а во внешнем хранилище. В качестве внешнего хранилища в текущей версии QHB выступает файловая система. Это может быть смонтированный в определенную точку файловой системы сервера внешний том или символическая ссылка.

Основной целью расширения является вынос двоичных данных из таблиц базы данных в нетранзакционное хранилище, тем самым разгрузив базу данных (зачастую двоичные данные имеют большой объем, который занимает значительный процент от общего размера базы, усложняя администрирование и обслуживание).

Тип данных rbytea в записи базы данных оставляет только небольшой заголовок, в котором содержатся служебные поля и ссылка на файл во внешнем хранилище. В качестве ссылки используется тип данных uuid, а генерация случайных идентификаторов использует возможности модуля pgcrypto.

Данные во внешнем хранилище могут быть зашифрованы алгоритмом Кузнечик.

Установка расширения

Установка расширения производится командой CREATE EXTENSION:

CREATE EXTENSION rbytea CASCADE;

Установку должен запускать суперпользователь баз данных. Эту команду следует запускать в той базе данных, в которой предполагается использовать модуль. Для работы фонового процесса необходимо обеспечить предварительную загрузку разделяемой библиотеки расширения, указав к конфигурации параметр:

shared_preload_libraries = 'librbytea'

Описание параметров расширения приведены ниже в параграфе Параметры конфигурации расширения.

Функции для работы с типом rbytea

Имя
Тип результата
Описание

uuid(rbytea)
uuid
Возвращает идентификатор данных

len(rbytea)
bigint
Возвращает длину данных в байтах

qss_mode(rbytea)
bigint
Возвращает признак зашифрования данных или его отсутствия

md5(rbytea)
text
Возвращает md5 сумму данных

sha256(rbytea)
text
Возвращает sha256 сумму данных

ext_file_dir(rbytea)
text
Возвращает каталог хранения данных
(абсолютный или от $PGDATA)

ext_file_path(rbytea)
text
Возвращает полное имя файла хранения данных
(путь абсолютный или от $PGDATA)

trash_file_dir(rbytea)
text
Возвращает каталог хранения удалённых данных
(абсолютный или от $PGDATA)

trash_file_path(rbytea)
text
Возвращает полное имя файла хранения удалённых данных
(путь абсолютный или от $PGDATA)

txid_created(rbytea)
bigint
Возвращает идентификатор транзакции, во время которой были созданы данные

rvacuum()
bigint
Выполняет очистку устаревших данных в хранилище

Фоновый процесс очистки устаревших копий

Поскольку на файловую систему не распространяется транзакционность базы данных, во внешнем хранилище могут оставаться данные полей таблиц типа rbytea, которые были удалены или сохранены в незаконченных, отмененных транзакциях. Поэтому периодически запускается фоновый процесс очистки, который проходит по некоторому диапазону транзакций, очищая данные. При этом файлы перемещаются в каталог TRASH, который создается для каждой базы данных, указанной в параметре rbytea.databases_for_vacuuming.

После каждого запуска максимальный номер транзакции запоминается и при следующем запуске используется как нижняя граница диапазона сканирования. В качестве верхней границы диапазона сканирования используется последняя завершенная транзакция.

Параметры запуска указаны в параграфе ниже.

Особенности функционирования расширения rbytea

Rbytea в кластере и при использовании шифрования

При работе расширения в кластере существуют некоторые ограничения.

Каталог (точки монтирования файловой системы/тома) для сохранения образов данных, задаваемый в параметре rbytea.filesystem_storage_path, см. Параметры конфигурации расширения Rbytea, может:

указывать на каталог в рамках одного локального хоста и использоваться исключительно этим одним хостом кластера,

или на некоторый каталог сетевого файлового хранилища, используемого сразу всеми хостами кластера.

В первом случае, образы данных будут доступны только на данном конкретном хосте, где они и были созданы, и обращение к данным на других хостах кластера будет приводить к ошибке. Но, в данном случае, возможно использование шифрование данных с использованием QSS.

Во втором случае, образы данных будут доступны на всех хостах кластера, но использование шифрования в текущей версии QHB и расширения rbytea запрещено, поскольку каждый хост кластера обязан использовать свои рабочие ключи QSS, и зашифрованные одним ключом данные будут недоступны на других хостах (попытка чтения будет приводить к ошибке).

Обновление расширения с предыдущих версий

Версия расширения rbytea версии 1.2 существенно отличается от предыдущих версий расширения. Из-за этого нет возможности провести обновление расширения с ранних версий на текущую с использованием команды

ALTER EXTENSION rbytea UPDATE TO '1.2';

Возможно только удаление расширения предыдущей версии и создание уже с текущей версией командой

CREATE EXTENSION rbytea CASCADE;

При этом необходимо позаботиться о сохранении образов данных, если нет цели их полного пересоздания с потерей.

Это можно сделать миграцией

через тип данных bytea,

через сохранение образов на диске, где они размещались до миграции (если они не были зашифрованы),

или через дамп, если образы данных были зашифрованы.

Подробные шаги такой миграции остаются на усмотрение администратора БД.

Разработчики QHB планируют разработку инструментария поддержки таких миграций в одном их следующих релизов.

Параметры конфигурации расширения

Для работы расширения нужно установить несколько параметров в конфигурационном файле (подробную информацию см. в подразделе Параметры конфигурации расширения Rbytea).

Расширение Qbim, пример реализации битемпоральной модели данных

Примеры:

Элементарный пример для Qbim

Пример использования Qbim со связанными объектами

Предпосылки

Модель представления данных близка к шестой нормальной форме, но без избыточной нормализации.

Шестая нормальная форма введена Кристофером Дейтом как обобщение пятой нормальной формы для хронологической базы данных в своей книге Введение в системы баз данных.

Расширение Qbim представляет собой конструктор для разработки ERM на темпоральной основе, то есть, моделируемые объекты представляются временными срезами, последовательностями своих состояний во времени. Основное внимание и акцент в данном расширении сделан не на "декомпозиции до конца", а на поддержании темпоральности данных. Расширение реализует краткий набор логики хранения (создания, изменения) объектов в реляционной СУБД (на примере qhb), реализованный в слое хранимых процедур.

Модель данных

В рамках расширения Qbim рассматривается пример реализации модели хранения данных с указанием интервалов времени их действия. В отличие от нехронологических баз данных, хранящих только текущие данные, такая модель предполагает хранение данных, относящихся к прошлым, а также будущим периодам времени.

В предлагаемой реализации наряду с идентификатором кортежа данных указывается еще и интервал времени, в течение которого эти данные считаются актуальными. Эти интервалы могут относиться или пересекаться с настоящим временем, находиться в прошлом или описывать будущие значения. Интервалы хранятся в двух специально выделенных для этого полях типа timestamp, образующих полузакрытый интервал диапазонного типа tsrange.

Серия данных с одним идентификатором (далее объект), может образовывать непересекающиеся отрезки, или «версии», на временной оси «действия» этих данных (business time).

Дополнительная временная ось (system time) фиксирует, когда эти версии были созданы или изменены.

Реализация модели

Терминология

Термин
Определение

Объект
Некоторая оцифрованная, смоделированная из реального мира сущность. Обладает свойствами (атрибутами) и операциями над ними (методами). В определённые временные интервалы имеет фиксированный набор атрибутов. Но во времени, от интервала к интервалу, может менять набор атрибутов. В базе данных набор Объектов представляется сущностью/таблицей, его атрибуты представлены полями таблицы, методы реализованы хранимыми процедурами и функциями.

Реализация объекта
Один из представителей объекта, выделяемый среди других с помощью идентификатора. В базе данных представляет собой серию записей одной таблицы/сущности.

Версия реализации объекта
Один из наборов атрибутов объекта, постоянный в течении определенного временного интервала. В базе данных - одна из записей таблицы/сущности, принадлежащая объекту (по идентификатору), с определенным интервалом действия.

Идентификатор реализации объекта
Идентификатор (id - обычно целое число, но может быть UUID-ом), однозначно определяющий реализацию объекта среди других объектов сущности. Все версии одного и того же объекта имеют один и тот же идентификатор.

Идентификатор версии реализации объекта
Кортеж из идентификатора и интервала действия версии ( <id[, sd, ed]> ), однозначно определяющий объект и версию.

Ограничения и умолчания

Зарезервированные поля

Для идентификатора и границ интервала действия в версионированных таблицах резервируются три поля:

Резервируемое поле | Тип | Назначение
id | bigint или uuid | идентификатор
sd | timestamp | start date, начало периода действия
ed | timestamp | end date, окончание периода действия

Далее для bigint может использоваться термин «локальный идентификатор», а для uuid — «глобальный идентификатор».

Любой версионированный объект представляется набором своих версий реализации, связанных уникальным идентификатором реализации (id) и набором атрибутов / свойств. Каждая его версия - кортежем id и периодом действия этой версии (период представляется интервалом [sd, ed) - включается левая граница и не включается правая, типы границ - timestamp).

Первичный ключ версии объекта - составной: <id,[sd,ed)>.

Версии не пересекаются и не содержат разрывов

Интервалы действия версий образуются как tsrange(sd,ed,'[)') — полуоткрытый интервал. Отсутствие пересечений интервалов реализуется ограничением вида:

constraint ... exclude using gist (id with =, tsrange(sd, ed) with &&) where ((sd <> ed))

Следует заметить, что при таких условиях допустимыми являются версии, в которых sd == ed.

Это допущение сделано намеренно, поскольку:

такие версии имеют нулевую длину, не пересекаются с другими версиями, не влияют на многие сценарии выбора версий и серий версий (непустых);

при наличии фиксации времени изменения (system time) не пропадает и не требует изменения привязка к версии события (поле action.link_dt), поскольку привязка осуществляется именно к значению поля sd версионированной таблицы.

Описание версионируемых таблиц

Все версионируемые таблицы и их поля описываются в специально выделяемых таблицах obj и obj_item.

Таблица obj:

Поле | Тип | Назначение
id | bigserial | Идентификатор
tab | text | Название таблицы, где хранится объект. Если требуется, вместе с именем схемы
dsc | text | Описание
parent | bigint | Ссылка на ID верхнего в иерархии объекта
is_temporal | bool | Признак темпоральности объекта
is_logging | bool | Признак логирования действий пользователя для объекта
is_global | bool | Признак глобального идентификатора у объекта. Для глобальных идентификаторов используется тип UUID, для локальных — bigint

Таблица obj_item:

Поле | Тип | Назначение
id | bigserial | Идентификатор
up | bigint | Ссылка на объект (obj.id)
ord | bigint | Порядковый номер атрибута
name | text | Имя
r_cls | bigint | Если имеется словарное поле, номер словаря (@CLS.ID)
r_obj | bigint | Если имеется ссылка на объект, номер объекта
option | jsonb | Дополнительные параметры. Например, указание о замене типа на массив для методов
dsc | text | Описание атрибута

Таблица action:

Поле | Тип | Назначение
id | bigserial | Идентификатор
r_object | uref | Ссылка на изменяемый объект и его идентификатор
lnk_dt | timestamp | Дата привязки к версии реализации события (SD)
fct_dt | timestamp | Реальная дата версии
action | bigint | Произведенное действие. Словарь
database | text | База данных, где расположен измененный объект
schema | text | Схема, где расположен измененный объект
dbuser | text | Пользователь базы данных, совершивший действие
pid | text | Идентификатор процесса (Process ID), в рамках работы которого произведено изменение
userid | uuid | UUID пользователя, вошедшего в систему и совершившего действие

В таблицу action, если указано в obj.is_logging, записываются основания действий с объектами.

«Действие» (поле action) имеет следующую кодировку:

Код | Действие
1 | Создание
2 | Изменение (при создании новой версии)
3 | Изменение (при сокращении периода действия)
4 | Закрытие версии

ВАЖНО!
Для функционирования приложения достаточно важно, чтобы записи в эту таблицу записывались максимально быстро. Для этого есть возможность создавать таблицу как unlogged, а при включенном TARQ — также ... using append_only.

Методы описания объектов

Для описания объектов в таблицах obj и obj_item предоставляется функция qbim_describe_object(...).

Параметры функции:

Параметр | Тип | Значение по умолчанию | Назначение
i_table_name | text |  | Имя объекта (таблицы), в комбинации с именем схемы или без него. При отсутствии явного указания на имя схемы таблица ищется в схемах, указанных в параметре search_path.
i_obj_id | bigint |  | Номер объекта. Если указать null, номер будет присвоен автоматически из последовательности для obj.id
i_parent_obj_id | bigint | null | Номер родительского объекта, если создаётся иерархия объектов.
i_temporal | bool | true | Признак версионированного объекта (темпоральности). В противном случае - неверсионированный, такие тоже можно описывать в таблицах объектов.
i_logging | bool | true | Признак логирования изменений в таблице регистрации действий action
i_global | bool | false | Признак "глобальности" идентификатора экземпляра объекта (тип uuid), в противном случае идентификатор "локальный" (тип bigint/int8).

При описании используются комментарии таблицы и её полей.

При заполнении описания полей объекта в комментарии могут применяться шаблоны ссылок на словари и связанные объекты (поля r_cls и r_obj в таблице obj_item). Шаблоны соответственно - CLS#[[:digit:]] и OBJ#[[:digit:]].

Например, следующий комментарий:

COMMENT ON COLUMN object_tbl.r_domain IS 'Ссылка на домен. OBJ#22';

при описании объекта заполнит в obj_item для данного поля указание, что он является ссылкой на другой объект:

id | up | ord | name | r_cls | r_obj | option | dsc
... | ... | ... | r_domain | ... | 22 | ... | Ссылка на домен. OBJ#22

а комментарий:

COMMENT ON COLUMN object_tbl.model_status IS 'Состояние когнитивной модели. CLS#18';

при описании объекта заполнит в obj_item для данного поля указание, что поле словарное:

id | up | ord | name | r_cls | r_obj | option | dsc
... | ... | ... | model_status | 18 | ... | ... | Состояние когнитивной модели. CLS#18

Методы работы с объектами

Для создания, изменения и закрытия объектов предлагаются методы, которые корректно отслеживают ограничения модели.

Методы выборки оставляются на выбор пользователя или приложения. Возможно выбрать конкретную версию, действующую в указанный момент времени, или серии версий за указанный период вместе со схлопнувшимися версиями (промежуточные версии с нулевой длительностью, sd == ed) или без них. Также возможны различные выборки совместно со связанными объектами.

Методы создания объекта

Для создания объекта (начальной версии объекта) предоставляются методы qbim_create():

CREATE FUNCTION qbim_create(
  obj_id int8, tab text,
  id int8,
  sd timestamp, ed timestamp,
  userid uuid,
  variadic others_args "any")
returns int8 as 'MODULE_PATHNAME','create_local_reg' language c volatile security definer;

CREATE FUNCTION qbim_create(
  obj_id int8, tab text,
  id uuid,
  sd timestamp, ed timestamp,
  userid uuid,
  variadic others_args "any")
returns uuid as 'MODULE_PATHNAME','create_global_reg' language c volatile security definer;

В одном из первых двух параметрах метода указывается идентификатор создаваемого объекта: по идентификатору в таблице obj описателя или по имени объекта (таблицы); возможно указание и идентификатора, и имени, но они должны соответствовать описателю.

В зависимости от типа идентификатора, используемого в объекте, третий параметр может иметь тип bigint или uuid; его пропуск (указание null::bigint или null::uuid) требует автоматической генерации идентификатора. В случае bigint значение может выбираться из соответствующей последовательности, а в случае uuid — генерируется случайное значение uuid.

Следующая пара параметров задает интервал действия версии, при пропуске (указании null:timestamp) используются значения по умолчанию, указанные при создании таблицы. Как правило, для sd это текущее время (CURRENT_TIMESTAMP), а для ed — «бесконечность» (infintity::timestamp).

В параметре userid можно указать идентификатор конечного пользователя, производящего операцию создания объекта, если таковой используется в приложении. Если не используется, можно пропустить (указать null::uuid).

Далее в переменном списке параметров (variadic "any") следует передавать параметры парами — имя и значение, или не передавать вовсе. В любом случае количество переменных должно быть четным. Атрибуты объекта, пропущенные в этом списке, получат значения NULL или будут установлены в соответствии с умолчаниями, заданными при создании таблицы.

При создании объекта, если это указано в obj, будет зафиксировано действие в таблице action.

Методы изменения объекта (создания версии)

Для изменения объекта предоставляются методы qbim_change(). Создание версии возможно в любой период, пересекающий периоды имеющихся у объекта версий (не должно получаться разрывов).

Параметры аналогичны методам qbim_create(). Умолчания для sd и ed — текущее время и «бесконечность» соответственно.

Новая версия может дополнять (с любой стороны) имеющиеся, пересекать любое количество имеющихся версий, покрывать полностью или частично уже имеющиеся версии.

В случае, если в переменном списке параметров опущено какое-то из полей, оно будет подставлено из соответствующей более ранней версии объекта, подходящей по времени.

Если в результате изменения какие-то из имеющихся версий будут полностью перекрыты новой версией, они будут считаться схлопнувшимися (sd == ed).

Методы qbim_change():

CREATE FUNCTION qbim_change(
  obj_id int8, tab text,
  id int8,
  sd timestamp, ed timestamp,
  userid uuid,
  variadic others_args "any")
returns int8 as 'MODULE_PATHNAME','change_local_reg' language c volatile security definer;

CREATE FUNCTION qbim_change(
  obj_id int8, tab text,
  id uuid,
  sd timestamp, ed timestamp,
  userid uuid,
  variadic others_args "any")
returns int8 as 'MODULE_PATHNAME','change_global_reg' language c volatile security definer;

При изменении объекта, если это указано в obj, будет зафиксировано действие в таблице action.

Методы закрытия объекта (версии)

Пара методов, закрывающих версии объекта текущей или указанной датой:

CREATE FUNCTION qbim_close(
  obj_id int8, tab text,
  id int8,
  ed timestamp,
  userid uuid)
returns int8 as 'MODULE_PATHNAME','close_local' language c volatile security definer;

CREATE FUNCTION qbim_close(
  obj_id int8, tab text,
  id uuid,
  ed timestamp,
  userid uuid)
returns int8 as 'MODULE_PATHNAME','close_global' language c volatile security definer;

В параметре ed указывается необходимое время для закрытия версий. Если пропущено (указано null::timestamp), то устанавливается текущее время. Остальные параметры аналогичны параметрам в методах qbim_create и qbim_change.

При закрытии объекта, если это указано в obj, будет зафиксировано действие в таблице action.

Примечание
Для неверсионированных объектов точно также доступно описание в таблицах obj и obj_item, а также с ними работают методы создания, изменения и закрытия объектов. Для неверсионированных объектов системным является только один атрибут - идентификатор (id). В соответствующих методах параметры sd и ed не имеют смысла и игнорируются. Метод изменения подразумевает простой update экземпляра объекта. Метод закрытия подразумевает удаление экземпляра объекта. Регистрация действий (в таблице action) также производится, но поля lnk_dt и fct_dt заполняются одним и тем же значением - временем регистрации действия.

Установка расширения

Установка расширения производится командой CREATE EXTENSION. Поскольку есть расширения, от которых зависит расширение Qbim, рекомендуется использовать установку с предложением CASCADE. Расширение Qbim переносимое, поэтому его можно установить в различные схемы базы данных, используя SCHEMA имя_схемы.

Начиная с версии расширения 1.2 есть возможность использовать различные опции таблицы action. Для управления созданием этой таблицы используется группа локальных параметров (см. Таблицу ниже).

При обновлении расширения до версии 1.2 с предыдущих версий используйте предложение UPDATE команды ALTER EXTENSION.

Таблица Локальных параметров для управления созданием таблицы action

Параметр | Назначение | Значение по-умолчанию
qbim.action.use_partitions | Создавать таблицу партиционированную по полю fct_dt. | 'true'
qbim.action.partition_period | - при создании партиционированной таблицы - размер секции, которые будут созданы при создании. Возможны варианты: 'day', 'week', 'month', 'year'. | 'month'
qbim.action.partition_count | - при создании партиционированной таблицы - количество создаваемых секций, не считая двух секций для "самых старых" и "самых новых" значений. | 5
qbim.action.use_unlogged | Создавать таблицу с параметром unlogged. Внимательно отнеситесь к этому параметры, его лучше не использовать при работе в кластере. | 'false'
qbim.action.use_appendonly | Создавать таблицу как неизменяемую (append_only). Возможно только при включённом TARQ. | 'true'
qbim.action.upgrade_exists | Мигрировать данные из уже существующей таблицы action, при обновлении с ранних версии расширения Qbim, до версии 1.2. Если расширение устанавливается впервые, параметр не имеет смысла. | 'true'

Параметры нужно установить перед вызовом команд CREATE EXTENSION или ALTER EXTENSION.

Примеры установки:

create extension qbim cascade;

set qbim.action.use_partitions='true';
set qbim.action.partition_period='year';
set qbim.action.partition_count=2;

create extension qbim schema my_schema cascade;

Пример использования Qbim для тестового объекта

Установка расширения

create extension qbim cascade;

Создание тестового объекта

create table test (
	id bigserial not null,
	sd timestamp not null default current_timestamp,
	ed timestamp not null default 'infinity'::timestamp,
	name text not null,
	state bigint not null,
	val bigint not null,
	dsc text not null,
	constraint cls_id_sd_ed_excl exclude using gist (id with =, tsrange(sd, ed) with &&) where ((sd <> ed))
);
create index test_id_sd_ed_excl on test using gist (id, tsrange(sd, ed)) where (sd <> ed);

comment on table test is 'Тестовый объект';
comment on column test.id is 'Идентификатор';
comment on column test.sd is 'Время начала действия версии';
comment on column test.ed is 'Время окончания действия версии';
comment on column test.name is 'Имя тестового экземпляра объекта';
comment on column test.state is 'Состояние экземпляра (CLS#3)';
comment on column test.val is 'Некоторое значение';
comment on column test.dsc is 'Описание';

Описание в таблицах описания объектов

select qbim_describe_object(i_table_name => 'test'::text, i_obj_id => null::bigint);

Примечание
Если в качестве параметра i_obj_id передаётся конкретное значение, нужно обновить значение последовательности obj_id_seq для предотвращения конфликтов на уникальность идентификатора.

Например,

select qbim_describe_object(i_table_name => 'test'::text, i_obj_id => 73::bigint);

select setval('obj_id_seq',73);

где 73 в данном примере - максимальный id таблице obj.

Создание реализации объекта

Примечание
Здесь и далее при вызове интерфейсных методов указывается случайный идентификатор пользователя '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid, который используется при регистрации действия.

select qbim_create(1::bigint,null::text,null::bigint,
  '-infinity'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'name','Test record'::text,
  'state',1::bigint,
  'val',5::bigint
);

Изменение объекта — создание версий

select qbim_change(1::bigint,null::text,1::bigint,
  '2021-03-30 13:35:30.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'state',2::bigint
);

select qbim_change(1::bigint,null::text,1::bigint,
  '2021-03-30 13:36:30.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'state',3::bigint
);

select qbim_change(1::bigint,null::text,1::bigint,
  '2021-03-30 13:35:30.0'::timestamp,'2021-03-30 13:36:00.0'::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'val',6::bigint
);

select qbim_change(1::bigint,null::text,1::bigint,
  '2021-03-30 13:35:20.0'::timestamp,'2021-03-30 13:36:15.0'::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'val',7::bigint
);

select qbim_change(1::bigint,null::text,1::bigint,
  '2021-03-30 13:36:00.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'val',9::bigint
);

select qbim_change(1::bigint,null::text,1::bigint,
  '2021-03-30 13:35:20.0'::timestamp,'2021-03-30 13:36:15.0'::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'val',10::bigint,
  'state', 6::bigint
);

select qbim_change(1::bigint,null::text,1::bigint,
  '2021-03-30 13:35:20.0'::timestamp,'2021-03-30 13:36:30.0'::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'val',11::bigint,
  'state', 7::bigint
);

Выборка, просмотр версий

Можно запускать на разных этапах изменения объекта.

Запрос версии «на момент времени»

select * from test
where '2021-03-30 13:35:45.0'::timestamp <@ tsrange(sd,ed)
order by id, sd, ed;

Запрос всех версий, кроме «схлопнувшихся»

select * from test where sd<>ed
order by id, sd, ed;

Запрос всех версий

select * from test
order by id, sd, ed;

Закрытие версии

select qbim_close(1::bigint,null::text,1::bigint,
  '2021-03-30 13:35:15.0'::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid
);

Регистрация действий

select
    r_object, lnk_dt, fct_dt, action, pid, userid
from action;

r_object | lnk_dt | fct_dt | action | pid | userid
1:1 | -infinity | 2021-05-05 11:47:43.957595 | 1 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | -infinity | 2021-05-05 11:48:08.117459 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:30 | 2021-05-05 11:48:08.117649 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:30 | 2021-05-05 11:48:09.595363 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:36:30 | 2021-05-05 11:48:09.596457 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | -infinity | 2021-05-05 11:48:10.988799 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:30 | 2021-05-05 11:48:10.989053 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:30 | 2021-05-05 11:48:10.989264 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | -infinity | 2021-05-05 11:48:12.515611 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:30 | 2021-05-05 11:48:12.516679 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:36:00 | 2021-05-05 11:48:12.517692 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:20 | 2021-05-05 11:48:12.518829 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:30 | 2021-05-05 11:48:12.519755 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:30 | 2021-05-05 11:48:15.094103 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:36:15 | 2021-05-05 11:48:15.094302 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:36:30 | 2021-05-05 11:48:15.094489 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:36:00 | 2021-05-05 11:48:15.094663 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:36:30 | 2021-05-05 11:48:15.094829 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | -infinity | 2021-05-05 11:48:17.110891 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:20 | 2021-05-05 11:48:17.111094 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:30 | 2021-05-05 11:48:17.111286 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:36:00 | 2021-05-05 11:48:17.111476 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:20 | 2021-05-05 11:48:17.111692 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | -infinity | 2021-05-05 11:48:19.36584 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:20 | 2021-05-05 11:48:19.366077 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:36:15 | 2021-05-05 11:48:19.366265 | 3 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:20 | 2021-05-05 11:48:19.366443 | 2 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-03-30 13:35:15 | 2021-05-05 11:51:57.079878 | 4 | 2522 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a

Значения полей описаны в разделе Описание версионируемых таблиц.

Примечание
Поля fct_dt и pid могут отличаться; в данной таблице они приведены как пример.

Пример использования Qbim со связанными объектами

Установка расширения

create extension qbim cascade;

Создание тестовых объектов

В данном примере рассматривается реализация с двумя объектами, связанными между собой.

Для описания связи будет использоваться составной первичный ключ <id, sd, ed> в одной таблице, и «тройка» <r_position, sd, ed> во второй. Записи (версии) будут считаться связанными, если первое поле в указанных тройках совпадает, а интервалы, создаваемые оставшимися двумя значениями, пересекаются.

Механизмы описания связей (foreing key) в темпоральной парадигме пока не позволяют сделать это на уровне задания ограничений таблиц.

Примечание
Но такое задание связи, вероятно, будет реализовано в следующих версиях QHB.

Моделируется предметная область — кадровый учет. Создаются два объекта: «персона» и «должность». Для демонстрации возможностей Qbim атрибутивный состав объектов сокращен.

create table person (
	id bigserial not null,
	sd timestamp not null default current_timestamp,
	ed timestamp not null default 'infinity'::timestamp,
	last_name varchar(50) not null,
	first_name varchar(50) not null,
	patronym varchar(50),
	r_position bigint not null,
	constraint person_id_sd_ed_excl exclude using gist (id with =, tsrange(sd, ed) with &&) where ((sd <> ed))
);
comment on table person is 'Персона';
comment on column person.id is 'Идентификатор';
comment on column person.sd is 'Время начала действия версии';
comment on column person.ed is 'Время окончания действия версии';
comment on column person.last_name is 'Фамилия';
comment on column person.first_name is 'Имя';
comment on column person.patronym is 'Отчество';
comment on column person.r_position is 'Ссылка на должность (OBJ#2)';

create table position (
	id bigserial not null,
	sd timestamp not null default current_timestamp,
	ed timestamp not null default 'infinity'::timestamp,
	position_name varchar(100) not null,
	constraint position_id_sd_ed_excl exclude using gist (id with =, tsrange(sd, ed) with &&) where ((sd <> ed))
);
comment on table position is 'Должность';
comment on column position.id is 'Идентификатор';
comment on column position.sd is 'Время начала действия версии';
comment on column position.ed is 'Время окончания действия версии';
comment on column position.position_name is 'Название доллжности';

Описание в таблицах описания объектов

select qbim_describe_object(i_table_name => 'person'::text, i_obj_id => 1::bigint);
select qbim_describe_object(i_table_name => 'position'::text, i_obj_id => 2::bigint);

select setval('obj_id_seq',2);

Создание реализаций объектов

Примечание
Здесь и далее при вызове интерфейсных методов указывается случайный идентификатор пользователя '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid, который используется при регистрации действия.

Должности. Создаем три должности с 1 января 2021 года:

select qbim_create(2::bigint, null::text, null::bigint,
  '2021-01-01 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'position_name','Инженер');

select qbim_create(2::bigint, null::text, null::bigint,
  '2021-01-01 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'position_name','Старший инженер');

select qbim_create(2::bigint, null::text, null::bigint,
  '2021-01-01 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'position_name','Менеджер');

Сотрудники. Создаем сотрудников с даты их фактического приема в штат, назначения на конкретные должности:

select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-11 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Начальников',
  'first_name','Игорь',
  'patronym','Петрович',
  'r_position',3);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-12 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Половинкин',
  'first_name','Семен',
  'patronym','Андреевич',
  'r_position',2);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-15 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Иванов',
  'first_name','Петр',
  'patronym','Васильевич',
  'r_position',1);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-16 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Малкин',
  'first_name','Иван',
  'patronym','Олегович',
  'r_position',2);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-17 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Селиванов',
  'first_name','Илья',
  'patronym','Максимович',
  'r_position',1);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-18 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Васин',
  'first_name','Сергей',
  'patronym','Петрович',
  'r_position',1);

Выведем обобщенную информацию по подразделению. В качестве интервалов выводим минимальные/максимальные границы интервалов. Условие tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed) означает пересечение интервалов [p.sd,p.ed) и [d.sd,d.ed) соответственно.

Запрос обобщенной информации

select
	p.id,
	greatest(p.sd,d.sd) as sd,
	least(p.ed,d.ed) as ed,
	p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
	d.position_name as "Должность"
from person p join position d
	on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
order by 1,2;

Результат:

id | sd | ed | ФИО | Должность
1 | 2021-01-11 00:00:00 | infinity | Начальников Игорь Петрович | Менеджер
2 | 2021-01-12 00:00:00 | infinity | Половинкин Семен Андреевич | Старший инженер
3 | 2021-01-15 00:00:00 | infinity | Иванов Петр Васильевич | Инженер
4 | 2021-01-16 00:00:00 | infinity | Малкин Иван Олегович | Старший инженер
5 | 2021-01-17 00:00:00 | infinity | Селиванов Илья Максимович | Инженер
6 | 2021-01-18 00:00:00 | infinity | Васин Сергей Петрович | Инженер

Запрос информации «на дату»

Условие '2021-02-15 00:00:00.0'::timestamp <@ tsrange(t.sd,t.ed) означает, что указанный момент времени должен содержаться внутри интервала.

select * from (
	select
		p.id,
		greatest(p.sd,d.sd) as sd,
		least(p.ed,d.ed) as ed,
		p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
		d.position_name as "Должность"
	from person p join position d
		on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
) t
where '2021-02-15 00:00:00.0'::timestamp <@ tsrange(t.sd,t.ed)
order by 1,2;

Результат выглядит одинаково:

id | sd | ed | ФИО | Должность
1 | 2021-01-11 00:00:00 | infinity | Начальников Игорь Петрович | Менеджер
2 | 2021-01-12 00:00:00 | infinity | Половинкин Семен Андреевич | Старший инженер
3 | 2021-01-15 00:00:00 | infinity | Иванов Петр Васильевич | Инженер
4 | 2021-01-16 00:00:00 | infinity | Малкин Иван Олегович | Старший инженер
5 | 2021-01-17 00:00:00 | infinity | Селиванов Илья Максимович | Инженер
6 | 2021-01-18 00:00:00 | infinity | Васин Сергей Петрович | Инженер

Изменения

С 1 марта сотрудник с id=5 был переведен на другую должность:

select qbim_change(1::bigint,null::text,5::bigint,
  '2021-03-01 00:00:00.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'r_position',2::bigint
);

Выведем моментальные снимки на даты 28 февраля и 1 марта:

select * from (
	select
		p.id,
		greatest(p.sd,d.sd) as sd,
		least(p.ed,d.ed) as ed,
		p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
		d.position_name as "Должность"
	from person p join position d
		on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
) t
where '2021-02-28 00:00:00.0'::timestamp <@ tsrange(t.sd,t.ed)
order by 1,2;

select * from (
	select
		p.id,
		greatest(p.sd,d.sd) as sd,
		least(p.ed,d.ed) as ed,
		p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
		d.position_name as "Должность"
	from person p join position d
		on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
) t
where '2021-03-01 00:00:00.0'::timestamp <@ tsrange(t.sd,t.ed)
order by 1,2;

Результаты различаются. Сотрудник в первом случае имеет одну должность, а во втором — другую.

Результат на 28 февраля:

id | sd | ed | ФИО | Должность
1 | 2021-01-11 00:00:00 | infinity | Начальников Игорь Петрович | Менеджер
2 | 2021-01-12 00:00:00 | infinity | Половинкин Семен Андреевич | Старший инженер
3 | 2021-01-15 00:00:00 | infinity | Иванов Петр Васильевич | Инженер
4 | 2021-01-16 00:00:00 | infinity | Малкин Иван Олегович | Старший инженер
5 | 2021-01-17 00:00:00 | 2021-03-01 00:00:00 | Селиванов Илья Максимович | Инженер
6 | 2021-01-18 00:00:00 | infinity | Васин Сергей Петрович | Инженер

Результат на 1 марта:

id | sd | ed | ФИО | Должность
1 | 2021-01-11 00:00:00 | infinity | Начальников Игорь Петрович | Менеджер
2 | 2021-01-12 00:00:00 | infinity | Половинкин Семен Андреевич | Старший инженер
3 | 2021-01-15 00:00:00 | infinity | Иванов Петр Васильевич | Инженер
4 | 2021-01-16 00:00:00 | infinity | Малкин Иван Олегович | Старший инженер
5 | 2021-03-01 00:00:00 | infinity | Селиванов Илья Максимович | Старший инженер
6 | 2021-01-18 00:00:00 | infinity | Васин Сергей Петрович | Инженер

Выведем обобщенную информацию по подразделению (см. раздел Запрос обобщенной информации). Видно, что у записи, соответствующей переведенному сотруднику, выводится две версии, соответствующие его двум различным должностям:

id | sd | ed | ФИО | Должность
1 | 2021-01-11 00:00:00 | infinity | Начальников Игорь Петрович | Менеджер
2 | 2021-01-12 00:00:00 | infinity | Половинкин Семен Андреевич | Старший инженер
3 | 2021-01-15 00:00:00 | infinity | Иванов Петр Васильевич | Инженер
4 | 2021-01-16 00:00:00 | infinity | Малкин Иван Олегович | Старший инженер
5 | 2021-01-17 00:00:00 | 2021-03-01 00:00:00 | Селиванов Илья Максимович | Инженер
5 | 2021-03-01 00:00:00 | infinity | Селиванов Илья Максимович | Старший инженер
6 | 2021-01-18 00:00:00 | infinity | Васин Сергей Петрович | Инженер

С 15 марта произошло изменение — название должности «Инженер» изменили на «Младший инженер»:

select qbim_change(2::bigint,null::text,1::bigint,
  '2021-03-15 00:00:00.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'position_name','Младший инженер');

Посмотрим, как изменился вывод обобщенной информации:

id | sd | ed | ФИО | Должность
1 | 2021-01-11 00:00:00 | infinity | Начальников Игорь Петрович | Менеджер
2 | 2021-01-12 00:00:00 | infinity | Половинкин Семен Андреевич | Старший инженер
3 | 2021-01-15 00:00:00 | 2021-03-15 00:00:00 | Иванов Петр Васильевич | Инженер
3 | 2021-03-15 00:00:00 | infinity | Иванов Петр Васильевич | Младший инженер
4 | 2021-01-16 00:00:00 | infinity | Малкин Иван Олегович | Старший инженер
5 | 2021-01-17 00:00:00 | 2021-03-01 00:00:00 | Селиванов Илья Максимович | Инженер
5 | 2021-03-01 00:00:00 | infinity | Селиванов Илья Максимович | Старший инженер
6 | 2021-01-18 00:00:00 | 2021-03-15 00:00:00 | Васин Сергей Петрович | Инженер
6 | 2021-03-15 00:00:00 | infinity | Васин Сергей Петрович | Младший инженер

Видно, что у всех сотрудников, имеющих должность «Инженер», появились новые версии с новым названием должности.

Вариант запроса информации «на дату» для конкретного сотрудника

select
	p.id,
	greatest(p.sd,d.sd) as sd,
	least(p.ed,d.ed) as ed,
	p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
	d.position_name as "Должность"
from person p join position d
	on p.r_position = d.id
where p.id=5 and '2021-02-28 00:00:00.0'::timestamp <@ tsrange(p.sd,p.ed)
    and '2021-02-28 00:00:00.0'::timestamp <@ tsrange(d.sd,d.ed)
order by 1,2;

id | sd | ed | ФИО | Должность
5 | 2021-01-17 00:00:00 | 2021-03-01 00:00:00 | Селиванов Илья Максимович | Инженер

Вариант запроса информации для конкретного сотрудника за весь период

select
	p.id,
	greatest(p.sd,d.sd) as sd,
	least(p.ed,d.ed) as ed,
	p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
	d.position_name as "Должность"
from person p join position d
	on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
where p.id=5
order by 1,2;

id | sd | ed | ФИО | Должность
5 | 2021-01-17 00:00:00 | 2021-03-01 00:00:00 | Селиванов Илья Максимович | Инженер
5 | 2021-03-01 00:00:00 | infinity | Селиванов Илья Максимович | Старший инженер

Отражаются версии «до» и «после» перевода на другую должность.

Еще один вариант запроса информации для конкретного сотрудника за весь период

Если уже после смены названия должности перевести сотрудника с id=6 на другую должность:

select qbim_change(1::bigint,null::text,6::bigint,
  '2021-03-20 00:00:00.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'r_position',2::bigint);

и запросить все версии по этому сотруднику с помощью запроса Вариант запроса информации для конкретного сотрудника за весь период, но с id=6, то в результате отобразятся версии, соответствующие периодам «до» и «после» смены названия должности, а также периоду «после» перевода на другую должность:

id | sd | ed | ФИО | Должность
6 | 2021-01-18 00:00:00 | 2021-03-15 00:00:00 | Васин Сергей Петрович | Инженер
6 | 2021-03-15 00:00:00 | 2021-03-20 00:00:00 | Васин Сергей Петрович | Младший инженер
6 | 2021-03-20 00:00:00 | infinity | Васин Сергей Петрович | Старший инженер

Регистрация действий

Поскольку в примере при описании объектов указывалось, что необходимо протоколировать информацию по действиям с объектами (поле obj.is_logging), все указанные в примере создания и изменения объектов отражаются в служебной таблице action.

select
    r_object, lnk_dt, fct_dt, action, pid, userid
from action;

r_object | lnk_dt | fct_dt | action | pid | userid
2:1 | 2021-01-01 00:00:00 | 2021-04-30 19:43:24.586786 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
2:2 | 2021-01-01 00:00:00 | 2021-04-30 19:43:27.352193 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
2:3 | 2021-01-01 00:00:00 | 2021-04-30 19:44:10.525477 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:1 | 2021-01-11 00:00:00 | 2021-04-30 19:55:28.109557 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:2 | 2021-01-12 00:00:00 | 2021-04-30 19:55:30.363525 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:3 | 2021-01-15 00:00:00 | 2021-04-30 19:55:32.670876 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:4 | 2021-01-16 00:00:00 | 2021-04-30 19:55:34.283938 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:5 | 2021-01-17 00:00:00 | 2021-04-30 19:55:39.770958 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:6 | 2021-01-18 00:00:00 | 2021-04-30 19:55:42.7058 | 1 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:5 | 2021-01-17 00:00:00 | 2021-04-30 20:43:03.597479 | 3 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:5 | 2021-03-01 00:00:00 | 2021-04-30 20:43:03.597673 | 2 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
2:1 | 2021-01-01 00:00:00 | 2021-04-30 20:56:12.488844 | 3 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
2:1 | 2021-03-15 00:00:00 | 2021-04-30 20:56:12.489592 | 2 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:6 | 2021-01-18 00:00:00 | 2021-04-30 21:38:37.966772 | 3 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:6 | 2021-03-20 00:00:00 | 2021-04-30 21:38:37.966962 | 2 | 2521 | 463404e8-ee5d-4597-b9a9-e3b69e29d18a

Значения полей описаны в разделе Описание версионируемых таблиц.

Примечание
Поля fct_dt и pid могут отличаться; в данной таблице они приведены как пример.

Uref

Тип данных uref используется в расширении Qbim как ссылочный тип на различные экземпляры объектов.

В настоящей реализации тип uref используется в поле r_object таблицы action, но также может использоваться в ссылочных полях объектов.

Структура типа uref

Тип данных uref состоит из двух частей:

идентификатора объекта, представленного типом bigint (obj.id),

идентификатора экземпляра объекта, представленного типом bigint или uuid («локальным» или «глобальным» соответственно).

Методы для работы с типом uref

Расширение Uref предоставляет семейство функций для работы с типом uref.

Функция

Описание
Пример(ы)

uref_local ( bigint, bigint ) → uref
uref ( bigint, bigint ) → uref

Конструктор с локальным идентификатором экземпляра.
uref_local(1,34) → 1:34
uref(1,34) → 1:34

uref_global ( bigint, uuid ) → uref
uref ( bigint, uuid ) → uref

Конструктор с глобальным идентификатором экземпляра.
uref_global(1,'463404eb-ee5d-4597-b9a9-e3b69e29d18a'::uuid) → 1:463404eb-ee5d-4597-b9a9-e3b69e29d18a
uref(1,'463404eb-ee5d-4597-b9a9-e3b69e29d18a'::uuid) → 1:463404eb-ee5d-4597-b9a9-e3b69e29d18a

uref_is_id_local ( uref ) → boolean

Проверка, что идентификатор локальный.
uref_is_id_local('1:34'::uref) → true
uref_is_id_local('1:463404eb-ee5d-4597-b9a9-e3b69e29d18a'::uref) → false

uref_is_id_global ( uref ) → boolean

Проверка, что идентификатор глобальный.
uref_is_id_global('1:34'::uref) → false
uref_is_id_global('1:463404eb-ee5d-4597-b9a9-e3b69e29d18a'::uref) → true

uref_obj ( uref ) → bigint

Получение идентификатора объекта.
uref_obj('1:34'::uref) → 1
uref_obj('1:463404eb-ee5d-4597-b9a9-e3b69e29d18a'::uref) → 1

uref_id_local ( uref ) → bigint
uref_id_global ( uref ) → uuid

Получение идентификатора экземпляра объекта.
uref_id_local('1:34'::uref) → 34
uref_id_global('1:463404eb-ee5d-4597-b9a9-e3b69e29d18a'::uref) → 463404eb-ee5d-4597-b9a9-e3b69e29d18a

Индексирование полей типа uref

Поля типа uref могут использоваться в индексах btree. Для этого в расширении Uref созданы необходимые операторы и классы операторов.

Qbayes

Qbayes — методы статистического анализа на основе условных вероятностей Байеса.

Назначение расширения

Расширение QHB Qbayes — это созданный командой разработчиков QHB инструментарий для задач расчета вероятностных моделей с применением сетей Байеса.

Исходные данные для различных вычислений могут храниться в таблицах и представлениях внутри базы данных QHB. К таким данным относятся исторические факты (срабатывание или несрабатывание гипотез) и соответствующие им причины (параметры). В качестве параметров рассматриваются логические (бинарные) значения или скалярные величины, которые методами расширения приводятся к дискретным значениям и разбиваются на диапазоны, определяемые пользователем. Значения гипотез всегда предполагаются логическими (бинарными).

После предварительной статистической обработки исторических фактов (обучения), модель позволяет рассчитывать условные вероятности тех же самых гипотез при новых условиях (новых значениях параметров).

Алгоритмы Байесовского предсказания

В настоящее время в расширении используются следующие алгоритмы.

Наивный байесовский классификатор

Простой классификатор, основанный на предположении о независимости параметров.

Этот тип модели используется при обучении с параметром model_type = 'naive' (Naive Bayesian classificator).

Классификатор, основанный на функции взаимной информации

Взаимная информация — это статистическая функция двух случайных величин, описывающая количество информации, содержащееся в одной случайной величине относительно другой.

Метод позволяет определить для гипотез зависимые параметры и в дальнейшем использовать эти зависимости для предсказания гипотез.

Этот тип модели используется при обучении с параметром model_type = 'mi' (Mutual Information).

Методы Монте-Карло с цепями Маркова

Класс алгоритмов, моделирующих некоторое распределение вероятностей. В расширении Qbayes один из таких алгоритмов семплирования применяется для установления зависимых параметров для гипотез.

В расширении Qbayes на данный момент используется одна из реализаций алгоритма Метрополиса — Гастингса, в дальнейшем возможно добавление иных алгоритмов.

Этот тип модели используется при обучении с параметром model_type = 'mcmc' (Markov chain Monte Carlo).

Методы расширения

При создании модели создается таблица описания моделей:

CREATE TABLE IF NOT EXISTS qbayes_model_description (
    имя_модели VARCHAR NOT NULL,
    параметры_модели json NOT NULL,
    CONSTRAINT qbayes_model_un UNIQUE (model_name)
);

Затем, после обучения статистической выборкой, создается таблица с данными:

CREATE TABLE IF NOT EXISTS qbayes_model_data (
    имя_модели VARCHAR NOT NULL,
    имя_графа VARCHAR NOT NULL,
    данные json,
    CONSTRAINT qbayes_model_graph_un UNIQUE (model_name, graph_name)
);

Примечание
Указанные таблицы создаются автоматически при вызове соответствующих методов в текущей схеме базы данных по умолчанию. Если таблицы уже существуют, они дополняются. При этом действуют ограничения уникальности на имя модели в первой таблице и имя модели с именем графа во второй.

Определение модели

Для определения модели используется метод:

CREATE OR REPLACE FUNCTION qbayes_create_model(
    имя_модели cstring,
    описание json)
returns BIGINT AS 'MODULE_PATHNAME' language c volatile security definer;

Возвращаемое значение

При успешном выполнении возвращается 0, при неуспешном — генерируется ошибка.

Параметры:

имя_модели задает имя модели, которое должно быть уникальным в рамках таблицы описания моделей.

описание описывает параметры и гипотезы модели. Каждый параметр или гипотеза описываются json-выражением.

Поля:

"name" — символическое имя параметра или гипотезы,

"type" — тип параметра или гипотезы в исходном представлении; поддерживаются только типы bool (логический), discrete_integer (дискретный при перечислении) или float8 (с плавающей запятой, при создании диапазонов значений),

"distrib" — тип параметра или гипотезы в модели; поддерживаются только типы bool (логический) или discrete (дискретный),

"ranges" — границы разделения на интервалы для создания дискретного параметра, используется только вместе с параметром с типом float8,

"acceptable" - значения, допустимые при перечислении, используется только вместе с параметром с типом discrete_integer,

"is_hypothesis" — признак гипотезы, по умолчанию имеет значение false.

Типы параметров и гипотез

Все алгоритмы Байеса в расширении используют только бинарные и дискретные типы параметров моделей, а гипотезы — только бинарные типы.

Для обеспечения возможности оперирования различными типами в исходном представлении допустимы различные комбинации полей "type" и "distrib" в определении модели.

Бинарные параметры

...
"type": "bool",
"distrib": "bool",
...

В этом случае параметр считается бинарным и во внешнем представлении, и во внутреннем представлении модели. И при обучении модели, и при расчете прогнозных значений гипотез допустимые входные значения — true и false.

Дискретные параметры при разбиении на диапазоны

...
"type": "float8",
"distrib": "discrete",
"ranges": [r0, r1, ... ,rN]

В этом случае параметр считается дискретным во внутреннем представлении модели. В качестве значения во внешнем представлении выступают числа типа float8, которые затем разбиваются на диапазоны по границам r0, r1, ..., rN и во внутреннем представлении обозначают номер диапазона, в который попадает значение.

Диапазоны задаются полем ranges. Перечисление значений в его массиве указывает на границы диапазонов, причем создается N+1 диапазонов по следующим правилам:

0: (-infinity; r0)
1: [r0; r1)
...
N: [r{N-1}; rN)
N+1: [rN; +infinity)

И при обучении модели, и при расчете прогнозных значений гипотез на вход параметров передаются значения типа float8, методы расширения уже сами определяют номер диапазона.

Дискретные параметры при перечислении

...
"type": "discrete_integer",
"distrib": "discrete",
"acceptable": [m0, m1, ... ,mN]

В этом случае параметр считается дискретны во внутреннем представлении модели. В качестве значения во внешнем представлении выступают дискретные значения от 0 до N из поля acceptable. Сами значения параметра могут быть произвольными целыми числами из перечисленных m0, m1, ..., mN, необязательно идущими подряд или начинающимися с 0.

И при обучении модели, и при расчете прогнозных значений гипотез на вход параметров передаются значения из поля acceptable, методы расширения уже сами определяют дискретное значение, соответствующее значению параметра. Если внешнее значение не соответствует ни одному из перечисленных m0, m1, ..., mN, то генерируется ошибка.

Внешнее и внутреннее представление гипотез

Для гипотез допустима только одна комбинация "type" и "distrib":

...
"type": "bool",
"distrib": "bool",
...

Обучение модели

Данные (факты) с выделенными параметрами могут содержаться либо в уже подготовленных таблицах (relation), либо в представлении (view), созданном на основе этих таблиц.

Ограничения для таблиц и представлений:

Они должны содержать поле value типа json, в котором содержатся по крайней мере такие же поля, какие были объявлены при определении модели. Могут существовать и другие поля, однако они не принимаются в расчет при обучении.

Поддерживаются бинарные или дискретные параметры (предполагается независимость параметров; используемый тип модели — наивная модель Байеса).

Обучение производится следующим методом:

CREATE OR REPLACE FUNCTION qbayes_learn(
  имя_модели cstring,
  имя_графа cstring,
  тип_модели cstring,
  имя_отношения cstring,
  число_итераций BIGINT)
returns BIGINT AS 'MODULE_PATHNAME' language c volatile security definer;

Возвращаемое значение

При успешном выполнении возвращается 0, при неуспешном — генерируется ошибка.

Параметры:

имя_модели указывает имя модели;

имя_графа указывает имя графа;

тип_модели указывает тип модели ('naive', 'mi' или 'mcmc'; см. Алгоритмы Байесовского предсказания);

имя_отношения указывает на подготовленные исходные данные с фактами и гипотезами;

число_итераций задает количество шагов алгоритма для сходимости к стационарному распределению с приемлемой ошибкой.

Примечание
Параметр число_итераций имеет значения только для модели типа mcmc, для остальных он просто игнорируется.

Дообучение модели

Примечание
Дообучение имеет смысл проводить только для модели типа mcmc, для остальных типов функция дообучения просто ничего не делает.

CREATE OR REPLACE FUNCTION qbayes_learn_again(
  имя_модели cstring,
  имя_графа cstring,
  тип_модели cstring,
  имя_отношения cstring,
  число_итераций BIGINT)
returns DOUBLE PRECISION AS 'MODULE_PATHNAME' language c volatile security definer;

Возвращаемое значение

Возвращается число с плавающей запятой от 0 до 1, отражающее степень совпадения получившегося распределения с идеальным, получаемую как среднее для всех гипотез модели на данных обучающей выборки.

При неуспешном выполнении генерируется ошибка.

Параметры

Параметры аналогичны функции qbayes_learn, но происходит уточнение распределения на число_итераций.

Предсказание

Вычисление вероятностей гипотез при заданных параметрах производится следующим образом:

CREATE OR REPLACE FUNCTION qbayes_solve(
  имя_модели cstring,
  имя_графа cstring,
  элементарное_событие json)
returns json AS 'MODULE_PATHNAME' language c volatile security definer;

Возвращаемое значение

При успешном выполнении возвращается json, в котором перечисляются пары:

символическое имя гипотезы,

вероятность, с которыми эти гипотеза сбудется.

При неуспешном выполнении генерируется ошибка.

Параметры:

имя_модели указывает имя модели.

имя_графа указывает имя графа.

элементарное_событие задает вектор параметров для расчета гипотез в виде json-выражения.

Для расчета вероятностей гипотез для множества векторов параметров имеется дополнительный метод. Ему на вход передается набор параметров в таком же виде, как при обучении модели:

CREATE OR REPLACE FUNCTION qbayes_solve_view(
  имя_модели cstring,
  имя_графа cstring,
  имя_отношения cstring)
returns setof json AS 'MODULE_PATHNAME' language c volatile security definer;

Примечание
Дополнительно предлагается группа методов для обработки большого количества гипотез: qbayes_learn_mt, qbayes_solve_mt и qbayes_solve_view_mt. Они выполняются в многопоточном режиме и позволяют ускорить обучение и расчет. По параметрам эти методы полностью совпадают с соответствующими однопоточными версиями.
Следует отметить, что на малом количестве гипотез ускорение может не произойти, более того, из-за накладных расходов на организацию многопоточной обработки может наблюдаться замедление.

Очистка

Дополнительно предлагаются методы очистки данных и моделей:

CREATE OR REPLACE FUNCTION qbayes_drop_graph(имя_модели cstring, имя_графа cstring)
returns BIGINT AS 'MODULE_PATHNAME' language c volatile security definer;

CREATE OR REPLACE FUNCTION qbayes_drop_model(имя_модели cstring)
returns BIGINT AS 'MODULE_PATHNAME' language c volatile security definer;

Возвращаемое значение

При успешном выполнении возвращается 0, при неуспешном — генерируется ошибка.

Ограничения и параметры

Ограничения

Как правило, вероятностные модели содержат большое количество различных параметров и гипотез. На небольших моделях, где количество параметров и гипотез исчисляется десятками, скорее всего не возникнет каких либо затруднений. При работе же с большими моделями полезно знать некоторые факты.

Для различных данных в расширении Qbayes намеренно выбран тип данных json.

Этот тип описания выбран потому, что позволяет передать большее количество параметров, по сравнению, например, с таблицами СУБД. В таблице можно задать не более 1600 столбцов (см. Ограничения QHB), в то время, как в одной строке json можно разместить несколько тысяч и даже несколько десятков тысяч описаний параметров и гипотез.

Тем ни менее и у типа данных json есть свои ограничения:

Длина строки не должна превышать размер 1 Гбайт, если она символьная (определяется ограничениями varlena).

Этот лимит установлен для символьного представления данных, поэтому если в ней содержатся двоичные данные, то они ограничены вдвое меньшим лимитом.

Этим ограничениям должны соответствовать строки json с описаниями параметров модели, данными для обучения, обученные модели (поля data таблицы qbayes_model_data) и результаты предсказания.

В некоторых случаях при работе алгоритмов создается ситуация, когда происходит экспоненциальный рост количества ресурсов, требуемых для расчетов. Иногда такая ситуация называется комбинаторным взрывом. Она может быть вызвана, например, слишком большим количеством вариантов значений у зависимых переменных (параметров), хотя для качественного определения вероятностей гипотез достаточно их основных значений, которые вносят наибольший вклад. Для предотвращения таких ситуаций в расширении Qbayes есть возможность ограничения расходования ресурсов с помощью параметров.

Параметры

qbayes.max_dependencies
Этот параметр применим для алгоритмов mi (Mutual Information) и mcmc (Markov chain Monte Carlo). Обозначает максимальное количество зависимых переменных, которые может иметь гипотеза. Значение по умолчанию — 5.

qbayes.max_variants
Этот параметр применим для алгоритмов mi (Mutual Information) и mcmc (Markov chain Monte Carlo). Используется для дополнительного ограничения количества зависимых переменных, которое может иметь гипотеза: если количество комбинаций значений зависимых переменных превышает qbayes.max_variants, то количество зависимых переменных уменьшается до той поры, пока количество комбинаций их значений не станет укладываться в qbayes.max_variants. Рекомендации по установке параметра следующие: фактически qbayes.max_variants вносит максимальный вклад в занимаемую память, потребляя qbayes.max_variants × «количество гипотез» × 16 байт. Значение по умолчанию - 50000.

Примечание
Параметры устанавливаются в файле конфигурации. См. Настройка параметров

Пример использования

Необходимо установить расширение Qbayes:

CREATE EXTENSION IF NOT EXISTS qbayes cascade;

Создадим данные для обучения модели (в примере указан всего один вектор параметров и гипотез, в действительности их должно быть много):

CREATE OR REPLACE VIEW view1 AS SELECT
'{"size": 15, "volume": 18.35, "is_roundy": true, "h1": false}'::json AS value;

Опишем модель:

SELECT qbayes_create_model('MyModel',
  '[{
    "name": "size",
    "type": "discrete_integer",
    "distrib": "discrete",
    "acceptable": [1, 2, 15]
  }, {
    "name": "volume",
    "type": "float8",
    "distrib": "discrete",
    "ranges": [0.1, 1, 10, 100, 1000, 1e4, 1e5, 1e6, 1e7, 1e8]
  }, {
    "name": "is_roundy",
    "is_hypothesis": true,
    "type": "bool",
    "distrib": "bool"
  }, {
    "name": "h1",
    "is_hypothesis": true,
    "type": "bool",
    "distrib": "bool"
  }]'::json);

Можем просмотреть описание модели:

SELECT * FROM qbayes_model_description;

Обучение:

SELECT qbayes_learn('MyModel', 'MyGraph', 'naive', 'view1', 0);

Можем просмотреть данные модели:

SELECT * FROM qbayes_model_data;

Предсказание одного события:

SELECT qbayes_solve('MyModel', 'MyGraph', '{ "size": 15, "volume": 1e6 }'::json );

Предсказание нескольких событий (в примере используется тот же массив данных, на самом деле он может быть другим):

SELECT qbayes_solve_view('MyModel', 'MyGraph', 'view1');

Очистка:

SELECT qbayes_drop_graph('MyModel', 'MyGraph');

SELECT qbayes_drop_model('MyModel');

Примечание
В поставке расширения Qbayes прилагаются и другие примеры с расширенными наборами параметров и гипотез, с использованием различных методов. Эти примеры можно найти в SQL-скриптах, прилагаемых к расширению.

Замечания, примеры и варианты работы с расширением Qbayes

Предсказания по большому набору параметров

С точки зрения получения результата, два представленных ниже способа выборки вероятностей гипотез равнозначны.

Первый способ:

select qbayes_solve_view('Model1', 'Graph1', 'input_data');

Здесь предполагается, что Model1 и Graph1 — некоторые модель и граф соответственно. То есть этап обучения уже прошел. И совершенно не важно, с каким алгоритмом мы имели дело.

А input_data - это набор параметров, по которому и производится расчет вероятностей гипотез.

Второй способ:

create function solve_loop() returns setof json
language plpgsql
as $$
declare
  r record;
  rr json;
begin
  for r in (select value from input_data) loop
    select qbayes_solve('Model1', 'Graph1', r.value) into rr;
  return next rr;
end loop;
end
$$

select solve_loop();

Но эти два способа отличаются по нагрузке и скорости выполнения. По скорости отличия могут быть как в худшую, так и в лучшую сторону. Многое зависит от способа выборки результатов, если их действительно много.

При выборке первым способом сокращается время на чтение описания модели, десериализацию описаний параметров и гипотез — это происходит один раз. Во втором же случае чтение и десериализация происходит при каждом вызове функции qbayes_solve. Если объем модели не очень большой, а объем input_data не огромный, это сокращение может оказаться несущественным.

Но, в первом случае, можно учесть особенности работы некоторых клиентов при работе с большими выборками. Бывает так, что они ограничивают выборку, оборачивая запрос указаниями LIMIT и OFFSET, формируя таким образом порции результата. Иногда это может приводить к повторному расчету вероятностей при каждом скроллинге к следующей порции. Кроме того, такие ограничения неэффективно работают с контекстом памяти процесса на сервере. Скроллинг может сопровождаться сообщением «transaction left non-empty SPI stack» в системном журнале сервера QHB.

Part_Import

Part_Import — частичный импорт файлов данных из внешней базы данных.

Описание

Данное расширение содержит две функции:

partial_import(строка_подключения, имена_таблиц) — основная функция расширения, которая служит для копирования файлов, содержащих данные таблиц, из внешней базы данных в локальную.
Под таблицами локальной базы данных подразумеваются таблицы, доступные в текущем сеансе подключения, под таблицами внешней — те, доступ к которым мы получаем, воспользовавшись строкой_подключения. Имена таблиц для копирования передаются в параметре имена_таблиц. Этот параметр имеет тип строкового массива, где каждая строка содержит имя одной таблицы. Имя может быть как полным — т. е. включать пространство имен, так и неполным — т.е. без пространства имен. В последнем случае сервер должен понимать по неполному имени, о какой конкретно таблице идет речь.

table_script(oid) — функция, возвращающая SQL-скрипт, позволяющий создать таблицу, OID которой передан в параметре. Скрипт содержит индексы, внешние индексы, ограничения, последовательности, комментарии.

partial_import

Особенности работы частичного импорта:

Блокировки на внешние таблицы не накладываются, блокировки на локальные таблицы накладываются только перед переключением на «новые» файлы с данными, и блокировки предельно короткие. Перенесенные данные нескольких таблиц согласованы между собой.

Поскольку копируются файлы с данными, импорт заботится, чтобы эти файлы были корректными, для чего сверяет метаданные таблиц внешней и локальной баз данных. В процессе сверки убеждаемся, что названия, типы и порядок полей, включая удаленные столбцы, полностью совпадают. Должны совпадать ограничения столбцов, тип хранения столбца. Таблицы TOAST должны быть либо у обеих таблиц, либо ни у одной из них. Должно совпадать количество индексов, их наименования и состав столбцов. Не имеет значение, если не совпадает табличное пространство. Допустим также импорт материализованных табличных представлений, не должно быть проблем и с переносом партиций.

В случае наличия ограничения внешнего ключа будут предложены скрипты по их удалению в локальной базе данных и восстановлению по окончании импорта. Если при импорте будет переноситься не только таблица с внешним ключом, но и таблица, на которую внешний ключ ссылается, никаких ограничений не возникнет; при этом данные будут согласованы.

Ввиду отсутствия блокировок внешних таблиц теоретически возможно выполнение операций DDL с внешней таблицей в период между завершением сверки метаданных и началом переноса файлов с данными таблиц. В этом случае результат работы непредсказуем.

В процессе работы переносятся только файлы с данными, поэтому рекомендуется пересобрать статистику по импортированными таблицам по окончанию работы импорта.

Хотя импорт и инициирует создание контрольной точки на локальной базы данных по окончании своей работы, теоретически возможно аварийное завершение работы базы данных в период между переключением на новые файлы и завершением создания контрольной точки — в этом случае процедура аварийного восстановления с данными импортированных таблиц может дать непредсказуемые результаты.

table_script

Эта функция является адаптацией кода утилиты pg_dump. Взята часть, отвечающая исключительно за генерацию скриптов таблиц, индексов, ограничений и последовательностей. Вся остальная функциональность, посвященная подготовке содержимого таблиц к выгрузке, а также сама выгрузка отброшены.

Тестовые сценарии

partial_import

Тестировать расширение можно и в рамках одного экземпляра базы данных, но полноценное тестирование следует проводить на разных экземплярах базы данных, причем в качестве внешней нужно выбрать ту, в которой работа уже ведется продолжительное время. Последнее можно проверить, зайдя в папку с базой данных и посмотрев количество и размер файлов в подпапке pg_xact: если файлов несколько, значит, работа в базе ведется какое-то заметное время. Экземпляр с локальной базой (т. е. той, в которую следует производить импорт) лучше создать с нуля.

В локальной базе должно быть установлено расширение part_import, во внешней достаточно наличия правильных бинарных файлов нужной версии. Установить расширение можно командой:

CREATE EXTENSION part_import;

Теперь во внешней базе данных нужно создать таблицы и заполнить их данными. Для этого создадим три таблицы: type_a и type_b как обычные словари и еще одну таблицу для тестирования TOAST и сохранения работоспособности таблицы с HOT- оптимизацией. После первого этапа будет добавлена четвертая таблица many2many_ab в качестве «точки пересечения» таблиц type_a и type_b (эмуляция отношения «многие ко многим»). Для создания таблиц type_a, type_b и accounts воспользуемся следующим скриптом (создавать таблицы следует и на внешнем, и на локальном серверах баз данных):

CREATE TABLE public.type_a (
    id integer NOT NULL,
    type_id smallint NOT NULL,
    name_1 character varying(40) NOT NULL,
    date_ins date,
    name_hash_1 integer NOT NULL
);
ALTER TABLE ONLY public.type_a
    ADD CONSTRAINT pk_type_a PRIMARY KEY (id);
CREATE INDEX idx_type_a_name_1 ON public.type_a USING btree (name_1);
CREATE INDEX idx_type_a_name_hash_1 ON public.type_a USING btree (name_hash_1);

CREATE TABLE public.type_b (
    id integer NOT NULL,
    name character varying(40) NOT NULL,
    date_ins date,
    name_hash integer NOT NULL
);
ALTER TABLE ONLY public.type_b
    ADD CONSTRAINT pk_type_b PRIMARY KEY (id);
CREATE INDEX idx_type_b_name ON public.type_b USING btree (name);
CREATE INDEX idx_type_b_name_hash ON public.type_b USING btree (name_hash);

CREATE TABLE public.accounts (
    id integer NOT NULL,
    number text,
    client text,
    amount numeric,
    CONSTRAINT amount_is_positive CHECK ((amount >= 0.0))
);
ALTER TABLE ONLY public.accounts ALTER COLUMN number SET STORAGE EXTERNAL;
ALTER TABLE public.accounts ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
    SEQUENCE NAME public.accounts_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);
ALTER TABLE ONLY public.accounts
    ADD CONSTRAINT accounts_number_key UNIQUE (number);
ALTER TABLE ONLY public.accounts
    ADD CONSTRAINT accounts_pkey PRIMARY KEY (id);

Далее следует заполнить таблицы внешней базы данных данными. В случае таблиц type_a и type_b это могут быть практически любые данные, желательно только, чтобы параметр id был линейным и начинался со значения 1, а количества данных было достаточно для того, чтобы файлов хранения было несколько сегментов хотя бы у одной из таблиц. Примерная оценка объема данных для таблицы type_a — 10 миллионов строк. Для таблицы type_b вполне достаточно 10 тысяч строк. Таблицу accounts нужно заполнить так, чтобы строки обязательно попали в TOAST- таблицу. По количеству строк особых требований нет, так как по этому параметру достаточно тестирования таблицы type_a. Для заполнения и изменения данных таблицы accounts воспользуемся следующим скриптом (по мотивам статьи Егора Рогова):

INSERT INTO accounts(id, "number", client, amount) VALUES(1, 1000, 'masha', 1000.),
    (2, 2001, 'vasya', 2000.), (3, 2002, 'misha', 1200.), (4, 2003, 'irina', 1300.);
UPDATE accounts SET client = (
  SELECT string_agg( chr(trunc(65+random()*26)::integer), '') FROM generate_series(1,2000)
);
UPDATE accounts SET amount = amount + 100 + id;
UPDATE accounts SET amount = amount + 100 + id;
UPDATE accounts SET amount = amount + 100 + id;
UPDATE accounts SET amount = amount + 100 + id;

После этого в таблице accounts должны появиться 4 (видимые) строки. Последние четыре команды UPDATE не меняют данные в индексируемых столбцах, поэтому должна быть задействована оптимизация HOT-обновления.

Теперь импортируем данные таблиц type_a, type_b и accounts на локальный сервер, для чего выполним команду:

SELECT partial_import('host=<external_db_adderss> user=<external_db_user> dbname=<external_db_name>', ARRAY['type_a', 'type_b', 'accounts']::text[]);

Первый параметр — это строка соединения с внешней базой данных. С этими параметрами у пользователя должно быть достаточно прав для чтения системных каталогов и выполнения команды BASE_BACKUP.

По окончании работы функции в локальной базе данных заполнение таблиц type_a, type_b и accounts должно совпадать с таковыми во внешней базе. Также следует убедиться, что запросы:

EXPLAIN ANALYZE SELECT * FROM type_a WHERE id = 7;
EXPLAIN ANALYZE SELECT * FROM type_b WHERE id = 7;
EXPLAIN ANALYZE SELECT * FROM accounts WHERE id = 2;

строят индексный план извлечения данных, например, такой:

Index Scan using pk_type_a on type_a  (cost=0.42..8.44 rows=1 width=55) (actual time=0.019..0.020 rows=1 loops=1)
  Index Cond: (id = 7)
Planning Time: 0.057 ms
Execution Time: 0.032 ms

а запросы:

SELECT * FROM type_a WHERE id = 7;
SELECT * FROM type_b WHERE id = 7;
SELECT * FROM accounts WHERE id = 2;

действительно извлекают данные (подразумевается, что строки с указанными значениями идентификаторов действительно существуют).

Описанный сценарий можно расширить следующим образом:

Заполнить таблицы type_a, type_b и accounts, как описано выше;

Запомнить количество строк в каждой из таблиц;

Запустить скрипт дополнительного заполнения данными таблицы type_a или type_b и параллельно запустить импорт;

По окончании работы импорта убедиться, что количество строк в таблице, в которую параллельно заливались данные, больше, чем на начало импорта (в теории это должно быть количество на момент LSN-окончания снимка, но точный подсчет затруднен).

Создадим во внешней и локальной базах данных таблицу many2many_ab с помощью следующего скрипта:

CREATE TABLE public.many2many_ab (
    ida integer NOT NULL,
    idb integer NOT NULL
);
ALTER TABLE ONLY public.many2many_ab
    ADD CONSTRAINT pk_many2many_ab PRIMARY KEY (ida, idb);
CREATE INDEX many2many_ab_idb ON public.many2many_ab USING btree (idb);
ALTER TABLE ONLY public.many2many_ab
    ADD CONSTRAINT fk_net_ab_type_a_id FOREIGN KEY (ida) REFERENCES public.type_a(id);
ALTER TABLE ONLY public.many2many_ab
    ADD CONSTRAINT fk_net_ab_type_b_id FOREIGN KEY (idb) REFERENCES public.type_b(id);

При попытке выполнить предыдущую команду частичного импорта, а именно:

SELECT partial_import('host=<external_db_adderss> user=<external_db_user> dbname=<external_db_name>', ARRAY['type_a', 'type_b', 'accounts']::text[]);

мы должны получить сообщение об ошибке:

ERROR:  Input tables list refer to 2 foreign keys, which not import from remote server.
For guarantee reference integrity you must drop this foreign keys before running
partial import and recreate it when done in an "not valid" state, and than try validate.
The scripts below will help you perform the described actions.
-- ОШИБКА: Список вводимых таблиц ссылается на 2 внешних ключа, которые не были
-- импортированы с внешнего сервера.
-- Для гарантии ссылочной целостности вам следует удалить эти внешние ключи, прежде
-- чем запустить частичный импорт, и воссоздать их в состоянии «недопустимые», а
-- затем попробовать их валидировать.
-- Следующие далее скрипты помогут вам выполнить описанные действия.

BEGIN;
  -- Этот код удалит все мешающие внешние ключи --
  ALTER TABLE IF EXISTS ONLY public.many2many_ab DROP CONSTRAINT IF EXISTS fk_net_ab_type_a_id;
  ALTER TABLE IF EXISTS ONLY public.many2many_ab DROP CONSTRAINT IF EXISTS fk_net_ab_type_b_id;
END;

------------------------------------------------------------------------------------
---- Здесь вы должны запустить процедуру "partial_import" с теми же параметрами, ----
---- что и в предыдущий раз.                                                   ----
------------------------------------------------------------------------------------

BEGIN;
  -- Этот код воссоздаст удаленные ранее внешние ключи в состоянии «недопустимые» --
  ALTER TABLE ONLY public.many2many_ab
    ADD CONSTRAINT fk_net_ab_type_a_id FOREIGN KEY (ida) REFERENCES type_a(id) NOT VALID;

  ALTER TABLE ONLY public.many2many_ab
    ADD CONSTRAINT fk_net_ab_type_b_id FOREIGN KEY (idb) REFERENCES type_b(id) NOT VALID;
END;

BEGIN;
  -- Этот код попытается валидировать удаленные, а затем воссозданные внешние ключи --
  ALTER TABLE IF EXISTS ONLY public.many2many_ab VALIDATE CONSTRAINT fk_net_ab_type_a_id;
  ALTER TABLE IF EXISTS ONLY public.many2many_ab VALIDATE CONSTRAINT fk_net_ab_type_b_id;
END;

SQL state: XX000

Сообщение об ошибке говорит нам, что у нас есть внешние ключи, целостность которых мы не можем гарантировать по окончании импорта, и предлагает последовательность действий, благодаря которым можно выполнить импорт, хоть и ценой потери временного контроля целостности ключа. Однако если мы добавим в импорт таблицу, ставшую причиной ошибки, то он выполнится без ошибок:

SELECT partial_import('host=<external_db_adderss> user=<external_db_user> dbname=<external_db_name>', ARRAY['type_a', 'type_b', 'many2many_ab', 'accounts']::text[]);

Импорт устойчив к расхождениям физического хранения атрибутов, расхождениям в их количестве, их порядку, но завершается ошибкой на стадии верификации таблиц. Выполним на локальной базе данных следующий скрипт для таблицы accounts:

ALTER TABLE public.accounts ADD amount2 numeric;
ALTER TABLE public.accounts DROP COLUMN amount;
ALTER TABLE public.accounts RENAME COLUMN amount2 TO amount;
ALTER TABLE public.accounts ADD CONSTRAINT amount_is_positive CHECK ((amount >= 0.0));

После его выполнения декларативное объявление таблиц на внешнем и локальном серверах выглядят идентично, однако попытка выполнить команду импорта:

SELECT partial_import('host=<external_db_adderss> user=<external_db_user> dbname=<external_db_name>', ARRAY['accounts']::text[]);

приведет к ошибке:

ERROR:  Remote table public.accounts2 has 4 columns, but local one has 5. It is possible that some attributes in one or both of the tables have been deleted earlier.
-- ОШИБКА: У внешней таблицы public.accounts2 имеется 4 столбца, однако у локальной
-- таблицы 5 столбцов. Возможно, некоторые атрибуты в одной или обеих таблицах
-- ранее были удалены.
SQL state: XX000

qhb_audit

Расширение для осуществления аудита действий QHB.

Расширение состоит из трёх основных компонентов:

Собственно динамическая библиотека расширения libqhb_audit.so.

Очередь событий в специальной области общей памяти.

Выделенный фоновый процесс qhb_audit_worker для обработки очереди.

Цикл работы расширения:

Процесс сервера выполняет операцию и генерирует событие.

Расширение помещает событие в очередь в общей памяти.

Фоновый процесс извлекает событие из очереди и либо записывает в специальный файл -- лог аудита, либо передаёт внешнему HTTP агенту.

Настройка

Подключение расширения осуществляется путем установки параметров в файле конфигурации qhb.conf. Для автоматической загрузки расширения при старте экземпляра нужно добавить в qhb.conf следующую строку:

shared_preload_libraries = 'libqhb_audit'

Если параметр shared_preload_libraries уже задан, то в него нужно добавить значение libqhb_audit через запятую, например:

shared_preload_libraries = 'pg_store_plans,pg_stat_statements,libqhb_audit'

Расширение (точнее, его фоновый процесс) получает события от сервера БД через очередь, размещённую в области общей памяти QHB. Это накладывает ограничения на максимальный размер очереди и отведённой под неё памяти. В случае превышения размера очереди в основном логе сервера появится предупреждение об этом.

Общие параметры считываются только при инициализации расширения (при старте СУБД):

qhb_audit.queue_memory: объем разделяемой памяти, выделяемой для внутренней очереди сообщений. По умолчанию — 10 МБ.

qhb_audit.queue_capacity: максимальный размер внутренней очереди сообщений. По умолчанию — 1000 событий.

Если перезагрузка сервера невозможна, допускается обновление параметров с помощью команды qhb_ctl reload.

Параметры

Параметры для записи событий в файл

qhb_audit.csv_file

Путь к файлу, куда будут записываться события. Данный параметр можно менять «на лету».

Если значение параметра не задано, запись событий в файл не осуществляется. Если заданное имя не существует в файловой системе, то будет создан новый файл.

Если заданное имя указывает на файл, запрещённый для записи, или на файл, который невозможно создать, в лог сервера будет выдано сообщение об ошибке, а запись событий в файл не будет осуществляться.

Если указан относительный путь к файлу, предполагается, что файл находится в каталоге PGDATA. Промежуточные каталоги, если указаны, не создаются автоматически; при их отсутствии в файловой системе такой путь будет ошибочным.

Если заданное имя указывает на каталог, выдаётся сообщение

qhb_audit: CSV output path is a directory '<имя>', ignoring

и запись событий в файл не будет осуществляться.

При включённой ротации лога (см. ниже) значение параметра csv_file имеет смысл "базового" имени: новые файлы для ротации генерируются на его основе (примеры см. ниже).

qhb_audit.csv_file_rotation

Режим ротации файла событий. Данный параметр можно менять «на лету».

Ротация предотвращает неконтролируемое разрастание файла событий путём разбиения его на множество файлов с перезаписью со временем "по кругу". Имена файлов ротации генерируются на основе значения параметра qhb_audit.csv_file. Файлы ротации создаются в том же каталоге, на который указывает csv_file. Этот каталог не создаётся автоматически, а должен существовать на момент запуска. Если указан относительный путь, файлы ротации (как и одиночный файл без ротации) создаются по указанному пути относительно каталога PGDATA.

Если этот параметр не задан или имеет пустое значение, ротация не осуществляется.

Если задано некорректное значение, работа всего расширения блокируется с выдачей сообщения

incorrect value of parameter csv_file_rotation

Разрешённые значения параметра:

off - ротация отключена, всё пишется в единственный файл;

day - дневная ротация в 24 файла;

week - недельная ротация в 7 файлов;

month - месячная ротация в 28-31 файл;

year - годичная ротация в 12 файлов.

Примерные имена файлов, генерируемые для каждого режима при "базовом" имени audit.csv. К имени файла (без расширения) присоединяется различающий суффикс, расширение сохраняется.

Режим ротации
Параметр
Имена файлов

дневной
day
audit_00h_of_day.csv
...

audit_23h_of_day.csv

недельный
week
audit_Mon_of_week.csv

audit_Tue_of_week.csv

audit_Wed_of_week.csv

audit_Thu_of_week.csv

audit_Fri_of_week.csv

audit_Sat_of_week.csv

audit_Sun_of_week.csv

месячный
month
audit_01d_of_month.csv
...

audit_31d_of_month.csv

годовой
year
audit_January_of_year.csv

audit_February_of_year.csv

audit_March_of_year.csv

audit_April_of_year.csv

audit_May_of_year.csv

audit_June_of_year.csv

audit_July_of_year.csv

audit_August_of_year.csv

audit_September_of_year.csv

audit_October_of_year.csv

audit_November_of_year.csv

audit_December_of_year.csv

Файлы от предыдущего режима ротации остаются и могут быть удалены вручную. То же самое относится и к внешним таблицам для доступа к данным аудита через SQL-запросы (см. ниже).

Предварительные шаги для настройки доступа к данным логов аудита через SQL-запросы

Для доступа к данным аудита через SQL-запросы используется расширение file_fdw. Для этого устанавливается расширение file_fdw, создается основная таблица qhb_audit_log, выбирается схема ротации логов. В зависимости от выбранной схемы берутся за основу приведенные варианты скриптов, где уточняется путь к каталогу данных PGDATA и каталогу файлов логов аудита.

В приведенных ниже скриптах подразумевается, что файлы аудита будут расположены в каталоге audit каталога данных PGDATA и начало имени файла будет также audit. Для этого в настройке qhb_audit.csv_file можно прописать либо абсолютный путь /audit/audit.csv (здесь ***

*** нужно заменить на реальный путь к каталогу данных), либо задать путь относительно текущего каталога данных, например audit/audit.csv. При необходимости, отредактируйте приведенные ниже скрипты в соответствии с реальным расположением файлов.

Задание переменной PGDATA и создание каталога audit внутри PGDATA

Здесь и во всех скриптах далее необходимо заменить ***

*** на реальный путь к каталогу PGDATA.

Создаем каталог для файлов логов аудита.

export PGDATA=<PGDATA>

sudo -u qhb mkdir $PGDATA/audit

Делаем данные аудита доступными на уровне системы только пользователю qhb:

sudo -u qhb chmod 700 $PGDATA/audit

Создание расширения file_fdw и регистрация стороннего сервера

Создайте расширение file_fdw и сторонний сервер (как вариант, можно сделать это в базе данных qhb).

CREATE EXTENSION file_fdw;
CREATE SERVER qhb_audit_log FOREIGN DATA WRAPPER file_fdw;

Создание основной таблицы

Данная таблица описывает все поля логов и от нее будут наследоваться при их создании все внешние таблицы логов, соответствующие выбранной схеме ротации.

create table qhb_audit_log
(
  event_id 	bigint,
  event_ts      timestamp,
  event_name    text,
  event_type  	text,
  user_name     text,
  db_name       text,
  table_name    text,
  query_text  	text,
  event_level 	text
);

Если ротация не нужна

Если необходимости в ротации нет, следует ограничиться созданием одной внешней таблицы для файла, указанного в параметре qhb_audit.csv_file.
В следующей команде нужно заменить ***

*** на реальный путь до каталога данных:

export PGDATA=<PGDATA>

В следующей команде нужно заменить <file.csv> на реальное имя файла из параметра qhb_audit.csv_file:

sudo -u qhb touch $PGDATA/audit/<file.csv>

В команде создания внешней таблицы при указании filename нужно заменить ***

*** на реальный путь до каталога данных, а <file.csv> на реальное имя файла из параметра qhb_audit.csv_file:

CREATE FOREIGN TABLE qhb_audit_log_csv() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/<file.csv>',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );

Дневная ротация в 24 файла (1 час - 1 файл)

Команды ОС для создания файлов часовых логов

Перед выполнением скриптов не забудьте заменить ***

*** на реальный путь к каталогу данных.

export PGDATA=<PGDATA>

sudo -u qhb touch $PGDATA/audit/audit_00h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_01h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_02h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_03h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_04h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_05h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_06h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_07h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_08h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_09h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_10h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_11h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_12h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_13h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_14h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_15h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_16h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_17h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_18h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_19h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_20h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_21h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_22h_of_day.csv
sudo -u qhb touch $PGDATA/audit/audit_23h_of_day.csv

Скрипты для создания таблиц часовых логов

Перед выполнением скриптов не забудьте заменить ***

*** на реальный путь к каталогу данных.

CREATE FOREIGN TABLE qhb_audit_00h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_00h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_01h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_01h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_02h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_02h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_03h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_03h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_04h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_04h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_05h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_05h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_06h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_06h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_07h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_07h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_08h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_08h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_09h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_09h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_10h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_10h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_11h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_11h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_12h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_12h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_13h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_13h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_14h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_14h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_15h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_15h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_16h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_16h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_17h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_17h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_18h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_18h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_19h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_19h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_20h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_20h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_21h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_21h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_22h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_22h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_23h_of_day() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_23h_of_day.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );

Недельная ротация логов в 7 файлов

Команды ОС для создания файлов логов за каждый день недели

Перед выполнением скриптов не забудьте заменить ***

*** на реальный путь к каталогу данных.

export PGDATA=<PGDATA>

sudo -u qhb touch $PGDATA/audit/audit_Mon_of_week.csv
sudo -u qhb touch $PGDATA/audit/audit_Tue_of_week.csv
sudo -u qhb touch $PGDATA/audit/audit_Wed_of_week.csv
sudo -u qhb touch $PGDATA/audit/audit_Thu_of_week.csv
sudo -u qhb touch $PGDATA/audit/audit_Fri_of_week.csv
sudo -u qhb touch $PGDATA/audit/audit_Sat_of_week.csv
sudo -u qhb touch $PGDATA/audit/audit_Sun_of_week.csv

Скрипты для логов для каждого дня недели

Перед выполнением скриптов не забудьте заменить ***

*** на реальный путь к каталогу данных.

CREATE FOREIGN TABLE qhb_audit_Mon_of_week() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_Mon_of_week.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_Tue_of_week() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_Tue_of_week.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_Wed_of_week() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_Wed_of_week.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_Thu_of_week() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_Thu_of_week.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_Fri_of_week() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_Fri_of_week.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_Sat_of_week() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_Sat_of_week.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_Sun_of_week() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_Sun_of_week.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );

Месячная ротация в 28-31 файл

Команды ОС для создания файлов логов за каждый день месяца

Перед выполнением скриптов не забудьте заменить ***

*** на реальный путь к каталогу данных.

export PGDATA=<PGDATA>

sudo -u qhb touch $PGDATA/audit/audit_01d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_02d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_03d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_04d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_05d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_06d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_07d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_08d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_09d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_10d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_11d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_12d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_13d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_14d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_15d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_16d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_17d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_18d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_19d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_20d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_21d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_22d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_23d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_24d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_25d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_26d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_27d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_28d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_29d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_30d_of_month.csv
sudo -u qhb touch $PGDATA/audit/audit_31d_of_month.csv

Скрипты для создания таблиц логов за каждый день месяца

Перед выполнением скриптов не забудьте заменить ***

*** на реальный путь к каталогу данных.

CREATE FOREIGN TABLE qhb_audit_01d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_01d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_02d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_02d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_03d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_03d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_04d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_04d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_05d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_05d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_06d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_06d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_07d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_07d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_08d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_08d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_09d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_09d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_10d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_10d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_11d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_11d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_12d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_12d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_13d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_13d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_14d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_14d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_15d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_15d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_16d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_16d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_17d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_17d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_18d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_18d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_19d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_19d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_20d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_20d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_21d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_21d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_22d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_22d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_23d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_23d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_24d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_24d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_25d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_25d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_26d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_26d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_27d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_27d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_28d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_28d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_29d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_29d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_30d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_30d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_31d_of_month() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_31d_of_month.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );

Логи c годичной ротацией в 12 файлов

Команды ОС для создания файлов помесячных логов

Перед выполнением скриптов не забудьте заменить ***

*** на реальный путь к каталогу данных.

export PGDATA=<PGDATA>

sudo -u qhb touch $PGDATA/audit/audit_January_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_February_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_March_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_April_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_May_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_June_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_July_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_August_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_September_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_October_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_November_of_year.csv
sudo -u qhb touch $PGDATA/audit/audit_December_of_year.csv

Скрипты для создания таблиц помесячных логов

Перед выполнением скриптов не забудьте заменить ***

*** на реальный путь к каталогу данных.

CREATE FOREIGN TABLE qhb_audit_January_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_January_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_February_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_February_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_March_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_March_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_April_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_April_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_May_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_May_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_June_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_June_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_July_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_July_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_August_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_August_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_September_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_September_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_October_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_October_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_November_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_November_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );
CREATE FOREIGN TABLE qhb_audit_December_of_year() INHERITS (qhb_audit_log) SERVER qhb_audit_log OPTIONS (filename '<PGDATA>/audit/audit_December_of_year.csv',  FORMAT 'csv', HEADER 'true', DELIMITER ',' );

Разделение доступности данных аудита разных баз данных

Пользователь с ролью информационной безопасности (ИБ) qhb_dbms_admin должен видеть все данные таблицы qhb_audit_log. Пользователь с ролью qhb_db_admin должен видеть данные аудита по тем базам данных, администратором которых он является. Подробную информацию о ролях администраторов ИБ см. в главе Описание Ролей ИБ. Разделение прав реализуется с помощью механизма RLS (Row Level Security). Для этого необходимо задать правила видимости данных, опираясь на данные по ролям и пользователям, а также активизировать механизм RLS для таблицы qhb_audit_log. Полная видимость данных для пользователя с ролью qhb_dbms_admin реализуется либо через установку для роли атрибута bypassrls (этот вариант используется, если таких пользователей несколько) либо через установку владельцем таблицы соответствующего пользователя (удобно в случае, когда такой пользователь всего один). Для владельца таблицы правила видимости по умолчанию не работают и ему доступны все данные таблицы.

Создание тестовых пользователей и выдача им соответствующих ролей

Для создания пользователей (здесь в качестве примера - db_admin и dbms_admin) и выдачи им соответствующих ролей нужно выполнить следующие команды:

\c qhb qhb
CREATE USER db_admin;
SELECT oid as user_oid FROM pg_authid WHERE rolname = 'dbms_admin';
SELECT oid as database_oid FROM pg_database WHERE datname = '<database_name>';
SELECT qhb_make_db_admin(<user_oid>, <database_oid>) FROM pg_database d WHERE d.datname = '<db_name>';

CREATE USER dbms_admin WITH bypassrls; -- пользователь с правом игнорирования правил RLS
SELECT user_oid FROM pg_authid WHERE rolname = 'dbms_admin';
SELECT qhb_make_dbms_admin(<user_oid>);

Использовать oid, соответствующий имени роли, можно и таким образом:

SELECT qhb_make_dbms_admin('dbms_admin'::regrole::oid);

Альтернативой установки атрибута bypassrls может стать задание владения таблицей qhb_audit_log ролью dbms_admin, т.к. политики доступа к данным по умолчанию не срабатывают по отношению к владельцу таблицы:

\c qhb qhb
alter table qhb_audit_log owner to dbms_admin;

Создание политики доступа к строкам и активация RLS для таблицы аудита

Для доступности данных только по конкретным базам создадим политику ограничения видимости строк, которая опирается на данные словаря. Нужно активировать механизм RLS для таблицы qhb_audit_log и выдать права на чтение таблицы qhb_audit_log пользователям db_admin и dbms_admin:

create policy policy_audit_log on qhb_audit_log using (
  db_name in (select db.datname
	        from pg_database db
		   , qhb_db_admin a
	       where db.oid = a.db_id
		 and a.user_id = user::regrole::oid)
);

-- Включаем RLS для таблицы аудита
alter table qhb_audit_log enable row level security;

-- Права на чтение на основную таблицу (дочерние внешние таблицы не затрагиваем)
grant select on qhb_audit_log to db_admin;
grant select on qhb_audit_log to dbms_admin;

Для проверки видимости данных можно поочередно подключиться и выполнить запрос. Для пользователя db_admin видны только записи из соответствующего списка баз:

\c qhb db_admin
select * from qhb_audit_log;

Для пользователя dbms_admin будут доступны все данные:

\c qhb dbms_admin
select * from qhb_audit_log;

Если нет строк, соответствующих правам пользователя, или не установлен атрибут bypassrls, выдается сообщение:

ERROR:  permission denied for table qhb_db_admin

Отключение RLS для таблицы, удаление политики RLS, отзыв прав на таблицу

\c qhb qhb
alter table qhb_audit_log disable row level security;
drop policy policy_audit_log on qhb_audit_log;
revoke select on qhb_audit_log from db_admin;
revoke select on qhb_audit_log from dbms_admin;

Параметры для отправки событий по REST API

qhb_audit.rest_url URL веб-сервера, принимающий сообщения через REST API. Данный параметр можно менять «на лету». Если он не задан, отправка событий не осуществляется. Если заданы qhb_audit.rest_url и qhb_audit.csv_file, то осуществляется только запись событий в CSV файл.

qhb_audit.rest_max_attempts Максимальное количество попыток отправить событие на сервер при его недоступности.

qhb_audit.rest_attempts_interval_ms Интервал в миллисекундах между попытками отправить сообщение на сервер при его недоступности.

Формат CSV файла

Вновь созданный CSV файл будет состоять из заголовка (первая строка) и непосредственно событий (остальные строки) со следующими полями:

уникальный в пределах файла или связанного набора ротируемых файлов идентификатор события;

время, когда событие произошло, в формате RFC3339, например 2018-02-14T00:28:07Z;

имя события;

имя типа события;

имя пользователя, под управлением которого произошло событие;

имя базы данных при создании соединения;

имя таблицы (задается только при DDL-операциях на таблице);

команда SQL, выполнение которой привело к событию;

степень важности события: low, medium, high, critical, fatal.

Примеры операций и соответствующих записей в CSV-файле

Успешная и неуспешная аутентификация пользователя plop

0,2023-12-22T08:23:40.516682141Z,connection created:[local]:,authentication control,plop,template1,,,medium

1,2023-12-22T08:23:40.516682141Z,connection refused:[local]:,authentication control,plop,template1,,,medium

CREATE USER zodd WITH PASSWORD '123456';
ALTER ROLE zodd RENAME TO gozz;
DROP ROLE gozz;

100,2023-12-22T08:25:14.444569958Z,create role,user account management,plop,,,create user zodd with password <REDACTED>,medium

101,2023-12-22T08:25:14.447219625Z,alter role rename,user account management,plop,,,alter role zodd rename to gozz;,medium

102,2023-12-22T08:25:15.284198137Z,drop role,user account management,plop,,,drop role gozz;,medium

CREATE DATABASE moon;
ALTER DATABASE moon RENAME TO dream;
DROP DATABASE moon;
CREATE TABLE temp (id int, name varchar(255));
ALTER TABLE temp RENAME TO permanent;
TRUNCATE TABLE permanent;
DROP TABLE permanent;

400,2023-12-22T08:29:19.313320493Z,create database,changing metadata,plop,moon,,,high

401,2023-12-22T08:29:19.331731681Z,alter database,changing metadata,plop,moon,,,high

403,2023-12-22T08:29:19.332957337Z,create table,changing metadata,plop,,temp,,high

404,2023-12-22T08:29:19.334730324Z,alter table,changing metadata,plop,,temp,,high

406,2023-12-22T08:29:19.334947840Z,truncate table,changing metadata,plop,,permanent,,high

405,2023-12-22T08:29:19.337225628Z,drop table,changing metadata,plop,,permanent,,high

Формат сообщений через REST API

Сообщения о событиях отправляются в формате JSON со следующей схемой:

Наименование | Тип переменной | Описание | Обязательный
tags | string[] | Сквозные идентификаторы и метки, позволяющие группировать события | Да
Datetime | integer | Время Unix. Время возникновения события в миллисекундах, прошедших с полуночи (00:00:00 UTC) 1 января 1970 года. (Fri Apr 03 2020 10:28:37 соответствует 1585909717000) | Да
serviceName | string | Идентификатор службы | Да
serviceVersion | string | Версия службы | Да
name | string | Наименование события аудита | Да
params | object[] | Список параметров события аудита в формате: Name (string) — наименование параметра события аудита, Value (string) — значение параметра события | Да
sessionID | string | Идентификатор сеанса (если есть) | Нет
userLogin | string | Логин пользователя | Да
userName | string | Имя пользователя | Нет
userNode | string | Узел (IP/FQDN), с которого пользователь выполняет действия | Нет

Отслеживаемые события

Попытки подключения клиентов.

Запуск и останов кластера QHB.

Восстановление данных после нештатного останова кластера QHB.

Команды управления ролями и их привилегиями: CREATE/ALTER/DROP ROLE/USER/GROUP, CREATE/ALTER/DROP USER MAPPING, GRANT, REVOKE.

Команды DDL: CREATE/ALTER/DROP DATABASE, CREATE/ALTER/TRUNCATE/DROP TABLE.

Команды изменения конфигурации SET, ALTER SYSTEM и ALTER DATABASE SET.

Нарушения целостности бинарных файлов (core и contrib) и каталога pg_proc (процедур).

Команды управления процедурами и представлениями: CREATE/ALTER/DROP PROCEDURE, CREATE/ALTER/DROP VIEW, CREATE/ALTER/DROP MATERIALIZED VIEW.

Типы событий

Тип события | Краткое наименование | Важность
успешные и неуспешные попытки аутентификации пользователей системы управления базами данных | authentication control | medium
создание учетных записей пользователей системы управления базами данных | user account management | medium
изменение атрибутов учетных записей пользователей системы управления базами данных | user account management | medium
восстановление базы данных | information recovery | medium
запуск и остановка системы управления базами данных | instance start/stop control | high
изменение конфигурации системы управления базами данных | changing instance settings | high
создание и удаление базы данных, таблицы за исключением временных таблиц, создаваемых системой управления базами данных в служебных целях | changing metadata | high
изменение правил разграничения доступа в системе управления базами данных | access attribute management | high
создание и изменение процедур (программного кода), хранимых в базах данных, и представлений | procedure and view management | critical
факты нарушения целостности объектов контроля | integrity violation | fatal

Полный перечень событий аудита, сгруппированных по типам

Тип
События

authentication control
connection created
connection refused

user account management
create role
alter role rename
drop role
alter role
alter role set
alter default privileges

information recovery
crash recovery initiated
archive recovery initiated

instance start/stop control
server started
server stopped

changing instance settings
set param
alter system
alter database set

changing metadata
create database
alter database
drop database
create table
alter table
drop table
truncate table

access attribute management
grant
grant role
revoke
revoke role
create user mapping
alter user mapping
drop user mapping

procedure and view management
create procedure
alter procedure
drop procedure
create view
alter view
drop view
create materialized view
alter materialized view
drop materialized view

integrity violation
integrity check failure for catalog
integrity check failure for files

Специализированные и экспериментальные модули PostgreSQL

adminpack — предоставляет ряд вспомогательных функций, которыми могут пользоваться pgAdmin и другие средства администрирования и управления для предоставления дополнительной функциональности

amcheck — предоставляет функции, позволяющие проверять логическую целостность структуры отношений

Функции

Дополнительная проверка heapallindexed

Эффективное использование amcheck

Исправление повреждений

auth_delay — заставляет сервер сделать небольшую паузу перед тем, как выдать сообщение об ошибке аутентификации, чтобы усложнить атаки методом прямого подбора паролей к базам данных

Параметры конфигурации

Автор

auto_explain — предоставляет возможность автоматического протоколирования планов выполнения медленных операторов, без необходимости выполнения EXPLAIN вручную

Параметры конфигурации

Пример

Автор

bloom — предоставляет индексный метод доступа, основанный на фильтрах Блума

Параметры

Примеры

Интерфейс класса операторов

Ограничения

Авторы

btree_gin — предоставляет типовые классы операторов GIN, реализующие поведение, схожее с тем, что реализуют классы В-дерева, для различных типов данных

Пример использования

Авторы

btree_gist — предоставляет классы операторов индексов GiST, реализующие поведение, схожее с тем, что реализуют классы В-дерева, для различных типов данных

Пример использования

Авторы

citext — предоставляет тип данных для строк символов, не учитывающих регистр, citext

Обоснование

Как его использовать

Поведение при сравнении строк

Ограничения

Автор

cube — реализует тип данных cube для представления многомерных кубов

Синтаксис

Точность

Применение

Поведение по умолчанию

Примечания

Благодарности

dblink — обеспечивает подключения к другим базам данных QHB из сеанса базы данных

dblink_connect — открывает постоянное подключение к удаленной базе данных

dblink_connect_u — открывает постоянное подключение к удаленной базе данных, небезопасно

dblink_disconnect — закрывает постоянное подключение к удаленной базе данных

dblink — выполняет запрос в удаленной базе данных

dblink_exec — выполняет команду в удаленной базе данных

dblink_open — открывает курсор в удаленной базе данных

dblink_fetch — возвращает строки из открытого курсора в удаленной базе данных

dblink_close — закрывает курсор в удаленной базе данных

dblink_get_connections — возвращает имена всех открытых именованных подключений dblink

dblink_error_message — выдает сообщение последней ошибки для именованного подключения

dblink_send_query — передает асинхронный запрос в удаленную базу данных

dblink_is_busy — проверяет, не занято ли подключение асинхронным запросом

dblink_get_notify — выдает асинхронные уведомления подключения

dblink_get_result — получает результат асинхронного запроса

dblink_cancel_query — отменяет любой активный запрос в именованном подключении

dblink_get_pkey — возвращает позиции и имена полей первичного ключа отношения

dblink_build_sql_insert — формирует оператор INSERT из локального кортежа, заменяя значения полей первичного ключа переданными альтернативными значениями

dblink_build_sql_delete — формирует оператор DELETE со значениями, передаваемыми для полей первичного ключа

dblink_build_sql_update — формирует оператор UPDATE из локального кортежа, заменяя значения полей первичного ключа переданными альтернативными значениями

dict_int — представляет собой пример дополнительного шаблона словаря для полнотекстового поиска, который был создан для управления индексацией целых чисел (со знаком и без)

Конфигурация

Применение

dict_xsyn — представляет собой пример дополнительного шаблона словаря для полнотекстового поиска, который заменяет слова группами их синонимов, тем самым позволяя находить слово по одному из его синонимов

Конфигурация

Применение

earthdistance — предоставляет два разных способа вычисления ортодромии (расстояния по дуге большого круга; кратчайшего расстояния между двумя точками на поверхности Земли)

Земные расстояния по кубам

Земные расстояния по точкам

file_fdw — предоставляет обертку сторонних данных file_fdw, с помощью которой можно обращаться к файлам данных в файловой системе сервера или выполнять программы на сервере и читать их вывод

fuzzystrmatch — предоставляет несколько функций для определения схожести и расстояния между строками

Soundex

Левенштейн

Metaphone

Double Metaphone

hstore — реализует тип данных hstore для хранения наборов пар ключ/значение внутри одного значения QHB

Внешнее представление hstore

Операторы и функции hstore

Индексы

Примеры

Статистика

Авторы

intagg — предоставляет агрегатор и нумератор целых чисел

Функции

Примеры использования

intarray — предоставляет ряд полезных функций и операторов для работы с массивами целых чисел без NULL

Функции и операторы intarray

Поддержка индексов

Пример

Тестирование производительности

Авторы

isn — предоставляет типы данных для следующих международных стандартов нумерации товаров: EAN13, UPC, ISBN (книги), ISMN (музыка) и ISSN (серийные номера)

Типы данных

Приведения

Функции и операторы

Примеры

Библиография

Автор

lo — предоставляет поддержку управления большими объектами

Обоснование

Как его использовать

Ограничения

Автор

ltree — реализует тип данных ltree для представления меток данных, хранящихся в древовидной иерархической структуре

Определения

Операторы и функции

Индексы

Пример

Авторы

old_snapshot — позволяет ознакомиться с состоянием сервера, которое используется в реализации old_snapshot_threshold

Функции

pageinspect — предоставляет функции, позволяющие просматривать содержимое страниц базы данных на низком уровне

Функции общего назначения

Функции для исследования кучи

Функции для индексов B-деревьев

Функции для индексов BRIN

Функции для индексов GIN

Функции для индексов GiST

Функции для хеш-индексов

passwordcheck — проверяет пароли пользователей, задаваемые командами CREATE ROLE или ALTER ROLE

pg_buffercache — предоставляет возможность проверить, что происходит в общем кэше буферов в реальном времени

Представление pg_buffercache

Пример вывода

Авторы

pgcrypto — предоставляет криптографические функции для QHB

Стандартные функции хеширования

Функции хеширования пароля

Функции шифрования на основе PGP

Низкоуровневые функции шифрования

Функции получения случайных данных

Примечания

Автор

pg_freespacemap — обеспечивает возможность исследовать карту свободного пространства

Функции

Пример вывода

Автор

pg_prewarm — предоставляет оптимальный способ загружать данные отношений в кэш буферов операционной системы или в кэш буферов QHB

Функции

Параметры конфигурации

Автор

pgrowlocks — предоставляет функцию, показывающую информацию о блокировке строк для заданной таблицы

Обзор

Пример вывода

Автор

pg_stat_statements — предоставляет возможность отслеживать статистику планирования и выполнения сервером всех операторов SQL

Представление pg_stat_statements

Представление pg_stat_statements_info

Функции

Параметры конфигурации

Пример вывода

Авторы

pgstattuple — предоставляет различные функции для получения статистики на уровне кортежей

Функции

Авторы

pg_surgery — предоставляет различные функции для проведения операций с поврежденными отношениями

Функции

Авторы

pg_trgm — предоставляет функции и операторы для определения схожести буквенно-цифрового текста на основе соответствия триграмм, а также классы операторов индексов, поддерживающие быстрый поиск схожих строк

Общее представление о триграммах (или триграфах)

Функции и операторы

Параметры GUC

Поддержка индексов

Интеграция с текстовым поиском

Ссылки

Авторы

pg_visibility — предоставляет средства для исследования карты видимости и информации о видимости на уровне страниц для таблицы, а также функции для проверки целостности карты видимости и ее принудительного пересоздания

Функции

Автор

postgres_fdw — предоставляет обертку сторонних данных postgres_fdw, которую можно использовать для обращения к данным, хранящимся на внешних серверах QHB

Параметры обертки сторонних данных для postgres_fdw

Функции

Управление соединением

Управление транзакциями

Оптимизация удаленных запросов

Среда удаленного выполнения запросов

Совместимость с разными версиями

Примеры

Автор

seg — реализует тип данных seg для представления отрезков или интервалов с плавающей запятой

Обоснование

Синтаксис

Точность

Применение

Примечания

Благодарности

sepgsql — поддерживает мандатное управление доступом с использованием меток, работающее на основе политик безопасности SELinux

Обзор

Установка

Регрессионные тесты

Параметры GUC

Функциональность

Функции sepgsql

Ограничения

Внешние ресурсы

Автор

spi — предоставляет несколько рабочих примеров использования Интерфейса программирования сервера (SPI) и триггеров

refint — функции для реализации ссылочной целостности

autoinc — функции для автоувеличения полей

insert_username — функции для отслеживания пользователя, меняющего таблицу

moddatetime — функции для отслеживания времени последнего изменения

sslinfo — предоставляет информацию о SSL-сертификате, который был предоставлен текущим клиентом при подключении к QHB

Предоставляемые функции

Автор

tablefunc — содержит ряд функций, возвращающих таблицы (то есть множества строк)

Предоставляемые функции

Автор

tcn — предоставляет триггерную функцию, уведомляющую блоки прослушивания об изменениях в любой таблице, к которой она привязана

test_decoding — представляет собой пример плагина вывода логического декодирования

tsm_system_rows — предоставляет метод взятия выборки из таблицы SYSTEM_ROWS, который можно использовать в предложении TABLESAMPLE команды SELECT

Примеры

tsm_system_time — предоставляет метод взятия выборки из таблицы SYSTEM_TIME, который можно использовать в предложении TABLESAMPLE команды SELECT

Примеры

unaccent — представляет собой словарь текстового поиска, убирающий надбуквенные (диакритические) знаки из лексем

Конфигурация

Применение

Функции

uuid-ossp — предоставляет функции для генерирования универсальных уникальных идентификаторов (UUID) по одному из нескольких стандартных алгоритмов

Функции uuid-ossp

Сборка uuid-ossp

Автор

xml2 — предоставляет функциональные возможности для выполнения запросов XPath и преобразований XSLT

Уведомление об исключении из числа рекомендованных

Описание функций

xpath_table

Функции XSLT

Автор

adminpack

Модуль adminpack предоставляет ряд вспомогательных функций, которыми могут пользоваться pgAdmin и другие средства администрирования и управления для предоставления дополнительной функциональности, например для удаленного управления файлами журналов сервера. По умолчанию использовать все эти функции разрешено только суперпользователю, но такое право можно дать и другим пользователям с помощью команды GRANT.

Функции, приведенные в Таблице 1, предоставляют возможность записи в файлы на компьютере, где работает сервер. (См. также функции в таблице Функции доступа к типовым файлам, которые открывают доступ только на чтение.) Обычным пользователям они предоставляют доступ только к файлам в каталоге кластера базы данных, но не ограничивают суперпользователей или членов ролей pg_read_server_files или pg_write_server_files. При этом путь может задаваться как относительный, так и абсолютный.

Таблица 1. Функции модуля adminpack

Функция

Описание

pg_catalog.pg_file_write ( имя_файла text, данные text, append boolean ) → bigint

Записывает или дописывает данные в текстовый файл.

pg_catalog.pg_file_sync ( имя_файла text ) → void

Сбрасывает файл или каталог на диск.

pg_catalog.pg_file_rename ( старое_имя text, новое_имя text [, архивное_имя text ] ) → boolean

Переименовывает файл.

pg_catalog.pg_file_unlink ( имя_файла text ) → boolean

Удаляет файл.

pg_catalog.pg_logdir_ls () → setof record

Выводит список файлов журналов в каталоге log_directory.

Функция pg_file_write записывает заданные данные в файл имя_файла. Если append равен false, этот файл не должен существовать. Если же append равен true, существование этого файла допускается, и в этом случае данные будут дописаны в него. Возвращает число записанных байт.

Функция pg_file_sync синхронизирует с файловой системой файл или каталог имя_файла. В случае неудачи (например, если указанный файл не существует) выдается ошибка. Обратите внимание, что значение параметра data_sync_retry на эту функцию не влияет, и поэтому даже при сбое во время сброса файлов базы данных на диск не будет выдана ошибка уровня PANIC.

Функция pg_file_rename переименовывает файл. Если параметр архивное_имя опущен или равен NULL, она просто переименовывает файл старое_имя в новое_имя (файл с новым именем не должен существовать). Если же параметр архивное_имя задан, она сначала переименовывает новое_имя в архивное_имя (файл с таким именем не должен существовать), а затем переименовывает старое_имя в новое_имя. В случае сбоя на втором этапе переименовывания она попытается переименовать архивное_имя обратно в новое_имя, прежде чем выдать ошибку. Возвращает true в случае успеха и false, если исходные файлы отсутствуют или их невозможно изменить; в остальных случаях выдаются ошибки.

Функция pg_file_unlink удаляет заданный файл. Возвращает true в случае успеха и false, если заданный файл отсутствует либо при сбое в вызове unlink(); в остальных случаях выдаются ошибки.

Функция pg_logdir_ls возвращает временные метки и пути всех файлов журналов в каталоге, заданном в log_directory. Чтобы эта функция работала, параметр log_filename должен иметь значение по умолчанию (qhb-%Y-%m-%d_%H%M%S.log).

amcheck

Модуль amcheck предоставляет функции, позволяющие проверять логическую целостность структуры отношений.

Функции проверки B-дерева проверяют различные инварианты в структуре представления определенных отношений. Корректность работы функций метода доступа, стоящих за сканированием индекса и другими важными операциями, зависит от всегда соблюдаемых инвариантов. Например, определенные функции проверяют, в том числе, что все страницы B-дерева содержат элементы в «логическом» порядке (например, у индексов B-деревьев по столбцу text кортежи должны быть упорядочены в лексическом порядке по правилу сортировки). Если этот конкретный инвариант каким-то образом нарушается, можно ожидать, что двоичный поиск на затронутой странице задаст ошибочное направление сканированию индекса, приведя к неверным ответам на запросы SQL. Если структура оказывается допустимой, ошибок не возникает.

Проверка осуществляется теми же процедурами, которые используются при сканировании индекса, и это может быть код пользовательского класса операторов. Например, проверка индекса B-дерева прибегает к сравнениям, выполняемым одной или несколькими вспомогательными функциями B-дерева под номером 1. Подробнее вспомогательные функции классов операторов описываются в подразделе Вспомогательные процедуры индексного метода.

В отличие функций проверки B-дерева, которые сообщают о повреждениях, выдавая ошибки, функция проверки кучи verify_heapam проверяет таблицу и пытается вернуть набор строк: по одной строке на каждое обнаруженное повреждение. Несмотря на это, если будут повреждены средства, которые задействует сама функция verify_heapam, она не сможет продолжить работу и вместо этого может выдать ошибку.

Право на выполнение функций amcheck можно выдавать и не суперпользователям, но перед этим следует тщательно проанализировать аспекты безопасности и конфиденциальности данных. Хотя сообщения о повреждениях, выдаваемые этими функциями, делают акцент не на содержимом поврежденных данных, а скорее на их структуре и характере найденных повреждений, злоумышленник, получивший разрешение на выполнение этих функций (особенно если ему также удастся вызвать повреждение данных), сможет из таких сообщений узнать что-то о самих данных.

Функции

bt_index_check(index regclass, heapallindexed boolean) returns void

bt_index_check проверяет, соблюдаются ли в целевом объекте, индексе B-дереве, различные инварианты. Пример использования:

test=# SELECT bt_index_check(index => c.oid, heapallindexed => i.indisunique),
               c.relname,
               c.relpages
FROM pg_index i
JOIN pg_opclass op ON i.indclass[0] = op.oid
JOIN pg_am am ON op.opcmethod = am.oid
JOIN pg_class c ON i.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE am.amname = 'btree' AND n.nspname = 'pg_catalog'
-- Не проверять временные таблицы, т. к. они могут относиться к другим сеансам:
AND c.relpersistence != 't'
-- Без этих условий функция может выдать ошибку:
AND c.relkind = 'i' AND i.indisready AND i.indisvalid
ORDER BY c.relpages DESC LIMIT 10;
 bt_index_check |             relname             | relpages
----------------+---------------------------------+----------
                | pg_depend_reference_index       |       43
                | pg_depend_depender_index        |       40
                | pg_proc_proname_args_nsp_index  |       31
                | pg_description_o_c_o_index      |       21
                | pg_attribute_relid_attnam_index |       14
                | pg_proc_oid_index               |       10
                | pg_attribute_relid_attnum_index |        9
                | pg_amproc_fam_proc_index        |        5
                | pg_amop_opr_fam_index           |        5
                | pg_amop_fam_strat_index         |        5
(10 rows)

Этот пример демонстрирует сеанс проверки 10 самых больших индексов каталогов в базе данных «test». Проверка наличия кортежей кучи в виде индексных кортежей запрашивается только для тех из этих индексов, которые являются уникальными. Поскольку ошибки не было, все проверенные индексы представляются логически целостными. Естественно, этот запрос легко можно изменить, чтобы функция bt_index_check вызывалась для всех индексов в базе данных, которые поддерживают эту проверку.

Функция bt_index_check запрашивает блокировку AccessShareLock для целевого индекса и отношения в куче, которому он принадлежит. Это тот же режим блокировки, что запрашивается для отношений обычными операторами SELECT. bt_index_check не проверяет инварианты, охватывающие взаимоотношения потомок/родитель, но проверит наличие всех кортежей кучи в индексе в виде индексных кортежей, когда параметр heapallindexed равен true. Когда в работающей производственной среде требуется регулярная упрощенная проверка, использование bt_index_check часто предоставляет наилучший компромисс между полнотой проверки и ограничением влияния на производительность и доступность приложения.

bt_index_parent_check(index regclass, heapallindexed boolean, rootdescend boolean) returns void

Функция bt_index_parent_check проверяет, соблюдаются ли в целевом объекте, индексе B-дереве, различные инварианты. Кроме того, когда аргумент heapallindexed равен true, эта функция проверяет наличие всех кортежей кучи, которые должны в нем находиться. Когда необязательный аргумент rootdescend равен true, при проверке для каждого кортежа на уровне листьев проводится повторный поиск, начиная с корневой страницы. Проверки, которые может осуществить bt_index_parent_check, являются расширенным набором проверок, выполняемых функцией bt_index_check. Функцию bt_index_parent_check можно считать более полным вариантом bt_index_check: в отличие от bt_index_check, bt_index_parent_check проверяет еще и инварианты, которые охватывают взаимоотношения потомок/родитель, в том числе на отсутствие потерянных связей в структуре индекса. bt_index_parent_check следует общему соглашению о выдаче ошибки в случае обнаружения логической несогласованности или другой проблемы.

Функция bt_index_parent_check запрашивает в целевом индексе блокировку ShareLock (которая также запрашивается в отношении в куче). Эти блокировки предотвращают одновременное изменение данных командами INSERT, UPDATE и DELETE. Эти блокировки также предотвращают одновременную обработку нижележащего отношения командой VACUUM и другими сервисными командами. Обратите внимание, что эта функция удерживает блокировки только во время выполнения, а не на протяжении всей транзакции.

Дополнительная проверка, проводимая функцией bt_index_parent_check, в большей степени ориентирована на выявление различных патологических случаев. Такие случаи могут включать в себя неправильно реализованный класс операторов B-дерева, используемый проверяемым индексом, или, гипотетически, неизвестные ошибки в нижележащем коде индексного метода доступа B-дерева. Обратите внимание, что bt_index_parent_check нельзя применять, когда включен режим горячего резерва (т. е. на физических репликах в режиме «только чтение»), в отличие от bt_index_check.

Совет
Функции bt_index_check и bt_index_parent_check выводят сообщения журнала о процессе проверки на уровнях важности DEBUG1 и DEBUG2. Эти сообщения содержат подробную информацию о процессе проверки, которая может представлять интерес для разработчиков QHB. Также эта информация может быть полезна для продвинутых пользователей, поскольку обеспечивает дополнительный контекст в случае, если при проверке действительно обнаружится несогласованность. Чтобы получать сообщения о процессе проверки с подходящим уровнем детализации, необходимо перед запуском проверяющего запроса выполнить в интерактивном сеансе psql:

 SET client_min_messages = DEBUG1;

verify_heapam(relation regclass, on_error_stop boolean, check_toast boolean, skip text, startblock bigint, endblock bigint, blkno OUT bigint, offnum OUT integer, attnum OUT integer, msg OUT text) returns setof record

Проверяет таблицу на наличие структурных повреждений (когда страницы отношения неправильно отформатированы) и логических повреждений (когда страницы структурно не повреждены, но не соответствуют остальной части кластера баз данных).

Принимаются следующие необязательные аргументы:

on_error_stop
Если true, проверка на наличие повреждений останавливается в конце первого блока, в котором обнаружены какие-либо повреждения.
По умолчанию false.

check_toast
Если true, значения TOAST проверяются по таблице TOAST целевого отношения.
Эта проверка может быть медленной. Кроме того, если таблица TOAST или ее индекс повреждены, проверка значений TOAST потенциально может привести к сбою сервера, хотя во многих случаях это просто вызовет ошибку.
По умолчанию false.

skip
Если указано значение, отличное от none, проверка на наличие повреждений пропускает блоки, помеченные как полностью видимые или полностью замороженные, в зависимости от указанного значения. Допустимые варианты: all-visible (полностью видимые), all-frozen (полностью замороженные) и none.
По умолчанию none.

startblock
Если этот параметр задан, проверка на наличие повреждений начинается с указанного блока, пропуская все предыдущие блоки. Если в startblock указано значение вне диапазона блоков целевой таблицы, выдается ошибка.
По умолчанию проверка начинается с первого блока.

endblock
Если этот параметр задан, проверка на наличие повреждений заканчивается на указанном блоке, пропуская все оставшиеся блоки. Если в endblock указано значение вне диапазона блоков целевой таблицы, выдается ошибка.
По умолчанию проверяются все блоки.

Для каждого выявленного повреждения verify_heapam возвращает строку со следующими столбцами:

blkno
Номер блока, содержащего поврежденную страницу.

offnum
Номер смещения поврежденного кортежа.

attnum
Номер атрибута поврежденного столбца в кортеже, если поврежден именно столбец, а не весь кортеж.

msg
Сообщение с описанием выявленной проблемы.

Дополнительная проверка heapallindexed

Когда аргумент heapallindexed функций проверки B-дерева равен true, для таблицы, связанной с отношением целевого индекса, проводится дополнительная фаза проверки. Она включает «фиктивную» операцию CREATE INDEX, которая проверяет наличие всех гипотетических новых индексных кортежей по временной сводной структуре в памяти (она создается при необходимости во время основной первой фазы проверки). Сводная структура «помечает» каждый кортеж, найденный в целевом индексе. На высоком уровне основная идея проверки heapallindexed состоит в том, что новый индекс, равнозначный уже существующему, целевому индексу, должен содержать только те записи, которые можно найти в существующей структуре.

Дополнительная фаза heapallindexed приводит к значительным издержкам: как правило, проверка выполняется в несколько раз дольше обычного. Однако никакие новые блокировки уровня отношения при проверке heapallindexed не запрашиваются.

Сводная структура ограничивается по объему значением maintenance_work_mem. Чтобы убедиться, что вероятность пропустить несогласованность в кортежах кучи, которые должны быть представлены в индексе, составляет менее 2%, требуется приблизительно 2 байта памяти на кортеж. По мере уменьшения объема памяти в пересчете на кортеж эта вероятность медленно растет. Такой подход значительно ограничивает издержки проверки, при этом лишь немного снижая вероятность выявления проблемы, особенно в установках, где проверка включена в процедуру регулярного обслуживания. При очередной проверке возникает новый шанс выявить пропущенное единичное отсутствие или повреждение кортежа.

Эффективное использование amcheck

Модуль amcheck может быть полезен для выявления различных типов отказов, которые могут остаться незамеченными при включении контрольных сумм. В частности это:

Структурные несоответствия, вызванные некорректной реализацией класса операторов.

В том числе это проблемы, вызванные изменениями правил сравнения в операционной системе. Сравнения данных сортируемого типа, например text, должны быть постоянными (как и все сравнения, применяемые при сканировании индекса B дерева), что подразумевает полную неизменность правил сортировки в операционной системе. Подобные проблемы, хоть и редко, могут возникать при обновлениях правил сортировки в операционной системе. Гораздо чаще проявляются несоответствия порядка сортировки между главным и резервным серверами, вероятно, из-за различия основной версии используемой операционной системы. Такие несоответствия обычно возникают только на резервных серверах, поэтому и выявить их обычно можно только на них.

Если возникает подобная проблема, она может затрагивать не полностью все индексы, построенные с пострадавшим правилом сортировки, просто потому что индексированные значения могут иметь тот же абсолютный порядок, независящий от различий поведения. Дополнительную информацию об использовании в QHB локалей и правил сортировки операционной системы см. в разделах Поддержка языковых стандартов и Поддержка правил сортировки.

Структурные несоответствия между индексами и проиндексированными отношениями в куче (когда выполняется проверка heapallindexed).

Во время обычных операций перекрестная проверка индексов по их отношениям в куче не проводится. Симптомы повреждения данных в куче могут быть незаметными.

Повреждение, вызванное гипотетическими неизвестными ошибками в нижележащем коде методов доступа, коде сортировки или коде управления транзакциями QHB.

Автоматическая проверка структурной целостности индексов играет важную роль в общем тестировании новых или предлагаемых возможностей QHB, с которыми возможно возникновение логической несогласованности. Такую же роль играет проверка структуры таблицы и связанной информации о видимости и состоянии транзакций. Поэтому одна из очевидных стратегий тестирования — регулярно вызывать функции amcheck при проведении стандартных регрессионных тестов. Подробную информацию о проведении тестов см. в разделе Выполнение тестов.

Ошибки в файловой системе или подсистеме хранения, когда просто не включены контрольные суммы.

Обратите внимание, что amcheck изучает страницу в том виде, в котором она представлена в некоем буфере разделяемой памяти на момент проверки, если при обращении к блоку тот уже находится в разделяемом буфере. Как следствие, amcheck не обязательно просматривает данные, считанные из файловой системы на момент проверки. Обратите внимание, что когда включены контрольные суммы, amcheck может выдать ошибку из-за несоответствия контрольных сумм, когда в буфер считывается поврежденный блок.

Повреждение, вызванное дефектным ОЗУ или вообще подсистемой памяти.

QHB не защищает от корректируемых ошибок памяти и предполагает, что вы будете применять в работе ОЗУ, использующее коды исправления ошибок (Error Correcting Codes, ECC), соответствующие отраслевому стандарту, или лучшую защиту. Однако память ECC memory обычно защищена только от ошибок в одном бите, и не следует считать ее абсолютной защитой от сбоев, приводящих к повреждению памяти.

Когда выполняется проверка heapallindexed, в целом значительно возрастает шанс выявить ошибки в отдельных битах, поскольку она тестирует точное двоичное равенство и индексированные атрибуты в куче.

Структурное повреждение может произойти по причине неисправности оборудования систем хранения или перезаписи либо изменения файлов отношения сторонним ПО. Повреждение такого рода также можно выявить с помощью контрольных сумм страниц данных.

Даже правильно форматированные и внутренне согласованные страницы отношений, данные которых соответствуют их контрольным суммам, все равно могут иметь логические повреждения. По этой причине повреждения такого типа невозможно выявить с помощью контрольных сумм. Примерами могут служить значения TOAST в основной таблице, для которых нет соответствующей записи в таблице TOAST, и кортежи в основной таблице с идентификатором транзакции, который старше, чем самый старый действительный идентификатор транзакции в базе данных или кластере.

В производственных системах наблюдаются разнообразные причины логических повреждений, в том числе: ошибки в серверном ПО QHB, неисправные и непродуманные инструменты резервного копирования и восстановления и ошибки пользователей.

Поврежденные отношения особенно опасны в работающей производственной среде, а точнее, в той среде, где менее всего приветствуется деятельность, сопряженная с высоким риском. По этой причине для диагностики повреждения данных без излишнего риска была разработана функция verify_heapam. Она не в состоянии защитить от всех сбоев сервера, так как даже выполнение вызывающего запроса может быть небезопасным на сильно поврежденной системе. Если повреждены сами каталоги, возможны проблемы с доступом к их таблицам.

В целом, amcheck может доказать только наличие повреждения, но не его отсутствие.

Исправление повреждений

Когда amcheck сигнализирует о повреждением данных, ложные срабатывания практически исключены. amcheck выдает ошибки при возникновении условий, которые никогда не должны наблюдаться по определению, поэтому ошибки amcheck зачастую требуют тщательного анализа.

Общего метода устранения проблем, выявляемых amcheck, не существует. Следует искать корень проблемы, приводящей к нарушению инварианта. Полезную роль в диагностике повреждений, которые выявляет amcheck, может сыграть модуль pageinspect. Для исправления повреждений одной лишь команды REINDEX может быть недостаточно.

auth_delay

Модуль auth_delay заставляет сервер сделать небольшую паузу перед тем, как выдать сообщение об ошибке аутентификации, чтобы усложнить атаки методом прямого подбора паролей к базам данных. Обратите внимание, что это никак не препятствует атакам типа «отказ в обслуживании» и может даже усугубить их, поскольку процессы, ожидающие сообщения об ошибке аутентификации, все равно будут занимать слоты подключения.

Чтобы этот модуль работал, его нужно загрузить посредством параметра конфигурации shared_preload_libraries в qhb.conf.

Параметры конфигурации

auth_delay.milliseconds (int)
Пауза (в миллисекундах), которую нужно выдержать, прежде чем сообщать об ошибке аутентификации. Значение по умолчанию — 0.

Эти параметры следует задавать в qhb.conf. Обычное использование может выглядеть так:

# qhb.conf
shared_preload_libraries = 'auth_delay'

auth_delay.milliseconds = '500'

Автор

КайГай Кохэй (KaiGai Kohei), kaigai@ak.jp.nec.com

auto_explain

Модуль auto_explain предоставляет возможность автоматического протоколирования планов выполнения медленных операторов, без необходимости выполнения EXPLAIN вручную. Это особенно полезно для обнаружения неоптимизированных запросов в больших приложениях.

Этот модуль не предоставляет функций, доступных из SQL. Чтобы использовать его, просто загрузите его на сервер. Это можно сделать в отдельном сеансе:

LOAD 'auto_explain';

(Для этого нужно быть суперпользователем.) Более типична практика, когда он предварительно загружается в некоторые или все сеансы путем включения auto_explain в переменную session_preload_libraries или shared_preload_libraries в файле конфигурации qhb.conf. После этого можно отслеживать неожиданно медленные запросы, независимо от того, где они происходят. Разумеется, это приводит к издержкам.

Параметры конфигурации

Есть несколько параметров конфигурации, которые управляют поведением auto_explain. Обратите внимание, что по умолчанию они ничего не делают, поэтому если вы хотите получить какие-либо результаты, необходимо установить как минимум параметр auto_explain.log_min_duration.

auto_explain.log_min_duration (integer)
Этот параметр задает минимальное время выполнения оператора в миллисекундах, при превышении которого план оператора будет протоколироваться. При значении 0 протоколируются все планы. При значении -1 (по умолчанию) протоколирование планов выключается. Например, если установить значение 250ms, то будут протоколироваться все операторы, выполняющиеся 250 мс и дольше. Изменить этот параметр могут только суперпользователи.

auto_explain.log_analyze (boolean)
При включении этого параметра в журнал будет записываться вывод команды EXPLAIN ANALYZE, а не простой EXPLAIN. По умолчанию он выключен. Изменить этот параметр могут только суперпользователи.

Примечание
Когда этот параметр включен, замер времени на уровне узлов плана производится для всех операторов, даже если они выполняются недостаточно долго для протоколирования. Это может оказать крайне негативное влияние на производительность. Выключение auto_explain.log_timing нейтрализует это влияние, но при этом собирается меньше информации.

auto_explain.log_buffers (boolean)
Этот параметр определяет, будет ли при протоколировании плана выполнения выводиться статистика использования буферов; он равнозначен параметру BUFFERS команды EXPLAIN. Этот параметр действует только при включенном параметре auto_explain.log_analyze. По умолчанию auto_explain.log_buffers выключен. Изменить этот параметр могут только суперпользователи.

auto_explain.log_wal (boolean)
Этот параметр определяет, будет ли при протоколировании плана выполнения выводиться статистка использования WAL; он равнозначен параметру WAL команды EXPLAIN. Этот параметр действует только при включенном параметре auto_explain.log_analyze. По умолчанию auto_explain.log_wal выключен. Изменить этот параметр могут только суперпользователи.

auto_explain.log_timing (boolean)
Этот параметр определяет, будет ли при протоколировании плана выполнения выводиться информация о длительности на уровне узлов; он равнозначен параметру TIMING команды EXPLAIN. Издержки от постоянного чтения системных часов могут значительно замедлить запросы в некоторых системах, поэтому может иметь смысл выключать этот параметр, когда нужно знать только количество строк, а не точную длительность каждого узла. Этот параметр действует только при включенном параметре auto_explain.log_analyze. По умолчанию auto_explain.log_timing выключен. Изменить этот параметр могут только суперпользователи.

auto_explain.log_triggers (boolean)
При включении этого параметра в журнал будет записываться статистика выполнения триггеров. Этот параметр действует только при включенном параметре auto_explain.log_analyze. По умолчанию auto_explain.log_triggers выключен. Изменить этот параметр могут только суперпользователи.

auto_explain.log_verbose (boolean)
Этот параметр определяет, будут ли при протоколировании плана выполнения выводиться подробные сведения; он равнозначен параметру VERBOSE команды EXPLAIN. По умолчанию он выключен. Изменить этот параметр могут только суперпользователи.

auto_explain.log_settings (boolean)
Этот параметр определяет, будет ли при протоколировании плана выполнения выводиться информация об измененных параметрах конфигурации. При этом в вывод попадут только те параметры, которые влияют на планирование запросов и имеют значения, отличающиеся от встроенных значений по умолчанию. По умолчанию auto_explain.log_settings выключен. Изменить этот параметр могут только суперпользователи.

auto_explain.log_format (enum)
Этот параметр выбирает формат вывода для команды EXPLAIN. Допустимые значения: text, xml, json и yaml. Значение по умолчанию — text. Изменить этот параметр могут только суперпользователи.

auto_explain.log_level (enum)
Этот параметр выбирает уровень, с которым auto_explain будет протоколировать планы запросов. Допустимые значения: DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING и LOG. Значение по умолчанию — LOG. Изменить этот параметр могут только суперпользователи.

auto_explain.log_nested_statements (boolean)
При включении этого параметра протоколированию могут подлежать и вложенные операторы (операторы, выполняемые внутри функции). При его выключении протоколируются только планы запросов верхнего уровня. По умолчанию он выключен. Изменить этот параметр могут только суперпользователи.

auto_explain.sample_rate (real)
Этот параметр задает для auto_explain процент операторов, который будет рассматриваться в каждом сеансе. Значение по умолчанию равно 1, то есть рассматриваются все запросы. Вложенные операторы рассматриваются полностью — либо все, либо никакой из них. Изменить этот параметр могут только суперпользователи.

В обычной ситуации эти параметры устанавливаются в qhb.conf, хотя суперпользователи могут изменять их на ходу в рамках своих сеансов. Типичное их использование может выглядеть так:

# qhb.conf
session_preload_libraries = 'auto_explain'

auto_explain.log_min_duration = '3s'

Пример

qhb=# LOAD 'auto_explain';
qhb=# SET auto_explain.log_min_duration = 0;
qhb=# SET auto_explain.log_analyze = true;
qhb=# SELECT count(*)
        FROM pg_class, pg_index
        WHERE oid = indrelid AND indisunique;

В результате можно получить такой вывод:

LOG:  duration: 3.651 ms  plan:
  Query Text: SELECT count(*)
              FROM pg_class, pg_index
              WHERE oid = indrelid AND indisunique;
  Aggregate  (cost=16.79..16.80 rows=1 width=0) (actual time=3.626..3.627 rows=1 loops=1)
    ->  Hash Join  (cost=4.17..16.55 rows=92 width=0) (actual time=3.349..3.594 rows=92 loops=1)
          Hash Cond: (pg_class.oid = pg_index.indrelid)
          ->  Seq Scan on pg_class  (cost=0.00..9.55 rows=255 width=4) (actual time=0.016..0.140 rows=255 loops=1)
          ->  Hash  (cost=3.02..3.02 rows=92 width=4) (actual time=3.238..3.238 rows=92 loops=1)
                Buckets: 1024  Batches: 1  Memory Usage: 4kB
                ->  Seq Scan on pg_index  (cost=0.00..3.02 rows=92 width=4) (actual time=0.008..3.187 rows=92 loops=1)
                      Filter: indisunique

Автор

Такахиро Итагаки (Takahiro Itagaki), itagaki.takahiro@oss.ntt.co.jp

bloom

Модуль bloom предоставляет индексный метод доступа, основанный на фильтрах Блума.

Фильтр Блума представляет собой экономичную структуру данных, которая применяется для проверки того, является ли элемент членом множества. В виде индексного метода доступа он позволяет быстро исключать неподходящие кортежи по сигнатурам, размер которых определяется при создании индекса.

Сигнатура — это неточное представление проиндексированных атрибутов, вследствие чего оно склонно выдавать ложноположительные результаты; то есть оно может показывать, что элемент содержится в множестве, хотя это не так. Поэтому результаты поиска по индексу всегда должны перепроверяться с помощью фактических значений атрибутов из записи в куче. Чем больше размер сигнатуры, тем меньше вероятность ложного срабатывания и, следовательно, число бесполезных обращений к куче, но, разумеется, это приводит к увеличению индекса и, соответственно, замедлению сканирования.

Этот тип индекса наиболее полезен, когда в таблице много атрибутов и в запросах проверяются их произвольные сочетания. Традиционный индекс В-дерево быстрее индекса Блума, но для поддержки всех возможных запросов может потребоваться много индексов В-деревьев, при том что индекс Блума нужен всего один. Обратите внимание, однако, что индексы Блума поддерживают только запросы на проверку равенства, тогда как индексы В-деревья могут также применяться при проверке неравенств и поисках в диапазоне.

Параметры

Индекс bloom принимает в своем предложении WITH следующие параметры:

length
Длина каждой сигнатуры (записи индекса) в битах, округленная вверх до ближайшего числа, кратного 16. Значение по умолчанию — 80 бит, а максимальное значение — 4096.

col1 — col32
Количество битов, генерируемых для каждого столбца индекса. В имени параметра отражается номер столбца индекса, которым этот параметр управляет. Значение по умолчанию — 2 бита, а максимальное значение — 4095. Параметры для неиспользуемых столбцов индекса игнорируются.

Примеры

Это пример создания индекса bloom:

CREATE INDEX bloomidx ON tbloom USING bloom (i1,i2,i3)
       WITH (length=80, col1=2, col2=2, col3=4);

Будет создан индекс с длиной сигнатуры 80 бит, в которой атрибуты i1 и i2 отображаются в 2 бита, а атрибут i3 — в 4. Указания length, col1 и col2 можно было бы опустить, поскольку в них задаются значения по умолчанию.

Ниже приводится более полный пример определения и использования индекса Блума, а также его сравнение с равнозначным индексом В-деревом. Индекс Блума значительно меньше индекса В-дерева, и при этом может работать быстрее.

=# CREATE TABLE tbloom AS
   SELECT
     (random() * 1000000)::int as i1,
     (random() * 1000000)::int as i2,
     (random() * 1000000)::int as i3,
     (random() * 1000000)::int as i4,
     (random() * 1000000)::int as i5,
     (random() * 1000000)::int as i6
   FROM
  generate_series(1,10000000);
SELECT 10000000

Последовательное сканирование по этой большой таблице занимает много времени:

=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
                                              QUERY PLAN
-------------------------------------------------------------------​-----------------------------------
 Seq Scan on tbloom  (cost=0.00..2137.14 rows=3 width=24) (actual time=16.971..16.971 rows=0 loops=1)
   Filter: ((i2 = 898732) AND (i5 = 123451))
   Rows Removed by Filter: 100000
 Planning Time: 0.346 ms
 Execution Time: 16.988 ms
(5 rows)

Даже с индексом В-деревом сканирование остается последовательным:

=# CREATE INDEX btreeidx ON tbloom (i1, i2, i3, i4, i5, i6);
CREATE INDEX
=# SELECT pg_size_pretty(pg_relation_size('btreeidx'));
 pg_size_pretty
----------------
 3976 kB
(1 row)
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
                                              QUERY PLAN
-------------------------------------------------------------------​-----------------------------------
 Seq Scan on tbloom  (cost=0.00..2137.00 rows=2 width=24) (actual time=12.805..12.805 rows=0 loops=1)
   Filter: ((i2 = 898732) AND (i5 = 123451))
   Rows Removed by Filter: 100000
 Planning Time: 0.138 ms
 Execution Time: 12.817 ms
(5 rows)

Если определить для таблицы индекс Блума, поиск такого рода осуществляется эффективнее, нежели с индексом В-деревом:

=# CREATE INDEX bloomidx ON tbloom USING bloom (i1, i2, i3, i4, i5, i6);
CREATE INDEX
=# SELECT pg_size_pretty(pg_relation_size('bloomidx'));
 pg_size_pretty
----------------
 1584 kB
(1 row)
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
                                                     QUERY PLAN
-------------------------------------------------------------------​--------------------------------------------------
 Bitmap Heap Scan on tbloom  (cost=1792.00..1799.69 rows=2 width=24) (actual time=0.388..0.388 rows=0 loops=1)
   Recheck Cond: ((i2 = 898732) AND (i5 = 123451))
   Rows Removed by Index Recheck: 29
   Heap Blocks: exact=28
   ->  Bitmap Index Scan on bloomidx  (cost=0.00..1792.00 rows=2 width=0) (actual time=0.356..0.356 rows=29 loops=1)
         Index Cond: ((i2 = 898732) AND (i5 = 123451))
 Planning Time: 0.099 ms
 Execution Time: 0.408 ms
(8 rows)

Итак, основная проблема поиска по В-дереву состоит в том, что В-дерево неэффективно, когда условия поиска не ограничивают ведущие столбцы индекса. Более успешной стратегией при использовании В-дерева будет создание отдельного индекса для каждого столбца. Тогда планировщик выберет примерно такой план:

=# CREATE INDEX btreeidx1 ON tbloom (i1);
CREATE INDEX
=# CREATE INDEX btreeidx2 ON tbloom (i2);
CREATE INDEX
=# CREATE INDEX btreeidx3 ON tbloom (i3);
CREATE INDEX
=# CREATE INDEX btreeidx4 ON tbloom (i4);
CREATE INDEX
=# CREATE INDEX btreeidx5 ON tbloom (i5);
CREATE INDEX
=# CREATE INDEX btreeidx6 ON tbloom (i6);
CREATE INDEX
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
                                                        QUERY PLAN
-------------------------------------------------------------------​--------------------------------------------------------
 Bitmap Heap Scan on tbloom  (cost=24.34..32.03 rows=2 width=24) (actual time=0.028..0.029 rows=0 loops=1)
   Recheck Cond: ((i5 = 123451) AND (i2 = 898732))
   ->  BitmapAnd  (cost=24.34..24.34 rows=2 width=0) (actual time=0.027..0.027 rows=0 loops=1)
         ->  Bitmap Index Scan on btreeidx5  (cost=0.00..12.04 rows=500 width=0) (actual time=0.026..0.026 rows=0 loops=1)
               Index Cond: (i5 = 123451)
         ->  Bitmap Index Scan on btreeidx2  (cost=0.00..12.04 rows=500 width=0) (never executed)
               Index Cond: (i2 = 898732)
 Planning Time: 0.491 ms
 Execution Time: 0.055 ms
(9 rows)

Хотя этот запрос выполняется быстрее, чем с каким-либо одиночным индексом, мы платим за это увеличением размера индекса. Каждый индекс В-дерево занимает 2 МБ, так что общий объем индексов составляет 12 МБ, что в восемь раз больше объема индекса Блума.

Интерфейс класса операторов

Класс операторов для индексов Блума требует наличия только хеш-функции для индексируемого типа данных и оператора равенства для поиска. Этот пример демонстрирует определение класса операторов для типа данных text:

CREATE OPERATOR CLASS text_ops
DEFAULT FOR TYPE text USING bloom AS
    OPERATOR    1   =(text, text),
    FUNCTION    1   hashtext(text);

Ограничения

В этот модуль включены только классы операторов для int4 и text.

При поиске поддерживается только оператор =. Но в будущем возможно добавление поддержки для массивов с операциями объединения и пересечения.

Метод доступа bloom не поддерживает уникальные индексы (UNIQUE).

Метод доступа bloom не поддерживает поиск значений NULL.

Авторы

Федор Сигаев (teodor@postgrespro.ru), Postgres Professional, Москва, Россия

Александр Коротков (a.korotkov@postgrespro.ru), Postgres Professional, Москва, Россия

Олег Бартунов (obartunov@postgrespro.ru), Postgres Professional, Москва, Россия

btree_gin

Модуль btree_gin предоставляет типовые классы операторов GIN, реализующие поведение, схожее с тем, что реализуют классы В-дерева, для типов данных int2, int4, int8, float4, float8, timestamp with time zone, timestamp without time zone, time with time zone, time without time zone, date, interval, oid, money, "char", varchar, text, bytea, bit, varbit, macaddr, macaddr8, inet, cidr, uuid, name, bool, bpchar и всех перечислимых типов (enum).

В целом, эти классы операторов не будут работать быстрее аналогичных стандартных методов индекса В-дерева, и им не хватает одной важной функциональности стандартного кода В-дерева: способности обеспечивать соблюдение уникальности. Тем не менее, они полезны для тестирования GIN, а также в качестве основы для разработки других классов операторов GIN. Кроме того, для запросов, где проверяется и столбец с индексом GIN, и столбец с индексом B-деревом, возможно, удобнее будет создать многостолбцовый индекс GIN, который использует один из этих классов операторов, чем создавать два отдельных индекса, которые придется объединять, вычисляя AND битовых карт.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Пример использования

CREATE TABLE test (a int4);
-- создание индекса
CREATE INDEX testidx ON test USING GIN (a);
-- запрос
SELECT * FROM test WHERE a < 10;

Авторы

Федор Сигаев (teodor@stack.net) и Олег Бартунов (oleg@sai.msu.su). Дополнительную информацию можно найти на странице http://www.sai.msu.su/~megera/oddmuse/index.cgi/Gin.

btree_gist

Модуль btree_gist предоставляет классы операторов индексов GiST, реализующие поведение, схожее с тем, что реализуют классы В-дерева, для типов данных int2, int4, int8, float4, float8, numeric, timestamp with time zone, timestamp without time zone, time with time zone, time without time zone, date, interval, oid, money, char, varchar, text, bytea, bit, varbit, macaddr, macaddr8, inet, cidr, uuid и всех перечислимых типов (enum).

В целом, эти классы операторов не будут работать быстрее аналогичных стандартных методов индекса В-дерева, и им не хватает одной важной функциональности стандартного кода В-дерева: способности обеспечивать соблюдение уникальности. Тем не менее, они предоставляют несколько других функциональностей (описаны ниже), недоступных с индексом В-деревом. Кроме того, эти классы операторов полезны, когда требуется многостолбцовый индекс GiST, в котором некоторые столбцы имеют типы данных, индексируемые только с GiST, а другие — простые типы данных. Наконец, эти классы операторов полезны для тестирования GiST, а также в качестве основы для разработки других классов операторов GiST.

Помимо типичных операторов поиска по B-дереву, btree_gist также поддерживает использование индекса для операции <> («не равно»). Это может быть полезно в сочетании с ограничением-исключением, как описано ниже.

Также для типов данных, имеющих естественную метрику расстояния, btree_gist определяет оператор расстояния <-> и поддерживает использование индексов GiST для поиска ближайших соседей с помощью этого оператора. Операторы расстояния предоставлены для типов int2, int4, int8, float4, float8, timestamp with time zone, timestamp without time zone, time without time zone, date, interval, oid и money.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Пример использования

Простой пример использования btree_gist вместо btree:

CREATE TABLE test (a int4);
-- создание индекса
CREATE INDEX testidx ON test USING GIST (a);
-- запрос
SELECT * FROM test WHERE a < 10;
-- поиск ближайших соседей: найти десять записей, ближайших к "42"
SELECT *, a <-> 42 AS dist FROM test ORDER BY a <-> 42 LIMIT 10;

Использование ограничения-исключения для реализации правила, по которому в клетке в зоопарке могут содержаться животные только одного вида:

=> CREATE TABLE zoo (
  cage   INTEGER,
  animal TEXT,
  EXCLUDE USING GIST (cage WITH =, animal WITH <>)
);

=> INSERT INTO zoo VALUES(123, 'zebra');
INSERT 0 1
=> INSERT INTO zoo VALUES(123, 'zebra');
INSERT 0 1
=> INSERT INTO zoo VALUES(123, 'lion');
ERROR:  conflicting key value violates exclusion constraint "zoo_cage_animal_excl"
DETAIL:  Key (cage, animal)=(123, lion) conflicts with existing key (cage, animal)=(123, zebra).
-- ОШИБКА: конфликтующее значение ключа нарушает ограничение-исключение "zoo_cage_animal_excl"
-- ДЕТАЛИЗАЦИЯ: Ключ (cage, animal)=(123, lion) конфликтует с существующим ключом (cage, animal)=(123, zebra).
=> INSERT INTO zoo VALUES(124, 'lion');
INSERT 0 1

Авторы

Федор Сигаев (teodor@stack.net), Олег Бартунов (oleg@sai.msu.su), Янко Рихтер (Janko Richter) (jankorichter@yahoo.de) и Пол Юнгвирт (Paul Jungwirth) (pj@illuminatedcomputing.com). Дополнительную информацию можно найти на странице http://www.sai.msu.su/~megera/postgres/gist/.

citext

Модуль citext предоставляет тип данных для строк символов, не учитывающих регистр, citext. По сути, сравнивая значения, он вызывает внутри себя функцию lower. В остальном он ведет себя почти так же, как тип text.

Совет
Возможно, вместо этого модуля имеет смысл использовать недетерминированные правила сортировки (см. подраздел Недетерминированные правила сортировки). Их можно использовать для сравнения без учета регистра, без учета ударения и в других комбинациях; при этом они более корректно обрабатывают особые случаи Unicode.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Обоснование

Стандартный способ выполнить сравнение строк без учета регистра в QHB заключается в использовании функции lower при сравнении значений, например

SELECT * FROM tab WHERE lower(col) = LOWER(?);

Этот подход работает довольно хорошо, но имеет ряд недостатков:

Операторы SQL становятся громоздкими, и нужно не забывать всегда обрабатывать функцией lower и столбец, и значение запроса.

Индекс не будет использоваться, если только не создать функциональный индекс с помощью функции lower.

Если объявить столбец как UNIQUE или PRIMARY KEY, неявно сгенерированный индекс будет чувствительным к регистру. Поэтому он бесполезен для регистронезависимого поиска и не будет обеспечивать уникальность без учета регистра.

Тип данных citext позволяет исключить вызовы lower в запросах SQL и позволяет сделать первичный ключ регистронезависимым. Тип citext учитывает локаль, так же, как тип text, что означает, что сравнение символов в верхнем и нижнем регистре зависит от настройки параметра LC_CTYPE для базы данных. Это поведение, опять же, идентично использованию lower в запросах. Но поскольку оно реализуется прозрачно типом данных, в самих запросах дополнительно не нужно ничего делать.

Как его использовать

Ниже приведен простой пример использования:

CREATE TABLE users (
    nick CITEXT PRIMARY KEY,
    pass TEXT   NOT NULL
);

INSERT INTO users VALUES ( 'larry',  sha256(random()::text::bytea) );
INSERT INTO users VALUES ( 'Tom',    sha256(random()::text::bytea) );
INSERT INTO users VALUES ( 'Damian', sha256(random()::text::bytea) );
INSERT INTO users VALUES ( 'NEAL',   sha256(random()::text::bytea) );
INSERT INTO users VALUES ( 'Bjørn',  sha256(random()::text::bytea) );

SELECT * FROM users WHERE nick = 'Larry';

Оператор SELECT вернет один кортеж, несмотря на то, что в столбце nick указано значение larry, а в запросе — Larry.

Поведение при сравнении строк

Модуль citext выполняет сравнения, приводя каждую строку к нижнему регистру (как если бы была вызвана функция lower) и затем сравнивая результаты как обычно. Так, например, две строки считаются равными, если функция lower выдаст для них одинаковые результаты.

Чтобы как можно точнее имитировать правило сортировки без учета регистра, в этом модуле имеются специальные, citext-специфичные версии ряда операторов и функций для обработки строки. Так, например, операторы регулярных выражений ~ и ~* демонстрируют то же поведение, когда применяются к типу citext: они оба не учитывают регистр. Это же распространяется на операторы !~ и !~*, а также операторы LIKE ~~, ~~*, !~~ и !~~*. Если требуется, чтобы эти операторы учитывали регистр, можно привести их аргументы к типу text.

Точно так же, все следующие функции выполняют сопоставления без учета регистра, если их аргументы имеют тип citext:

regexp_match()

regexp_matches()

regexp_replace()

regexp_split_to_array()

regexp_split_to_table()

replace()

split_part()

strpos()

translate()

Для функций с регулярными выражениями, если требуется сопоставление с учетом регистра, можно добавить флаг «c», чтобы принудительно включить этот режим. Или, как вариант, привести аргумент к типу text, прежде чем вызвать одну из этих функций.

Ограничения

Смена регистра символов в citext зависит от настройки параметра LC_CTYPE для вашей базы данных. Таким образом, как будут сравниваться значения, определяется при создании базы данных. По определениям стандарта Unicode это сравнение не будет истинно регистронезависимым. В сущности, это означает, что если вас устраивает установленное правило сортировки, вас должны устраивать и сравнения citext. Но если в вашей базе данных хранятся данные на разных языках, пользователи одного языка могут получать неожиданные результаты запросов, если правило сортировки предназначено для другого языка.

К значениям данных или столбцам citext можно добавлять указание COLLATE. В настоящее время операторы citext принимают такое явное указание COLLATE при сравнении строк в нижнем регистре, но изначальное приведение к нижнему регистру всегда выполняется согласно настройкам LC_CTYPE базы данных (как если бы указывалось COLLATE "default"). Это может измениться в будущем, чтобы на обоих этапах учитывалось указание COLLATE во входных данных.

Тип citext не так эффективен, как text, потому что функции операторов и функции сравнения для B-дерева должны делать копии данных и переводить их в нижний регистр для сравнения. Кроме того, только тип text может поддерживать исключение дубликатов в B-дереве. Тем не менее с citext сопоставление без учета регистра реализуется немного эффективнее, чем с использованием lower.

Тип citext не особенно поможет, если вам нужно сравнивать данные с учетом регистра в одних контекстах, и без учета регистра — в других. Стандартное решение — использовать тип text и вручную применять функцию lower, когда нужно выполнить сравнение без учета регистра; это прекрасно работает, если регистронезависимое сравнение требуется выполнять изредка. Если же сравнение почти всегда должно быть регистронезависимым и только иногда проводится с учетом регистра, имеет смысл сохранять данные в виде citext и явно приводить столбец к типу text, когда требуется регистрозависимое сравнение. В любом случае, чтобы оба варианта поиска были быстрыми, вам понадобится два индекса.

Схема, содержащая операторы citext, должна находиться в текущем пути поиска (search_path; обычно это схема public); в противном случае будут вызываться обычные, чувствительные к регистру операторы для типа text.

Подход с переводом строк в нижний регистр для сравнения некорректно работает в некоторых особых случаях Unicode, например, когда одной букве в верхнем регистре соответствуют две буквы в нижнем регистре. По этой причине в Unicode различаются понятия преобразование регистра и выравнивание регистра. Чтобы сравнение производилось корректно и в этих случаях, используйте вместо citext недетерминированные правила сортировки.

Автор

Дэвид Е. Уилер (David E. Wheeler), david@kineticode.com

Разработку вдохновил оригинальный модуль citext авторства Дональда Фрейзера (Donald Fraser).

cube

Этот модуль реализует тип данных cube для представления многомерных кубов.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Синтаксис

В Таблице 2 показаны допустимые внешние представления типа cube. Буквы x, y и т. д. обозначают числа с плавающей запятой.

Таблица 2. Внешние представления кубов

Внешний синтаксис | Значение
x | Одномерная точка (или одномерный интервал нулевой длины)
(x) | То же, что и выше
x1,x2,...,xn | Точка в n-мерном пространстве, представленная внутри как куб нулевого объема
(x1,x2,...,xn) | То же, что и выше
(x),(y) | Одномерный интервал, начинающийся в точке x и заканчивающийся в y, либо наоборот; порядок значения не имеет
[(x),(y)] | То же, что и выше
(x1,...,xn),(y1,...,yn) | n-мерный куб, представленный парой диагонально противоположных углов
[(x1,...,xn),(y1,...,yn)] | То же, что и выше

В каком порядке вводятся противоположные углы куба, не имеет значения. Функции, принимающие тип cube, автоматически меняют углы местами, если нужно создать единое внутреннее представление «левый нижний — правый верхний». Когда эти углы совмещаются, в cube для экономии пространства хранится только один угол с флагом «является точкой».

Пробелы во входных данных игнорируются, так что [(x),(y)] не отличается от [ ( x ), ( y ) ].

Точность

Значения хранятся внутри как 64-битные числа с плавающей запятой. Это означает, что числа с более чем 16 значащими цифрами будут усекаться.

Применение

В Таблице 3 показаны специальные операторы, предоставляемые для работы с типом cube.

Таблица 3. Операторы для кубов

Оператор

Описание

cube && cube → boolean

Кубы пересекаются?

cube @> cube → boolean

Первый куб содержит второй?

cube <@ cube → boolean

Первый куб содержится во втором?

cube -> integer → float8

Извлекает n-ю координату куба (считая с 1).

cube ~> integer → float8

Извлекает n-ю координату куба, считая следующим образом: n = 2 * k - 1 обозначает нижнюю границу куба k-й размерности, а n = 2 * k обозначает верхнюю границу k-й размерности. Отрицательные n обозначают обратное значение соответствующей положительной координаты. Этот оператор разработан для поддержки KNN-GiST.

cube <-> cube → float8

Вычисляет евклидово расстояние между двумя кубами.

cube <#> cube → float8

Вычисляет расстояние городских кварталов (метрику L-1) между двумя кубами.

cube <=> cube → float8

Вычисляет расстояние Чебышева (метрику L-бесконечность) между двумя кубами.

Помимо операторов, показанных выше, для типа cube имеются обычные операторы сравнения, показанные в таблице Операторы сравнения. Эти операторы сначала сравнивают первые координаты, и если они равны, сравнивают вторые и т. д. Они предназначены в основном для поддержки класса операторов индекса В-дерева для типа cube, который может быть полезен, например, если вы хотите создать наложить ограничение UNIQUE на столбец типа cube. В других случаях эта сортировка не очень полезна с практической точки зрения.

Модуль cube также предоставляет класс операторов индекса GiST для значений cube. Индекс GiST для cube можно использовать для поиска значений при помощи операторов =, &&, @> и <@ в предложениях WHERE.

Кроме того, индекс GiST для cube можно использовать для поиска ближайших соседей при помощи операторов метрики <->, <#> и <=> в предложениях ORDER BY. Например, ближайшего соседа точки в трехмерном пространстве (0.5, 0.5, 0.5) можно эффективно найти так:

SELECT c FROM test ORDER BY c <-> cube(array[0.5,0.5,0.5]) LIMIT 1;

Оператор ~> можно также использовать таким образом, чтобы эффективно выбрать первые несколько значений, отсортированных по выбранной координате. Например, чтобы получить первые несколько кубов, упорядоченных по возрастанию первой координаты (левого нижнего угла), можно использовать следующий запрос:

SELECT c FROM test ORDER BY c ~> 1 LIMIT 5;

А чтобы получить двумерные кубы, упорядоченные по убыванию первой координаты (правого верхнего угла):

SELECT c FROM test ORDER BY c ~> 3 DESC LIMIT 5;

В Таблице 4 показаны все доступные функции.

Таблица 4. Функции для работы с кубами

Функция

Описание
Пример(ы)

cube ( float8 ) → cube

Создает одномерный куб, у которого обе координаты равны.
cube(1) → (1)

cube ( float8, float8 ) → cube

Создает одномерный куб.
cube(1, 2) → (1),(2)

cube ( float8[] ) → cube

Создает куб нулевого объема, используя координаты, определяемые массивом.
cube(ARRAY[1,2,3]) → (1, 2, 3)

cube ( float8[], float8[] ) → cube

Создает куб с координатами правого верхнего и левого нижнего углов, определяемыми двумя массивами, которые должны быть одинаковой длины.
cube(ARRAY[1,2], ARRAY[3,4]) → (1, 2),(3, 4)

cube ( cube, float8 ) → cube

Создает новый куб, добавляя размерность к существующему кубу с одинаковым значением новой координаты для обоих углов. Это полезно для поэтапного построения кубов из вычисляемых значений.
cube('(1,2),(3,4)'::cube, 5) → (1, 2, 5),(3, 4, 5)

cube ( cube, float8, float8 ) → cube

Создает новый куб, добавляя размерность к существующему кубу. Это полезно для поэтапного построения кубов из вычисляемых значений.
cube('(1,2),(3,4)'::cube, 5, 6) → (1, 2, 5),(3, 4, 6)

cube_dim ( cube ) → integer

Возвращает число размерностей куба.
cube_dim('(1,2),(3,4)') → 2

cube_ll_coord ( cube, integer ) → float8

Возвращает значение n-й координаты левого нижнего угла куба.
cube_ll_coord('(1,2),(3,4)', 2) → 2

cube_ur_coord ( cube, integer ) → float8

Возвращает значение n-й координаты правого верхнего угла куба.
cube_ur_coord('(1,2),(3,4)', 2) → 4

cube_is_point ( cube ) → boolean

Возвращает true, если куб является точкой, то есть если два определяющих его угла совпадают.
cube_is_point(cube(1,1)) → t

cube_distance ( cube, cube ) → float8

Возвращает расстояние между двумя кубами. Если оба куба являются точками, вычисляется обычная функция расстояния.
cube_distance('(1,2)', '(3,4)') → 2.8284271247461903

cube_subset ( cube, integer[] ) → cube

Создает новый куб из существующего, используя список индексов размерностей из массива. Может применяться для получения конечных точек в одном измерении, для удаления измерений или изменения их порядка.
cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[2]) → (3),(7)
cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[3,2,1,1]) → (5, 3, 1, 1),(8, 7, 6, 6)

cube_union ( cube, cube ) → cube

Создает объединение двух кубов.
cube_union('(1,2)', '(3,4)') → (1, 2),(3, 4)

cube_inter ( cube, cube ) → cube

Создает пересечение двух кубов.
cube_inter('(1,2)', '(3,4)') → (3, 4),(1, 2)

cube_enlarge ( c cube, r double, n integer ) → cube

Увеличивает размер куба на заданный радиус r как минимум в n измерениях. Если радиус отрицательных, куб, наоборот, уменьшается. Все определенные размерности изменяются на величину радиуса r. Координаты левого нижнего угла уменьшаются на r, а координаты правого верхнего угла увеличиваются на r. Если координата левого нижнего угла становится больше соответствующей координаты правого верхнего угла (это происходит, только когда r < 0), обе координаты устанавливаются в свое среднее значение. Если n превышает число определенных размерностей и куб увеличивается (r > 0), то добавляются дополнительные размерности, недостающие до n; в качестве начального значения для дополнительных координат используется 0. Эта функция полезна для создания вокруг точки ограничивающих прямоугольников с целью поиска ближайших точек.
cube_enlarge('(1,2),(3,4)', 0.5, 3) → (0.5, 1.5, -0.5),(3.5, 4.5, 0.5)

Поведение по умолчанию

Думается, что это объединение:

select cube_union('(0,5,2),(2,3,1)', '0');
cube_union
-------------------
(0, 0, 0),(2, 5, 2)
(1 row)

не противоречит здравому смыслу, как и это пересечение:

select cube_inter('(0,-1),(1,1)', '(-2),(2)');
cube_inter
-------------
(0, 0),(1, 0)
(1 row)

Во всех бинарных операциях с кубами разных размерностей куб с меньшей размерностью наверняка будет декартовой проекцией; т. е. в опущенных в строковом представлении координатах находятся нули. Значит, показанные выше примеры равнозначны следующим:

cube_union('(0,5,2),(2,3,1)','(0,0,0),(0,0,0)');
cube_inter('(0,-1),(1,1)','(-2,0),(2,0)');

В следующем предикате включения применяется синтаксис точек, хотя фактически второй аргумент представляется внутри кубом. Этот синтаксис избавляет от необходимости определять отдельный тип точек и функции для предикатов (box,point).

select cube_contains('(0,0),(1,1)', '0.5,0.5');
cube_contains
--------------
t
(1 row)

Примечания

Примеры использования можно увидеть в регрессионном тесте sql/cube.sql.

Во избежание некорректного применения этого типа, число размерностей кубов ограничено значением 100. Если вы хотите его увеличить, его можно изменить в cubedata.h.

Благодарности

Оригинальный автор: Джин Селков мл. (Gene Selkov, Jr.), selkovjr@mcs.anl.gov, Аргоннская национальная лаборатория, Отдел математики и компьютерных наук.

Я благодарен в первую очередь профессору Джо Геллерштейну (Joe Hellerstein) (https://dsf.berkeley.edu/jmh/) за пояснение сути GiST (http://gist.cs.berkeley.edu/) и его бывшему студенту, Энди Донгу (Andy Dong), за пример, написанный для Illustra. Я также признателен всем разработчикам Postgres в настоящем и прошлом за возможность создать мой собственный мир и спокойно жить в нем. Еще я хотел бы выразить признательность Аргоннской лаборатории и Министерству энергетики США за годы постоянной поддержки моих исследований в области баз данных.

Небольшие изменения в этот пакет внес Бруно Вольф III (Bruno Wolff III) (bruno@wolff.to) в августе/сентябре 2002 г. В том числе он перешел от одинарной к двойной точности и добавил несколько новых функций.

Дополнительные изменения внес Джошуа Рейх (Joshua Reich) (josh@root.net) в июле 2006 г. В частности, он добавил cube(float8[], float8[]), подчистил код и перевел его на протокол вызовов версии V1 с устаревшего протокола V0.

dblink

Модуль dblink обеспечивает подключения к другим базам данных QHB из сеанса базы данных.

См. также описание модуля postgres_fdw, который предоставляет примерно ту же функциональность, используя более современную и стандартизированную инфраструктуру.

dblink_connect

dblink_connect — открывает постоянное подключение к удаленной базе данных.

Синтаксис

dblink_connect(text connstr) returns text
dblink_connect(text connname, text connstr) returns text

Описание

Функция dblink_connect() устанавливает подключение к удаленной базе данных QHB. Целевые сервер и база данных указываются в стандартной строке подключения libpq. При необходимости этому подключению можно назначить имя. Одновременно могут быть открытыми несколько именованных подключений, но только одно подключение без имени. Подключение будет сохраняться, пока не будет закрыто или до завершения сеанса базы данных.

В строке подключения также можно задать имя существующего стороннего сервера. Для определения стороннего сервера рекомендуется использовать обертку сторонних данных dblink_fdw. См. пример ниже, а также CREATE SERVER and CREATE USER MAPPING.

Аргументы

connname
Имя, назначаемое этому подключению; если опущено, открывается безымянное подключение, заменяя любое существующее безымянное подключение.

connstr
Строка подключения в стиле libpq, например hostaddr=127.0.0.1 port=5432 dbname=mydb user=postgres password=mypasswd options=-csearch_path=. Дополнительную информацию см. в подразделе Строки подключения. Как вариант, в ней можно задавать имя стороннего сервера.

Возвращаемое значение

Возвращает состояние (Это всегда строка OK, поскольку в случае любого сбоя функция выдает ошибку и не возвращает никакие значения).

Примечания

Если к базе данных, в которой не был внедрен шаблон безопасного использования схем, имеют доступ недоверенные пользователи, начинайте каждый сеанс с удаления общедоступных для записи схем из пути поиска (search_path). Например, для этого можно добавить options=-csearch_path= в connstr. Это касается не только dblink, но и любых других интерфейсов для выполнения произвольных команд SQL.

Только суперпользователи могут использовать dblink_connect для создания подключений, не требующих аутентификации по паролю. Если эта возможность нужна обычным пользователям, следует воспользоваться функцией dblink_connect_u.

Использовать в именах подключений знаки равенства не рекомендуется, так как при этом возникает риск перепутать их со строками подключения в других функциях dblink.

Примеры

SELECT dblink_connect('dbname=qhb options=-csearch_path=');
 dblink_connect
----------------
 OK
(1 row)

SELECT dblink_connect('myconn', 'dbname=qhb options=-csearch_path=');
 dblink_connect
----------------
 OK
(1 row)

-- Функциональность обертки сторонних данных (FOREIGN DATA WRAPPER)
-- Примечание: чтобы это работало как следует, для локальных подключений требуется аутентификация по паролю
--       Иначе, вызвав dblink_connect(), вы получите следующую ошибку:
--       ERROR:  password is required
--       DETAIL:  Non-superuser cannot connect if the server does not request a password.
--       HINT:  Target server's authentication method must be changed.
--       (ОШИБКА:  требуется пароль
--       ДЕТАЛИЗАЦИЯ:  Обычный пользователь не может подключиться, если сервер не требует пароль.
--       ПОДСКАЗКА:  Необходимо изменить метод аутентификации целевого сервера.)

CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw OPTIONS (hostaddr '127.0.0.1', dbname 'contrib_regression');

CREATE USER regress_dblink_user WITH PASSWORD 'secret';
CREATE USER MAPPING FOR regress_dblink_user SERVER fdtest OPTIONS (user 'regress_dblink_user', password 'secret');
GRANT USAGE ON FOREIGN SERVER fdtest TO regress_dblink_user;
GRANT SELECT ON TABLE foo TO regress_dblink_user;

\set ORIGINAL_USER :USER
\c - regress_dblink_user
SELECT dblink_connect('myconn', 'fdtest');
 dblink_connect
----------------
 OK
(1 row)

SELECT * FROM dblink('myconn', 'SELECT * FROM foo') AS t(a int, b text, c text[]);
 a  | b |       c
----+---+---------------
  0 | a | {a0,b0,c0}
  1 | b | {a1,b1,c1}
  2 | c | {a2,b2,c2}
  3 | d | {a3,b3,c3}
  4 | e | {a4,b4,c4}
  5 | f | {a5,b5,c5}
  6 | g | {a6,b6,c6}
  7 | h | {a7,b7,c7}
  8 | i | {a8,b8,c8}
  9 | j | {a9,b9,c9}
 10 | k | {a10,b10,c10}
(11 rows)

\c - :ORIGINAL_USER
REVOKE USAGE ON FOREIGN SERVER fdtest FROM regress_dblink_user;
REVOKE SELECT ON TABLE foo FROM regress_dblink_user;
DROP USER MAPPING FOR regress_dblink_user SERVER fdtest;
DROP USER regress_dblink_user;
DROP SERVER fdtest;

dblink_connect_u

dblink_connect_u — открывает постоянное подключение к удаленной базе данных, небезопасно

Синтаксис

dblink_connect_u(text connstr) returns text
dblink_connect_u(text connname, text connstr) returns text

Описание

Функция dblink_connect_u() идентична функции dblink_connect(), за исключением того, что она позволяет подключаться обычным пользователям с любым методом аутентификации.

Если удаленный сервер выбирает метод аутентификации без пароля, возможна имперсонация и последующее повышение прав, поскольку сеанс будет установлен от имени пользователя, который исполняет локальный процесс QHB. Кроме того, даже если удаленный сервер и запрашивает пароль, этот пароль можно получить из среды сервера, например из файла ~/.pgpass, принадлежащего пользователю сервера. Это чревато не только имперсонацией, но и возможностью выдачи пароля не заслуживающему доверия удаленному серверу. Поэтому dblink_connect_u() изначально устанавливается так, что роль PUBLIC лишена всех прав на ее использование, т. е. вызвать ее могут только суперпользователи. В некоторых ситуациях может оказаться допустимым дать право EXECUTE для dblink_connect_u() определенным пользователям, которые считаются доверенными, но это следует делать с осторожностью. Также рекомендуется, чтобы файл ~/.pgpass, принадлежащий пользователю сервера, не содержал никаких записей с подстановочным знаком (например *) в качестве имени узла.

Дополнительную информацию см. в описании dblink_connect().

dblink_disconnect

dblink_disconnect — закрывает постоянное подключение к удаленной базе данных

Синтаксис

dblink_disconnect() returns text
dblink_disconnect(text connname) returns text

Описание

Функция dblink_disconnect() закрывает подключение, ранее открытое функцией dblink_connect(). Форма без аргументов закрывает безымянное подключение.

Аргументы

connname
Имя закрываемого именованного подключения.

Возвращаемое значение

Возвращает состояние (Это всегда строка OK, поскольку в случае любого сбоя функция выдает ошибку и не возвращает никакие значения).

Примеры

SELECT dblink_disconnect();
 dblink_disconnect
-------------------
 OK
(1 row)

SELECT dblink_disconnect('myconn');
 dblink_disconnect
-------------------
 OK
(1 row)

dblink

dblink — выполняет запрос в удаленной базе данных

Синтаксис

dblink(text connname, text sql [, bool fail_on_error]) returns setof record
dblink(text connstr, text sql [, bool fail_on_error]) returns setof record
dblink(text sql [, bool fail_on_error]) returns setof record

Описание

Функция dblink выполняет запрос (обычно SELECT, но это может быть и любой другой оператор SQL, возвращающий строки) в удаленной базе данных.

Когда этой функции передаются два аргумента типа text, первый сначала рассматривается как имя постоянного подключения; если таковое находится, команда выполняется для него. Если подключение не находится, первый аргумент воспринимается как строка подключения, как для функции dblink_connect, и указанное подключение устанавливается только на время выполнения этой команды.

Аргументы

connname
Имя используемого подключения; чтобы использовать безымянное подключение, нужно опустить этот параметр.

connstr
Строка подключения, описанная ранее для dblink_connect.

sql
Запрос SQL, который вы хотите выполнить в удаленной базе данных, например select * from foo.

fail_on_error
Если равен true (это значение по умолчанию, поэтому его необязательно указывать), то в случае ошибки, выданной на удаленной стороне соединения, ошибка также выдается локально. Если равен false, удаленная ошибка выдается локально как замечание (уровень важности NOTICE), и функция не возвращает строки.

Возвращаемое значение

Эта функция возвращает строки, выдаваемые в результате запроса. Поскольку dblink может выполнять любые запросы, она объявлена как возвращающая тип record, а не некоторый определенный набор столбцов. Это означает, что нужно указать ожидаемый набор столбцов в вызывающем запросе — иначе QHB не будет знать, чего ожидать. Например:

SELECT *
    FROM dblink('dbname=mydb options=-csearch_path=',
                'select proname, prosrc from pg_proc')
      AS t1(proname name, prosrc text)
    WHERE proname LIKE 'bytea%';

В части «псевдонима» предложения FROM нужно указывать имена столбцов и типы, которые будет возвращать функция. (Указание имен столбцов в псевдонимах на самом деле относится к стандартному синтаксису SQL, а вот указание типов столбцов является расширением QHB.) Это позволяет системе понять, во что должен разворачиваться знак * и на что ссылается proname в предложении WHERE, прежде чем пытаться выполнить эту функцию. Если во время выполнения действительный результат запроса из удаленной базы данных не будет содержать столько столбцов, сколько указано в предложении FROM, произойдет ошибка. Однако имена столбцов могут не совпадать, а еще dblink не настаивает на точном совпадении типов. Функция завершится успешно, если возвращаемые строки данных будут допустимыми для ввода в тип столбца, объявленный в предложении FROM.

Примечания

Использовать dblink с предопределенными запросами будет удобнее, если создать представление. Это позволит скрыть в его определении информацию о типах столбцов и не прописывать ее в каждом запросе. Например:

CREATE VIEW myremote_pg_proc AS
  SELECT *
    FROM dblink('dbname=qhb options=-csearch_path=',
                'select proname, prosrc from pg_proc')
    AS t1(proname name, prosrc text);

SELECT * FROM myremote_pg_proc WHERE proname LIKE 'bytea%';

Примеры

SELECT * FROM dblink('dbname=qhb options=-csearch_path=',
                     'select proname, prosrc from pg_proc')
  AS t1(proname name, prosrc text) WHERE proname LIKE 'bytea%';
  proname   |   prosrc
------------+------------
 byteacat   | byteacat
 byteaeq    | byteaeq
 bytealt    | bytealt
 byteale    | byteale
 byteagt    | byteagt
 byteage    | byteage
 byteane    | byteane
 byteacmp   | byteacmp
 bytealike  | bytealike
 byteanlike | byteanlike
 byteain    | byteain
 byteaout   | byteaout
(12 rows)

SELECT dblink_connect('dbname=qhb options=-csearch_path=');
 dblink_connect
----------------
 OK
(1 row)

SELECT * FROM dblink('select proname, prosrc from pg_proc')
  AS t1(proname name, prosrc text) WHERE proname LIKE 'bytea%';
  proname   |   prosrc
------------+------------
 byteacat   | byteacat
 byteaeq    | byteaeq
 bytealt    | bytealt
 byteale    | byteale
 byteagt    | byteagt
 byteage    | byteage
 byteane    | byteane
 byteacmp   | byteacmp
 bytealike  | bytealike
 byteanlike | byteanlike
 byteain    | byteain
 byteaout   | byteaout
(12 rows)

SELECT dblink_connect('myconn', 'dbname=regression options=-csearch_path=');
 dblink_connect
----------------
 OK
(1 row)

SELECT * FROM dblink('myconn', 'select proname, prosrc from pg_proc')
  AS t1(proname name, prosrc text) WHERE proname LIKE 'bytea%';
  proname   |   prosrc
------------+------------
 bytearecv  | bytearecv
 byteasend  | byteasend
 byteale    | byteale
 byteagt    | byteagt
 byteage    | byteage
 byteane    | byteane
 byteacmp   | byteacmp
 bytealike  | bytealike
 byteanlike | byteanlike
 byteacat   | byteacat
 byteaeq    | byteaeq
 bytealt    | bytealt
 byteain    | byteain
 byteaout   | byteaout
(14 rows)

dblink_exec

dblink_exec — выполняет команду в удаленной базе данных

Синтаксис

dblink_exec(text connname, text sql [, bool fail_on_error]) returns text
dblink_exec(text connstr, text sql [, bool fail_on_error]) returns text
dblink_exec(text sql [, bool fail_on_error]) returns text

Описание

Функция dblink_exec выполняет команду (то есть любой оператор SQL, не возвращающий строки) в удаленной базе данных.

Когда этой функции передаются два аргумента типа text, первый сначала рассматривается как имя постоянного подключения; если таковое находится, команда выполняется для него. Если подключение не находится, первый аргумент воспринимается как строка подключения, как для функции dblink_connect, и указанное подключение устанавливается только на время выполнения этой команды.

Аргументы

connname
Имя используемого подключения; чтобы использовать безымянное подключение, нужно опустить этот параметр.

connstr
Строка подключения, описанная ранее для dblink_connect.

sql
Команда SQL, которую вы хотите выполнить в удаленной базе данных, например insert into foo values(0, 'a', '{"a0","b0","c0"}').

fail_on_error
Если равен true (это значение по умолчанию, поэтому его необязательно указывать), то в случае ошибки, выданной на удаленной стороне соединения, ошибка также выдается локально. Если равен false, удаленная ошибка выдается локально как замечание (уровень важности NOTICE), и возвращаемым значением функции будет ERROR.

Возвращаемое значение

Возвращает состояние (либо строки состояния команды, либо ERROR).

Примеры

SELECT dblink_connect('dbname=dblink_test_standby');
 dblink_connect
----------------
 OK
(1 row)

SELECT dblink_exec('insert into foo values(21, ''z'', ''{"a0","b0","c0"}'');');
   dblink_exec
-----------------
 INSERT 943366 1
(1 row)

SELECT dblink_connect('myconn', 'dbname=regression');
 dblink_connect
----------------
 OK
(1 row)

SELECT dblink_exec('myconn', 'insert into foo values(21, ''z'', ''{"a0","b0","c0"}'');');
   dblink_exec
------------------
 INSERT 6432584 1
(1 row)

SELECT dblink_exec('myconn', 'insert into pg_class values (''foo'')',false);
NOTICE:  sql error
DETAIL:  ERROR:  null value in column "relnamespace" violates not-null constraint
-- УВЕДОМЛЕНИЕ: ошибка SQL
-- ДЕТАЛИЗАЦИЯ: ОШИБКА: значение NULL в столбце "relnamespace" нарушает ограничение NOT NULL

 dblink_exec
-------------
 ERROR
(1 row)

dblink_open

dblink_open — открывает курсор в удаленной базе данных

Синтаксис

dblink_open(text cursorname, text sql [, bool fail_on_error]) returns text
dblink_open(text connname, text cursorname, text sql [, bool fail_on_error]) returns text

Описание

Функция dblink_open() открывает курсор в удаленной базе данных. Затем этим курсором можно будет манипулировать с помощью функций dblink_fetch() и dblink_close().

Аргументы

connname
Имя используемого подключения; чтобы использовать безымянное подключение, нужно опустить этот параметр.

cursorname
Имя, назначаемое курсору.

sql
Оператор SELECT, который вы хотите выполнять в удаленной базе данных, например select * from pg_class.

fail_on_error
Если равен true (это значение по умолчанию, поэтому его необязательно указывать), то в случае ошибки, выданной на удаленной стороне соединения, ошибка также выдается локально. Если равен false, удаленная ошибка выдается локально как замечание (уровень важности NOTICE), и возвращаемым значением функции будет ERROR.

Возвращаемое значение

Возвращает состояние (OK или ERROR).

Примечания

Поскольку курсор может существовать только в рамках транзакции, функция dblink_open начинает явный блок транзакции (командой BEGIN) на удаленной стороне, если транзакция там еще не начата. Эта транзакция будет снова закрыта при соответствующем выполнении функции dblink_close. Обратите внимание, что если вы используете dblink_exec для изменения данных между вызовами dblink_open и dblink_close, а затем происходит ошибка, или если вы вызываете dblink_disconnect перед dblink_close, ваши изменения будут потеряны, так как транзакция прервется.

Примеры

SELECT dblink_connect('dbname=qhb options=-csearch_path=');
 dblink_connect
----------------
 OK
(1 row)

SELECT dblink_open('foo', 'select proname, prosrc from pg_proc');
 dblink_open
-------------
 OK
(1 row)

dblink_fetch

dblink_fetch — возвращает строки из открытого курсора в удаленной базе данных

Синтаксис

dblink_fetch(text cursorname, int howmany [, bool fail_on_error]) returns setof record
dblink_fetch(text connname, text cursorname, int howmany [, bool fail_on_error]) returns setof record

Описание

Функция dblink_fetch выбирает строки из курсора, ранее установленного функцией dblink_open.

Аргументы

connname
Имя используемого подключения; чтобы использовать безымянное подключение, нужно опустить этот параметр.

cursorname
Имя курсора, из которого будут выбираться данные.

howmany
Максимальное число строк, которое нужно получить. Выбираются следующие howmany строк, начиная с текущей позиции курсора и двигаясь вперед. Когда курсор доходит до конца, строки больше не выдаются.

fail_on_error
Если равен true (это значение по умолчанию, поэтому его необязательно указывать), то в случае ошибки, выданной на удаленной стороне соединения, ошибка также выдается локально. Если равен false, удаленная ошибка выдается локально как замечание (уровень важности NOTICE), и функция не возвращает строки.

Возвращаемое значение

Эта функция возвращает строки, выбираемые через курсор. Для использования этой функции нужно задать ожидаемый набор столбцов, как ранее говорилось в описании dblink.

Примечания

При несовпадении числа возвращаемых столбцов, указанного в предложении FROM, с фактическим числом столбцов, возвращенных удаленным курсором, будет выдана ошибка. В этом случае удаленный курсор все равно продвигается на столько строк, на сколько он продвинулся бы, если бы ошибка не произошла. То же самое верно для любых других ошибок, происходящих в локальном запроса после выполнения удаленной команды FETCH.

Примеры

SELECT dblink_connect('dbname=qhb options=-csearch_path=');
 dblink_connect
----------------
 OK
(1 row)

SELECT dblink_open('foo', 'select proname, prosrc from pg_proc where proname like ''bytea%''');
 dblink_open
-------------
 OK
(1 row)

SELECT * FROM dblink_fetch('foo', 5) AS (funcname name, source text);
 funcname |  source
----------+----------
 byteacat | byteacat
 byteacmp | byteacmp
 byteaeq  | byteaeq
 byteage  | byteage
 byteagt  | byteagt
(5 rows)

SELECT * FROM dblink_fetch('foo', 5) AS (funcname name, source text);
 funcname  |  source
-----------+-----------
 byteain   | byteain
 byteale   | byteale
 bytealike | bytealike
 bytealt   | bytealt
 byteane   | byteane
(5 rows)

SELECT * FROM dblink_fetch('foo', 5) AS (funcname name, source text);
  funcname  |   source
------------+------------
 byteanlike | byteanlike
 byteaout   | byteaout
(2 rows)

SELECT * FROM dblink_fetch('foo', 5) AS (funcname name, source text);
 funcname | source
----------+--------
(0 rows)

dblink_close

dblink_close — закрывает курсор в удаленной базе данных

Синтаксис

dblink_close(text cursorname [, bool fail_on_error]) returns text
dblink_close(text connname, text cursorname [, bool fail_on_error]) returns text

Описание

Функция dblink_close закрывает курсор, ранее открытый функцией dblink_open.

Аргументы

connname
Имя используемого подключения; чтобы использовать безымянное подключение, нужно опустить этот параметр.

cursorname
Имя закрываемого курсора.

fail_on_error
Если равен true (это значение по умолчанию, поэтому его необязательно указывать), то в случае ошибки, выданной на удаленной стороне соединения, ошибка также выдается локально. Если равен false, удаленная ошибка выдается локально как замечание (уровень важности NOTICE), и возвращаемым значением функции будет ERROR.

Возвращаемое значение

Возвращает состояние (OK или ERROR).

Примечания

Если вызов dblink_open начал явный блок транзакции и это последний открытый курсор, оставшийся в этом подключении, то dblink_close выполнит соответствующую команду COMMIT.

Примеры

SELECT dblink_connect('dbname=qhb options=-csearch_path=');
 dblink_connect
----------------
 OK
(1 row)

SELECT dblink_open('foo', 'select proname, prosrc from pg_proc');
 dblink_open
-------------
 OK
(1 row)

SELECT dblink_close('foo');
 dblink_close
--------------
 OK
(1 row)

dblink_get_connections

dblink_get_connections — возвращает имена всех открытых именованных подключений dblink

Синтаксис

dblink_get_connections() returns text[]

Описание

Функция dblink_get_connections возвращает массив имен всех открытых именованных подключений dblink.

Возвращаемое значение

Возвращает текстовый массив имен подключений или NULL, если они отсутствуют.

Примеры

SELECT dblink_get_connections();

dblink_error_message

dblink_error_message — выдает сообщение последней ошибки для именованного подключения

Синтаксис

dblink_error_message(text connname) returns text

Описание

Функция dblink_error_message извлекает самое последнее сообщение удаленной ошибки для заданного подключения.

Аргументы

connname
Имя используемого подключения.

Возвращаемое значение

Возвращает последнее сообщение ошибки или OK, если в сеансе этого подключения не было ошибок.

Примечания

Когда функцией dblink_send_query передаются асинхронные запросы, сообщение об ошибке, связанное с подключением, может не обновиться до получения от сервера ответного сообщения. Обычно это означает, что до вызова dblink_error_message следует вызвать функцию dblink_is_busy или dblink_get_result, чтобы были видны все ошибки, возникшие при выполнении асинхронного запроса.

Примеры

SELECT dblink_error_message('dtest1');

dblink_send_query

dblink_send_query — передает асинхронный запрос в удаленную базу данных

Синтаксис

dblink_send_query(text connname, text sql) returns int

Описание

Функция dblink_send_query передает запрос для асинхронного выполнения, то есть не дожидается получения результата. В этом подключении не должно быть уже выполняющегося асинхронного запроса.

После успешной передачи асинхронного запроса состояние его завершения можно проверить при помощи функции dblink_is_busy, и в итоге получить результаты собранные функцией dblink_get_result. Также можно попытаться отменить активный асинхронный запрос, вызвав функцию dblink_cancel_query.

Аргументы

connname
Имя используемого подключения.

sql
Оператор SQL, который вы хотите выполнить в удаленной базе данных, например select * from pg_class.

Возвращаемое значение

Возвращает 1, если запрос был успешно отправлен на обработку, или 0 в противном случае.

Примеры

SELECT dblink_send_query('dtest1', 'SELECT * FROM foo WHERE f1 < 3');

dblink_is_busy

dblink_is_busy — проверяет, не занято ли подключение асинхронным запросом

Синтаксис

dblink_is_busy(text connname) returns int

Описание

Функция dblink_is_busy проверяет, не выполняется ли асинхронный запрос.

Аргументы

connname
Имя проверяемого подключения.

Возвращаемое значение

Возвращает 1, если подключение занято, или 0 в противном случае. Если эта функция возвращает 0, гарантируется, что вызов dblink_get_result не будет заблокирован.

Примеры

SELECT dblink_is_busy('dtest1');

dblink_get_notify

dblink_get_notify — выдает асинхронные уведомления подключения

Синтаксис

dblink_get_notify() returns setof (notify_name text, be_pid int, extra text)
dblink_get_notify(text connname) returns setof (notify_name text, be_pid int, extra text)

Описание

Функция dblink_get_notify выдает уведомления либо безымянного подключения, либо подключения с указанным именем. Для получения уведомлений через dblink необходимо сначала выполнить LISTEN, используя функцию dblink_exec. Дополнительную информацию см. на справочных страницах команд команд LISTEN и NOTIFY.

Аргументы

connname
Имя именованного подключения, уведомления которого нужно получить.

Возвращаемое значение

Возвращает setof (notify_name text, be_pid int, extra text) или пустой набор, если уведомлений нет.

Примеры

SELECT dblink_exec('LISTEN virtual');
 dblink_exec
-------------
 LISTEN
(1 row)

SELECT * FROM dblink_get_notify();
 notify_name | be_pid | extra
-------------+--------+-------
(0 rows)

NOTIFY virtual;
NOTIFY

SELECT * FROM dblink_get_notify();
 notify_name | be_pid | extra
-------------+--------+-------
 virtual     |   1229 |
(1 row)

dblink_get_result

dblink_get_result — получает результат асинхронного запроса

Синтаксис

dblink_get_result(text connname [, bool fail_on_error]) returns setof record

Описание

Функция dblink_get_result собирает результаты асинхронного запроса, ранее переданного функцией dblink_send_query. Если запрос еще выполняется, dblink_get_result будет ждать его завершения.

Аргументы

connname
Имя используемого подключения.

fail_on_error
Если равен true (это значение по умолчанию, поэтому его необязательно указывать), то в случае ошибки, выданной на удаленной стороне соединения, ошибка также выдается локально. Если равен false, удаленная ошибка выдается локально как замечание (уровень важности NOTICE), и функция не возвращает строки.

Возвращаемое значение

Для асинхронного запроса (то есть оператора SQL, возвращающего строки) эта функция возвращает строки, полученные в результате запроса. Чтобы использовать эту функцию, нужно задать ожидаемый набор столбцов, как говорилось ранее в описании dblink.

Для асинхронной команды (то есть оператора SQL, не возвращающего строки) эта функция возвращает одну строку с одним текстовым столбцом, содержащим строку состояния команды. Однако в предложении FROM для такого вызова необходимо указать, что результат будет содержать один текстовый столбец.

Примечания

Эта функция должна вызываться, если dblink_send_query вернула 1. Ее нужно вызывать по одному разу для каждого отправленного запроса, а затем еще раз для получения результата с пустым набором, прежде чем подключение можно будет пользоваться снова.

Когда используются dblink_send_query и dblink_get_result, dblink извлекает результат удаленного запроса целиком, прежде чем передавать любую его часть локальному процессору запросов. Если запрос возвращает большое количество строк, это может занимать много памяти в локальном сеансе. Возможно, в этом случае будет лучше открыть такой запрос как курсор с помощью dblink_open, а затем извлекать приемлемыми порциями. Как вариант, можно воспользоваться простой функцией dblink(), которая избегает заполнения памяти, выгружая большие наборы результатов на диск.

Примеры

contrib_regression=# SELECT dblink_connect('dtest1', 'dbname=contrib_regression');
 dblink_connect
----------------
 OK
(1 row)

contrib_regression=# SELECT * FROM
contrib_regression-# dblink_send_query('dtest1', 'select * from foo where f1 < 3') AS t1;
 t1
----
  1
(1 row)

contrib_regression=# SELECT * FROM dblink_get_result('dtest1') AS t1(f1 int, f2 text, f3 text[]);
 f1 | f2 |     f3
----+----+------------
  0 | a  | {a0,b0,c0}
  1 | b  | {a1,b1,c1}
  2 | c  | {a2,b2,c2}
(3 rows)

contrib_regression=# SELECT * FROM dblink_get_result('dtest1') AS t1(f1 int, f2 text, f3 text[]);
 f1 | f2 | f3
----+----+----
(0 rows)

contrib_regression=# SELECT * FROM
contrib_regression-# dblink_send_query('dtest1', 'select * from foo where f1 < 3; select * from foo where f1 > 6') AS t1;
 t1
----
  1
(1 row)

contrib_regression=# SELECT * FROM dblink_get_result('dtest1') AS t1(f1 int, f2 text, f3 text[]);
 f1 | f2 |     f3
----+----+------------
  0 | a  | {a0,b0,c0}
  1 | b  | {a1,b1,c1}
  2 | c  | {a2,b2,c2}
(3 rows)

contrib_regression=# SELECT * FROM dblink_get_result('dtest1') AS t1(f1 int, f2 text, f3 text[]);
 f1 | f2 |      f3
----+----+---------------
  7 | h  | {a7,b7,c7}
  8 | i  | {a8,b8,c8}
  9 | j  | {a9,b9,c9}
 10 | k  | {a10,b10,c10}
(4 rows)

contrib_regression=# SELECT * FROM dblink_get_result('dtest1') AS t1(f1 int, f2 text, f3 text[]);
 f1 | f2 | f3
----+----+----
(0 rows)

dblink_cancel_query

dblink_cancel_query — отменяет любой активный запрос в именованном подключении

Синтаксис

dblink_cancel_query(text connname) returns text

Описание

Функция dblink_cancel_query пытается отменить любой запрос, выполняющийся через именованное подключение. Обратите внимание, что ее вызов не обязательно будет успешным (например, потому что удаленный запрос может уже завершиться). Запрос отмены просто увеличивает шансы на то, что этот запрос вскоре будет прерван. При этом все равно нужно будет завершить обычную процедуру обработки запроса, например вызвав dblink_get_result.

Аргументы

connname
Имя используемого подключения.

Возвращаемое значение

Возвращает OK, если запрос отмены был отправлен, или текст сообщения об ошибке в случае неудачи.

Примеры

SELECT dblink_cancel_query('dtest1');

dblink_get_pkey

dblink_get_pkey — возвращает позиции и имена полей первичного ключа отношения

Синтаксис

dblink_get_pkey(text relname) returns setof dblink_pkey_results

Описание

Функция dblink_get_pkey предоставляет информацию о первичном ключе отношения в локальной базе данных. Иногда это полезно при формировании запросов, отправляемых в удаленные базы данных.

Аргументы

relname
Имя локального отношения, например foo или myschema.mytab. Заключите его в кавычки, если это имя в смешанном регистре или содержит специальные символы, например "FooBar"; без кавычек эта строка приводится к нижнему регистру.

Возвращаемое значение

Возвращает по одной строке для каждого поля первичного ключа или не возвращает строк, если в отношении нет первичного ключа. Тип результирующей строки определяется как

CREATE TYPE dblink_pkey_results AS (position int, colname text);

В столбце position содержатся просто числа от 1 до N; это номер поля в первичном ключе, а не номер столбца таблицы.

Примеры

CREATE TABLE foobar (
    f1 int,
    f2 int,
    f3 int,
    PRIMARY KEY (f1, f2, f3)
);
CREATE TABLE

SELECT * FROM dblink_get_pkey('foobar');
 position | colname
----------+---------
        1 | f1
        2 | f2
        3 | f3
(3 rows)

dblink_build_sql_insert

dblink_build_sql_insert — формирует оператор INSERT из локального кортежа, заменяя значения полей первичного ключа переданными альтернативными значениями

Синтаксис

dblink_build_sql_insert(text relname,
                        int2vector primary_key_attnums,
                        integer num_primary_key_atts,
                        text[] src_pk_att_vals_array,
                        text[] tgt_pk_att_vals_array) returns text

Описание

Функция dblink_build_sql_insert может быть полезна при избирательной репликации локальной таблицы в удаленную базу данных. Она выбирает строку из локальной таблицы по заданному первичному ключу, а затем формирует команду SQL INSERT, которая дублирует эту строку, но заменяет в ней значения первичного ключа значениями из последнего аргумента. (Чтобы получить точную копию строки, просто укажите одинаковые значения в двух последних аргументах.)

Аргументы

relname
Имя локального отношения, например foo или myschema.mytab. Заключите его в кавычки, если это имя в смешанном регистре или содержит специальные символы, например "FooBar"; без кавычек эта строка приводится к нижнему регистру.

primary_key_attnums
Номера атрибутов (начиная с 1) полей первичного ключа, например 1 2.

num_primary_key_atts
Число полей первичного ключа.

src_pk_att_vals_array
Значения полей первичного ключа, по которым будет выполняться поиск локального кортежа. Каждое поле представляется в текстовом виде. Если локальной строки с этими значениями первичного ключа нет, выдается ошибка.

tgt_pk_att_vals_array
Значения полей первичного ключа, которые будут помещены в результирующую команду INSERT. Каждое поле представляется в текстовом виде.

Возвращаемое значение

Возвращает запрошенный оператор SQL в текстовом виде.

Примечания

Номера атрибутов в primary_key_attnums воспринимаются как логические номера столбцов, соответствующие позициям столбцов в SELECT * FROM relname, а не как физические позиции столбцов.

Примеры

SELECT dblink_build_sql_insert('foo', '1 2', 2, '{"1", "a"}', '{"1", "b''a"}');
             dblink_build_sql_insert
--------------------------------------------------
 INSERT INTO foo(f1,f2,f3) VALUES('1','b''a','1')
(1 row)

dblink_build_sql_delete

dblink_build_sql_delete — формирует оператор DELETE со значениями, передаваемыми для полей первичного ключа

Синтаксис

dblink_build_sql_delete(text relname,
                        int2vector primary_key_attnums,
                        integer num_primary_key_atts,
                        text[] tgt_pk_att_vals_array) returns text

Описание

Функция dblink_build_sql_delete может быть полезна при избирательной репликации локальной таблицы в удаленную базу данных. Она формирует команду SQL DELETE, которая удалит строку с заданными значениями первичного ключа.

Аргументы

relname
Имя локального отношения, например foo или myschema.mytab. Заключите его в кавычки, если это имя в смешанном регистре или содержит специальные символы, например "FooBar"; без кавычек эта строка приводится к нижнему регистру.

primary_key_attnums
Номера атрибутов (начиная с 1) полей первичного ключа, например 1 2.

num_primary_key_atts
Число полей первичного ключа.

tgt_pk_att_vals_array
Значения полей первичного ключа, которые будут использоваться в результирующей команде DELETE. Каждое поле представляется в текстовом виде.

Возвращаемое значение

Возвращает запрошенный оператор SQL в текстовом виде.

Примечания

Номера атрибутов в primary_key_attnums воспринимаются как логические номера столбцов, соответствующие позициям столбцов в SELECT * FROM relname, а не как физические позиции столбцов.

Примеры

SELECT dblink_build_sql_delete('"MyFoo"', '1 2', 2, '{"1", "b"}');
           dblink_build_sql_delete
---------------------------------------------
 DELETE FROM "MyFoo" WHERE f1='1' AND f2='b'
(1 row)

dblink_build_sql_update

dblink_build_sql_update — формирует оператор UPDATE из локального кортежа, заменяя значения полей первичного ключа переданными альтернативными значениями

Синтаксис

dblink_build_sql_update(text relname,
                        int2vector primary_key_attnums,
                        integer num_primary_key_atts,
                        text[] src_pk_att_vals_array,
                        text[] tgt_pk_att_vals_array) returns text

Описание

Функция dblink_build_sql_update может быть полезна при избирательной репликации локальной таблицы в удаленную базу данных. Она выбирает строки из локальной таблицы по первичному ключу, а затем формирует команду SQL UPDATE, которая дублирует эту строку, но заменяет значения первичного ключа на значения из последнего аргумента. (Чтобы получить точную копию строки, просто укажите одинаковые значения в двух последних аргументах.) Команда UPDATE всегда присваивает значения всем полям строки — главное отличие этой функции от dblink_build_sql_insert в том, что она предполагает, что целевая строка уже существует в удаленной таблице.

Аргументы

relname
Имя локального отношения, например foo или myschema.mytab. Заключите его в кавычки, если это имя в смешанном регистре или содержит специальные символы, например "FooBar"; без кавычек эта строка приводится к нижнему регистру.

primary_key_attnums
Номера атрибутов (начиная с 1) полей первичного ключа, например 1 2.

num_primary_key_atts
Число полей первичного ключа.

src_pk_att_vals_array
Значения полей первичного ключа, по которым будет выполняться поиск локального кортежа. Каждое поле представляется в текстовом виде. Если локальной строки с этими значениями первичного ключа нет, выдается ошибка.

tgt_pk_att_vals_array
Значения полей первичного ключа, которые будут помещены в результирующую команду UPDATE. Каждое поле представляется в текстовом виде.

Возвращаемое значение

Возвращает запрошенный оператор SQL в текстовом виде.

Примечания

Номера атрибутов в primary_key_attnums воспринимаются как логические номера столбцов, соответствующие позициям столбцов в SELECT * FROM relname, а не как физические позиции столбцов.

Примеры

SELECT dblink_build_sql_update('foo', '1 2', 2, '{"1", "a"}', '{"1", "b"}');
                   dblink_build_sql_update
-------------------------------------------------------------
 UPDATE foo SET f1='1',f2='b',f3='1' WHERE f1='1' AND f2='b'
(1 row)

dict_int

Модуль dict_int представляет собой пример дополнительного шаблона словаря для полнотекстового поиска. Этот пример словаря был создан для управления индексацией целых чисел (со знаком и без); он позволяет индексировать такие числа и при этом избежать чрезмерного возрастания количества уникальных слов, что значительно увеличивает производительность поиска.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Конфигурация

Этот словарь принимает три параметра:

Параметр maxlen задает максимальное количество цифр, из которого может состоять целое число parameter. Значение по умолчанию — 6.

Параметр rejectlong определяет, должны ли чрезмерно длинные числа усекаться или игнорироваться. Если rejectlong имеет значение false (по умолчанию), этот словарь возвращает первые maxlen цифр целого числа. Если rejectlong имеет значение true, этот словарь воспринимает чрезмерно длинное число как стоп-слово, поэтому оно не будет индексироваться. Обратите внимание, что это означает, что такое целое число нельзя будет найти.

Параметр absval определяет, должны ли удаляться стоящие перед целыми числами знаки «+» и «-». Значение по умолчанию — false. При значении true знак удаляется до применения ограничения maxlen.

Применение

При установке расширения dict_int создается шаблон текстового поиска intdict_template и словарь intdict на его основе, с параметрами по умолчанию. Эти параметры можно изменить, например:

mydb# ALTER TEXT SEARCH DICTIONARY intdict (MAXLEN = 4, REJECTLONG = true);
ALTER TEXT SEARCH DICTIONARY

или создать новые словари на основе этого шаблона.

Протестировать этот словарь можно так:

mydb# select ts_lexize('intdict', '12345678');
 ts_lexize
-----------
 {123456}

но для практического применения его нужно включить в конфигурацию текстового поиска, как описано в главе Полнотекстовый поиск. Это может выглядеть так:

ALTER TEXT SEARCH CONFIGURATION english
    ALTER MAPPING FOR int, uint WITH intdict;

dict_xsyn

Модуль dict_xsyn (Extended Synonym Dictionary, расширенный словарь синонимов) представляет собой пример дополнительного шаблона словаря для полнотекстового поиска. Этот тип словаря заменяет слова группами их синонимов, тем самым позволяя находить слово по одному из его синонимов.

Конфигурация

Словарь dict_xsyn принимает следующие параметры:

Параметр matchorig определяет, будет ли словарь принимать исходное слово. Значение по умолчанию — true (включен).

Параметр matchsynonyms определяет, будет ли словарь принимать синонимы. Значение по умолчанию — false (выключен).

Параметр keeporig определяет, будет ли в вывод словаря включаться исходное слово. Значение по умолчанию — true (включен).

Параметр keepsynonyms определяет, будут ли в вывод словаря включаться синонимы. Значение по умолчанию — true (включен).

Параметр rules задает базовое имя файла, содержащего список синонимов. Этот файл должен храниться в каталоге $SHAREDIR/tsearch_data/ (где под $SHAREDIR подразумевается каталог с общими данными установки QHB). Имя файла должно заканчиваться расширением .rules (которое не нужно указывать в параметре rules).

Файл правил имеет следующий формат:

Каждая строка представляет группу синонимов для одного слова, которое задается первым в этой строке. Синонимы разделяются пробелами, поэтому строка выглядит так:

слово син1 син2 син3

Символ решетки (#) является знаком-разграничителем для комментария. Он может находиться в любом месте строки. Следующая за ним часть строки будет пропущена.

Пример словаря можно найти в файле xsyn_sample.rules, устанавливаемом в $SHAREDIR/tsearch_data/.

Применение

При установке расширения dict_xsyn в базе создается шаблон текстового поиска xsyn_template и словарь xsyn на его основе, с параметрами по умолчанию. Эти параметры можно изменить, например так:

mydb# ALTER TEXT SEARCH DICTIONARY xsyn (RULES='my_rules', KEEPORIG=false);
ALTER TEXT SEARCH DICTIONARY

или создать новые словари на основе этого шаблона.

Протестировать этот словарь можно так:

mydb=# SELECT ts_lexize('xsyn', 'word');
      ts_lexize
-----------------------
 {syn1,syn2,syn3}

mydb# ALTER TEXT SEARCH DICTIONARY xsyn (RULES='my_rules', KEEPORIG=true);
ALTER TEXT SEARCH DICTIONARY

mydb=# SELECT ts_lexize('xsyn', 'word');
      ts_lexize
-----------------------
 {word,syn1,syn2,syn3}

mydb# ALTER TEXT SEARCH DICTIONARY xsyn (RULES='my_rules', KEEPORIG=false, MATCHSYNONYMS=true);
ALTER TEXT SEARCH DICTIONARY

mydb=# SELECT ts_lexize('xsyn', 'syn1');
      ts_lexize
-----------------------
 {syn1,syn2,syn3}

mydb# ALTER TEXT SEARCH DICTIONARY xsyn (RULES='my_rules', KEEPORIG=true, MATCHORIG=false, KEEPSYNONYMS=false);
ALTER TEXT SEARCH DICTIONARY

mydb=# SELECT ts_lexize('xsyn', 'syn1');
      ts_lexize
-----------------------
 {word}

Для практического же применения его нужно включить в конфигурацию текстового поиска, как описано в главе Полнотекстовый поиск. Это может выглядеть примерно так:

ALTER TEXT SEARCH CONFIGURATION english
    ALTER MAPPING FOR word, asciiword WITH xsyn, english_stem;

earthdistance

Модуль earthdistance предоставляет два разных способа вычисления ортодромии (расстояния по дуге большого круга; кратчайшего расстояния между двумя точками на поверхности Земли). Первый описанный способ зависит от модуля cube. Второй основан на встроенном типе данных point, использующим в качестве координат долготу и широту.

В этом модуле Земля считается идеальной сферой. (Если для вас это недостаточно точно, обратите внимание на проект PostGIS.)

Прежде чем устанавливать earthdistance, необходимо установить модуль cube (хотя можно воспользоваться параметром CASCADE команды CREATE EXTENSION и установить сразу оба расширения).

ВНИМАНИЕ!
Настоятельно рекомендуется устанавливать модули earthdistance и cube в одну схему, и при этом в данной схеме недоверенные пользователи не должны в настоящем и будущем иметь право CREATE. В противном случае, если схема модуля earthdistance будет содержать объекты, определенные злонамеренным пользователем, во время установки возникнет угроза безопасности. Более того, при использовании функций модуля earthdistance после установки расширения весь путь поиска должен содержать только доверенные схемы.

Земные расстояния по кубам

Данные хранятся в кубах, представляющих собой точки (оба угла куба совпадают) по 3 координатам, выражающим расстояния x, y и z от центра Земли. Этот модуль предоставляет домен earth на базе cube, включающий проверки того, что значение соответствует этим ограничениям и достаточно близко к реальной поверхности Земли.

Радиус Земли выдает функция earth(). Он выражается в метрах. Однако, изменив одну эту функцию, можно сделать так, чтобы модуль работал с какими-то другими единицами, либо использовать другое значение радиуса, которое кажется более подходящим.

Этот пакет также может применяться в астрономических базах данных. Вероятно, астрономы предпочтут изменить функцию earth(), чтобы та возвращала радиус, равный 180/pi(), и, как следствие, расстояния выдавались в градусах.

В этом модуле реализованы функции, поддерживающие ввод данных, выражающих широту и долготу (в градусах), вывод широты и долготы, вычисление ортодромии между двумя точками и простое указание ограничивающего прямоугольника, полезного для поиска по индексу.

Предоставляемые функции показаны в Таблице 5.

Таблица 5. Функции земных расстояний по кубам

Функция

Описание

earth () → float8

Возвращает предполагаемый радиус Земли.

sec_to_gc ( float8 ) → float8

Переводит расстояние по обычной прямой (секущей) между двумя точками на поверхности Земли в ортодромическое расстояние между ними.

gc_to_sec ( float8 ) → float8

Переводит ортодромическое расстояние между двумя точками на поверхности Земли в расстояние по обычной прямой (секущей) между ними.

ll_to_earth ( float8, float8 ) → earth

Возвращает положение точки на поверхности Земли по заданным широте (аргумент 1) и долготе (аргумент 2) в градусах.

latitude ( earth ) → float8

Возвращает широту (в градусах) точки на поверхности Земли.

longitude ( earth ) → float8

Возвращает долготу (в градусах) точки на поверхности Земли.

earth_distance ( earth, earth ) → float8

Возвращает ортодромическое расстояние между двумя точками на поверхности Земли.

earth_box ( earth, float8 ) → cube

Возвращает ограничивающий прямоугольник, подходящий для поиска по индексу с применением реализованного для типа cube оператора @> для точек в пределах заданной ортодромии от цели. Некоторые точки в этом прямоугольнике будут отстоять от цели дальше, чем на заданную ортодромию, поэтому в запрос следует включить вторую проверку с функцией earth_distance.

Земные расстояния по точкам

Вторая часть этого модуля основана на представлении точек на Земле в виде значений типа point, в которых первый компонент представляет долготу в градусах, а второй — широту в градусах. Точки воспринимаются как (долгота, широта), а не наоборот, так как долгота ближе к интуитивному представлению оси x, а широта — оси y.

Модуль предоставляет один оператор, показанный в Таблице 6.

Таблица 6. Операторы земных расстояний по точкам

Оператор

Описание

point <@> point → float8

Вычисляет расстояние в сухопутных милях между двумя точками на поверхности Земли.

Обратите внимание, что, в отличие от части модуля на основе cube, в этой части единицы запрограммированы жестко: изменение функции earth() не повлияет на результаты этого оператора.

Единственный недостаток представления в виде долготы/широты состоит в том, что вам придется учитывать граничные условия возле полюсов и в районе +/- 180 градусов долготы. Представление на основе cube лишено таких нарушений непрерывности.

file_fdw

Модуль file_fdw предоставляет обертку сторонних данных file_fdw, с помощью которой можно обращаться к файлам данных в файловой системе сервера или выполнять программы на сервере и читать их вывод. Файлы данных и вывод программ должны быть в формате, который может прочесть команда COPY FROM; подробную информацию см. на справочной странице команды COPY. В настоящее время файлы данных доступны только для чтения.

Для сторонней таблицы, создаваемой посредством этой обертки, можно задать следующие параметры:

filename
Задает имя файла, который нужно прочитать. Относительные пути рассматриваются от каталога данных. Следует указать либо параметр filename, либо параметр program, но не оба сразу.

program
Задает команду, которую нужно выполнить. Стандартный вывод этой команды будет прочитан так же, как если бы выполнялась COPY FROM PROGRAM. Следует указать либо параметр program, либо параметр filename, но не оба сразу.

format
Задает формат данных аналогично параметру FORMAT команды COPY.

header
Определяет, будут ли данные содержать строку заголовка, аналогично параметру HEADER команды COPY.

delimiter
Задает символ-разделитель данных аналогично параметру DELIMITER команды COPY.

quote
Задает символ для заключения данных в кавычки аналогично параметру QUOTE команды COPY.

escape
Задает символ, экранирующий данные, аналогично параметру ESCAPE команды COPY.

null
Задает нулевую строку данных аналогично параметру NULL команды COPY.

encoding
Задает кодировку данных аналогично параметру ENCODING команды COPY.

Обратите внимание, что хотя COPY принимает параметры, такие как HEADER, без соответствующего значения, синтаксис обертки сторонних данных требует, чтобы значение присутствовало во всех случаях. Для активации параметров COPY, которые обычно записываются без значения, им можно просто передать значение TRUE, так как все они являются логическими.

Для столбцов сторонней таблицы, создаваемой посредством этой обертки, можно задать следующие параметры:

force_not_null
Это логический параметр. true указывает, что значения столбца не должны сверяться с нулевой строкой (то есть со значением, заданным параметром null на уровне таблицы). Эффект аналогичен включению столбца в список параметра FORCE_NOT_NULL команды COPY.

force_null
Это логический параметр. true указывает, что значения столбца, соответствующие нулевой строке, должны возвращаться как NULL, даже если они заключены в кавычки. Без этого параметра как NULL будут возвращаться только значения без кавычек. Эффект аналогичен включению столбца в список параметра FORCE_NULL команды COPY.

В настоящее время file_fdw не поддерживает параметр FORCE_QUOTE команды COPY.

Эти параметры можно задать только для сторонней таблицы или ее столбцов, но не для обертки сторонних данных file_fdw и не сервера или сопоставления пользователя, использующих эту обертку.

По соображениям безопасности для изменения параметров на уровне таблицы нужно быть суперпользователем или иметь права роли pg_read_server_files (для указания имени файла) или роли pg_execute_server_program (для указания программы): только избранные пользователи должны иметь возможность выбирать, какой файл читать или какую программу запускать. В принципе, право изменять остальные параметры можно предоставить и обычным пользователям, но в настоящее время это не поддерживается.

Задавая параметр program, не забывайте, что эта строка выполняется оболочкой операционной системы. Если нужно передать заданной команде аргументы из недоверенного источника, позаботьтесь об исключении или экранировании всех символов, которые могут иметь особое значение для оболочки. По соображениям безопасности лучше использовать фиксированную командную строку или как минимум не передавать в ней данные, поступающие от пользователя.

Для сторонних таблиц, использующих file_fdw, команда EXPLAIN показывает имя читаемого файла или запускаемой программы. Если не указывать COSTS OFF, то показывается также размер файла (в байтах).

Пример 1. Создание сторонней таблицы для журналов сервера QHB

Одно из очевидных применений file_fdw — это предоставления запросам доступа к журналу операций QHB как к таблице. Для этого необходимо заранее настроить вывод сообщений в файл CSV, который мы далее будем называть qhblog.csv. Сначала установите расширение file_fdw:

CREATE EXTENSION file_fdw;

Затем создайте сторонний сервер:

CREATE SERVER qhblog FOREIGN DATA WRAPPER file_fdw;

Теперь вы готовы к созданию сторонней таблицы. В команде CREATE FOREIGN TABLE нужно определить столбцы таблицы, имя файла CSV и его формат:

CREATE FOREIGN TABLE qhblog (
  log_time timestamp(3) with time zone,
  user_name text,
  database_name text,
  process_id integer,
  connection_from text,
  session_id text,
  session_line_num bigint,
  command_tag text,
  session_start_time timestamp with time zone,
  virtual_transaction_id text,
  transaction_id bigint,
  error_severity text,
  sql_state_code text,
  message text,
  detail text,
  hint text,
  internal_query text,
  internal_query_pos integer,
  context text,
  query text,
  query_pos integer,
  location text,
  application_name text,
  backend_type text,
  leader_pid integer,
  query_id bigint
) SERVER qhblog
OPTIONS ( filename 'log/qhblog.csv', format 'csv' );

Вот и все — теперь вы можете выполнять запросы напрямую к журналу сервера. Конечно, в производственной среде еще понадобится как-то разобраться с ротацией файлов журнала.

fuzzystrmatch

Модуль fuzzystrmatch предоставляет несколько функций для определения схожести и расстояния между строками.

ВНИМАНИЕ!
В настоящее время функции soundex, metaphone, dmetaphone и dmetaphone_alt плохо работают с многобайтными кодировками (такими, как UTF-8).

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Soundex

Система Soundex представляет собой метод сопоставления похожих по звучанию имен путем приведения их к одному коду. Изначально она использовалась для обработки данных переписи населения США в 1880, 1900 и 1910 г. Обратите внимание, что Soundex не очень полезна для неанглоязычных имен.

Модуль fuzzystrmatch предоставляет две функции для работы с кодами Soundex:

soundex(text) returns text
difference(text, text) returns int

Функция soundex преобразует строку в ее код Soundex. Функция difference преобразует две строки в их коды Soundex, а затем сообщает количество совпадающих позиций в этих кодах. Поскольку коды Soundex состоят из четырех символов, результат находится в диапазоне от нуля до четырех, где ноль обозначает полное несоответствие, а четыре — точное совпадение. (Таким образом, имя этой функции некорректно — лучшим именем для нее было бы similarity.)

Несколько примеров использования:

SELECT soundex('hello world!');

SELECT soundex('Anne'), soundex('Ann'), difference('Anne', 'Ann');
SELECT soundex('Anne'), soundex('Andrew'), difference('Anne', 'Andrew');
SELECT soundex('Anne'), soundex('Margaret'), difference('Anne', 'Margaret');

CREATE TABLE s (nm text);

INSERT INTO s VALUES ('john');
INSERT INTO s VALUES ('joan');
INSERT INTO s VALUES ('wobbly');
INSERT INTO s VALUES ('jack');

SELECT * FROM s WHERE soundex(nm) = soundex('john');

SELECT * FROM s WHERE difference(s.nm, 'john') > 2;

Левенштейн

Эта функция вычисляет расстояние Левенштейна между двумя строками:

levenshtein(text source, text target, int ins_cost, int del_cost, int sub_cost) returns int
levenshtein(text source, text target) returns int
levenshtein_less_equal(text source, text target, int ins_cost, int del_cost, int sub_cost, int max_d) returns int
levenshtein_less_equal(text source, text target, int max_d) returns int

И в source, и в target может быть передана любая строка, отличная от NULL, не длиннее 255 символов. Параметры стоимости (ins_cost, del_cost, sub_cost) определяют затраты на добавление, удаление или замену символов соответственно. Эти параметры можно опустить, как во второй версии функции; в этом случае все они по умолчанию равны 1.

Функция levenshtein_less_equal является ускоренной версией функции Левенштейна, предназначенной для использования, когда интерес представляют только небольшие расстояния. Если фактическое расстояние меньше или равно max_d, то levenshtein_less_equal возвращает его точное значение; в противном случае она возвращает некоторое значение, превышающее max_d. Если значение max_d отрицательное, она работает так же, как функция levenshtein.

Примеры:

test=# SELECT levenshtein('GUMBO', 'GAMBOL');
 levenshtein
-------------
           2
(1 row)

test=# SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1);
 levenshtein
-------------
           3
(1 row)

test=# SELECT levenshtein_less_equal('extensive', 'exhaustive', 2);
 levenshtein_less_equal
------------------------
                      3
(1 row)

test=# SELECT levenshtein_less_equal('extensive', 'exhaustive', 4);
 levenshtein_less_equal
------------------------
                      4
(1 row)

Metaphone

Metaphone, как и Soundex, основывается на идее составления кода, представляющего входную строку. Две строки признаются похожими, если их коды совпадают.

Эта функция вычисляет кода метафона входной строки:

metaphone(text source, int max_output_length) returns text

В source должна быть передана строка, отличная от NULL, не длиннее 255 символов. Параметр max_output_length устанавливает максимальную длину выходного кода метафона; если код оказывается длиннее, выходная строка обрезается до этой длины.

Пример:

test=# SELECT metaphone('GUMBO', 4);
 metaphone
-----------
 KM
(1 row)

Double Metaphone

Система Double Metaphone (двойной метафон) вычисляет две строки «похожего звучания» для заданной строки — «первичную» и «альтернативную». В большинстве случаев они совпадают, но для неанглоязычных имен в особенности они могут немного отличаться, в зависимости от произношения. Эти функции вычисляют первичный и альтернативный коды:

dmetaphone(text source) returns text
dmetaphone_alt(text source) returns text

Ограничение на длину входных строк отсутствует.

Пример:

test=# SELECT dmetaphone('gumbo');
 dmetaphone
------------
 KMP
(1 row)

hstore

Этот модуль реализует тип данных hstore для хранения наборов пар ключ/значение внутри одного значения QHB. Это может быть полезно в различных сценариях, например для строк со множеством редко проверяемых атрибутов или частично структурированных данных. Ключи и значения представляют собой простые текстовые строки.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Внешнее представление hstore

Текстовое представление типа hstore, применяемое для ввода и вывода, включает ноль или более пар ключ => значение, разделенных запятыми. Несколько примеров:

k => v
foo => bar, baz => whatever
"1-a" => "anything at all"

Порядок пар не имеет значения (и может не воспроизводиться в выводе). Пробелы между парами и вокруг знака => игнорируются. Ключи и значения, содержащие пробелы, запятые и знаки = или >, нужно заключать в кавычки. Чтобы включить в ключ или значение обратный слэш или кавычки, следует экранировать его обратным слэшем.

Все ключи в hstore уникальны. Если объявить hstore с дублирующимися ключами, в нем будет сохранен только один ключ без гарантии выбора, какой именно:

SELECT 'a=>1,a=>2'::hstore;
  hstore
----------
 "a"=>"1"

Значением (но не ключом) может быть SQL NULL. Например:

key => NULL

Ключевое слово NULL нечувствительно к регистру. Чтобы NULL воспринимался как обычная строка «NULL», следует заключить его в кавычки.

Примечание
Учтите, что когда текстовый формат hstore используется для ввода данных, он применяется до всех требуемых кавычек или управляющих символов. Если вы передаете литерал hstore в параметре, в дополнительной обработке нет необходимости. Но если вы передаете его в виде строковой константы в кавычках, то все символы апострофов и (в зависимости от параметра конфигурации standard_conforming_strings) обратного слэша нужно корректно экранировать. Подробнее о работе со строковыми константами можно узнать в подразделе Строковые константы.

При выводе ключи и значения всегда заключаются в кавычки, даже если в этом нет особой необходимости.

Операторы и функции hstore

Операторы, предоставляемые модулем hstore, перечислены в Таблице 7, функции — в Таблице 8.

Таблица 7. Операторы hstore

Оператор

Описание
Пример(ы)

hstore -> text → text

Возвращает значение, связанное с заданным ключом, или NULL, если ключ отсутствует.
'a=>x, b=>y'::hstore -> 'a' → x

hstore -> text[] → text[]

Возвращает значения, связанные с заданными ключами, или NULL, если ключи отсутствуют.
'a=>x, b=>y, c=>z'::hstore -> ARRAY['c','a'] → {"z","x"}

hstore || hstore → hstore

Конкатенирует два набора hstore.
'a=>b, c=>d'::hstore || 'c=>x, d=>q'::hstore → "a"=>"b", "c"=>"x", "d"=>"q"

hstore ? text → boolean

Набор hstore содержит ключ?
'a=>1'::hstore ? 'a' → t

hstore ?& text[] → boolean

Набор hstore содержит все указанные ключи?
'a=>1,b=>2'::hstore ?& ARRAY['a','b'] → t

hstore ?| text[] → boolean

Набор hstore содержит какой-либо из указанных ключей?
'a=>1,b=>2'::hstore ?| ARRAY['b','c'] → t

hstore @> hstore → boolean

Левый операнд содержит правый?
'a=>b, b=>1, c=>NULL'::hstore @> 'b=>1' → t

hstore <@ hstore → boolean

Левый операнд содержится в правом?
'a=>c'::hstore <@ 'a=>b, b=>1, c=>NULL' → f

hstore - text → hstore

Удаляет ключ из левого операнда.
'a=>1, b=>2, c=>3'::hstore - 'b'::text → "a"=>"1", "c"=>"3"

hstore - text[] → hstore

Удаляет ключи из левого операнда.
'a=>1, b=>2, c=>3'::hstore - ARRAY['a','b'] → "c"=>"3"

hstore - hstore → hstore

Удаляет из левого операнда пары ключ/значение, совпадающие с парами в правом.
'a=>1, b=>2, c=>3'::hstore - 'a=>4, b=>2'::hstore → "a"=>"1", "c"=>"3"

anyelement #= hstore → anyelement

Заменяет поля в левом операнде (который должен иметь составной тип) соответствующими значениями из hstore.
ROW(1,3) #= 'f1=>11'::hstore → (11,3)

%% hstore → text[]

Преобразует hstore в массив перемежающихся ключей и значений.
%% 'a=>foo, b=>bar'::hstore → {a,foo,b,bar}

%# hstore → text[]

Преобразует hstore в двумерный массив ключей/значений.
%# 'a=>foo, b=>bar'::hstore → {{a,foo},{b,bar}}

Таблица 8. Функции hstore

Функция

Описание
Пример(ы)

hstore ( record ) → hstore

Формирует hstore из записи или кортежа.
hstore(ROW(1,2)) → "f1"=>"1", "f2"=>"2"

hstore ( text[] ) → hstore

Формирует hstore из массива, который может быть двумерным или содержать перемежающиеся ключи/значения.
hstore(ARRAY['a','1','b','2']) → "a"=>"1", "b"=>"2"
hstore(ARRAY[['c','3'],['d','4']]) → "c"=>"3", "d"=>"4"

hstore ( text[], text[] ) → hstore

Формирует hstore из отдельных массивов ключей и значений.
hstore(ARRAY['a','b'], ARRAY['1','2']) → "a"=>"1", "b"=>"2"

hstore ( text, text ) → hstore

Формирует hstore с одним элементом.
hstore('a', 'b') → "a"=>"b"

akeys ( hstore ) → text[]

Извлекает ключи из hstore в виде массива.
akeys('a=>1,b=>2') → {a,b}

skeys ( hstore ) → setof text

Извлекает ключи из hstore в виде множества.

skeys('a=>1,b=>2') →
    a
    b

avals ( hstore ) → text[]

Извлекает значения из hstore в виде массива.
avals('a=>1,b=>2') → {1,2}

svals ( hstore ) → setof text

Извлекает значения из hstore в виде множества.

svals('a=>1,b=>2') →
    1
    2

hstore_to_array ( hstore ) → text[]

Извлекает ключи и значения из hstore в виде массива перемежающихся ключей и значений.
hstore_to_array('a=>1,b=>2') → {a,1,b,2}

hstore_to_matrix ( hstore ) → text[]

Извлекает ключи и значения из hstore в виде двумерного массива.
hstore_to_matrix('a=>1,b=>2') → {{a,1},{b,2}}

hstore_to_json ( hstore ) → json

Преобразует hstore в json, конвертируя все отличные от NULL в строки JSON.
Эта функция используется неявно, когда значение hstore приводится к типу json.
hstore_to_json('"a key"=>1, b=>t, c=>null, d=>12345, e=>012345, f=>1.234, g=>2.345e+4') → {"a key": "1", "b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4"}

hstore_to_jsonb ( hstore ) → jsonb

Преобразует hstore в jsonb, конвертируя все отличные от NULL в строки JSON.
Эта функция используется неявно, когда значение hstore приводится к типу jsonb.
hstore_to_jsonb('"a key"=>1, b=>t, c=>null, d=>12345, e=>012345, f=>1.234, g=>2.345e+4') → {"a key": "1", "b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4"}

hstore_to_json_loose ( hstore ) → json

Преобразует hstore в json, но при этом пытается распознать числовые и логические и передать их в JSON без кавычек.
hstore_to_json_loose('"a key"=>1, b=>t, c=>null, d=>12345, e=>012345, f=>1.234, g=>2.345e+4') → {"a key": 1, "b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4}

hstore_to_jsonb_loose ( hstore ) → jsonb

Преобразует hstore в jsonb, но при этом пытается распознать числовые и логические и передать их в JSON без кавычек.
hstore_to_jsonb_loose('"a key"=>1, b=>t, c=>null, d=>12345, e=>012345, f=>1.234, g=>2.345e+4') → {"a key": 1, "b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4}

slice ( hstore, text[] ) → hstore

Извлекает из hstore подмножество, содержащее только указанные ключи.
slice('a=>1,b=>2,c=>3'::hstore, ARRAY['b','c','x']) → "b"=>"2", "c"=>"3"

each ( hstore ) → setof record ( ключ text, значение text )

Извлекает из hstore ключи и значения в виде набора записей.

select * from each('a=>1,b=>2') →
 key | value
-----+-------
 a   | 1
 b   | 2

exist ( hstore, text ) → boolean

hstore содержит ключ?
exist('a=>1', 'a') → t

defined ( hstore, text ) → boolean

hstore содержит отличное от NULL значение для ключа?
defined('a=>NULL', 'a') → f

delete ( hstore, text ) → hstore

Удаляет пару с соответствующим ключом.
delete('a=>1,b=>2', 'b') → "a"=>"1"

delete ( hstore, text[] ) → hstore

Удаляет пары с соответствующими ключами.
delete('a=>1,b=>2,c=>3', ARRAY['a','b']) → "c"=>"3"

delete ( hstore, hstore ) → hstore

Удаляет пары, соответствующие парам во втором аргументе.
delete('a=>1,b=>2', 'a=>4,b=>2'::hstore) → "a"=>"1"

populate_record ( anyelement, hstore ) → anyelement

Заменяет поля в левом операнде (который должен иметь составной тип) соответствующими значениями из hstore.
populate_record(ROW(1,2), 'f1=>42'::hstore) → (42,2)

Помимо этих операторов и функций к значениям типа hstore можно обратиться по индексу, позволяя им действовать подобно ассоциативным массивам. Можно указать только один индекс типа text; он воспринимается как ключ, для которого извлекается или сохраняется соответствующее значение. Например:

CREATE TABLE mytable (h hstore);
INSERT INTO mytable VALUES ('a=>b, c=>d');
SELECT h['a'] FROM mytable;
 h
---
 b
(1 row)

UPDATE mytable SET h['c'] = 'new';
SELECT h FROM mytable;
          h
----------------------
 "a"=>"b", "c"=>"new"
(1 row)

При обращении по индексу возвращается NULL, если индекс — NULL или указанный ключ не существует в hstore. (Таким образом, обращение по индексу не сильно отличается от оператора ->.) Изменение по индексу не выполняется, если индекс — NULL; в остальных случаях заменяется значение указанного ключа (если такой ключ еще не существует, он добавляется в hstore).

Индексы

Тип hstore поддерживает индексы GiST и GIN для операторов @>, ?, ?& и ?|. Например:

CREATE INDEX hidx ON testhstore USING GIST (h);

CREATE INDEX hidx ON testhstore USING GIN (h);

Класс операторов GiST gist_hstore_ops аппроксимирует набор пар ключ/значение как сигнатуру битовой карты. Его необязательный целочисленный параметр siglen определяет длину сигнатуры в байтах. Значение по умолчанию — 16 байт. Допустима длина сигнатуры в пределах от 1 до 2024 байт. При увеличении размера сигнатур поиск работает точнее (сканируется меньшая область индекса и меньше страниц кучи), но сам индекс становится больше.

Пример создания такого индекса с длиной сигнатуры 32 байта:

CREATE INDEX hidx ON testhstore USING GIST (h gist_hstore_ops(siglen=32));

Тип hstore также поддерживает индексы btree и hash для оператора =. Это позволяет объявлять столбцы hstore как уникальные (UNIQUE) или использовать их в выражениях GROUP BY, ORDER BY или DISTINCT. Порядок сортировки значений hstore не имеет особого смысла, но эти индексы могут быть полезны для поиска по равенству. Создать индексы для сравнений с помощью = можно так:

CREATE INDEX hidx ON testhstore USING BTREE (h);

CREATE INDEX hidx ON testhstore USING HASH (h);

Примеры

Присвоение значения новому или уже существующему ключу:

UPDATE tab SET h['c'] = '3';

Другой способ сделать то же самое:

UPDATE tab SET h = h || hstore('c', '3');

Если в одной операции нужно добавить или изменить несколько ключей, эффективнее использовать конкатенацию, а не обращение по индексу:

UPDATE tab SET h = h || hstore(array['q', 'w'], array['11', '12']);

Удаление ключа:

UPDATE tab SET h = delete(h, 'k1');

Преобразование типа record в тип hstore:

CREATE TABLE test (col1 integer, col2 text, col3 text);
INSERT INTO test VALUES (123, 'foo', 'bar');

SELECT hstore(t) FROM test AS t;
                   hstore
---------------------------------------------
 "col1"=>"123", "col2"=>"foo", "col3"=>"bar"
(1 row)

Преобразование типа hstore в предопределенный тип record:

CREATE TABLE test (col1 integer, col2 text, col3 text);

SELECT * FROM populate_record(null::test,
                              '"col1"=>"456", "col2"=>"zzz"');
 col1 | col2 | col3
------+------+------
  456 | zzz  |
(1 row)

Изменение существующей записи с использованием значений из hstore:

CREATE TABLE test (col1 integer, col2 text, col3 text);
INSERT INTO test VALUES (123, 'foo', 'bar');

SELECT (r).* FROM (SELECT t #= '"col3"=>"baz"' AS r FROM test t) s;
 col1 | col2 | col3
------+------+------
  123 | foo  | baz
(1 row)

Статистика

Из-за свойственной ему либеральности тип hstore может содержать множество различных ключей. Проверка ключей на допустимость является задачей этого приложения. Следующие примеры демонстрируют несколько приемов проверки ключей и получения статистики.

Простой пример:

SELECT * FROM each('aaa=>bq, b=>NULL, ""=>1');

С использованием таблицы:

CREATE TABLE stat AS SELECT (each(h)).key, (each(h)).value FROM testhstore;

Актуальная статистика:

SELECT key, count(*) FROM
  (SELECT (each(h)).key FROM testhstore) AS stat
  GROUP BY key
  ORDER BY count DESC, key;
    key    | count
-----------+-------
 line      |   883
 query     |   207
 pos       |   203
 node      |   202
 space     |   197
 status    |   195
 public    |   194
 title     |   190
 org       |   189
...................

Авторы

Олег Бартунов (oleg@sai.msu.su), Москва, Московский государственный университет имени М. В. Ломоносова, Россия

Федор Сигаев (teodor@sigaev.ru), Москва, ООО «Дельта-Софт», Россия

Дополнительные улучшения внес Эндрю Гирт (andrew@tao11.riddles.org.uk), Великобритания

intagg

Модуль intagg предоставляет агрегатор и нумератор целых чисел. На данный момент intagg считается устаревшим, поскольку в QHB имеются встроенные функции, предлагающие более широкие возможности. Однако этот модуль по-прежнему поставляется в целях обратной совместимости, в качестве обертки для встроенных функций.

Функции

Агрегатор реализуется функцией int_array_aggregate(integer), которая выдает массив целых чисел, содержащий в точности те числа, что переданы ей. Это обертка встроенной функции array_agg, которая делает то же самое для массива любого типа.

Нумератор реализуется функцией int_array_enum(integer[]), которая возвращает множество целых чисел (setof integer). По сути его действие обратно действию агрегатора: получив массив целых чисел, нумератор разворачивает его в набор строк. Это обертка встроенной функции unnest, которая делает то же самое для массива любого типа.

Примеры использования

Во многих СУБД есть понятие таблицы отношений «один ко многим». Такая таблица обычно находится между двумя индексированными таблицами, например:

CREATE TABLE left (id INT PRIMARY KEY, ...);
CREATE TABLE right (id INT PRIMARY KEY, ...);
CREATE TABLE one_to_many(left INT REFERENCES left, right INT REFERENCES right);

Обычно она используется так:

SELECT right.* from right JOIN one_to_many ON (right.id = one_to_many.right)
  WHERE one_to_many.left = item;

Этот запрос вернет все элементы из таблицы справа для записи в таблице слева. Это очень распространенная конструкция в SQL.

Однако этот подход может оказаться трудоемким, если в таблице one_to_many очень большое количество записей. Зачастую такое соединение приводит к сканированию индекса и выборке для каждой записи в таблице справа для определенного элемента слева. Если у вас крайне динамическая система, с этим ничего не поделать. Однако если некоторые данные относительно статичны, вы можете создать сводную таблицу при помощи агрегатора.

CREATE TABLE summary AS
  SELECT left, int_array_aggregate(right) AS right
  FROM one_to_many
  GROUP BY left;

Эта команда создаст таблицу, содержащую одну строку для каждого элемента слева с массивом элементов справа. Она довольно бесполезна, пока нет подходящего способа использования этого массива; именно для этого и нужен нумератор массива. Вы можете выполнить:

SELECT left, int_array_enum(right) FROM summary WHERE left = item;

Приведенный выше запрос с вызовом int_array_enum выдает те же результаты, что и

SELECT left, right FROM one_to_many WHERE left = item;

Разница между ними в том, что запрос к сводной таблице должен выдать только одну строку таблицы, тогда как непосредственный запрос к one_to_many потребует сканирования индекса и выборки строки для каждой записи.

В отдельной системе команда EXPLAIN показала, что стоимость запроса снизилась с 8488 до 329. Исходный запрос выполнял соединение с таблицей one_to_many и был заменен на:

SELECT right, count(right) FROM
  ( SELECT left, int_array_enum(right) AS right
    FROM summary JOIN (SELECT left FROM left_table WHERE left = item) AS lefts
         ON (summary.left = lefts.left)
  ) AS list
  GROUP BY right
  ORDER BY count DESC;

intarray

Модуль intarray предоставляет ряд полезных функций и операторов для работы с массивами целых чисел без NULL. Также для некоторых из этих операторов он поддерживает поиск по индексу.

Все эти операции выдадут ошибку, если передаваемый массив содержит элементы NULL.

Многие из этих операций имеют смысл только с одномерными массивами. Хотя они примут на вход массивы и большей размерности, данные из них будут обрабатываться как из линейного массива в порядке хранения.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Функции и операторы intarray

Предоставляемые модулем intarray функции перечислены в Таблице 9, а операторы — в Таблице 10.

Таблица 9. Функции intarray

Функция

Описание
Пример(ы)

icount ( integer[] ) → integer

Возвращает число элементов в массиве.
icount('{1,2,3}'::integer[]) → 3

sort ( integer[], напр text ) → integer[]

Сортирует массив в порядке возрастания или убывания. Направление задает параметр напр, значением которого должно быть asc (по возрастанию) или desc (по убыванию).
sort('{1,3,2}'::integer[], 'desc') → {3,2,1}

sort ( integer[] ) → integer[]
sort_asc ( integer[] ) → integer[]

Сортирует в порядке возрастания.
sort(array[11,77,44]) → {11,44,77}

sort_desc ( integer[] ) → integer[]

Сортирует в порядке убывания.
sort_desc(array[11,77,44]) → {77,44,11}

uniq ( integer[] ) → integer[]

Удаляет соседние дубликаты. Часто используется вместе с sort для удаления всех дубликатов
uniq('{1,2,2,3,1,1}'::integer[]) → {1,2,3,1}
uniq(sort('{1,2,3,2,1}'::integer[])) → {1,2,3}

idx ( integer[], элемент integer ) → integer

Возвращает индекс первого элемента массива, соответствующего заданному элементу, или 0, если такого элемента нет.
idx(array[11,22,33,22,11], 22) → 2

subarray ( integer[], начало integer, длина integer ) → integer[]

Извлекает часть массива, начинающуюся с позиции начало и длиной в заданное число элементов.
subarray('{1,2,3,2,1}'::integer[], 2, 3) → {2,3,2}

subarray ( integer[], начало integer ) → integer[]

Извлекает часть массива, начинающуюся с позиции начало.
subarray('{1,2,3,2,1}'::integer[], 2) → {2,3,2,1}

intset ( integer ) → integer[]

Создает массив с одним элементом.
intset(42) → {42}

Таблица 10. Операторы intarray

Оператор

Описание

integer[] && integer[] → boolean

Массивы пересекаются (имеют хотя бы один общий элемент)?

integer[] @> integer[] → boolean

Левый массив содержит правый?

integer[] <@ integer[] → boolean

Левый массив содержится в правом?

# integer[] → integer

Возвращает число элементов в массиве.

integer[] # integer → integer

Возвращает индекс первого элемента массива, соответствующего правому аргументу, или 0, если такого элемента нет. (Аналог функции idx.)

integer[] + integer → integer[]

Добавляет элемент в конец массива.

integer[] + integer[] → integer[]

Конкатенирует массивы.

integer[] - integer → integer[]

Убирает из массива записи, соответствующие правому аргументу.

integer[] - integer[] → integer[]

Убирает из левого массива элементы правого массива.

integer[] | integer → integer[]

Вычисляет объединение аргументов.

integer[] | integer[] → integer[]

Вычисляет объединение аргументов.

integer[] & integer[] → integer[]

Вычисляет пересечение аргументов.

integer[] @@ query_int → boolean

Массив удовлетворяет запросу? (см. ниже)

query_int ~~ integer[] → boolean

Массив удовлетворяет запросу? (коммутатор к @@)

Операторы &&, @> и <@ равнозначны встроенным операторам QHB с теми же именами, за исключением того, что они работают только с целочисленными массивами, не содержащими NULL, тогда как встроенные операторы работают с массивами любого типа. Из-за этого ограничения они в большинстве случаев работают быстрее встроенных операторов.

Операторы @@ и ~~ проверяют, удовлетворяет ли массив запросу, который выражается в виде значения специализированного типа данных query_int. Запрос состоит из целочисленных значений, сравниваемых с элементами массива, возможно, с применением операторов & (AND), | (OR) и ! (NOT). При необходимости можно использовать скобки. Например, запросу 1&(2\|3) соответствуют массивы, которые содержат 1 и также содержат 2 или 3.

Поддержка индексов

Модуль intarray поддерживает индексы для операторов &&, @> и @@ наряду с обычной проверкой равенства массивов.

Модуль предоставляет два параметризированных класса операторов индексов GiST: gist__int_ops (используемый по умолчанию) подходит для маленьких и средних по размеру наборов данных, тогда как gist__intbig_ops применяет сигнатуру большего размера и лучше подходит для индексации больших наборов данных (т. е. столбцов, содержащих много уникальных значений массива). В этой реализации используется структура данных RD-дерева со встроенным сжатием с потерями.

Класс gist__int_ops аппроксимирует набор целых чисел в виде массива целочисленных диапазонов. Его необязательный целочисленный параметр numranges определяет максимальное количество диапазонов в одном ключе индекса. Значение по умолчанию — 100. Допустимые значения лежат в пределах от 1 до 253. При увеличении размера массивов, составляющих ключ индекса GiST, поиск работает точнее (сканируется меньшая область индекса и меньше страниц кучи), но сам индекс становится больше.

Класс gist__intbig_ops аппроксимирует набор целых чисел в виде сигнатуры битовой карты. Его необязательный целочисленный параметр siglen определяет длину сигнатуры в байтах. Значение по умолчанию — 16 байт. Допустима длина сигнатуры в пределах от 1 до 2024 байт. При увеличении размера сигнатур поиск работает точнее (сканируется меньшая область индекса и меньше страниц кучи), но сам индекс становится больше.

Имеется также нестандартный класс операторов GIN, gin__int_ops, который поддерживает эти операторы наряду с <@.

Выбор между поиском по индексам GiST и GIN зависит от относительных характеристик производительности GiST и GIN, которые здесь не рассматриваются.

Пример

-- сообщение может находиться в одной или нескольких «секциях»
CREATE TABLE message (mid INT PRIMARY KEY, sections INT[], ...);

-- создать специализированный индекс с длиной сигнатуры 32 байта
CREATE INDEX message_rdtree_idx ON message USING GIST (sections gist__intbig_ops (siglen = 32));

-- выбрать сообщения в секции 1 ИЛИ 2 - оператор ПЕРЕСЕЧЕНИЯ
SELECT message.mid FROM message WHERE message.sections && '{1,2}';

-- выбрать сообщения в секциях 1 И 2 - оператор ВКЛЮЧЕНИЯ
SELECT message.mid FROM message WHERE message.sections @> '{1,2}';

-- то же самое, но с применением оператора ЗАПРОСА
SELECT message.mid FROM message WHERE message.sections @@ '1&2'::query_int;

Тестирование производительности

В каталоге исходного кода contrib/intarray/bench содержится набор тестов производительности, которые можно провести на установленном сервере QHB. (Для этого также нужно установить пакет DBD::Pg.) Для запуска тестов выполните:

cd .../contrib/intarray/bench
createdb TEST
psql -c "CREATE EXTENSION intarray" TEST
./create_test.pl | psql TEST
./bench.pl

Скрипт bench.pl имеет несколько параметров, которые отображаются, если запустить его без аргументов.

Авторы

Разработку осуществили Федор Сигаев (teodor@sigaev.ru) и Олег Бартунов (oleg@sai.msu.su). Дополнительную информацию см. на странице http://www.sai.msu.su/~megera/postgres/gist/. Андрей Октябрьский проделал отличную работу, добавив новые функции и операторы.

isn

Модуль isn предоставляет типы данных для следующих международных стандартов нумерации товаров: EAN13, UPC, ISBN (книги), ISMN (музыка) и ISSN (серийные номера). Номера проверяются на входе согласно жестко закодированному списку префиксов; этот список префиксов также используется для разбивки (при помощи дефиса) номеров при выводе. Поскольку время от времени появляются новые префиксы, этот список префиксов может устаревать. Ожидается, что будущая версия этого модуля будет получать список префиксов из одной или нескольких таблиц, которые при необходимости смогут легко обновлять пользователи; однако в настоящее время этот список можно обновить, только модифицировав и перекомпилировав исходный код. Также есть вероятность, что из будущей версии этого модуля будет удалена возможность проверки префиксов и поддержка разбивки номеров.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Типы данных

В Таблице 11 перечислены типы данных, предоставляемые модулем isn.

Таблица 11. Типы данных isn

Тип данных | Описание
EAN13 | Европейский номер товара (European Article Number), всегда отображается в формате EAN13
ISBN13 | Международный стандартный книжный номер (International Standard Book Number), отображаемый в новом формате EAN13
ISMN13 | Международный стандартный музыкальный номер (International Standard Music Number), отображаемый в новом формате EAN13
ISSN13 | Международный стандартный серийный номер (International Standard Serial Number), отображаемый в новом формате EAN13
ISBN | Международный стандартный книжный номер, отображаемый в старом коротком формате
ISMN | Международный стандартный музыкальный номер, отображаемый в старом коротком формате
ISSN | Международный стандартный серийный номер, отображаемый в старом коротком формате
UPC | Универсальный код товара (Universal Product Code)

Несколько примечаний:

Номера ISBN13, ISMN13, ISSN13 являются номерами EAN13.

Не все номера EAN13 (только некоторые) представляют ISBN13, ISMN13 или ISSN13.

Некоторые номера ISBN13 можно отобразить в формате ISBN.

Некоторые номера ISMN13 можно отобразить в формате ISMN.

Некоторые номера ISSN13 можно отобразить в формате ISSN.

Номера UPC являются подмножеством номеров EAN13 (по сути они являются номерами EAN13 без первой цифры 0).

Все номера UPC, ISBN, ISMN и ISSN можно представить как номера EAN13.

Внутри все эти типы используют одинаковое представление (64-битное целое число), и все они взаимозаменяемы. Различные типы предоставляются для управления форматированием при выводе и для более строгой проверки правильности ввода, что и должно определять конкретный тип номера.

Типы ISBN, ISMN и ISSN отображают короткую версию номера (ISxN 10), когда это возможно, и выбирают формат ISxN 13 для номеров, не умещающихся в короткую версию. Типы EAN13, ISBN13, ISMN13 и ISSN13 всегда отображают длинную версию ISxN (EAN13).

Приведения

Модуль isn предоставляет следующие пары приведений типов:

ISBN13 <=> EAN13

ISMN13 <=> EAN13

ISSN13 <=> EAN13

ISBN <=> EAN13

ISMN <=> EAN13

ISSN <=> EAN13

UPC <=> EAN13

ISBN <=> ISBN13

ISMN <=> ISMN13

ISSN <=> ISSN13

При приведении EAN13 к другому типу проводится проверка времени выполнения, соответствует ли это значение целевому типу, и если это не так, выдается ошибка. При других приведениях значения просто перемечаются, поэтому они всегда выполняются успешно.

Функции и операторы

Модуль Функции предоставляет стандартные операторы сравнения плюс поддержку индексов по хешу и B-деревьев для всех этих типов данных. Кроме того, он реализует несколько специализированных функций, перечисленных в Таблице 12. В этой таблице isn обозначает один из типов данных модуля.

Таблица 12. Функции isn

Функция

Описание

isn_weak ( boolean ) → boolean

Устанавливает ослабленный режим проверки ввода и возвращает новую настройку.

isn_weak () → boolean

Возвращает текущее состояние ослабленного режима.

make_valid ( isn ) → isn

Валидирует неверный номер (сбрасывает флаг ошибки).

is_valid ( isn ) → boolean

Проверяет присутствие флага ошибки.

Установка ослабленного режима позволяет вставлять в таблицу неверные данные. Под неверными данными подразумеваются номера, в которых не сходится проверочная цифра, а не то, что номера отсутствуют.

Зачем может понадобиться ослабленный режим? Ну, может случиться так, что у вас есть большой набор номеров ISBN, и очень многие из них по каким-то странным причинам содержат неверную контрольную цифру (возможно, эти номера были отсканированы с печатного листа и были неправильно распознаны, возможно, они вводились вручную... кто знает). В любом случае, смысл в том, что вы хотите с этим разобраться, но вам все равно нужно загрузить все эти номера в вашу базу данных и, может быть, использовать дополнительный инструмент для поиска неверных номеров в базе данных, чтобы было проще проверить и исправить информацию; так что, например, вам нужно будет выбрать все неверные номера в таблице.

Если вставить в таблице неверные номер в ослабленном режиме, этот номер будет вставлен с исправленной проверочной цифрой, но отобразится с восклицательным знаком (!) в конце, например 0-11-000322-5!. Этот маркер ошибки можно проверить с помощью функции is_valid и очистить функцией make_valid.

Также можно принудительно вставить неверные номера даже не в ослабленном режиме, добавив символ ! в конце номера.

Есть еще одна специальная возможность — во время ввода вместо проверочной цифры можно написать ?, и правильная проверочная цифра будет вставлена автоматически.

Примеры

--Использование типов напрямую:
SELECT isbn('978-0-393-04002-9');
SELECT isbn13('0901690546');
SELECT issn('1436-4522');

--Приведение типов:
-- обратите внимание, что номер ean13 можно привести к другому типу, только когда
-- этот номер будет верным по меркам целевого типа;
-- таким образом, это: select isbn(ean13('0220356483481')); — НЕ будет работать,
-- а это будет:
SELECT upc(ean13('0220356483481'));
SELECT ean13(upc('220356483481'));

--Создание таблицы с одним столбцом, который будет содержать номера ISBN:
CREATE TABLE test (id isbn);
INSERT INTO test VALUES('9780393040029');

--Автоматическое вычисление проверочных цифр (обратите внимание на '?'):
INSERT INTO test VALUES('220500896?');
INSERT INTO test VALUES('978055215372?');

SELECT issn('3251231?');
SELECT ismn('979047213542?');

--Использование ослабленного режима:
SELECT isn_weak(true);
INSERT INTO test VALUES('978-0-11-000533-4');
INSERT INTO test VALUES('9780141219307');
INSERT INTO test VALUES('2-205-00876-X');
SELECT isn_weak(false);

SELECT id FROM test WHERE NOT is_valid(id);
UPDATE test SET id = make_valid(id) WHERE id = '2-205-00876-X!';

SELECT * FROM test;

SELECT isbn13(id) FROM test;

Библиография

Информация, на основе которой реализован этот модуль, была собрана с нескольких сайтов, включая:

https://www.isbn-international.org/

https://www.issn.org/

https://www.ismn-international.org/

https://www.wikipedia.org/

Префиксы, используемые для разбивки номеров, были также собраны с:

https://www.gs1.org/standards/id-keys

https://en.wikipedia.org/wiki/List_of_ISBN_identifier_groups

https://www.isbn-international.org/content/isbn-users-manual/29

https://en.wikipedia.org/wiki/International_Standard_Music_Number

https://www.ismn-international.org/ranges/tools

Алгоритмы были созданы со всей тщательностью и были скрупулезно сверены с алгоритмами, предлагаемыми в официальных руководствах ISBN, ISMN и ISSN.

Автор

Герман Мендез Браво (Germán Méndez Bravo) (Kronuz), 2004–2006

Этот модуль был написан под влиянием кода isbn_issn Гаррета А. Уоллмена (Garrett A. Wollman).

lo

Модуль lo предоставляет поддержку управления большими объектами (также называемыми БО или LO (Large Objects) либо BLOB (Binary Large OBjects)). Это включает в себя тип данных lo и триггер lo_manage.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Обоснование

Одна из проблем с драйвером JDBC (которая распространяется и на драйвер ODBC), в том, что спецификация предполагает, что ссылки на BLOB хранятся в таблице, и если эта запись меняется, связанный BLOB удаляется из базы данных.

Однако с QHB этого не происходит. Большие объекты обрабатываются как самостоятельные объекты; запись в таблице может ссылаться на большой объект по OID, но при этом на один и тот же OID большого объекта могут ссылаться несколько записей таблицы, так что система не удаляет большой объект только потому, что вы меняете или удаляете одну такую запись.

Это не проблема для приложений, специфичных для QHB, но стандартный код, использующий JDBC или ODBC, не будет удалять эти объекты, в результате чего они окажутся потерянными — объектами, на которые ничто не ссылается и которые просто занимают место на диске.

Модуль lo позволяет это исправить, добавляя триггер к таблицам, которые содержат столбцы, ссылающиеся на БО. Этот триггер по сути просто вызывает lo_unlink, когда вы удаляете или изменяете значение, ссылающееся на большой объект. При использовании этого триггера предполагается, что на любой большой объект, на который ссылается управляемый триггером столбец, указывает только одна ссылка базы данных!

Этот модуль также предоставляет тип данных lo, который на самом деле является просто доменом на базе типа oid. Это полезно для дифференциации столбцов базы данных, хранящих ссылки на большие объекты, от столбцов, содержащих OID или какие-то другие данные. Для использования триггера применять тип lo необязательно, но он может быть полезен для отслеживания того, какие столбцы в вашей базе данных предоставляют большие объекты, которыми управляет триггер. Также есть мнение, что драйвер ODBC не работает корректно, если не использовать для столбцов BLOB тип lo.

Как его использовать

Простой пример его использования:

CREATE TABLE image (title text, raster lo);

CREATE TRIGGER t_raster BEFORE UPDATE OR DELETE ON image
    FOR EACH ROW EXECUTE FUNCTION lo_manage(raster);

Для каждого столбца, который будет содержать уникальные ссылки на большие объекты, создайте триггер BEFORE UPDATE OR DELETE и передайте имя столбца в качестве единственного аргумента триггера. Также можно ограничить этот триггер, чтобы он срабатывал только при изменениях в столбце, указав BEFORE UPDATE OF имя_столбца. Если вам нужно иметь в одной таблице несколько столбцов lo, создайте отдельный триггер для каждого, не забывая давать всем этим триггерам разные имена.

Ограничения

При удалении таблицы относящиеся к ней объекты все равно будут потеряны, так как триггер не выполнится. Этого можно избежать, выполнив перед командой DROP TABLE команду DELETE FROM таблица.

То же касается и команды TRUNCATE.

Если у вас уже есть, или вы подозреваете, что есть потерянные большие объекты, обратите внимание на модуль vacuumlo, который поможет вам их вычистить. Имеет смысл периодически запускать vacuumlo в качестве поддержки триггера lo_manage.

Некоторые клиентские части приложений могут создавать собственные таблицы, но не будут создавать для них соответствующие триггеры. Кроме того, пользователи могут забыть и не создать такие триггеры (или не знать, как это делается).

Автор

Питер Маунт (Peter Mount), peter@retep.org.uk

ltree

Этот модуль реализует тип данных ltree для представления меток данных, хранящихся в древовидной иерархической структуре. Он также предоставляет расширенные средства для поиска в деревьях меток.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Определения

Метка — это последовательность буквенно-цифровых символов и знаков подчеркивания (например, в локали C допускаются символы A-Za-z0-9_). Длина метки должна быть меньше 256 символов.

Примеры: 42, Personal_Services

Путь метки — это последовательность из нуля и более разделенных точками меток, например L1.L2.L3, представляющая путь от корня иерархического дерева к конкретному узлу. Длина пути метки не может составлять более 65535 меток.

Пример: Top.Countries.Europe.Russia

Модуль ltree предоставляет несколько типов данных:

ltree хранит путь метки.

lquery представляет похожий на регулярные выражения шаблон для поиска соответствующих значений ltree. В нем простое слово выбирает соответствующую метку в пути, а символ звездочки (*) — ноль и более любых меток. Отдельные элементы можно соединить точками, чтобы сформировать шаблон, которому должен соответствовать весь путь указанных меток. Например:

foo         Выбирает путь ровно с одной меткой foo
*.foo.*     Выбирает любой путь, содержащий метку foo
*.foo       Выбирает любой путь с последней меткой foo

И для символов звездочки, и для простых слов можно добавить количественное значение, ограничивающее число меток, которые будут соответствовать этому элементу:

*{n}        Выбирает ровно n меток
*{n,}       Выбирает как минимум n меток
*{n,m}      Выбирает как минимум n, но не более m меток
*{,m}       Выбирает не более m меток — равнозначно *{0,m}
foo{n,m}    Выбирает как минимум n, но не более m вхождений foo
foo{,}      Выбирает любое количество вхождений foo, включая ноль

В отсутствие явного квантификатора символу звездочки по умолчанию соответствует любое количество меток (то есть {,}), тогда как простому слову — ровно одна (то есть {1}).

Существует несколько модификаторов, которые можно добавить после отличного от звездочки элемента lquery, что позволит выбирать не только точные совпадения:

@           Выбирает совпадение без учета регистра, например, шаблон a@ соответствует A
*           Выбирает любую метку с этим префиксом, например, foo* соответствует foobar
%           Выбирает в метке начальные слова, разделенные подчеркиваниями

Поведение модификатора % несколько замысловатое. Он пытается найти соответствие по словам, а не по всей метке. Например, шаблон foo_bar% выбирает foo_bar_baz, но не foo_barbaz. В сочетании с * сопоставление префикса применяется отдельно к каждому слову, например, шаблон foo_bar%* выбирает foo1_bar2_baz, но не foo1_br2_baz.

Кроме того, можно записать несколько различных (в том числе модифицированных) элементов, отличных от звездочек, через знак | (ИЛИ) для выбора любой из этих меток или добавить знак ! (НЕ) в начале группа без звездочки для выбора любой метки, не соответствующей ни одной из указанных альтернатив. Если задается квантификатор, он ставится в конце группы; это означает, что он действует на всю группу в целом (то есть ограничивает количество меток, соответствующих или не соответствующих любой из альтернатив).

Расширенный пример lquery:

Top.*{0,2}.sport*@.!football|tennis{1,}.Russ*|Spain
a.  b.     c.      d.                   e.

Этот запрос выберет любой путь, который:
a. начинается с метки Top
b. затем включает от нуля до двух меток до
c. метки, начинающейся с префикса sport (без учета регистра)
d. затем одну или более меток, отличных от football и tennis
e. и заканчивается меткой, которая начинается с Russ или точно соответствует Spain.

ltxtquery представляет похожий на полнотекстовый запрос шаблон для поиска соответствующих значений ltree. Значение ltxtquery содержит слова, возможно, с модификаторами @, *, % в конце; эти модификаторы имеют тот же смысл, что и в lquery Слова можно комбинировать с символами & (И), | (ИЛИ), ! (НЕ) и скобками. Ключевое отличие от lquery состоит в том, что ltxtquery выбирает слова независимо от их положения в пути метки.

Пример ltxtquery:

Europe & Russia*@ & !Transportation

Этот шаблон выберет пути, содержащие метку Europe и любую метку, начинающуюся с Russia (без учета регистра), но не пути, содержащие метку Transportation. Положение этих слов в пути не имеет значения. Кроме того, когда используется %, слово может быть сопоставлено с любым другим отделенным подчеркиванием словом в метке, независимо от его положения.

Примечание: ltxtquery допускает пробелы между символами, а ltree и lquery — нет.

Операторы и функции

Для типа ltree определены обычные операторы сравнения =, <>, <, >, <=, >=. Сравнение сортирует пути в порядке движения по дереву, а потомки узла сортируются по тексту метки. В дополнение к ним доступны специализированные операторы, перечисленные в Таблице 13.

Таблица 13. Операторы ltree

Оператор

Описание

ltree @> ltree → boolean

Левый аргумент является предком правого (или равен ему)?

ltree <@ ltree → boolean

Левый аргумент является потомком правого (или равен ему)?

ltree ~ lquery → boolean
lquery ~ ltree → boolean

Значение ltree соответствует lquery?

ltree ? lquery[] → boolean
lquery[] ? ltree → boolean

Значение ltree соответствует какому-либо lquery в массиве?

ltree @ ltxtquery → boolean
ltxtquery @ ltree → boolean

Значение ltree соответствует ltxtquery?

ltree || ltree → ltree

Конкатенирует пути ltree.

ltree || text → ltree
text || ltree → ltree

Преобразует текст в ltree и конкатенирует его с путем.

ltree[] @> ltree → boolean
ltree <@ ltree[] → boolean

Массив содержит предка ltree?

ltree[] <@ ltree → boolean
ltree @> ltree[] → boolean

Массив содержит потомка ltree?

ltree[] ~ lquery → boolean
lquery ~ ltree[] → boolean

Массив содержит какой-либо путь, соответствующий lquery?

ltree[] ? lquery[] → boolean
lquery[] ? ltree[] → boolean

Массив ltree содержит какой-либо путь, соответствующий какому-либо lquery?

ltree[] @ ltxtquery → boolean
ltxtquery @ ltree[] → boolean

Массив содержит какой-либо путь, соответствующий ltxtquery?

ltree[] ?@> ltree → ltree

Возвращает первую запись массива, являющуюся предком ltree, или NULL, если такой записи нет.

ltree[] ?<@ ltree → ltree

Возвращает первую запись массива, являющуюся потомком ltree, или NULL, если такой записи нет.

ltree[] ?~ lquery → ltree

Возвращает первую запись массива, соответствующую lquery, или NULL, если такой записи нет.

ltree[] ?@ ltxtquery → ltree

Возвращает первую запись массива, соответствующую ltxtquery, или NULL, если такой записи нет.

У операторов <@, @>, @ и ~ есть аналоги ^<@, ^@>, ^@, ^~, которые отличаются только тем, что не используют индексы. Они полезны только для тестирования.

Доступные функции перечислены в Таблице 14.

Таблица 14. Функции ltree

Функция

Описание
Пример(ы)

subltree ( ltree, начало integer, конец integer ) → ltree

Возвращает подпуть ltree от позиции начало до позиции конец-1 (считая от 0).
subltree('Top.Child1.Child2', 1, 2) → Child1

subpath ( ltree, смещение integer, длина integer ) → ltree

Возвращает подпуть ltree, начиная с позиции смещение, длиной в заданное число меток. Если смещение отрицательное, подпуть начинается с этого смещения от конца пути. Если длина отрицательная, функция отбрасывает заданное число меток с конца пути.
subpath('Top.Child1.Child2', 0, 2) → Top.Child1

subpath ( ltree, смещение integer ) → ltree

Возвращает подпуть ltree, начиная с позиции смещение и до конца пути. Если смещение отрицательное, подпуть начинается с этой позиции от конца пути.
subpath('Top.Child1.Child2', 1) → Child1.Child2

nlevel ( ltree ) → integer

Возвращает количество меток в пути.
nlevel('Top.Child1.Child2') → 3

index ( a ltree, b ltree ) → integer

Возвращает позицию первого вхождения b в a или -1, если такое вхождение не найдено.
index('0.1.2.3.5.4.5.6.8.5.6.8', '5.6') → 6

index ( a ltree, b ltree, смещение integer ) → integer

Возвращает позицию первого вхождения b в a или -1, если такое вхождение не найдено. Поиск начинается с позиции смещение; отрицательное смещение означает, что поиск начинается с этой позиции от конца пути.
index('0.1.2.3.5.4.5.6.8.5.6.8', '5.6', -4) → 9

text2ltree ( text ) → ltree

Приводит значение text к типу ltree.

ltree2text ( ltree ) → text

Приводит значение ltree к типу text.

lca ( ltree [, ltree [, ... ]] ) → ltree

Вычисляет длиннейшего общего предка путей (способна принимать до 8 аргументов).
lca('1.2.3', '1.2.3.4.5.6') → 1.2

lca ( ltree[] ) → ltree

Вычисляет длиннейшего общего предка путей в массиве.
lca(array['1.2.3'::ltree,'1.2.3.4']) → 1.2

Индексы

ltree поддерживает несколько типов индексов, которые могут ускорить указанные операторы:

Индекс B-дерево по значениям ltree: <, <=, =, >=, >

Индекс GiST по значениям ltree (класс операторов gist_ltree_ops): <, <=, =, >=, >, @>, <@, @, ~, ?

Класс операторов GiST gist_ltree_ops аппроксимирует набор меток пути в виде сигнатуры битовой карты. Его необязательный целочисленный параметр siglen определяет длину сигнатуры в байтах. Значение по умолчанию — 8 байт. Длина сигнатуры должна быть положительным значением до 2024, кратным выравниванию по целым (int) (4 байта для большинства машин). При увеличении размера сигнатуры поиск работает точнее (сканируется меньшая область индекса и меньше страниц кучи), но сам индекс становится больше.

Пример создания такого индекса с длиной сигнатуры по умолчанию (8 байт):

CREATE INDEX path_gist_idx ON test USING GIST (path);

Пример создания такого индекса с длиной сигнатуры 100 байт:

CREATE INDEX path_gist_idx ON test USING GIST (path gist_ltree_ops(siglen=100));

Индекс GiST по массиву ltree[] (класс операторов gist__ltree_ops): ltree[] <@ ltree, ltree @> ltree[], @, ~, ?

Класс операторов GiST gist__ltree_ops работает подобно gist_ltree_ops и тоже принимает в качестве параметра длину сигнатуры. По умолчанию значение siglen в gist__ltree_ops составляет 28 байт.

Пример создания такого индекса с длиной сигнатуры по умолчанию (28 байт):

CREATE INDEX path_gist_idx ON test USING GIST (array_path);

Пример создания такого индекса с длиной сигнатуры 100 байт:

CREATE INDEX path_gist_idx ON test USING GIST (array_path gist__ltree_ops(siglen=100));

Примечание: Индекс этого типа является неточным.

Пример

Для этого примера используются следующие данные:

CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
INSERT INTO test VALUES ('Top.Hobbies');
INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
INSERT INTO test VALUES ('Top.Collections');
INSERT INTO test VALUES ('Top.Collections.Pictures');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
CREATE INDEX path_gist_idx ON test USING GIST (path);
CREATE INDEX path_idx ON test USING BTREE (path);

Итак, мы получаем таблицу test, наполненную данными, описывающими представленную ниже иерархию:

                        Top
                     /   |  \
             Science Hobbies Collections
                 /       |              \
        Astronomy   Amateurs_Astronomy Pictures
           /  \                            |
Astrophysics  Cosmology                Astronomy
                                        /  |    \
                                 Galaxies Stars Astronauts

Мы можем выбрать потомков в иерархии наследования:

ltreetest=> SELECT path FROM test WHERE path <@ 'Top.Science';
                path
------------------------------------
 Top.Science
 Top.Science.Astronomy
 Top.Science.Astronomy.Astrophysics
 Top.Science.Astronomy.Cosmology
(4 rows)

Несколько примеров выборки по путям:

ltreetest=> SELECT path FROM test WHERE path ~ '*.Astronomy.*';
                     path
-----------------------------------------------
 Top.Science.Astronomy
 Top.Science.Astronomy.Astrophysics
 Top.Science.Astronomy.Cosmology
 Top.Collections.Pictures.Astronomy
 Top.Collections.Pictures.Astronomy.Stars
 Top.Collections.Pictures.Astronomy.Galaxies
 Top.Collections.Pictures.Astronomy.Astronauts
(7 rows)

ltreetest=> SELECT path FROM test WHERE path ~ '*.!pictures@.Astronomy.*';
                path
------------------------------------
 Top.Science.Astronomy
 Top.Science.Astronomy.Astrophysics
 Top.Science.Astronomy.Cosmology
(3 rows)

Несколько примеров полнотекстового поиска:

ltreetest=> SELECT path FROM test WHERE path @ 'Astro*% & !pictures@';
                path
------------------------------------
 Top.Science.Astronomy
 Top.Science.Astronomy.Astrophysics
 Top.Science.Astronomy.Cosmology
 Top.Hobbies.Amateurs_Astronomy
(4 rows)

ltreetest=> SELECT path FROM test WHERE path @ 'Astro* & !pictures@';
                path
------------------------------------
 Top.Science.Astronomy
 Top.Science.Astronomy.Astrophysics
 Top.Science.Astronomy.Cosmology
(3 rows)

Создание пути с помощью функций:

ltreetest=> SELECT subpath(path,0,2)||'Space'||subpath(path,2) FROM test WHERE path <@ 'Top.Science.Astronomy';
                 ?column?
------------------------------------------
 Top.Science.Space.Astronomy
 Top.Science.Space.Astronomy.Astrophysics
 Top.Science.Space.Astronomy.Cosmology
(3 rows)

Эту процедуру можно упростить, создав функцию SQL, вставляющую метку в заданную позицию в пути:

CREATE FUNCTION ins_label(ltree, int, text) RETURNS ltree
    AS 'select subpath($1,0,$2) || $3 || subpath($1,$2);'
    LANGUAGE SQL IMMUTABLE;

ltreetest=> SELECT ins_label(path,2,'Space') FROM test WHERE path <@ 'Top.Science.Astronomy';
                ins_label
------------------------------------------
 Top.Science.Space.Astronomy
 Top.Science.Space.Astronomy.Astrophysics
 Top.Science.Space.Astronomy.Cosmology
(3 rows)

Авторы

Разработку осуществили Федор Сигаев (teodor@stack.net) и Олег Бартунов (oleg@sai.msu.su). Дополнительную информацию см. на странице http://www.sai.msu.su/~megera/postgres/gist/. Авторы выражают благодарность Евгению Родичеву за полезные дискуссии. Комментарии и сообщения об ошибках приветствуются.

old_snapshot

Модуль old_snapshot позволяет ознакомиться с состоянием сервера, которое используется в реализации old_snapshot_threshold.

Функции

pg_old_snapshot_time_mapping(array_offset OUT int4, end_timestamp OUT timestamptz, newest_xmin OUT xid) returns setof record

Возвращает все записи из серверного массива сопоставлений меток времени и XID. Каждая запись представляет последний xmin снимка, сделанного в соответствующую минуту.

pageinspect

Модуль pageinspect предоставляет функции, позволяющие просматривать содержимое страниц базы данных на низком уровне, что полезно для отладки. Все эти функции могут применять только суперпользователи.

Функции общего назначения

get_raw_page(relname text, fork text, blkno bigint) returns bytea

Функция get_raw_page читает заданный блок именованного отношения и возвращает его копию в виде значения bytea. Это позволяет получить одну согласованную во времени копию блока. В параметре fork нужно передать 'main', чтобы обратиться к основной ветви данных, 'fsm' — к карте свободного пространства, 'vm' — к карте видимости, или 'init' — к ветви инициализации.

get_raw_page(relname text, blkno bigint) returns bytea

Упрощенная версия функции get_raw_page для чтения данных из основной ветви. Равнозначна get_raw_page(relname, 'main', blkno)

page_header(page bytea) returns record

Функция page_header показывает поля, общие для всех страниц кучи и индекса QHB.

В качестве аргумента ей следует передавать образ страницы, полученный с помощью get_raw_page. Например:

test=# SELECT * FROM page_header(get_raw_page('pg_class', 0));
    lsn    | checksum | flags  | lower | upper | special | pagesize | version | prune_xid
-----------+----------+--------+-------+-------+---------+----------+---------+-----------
 0/24A1B50 |        0 |      1 |   232 |   368 |    8192 |     8192 |       4 |         0

Возвращаемые столбцы соответствуют полям в структуре PageHeaderData.

Поле checksum содержит контрольную сумму, сохраненную в странице. Эта сумма может быть неверной, если страница каким-то образом повреждена. Если в данном экземпляре СУБД контрольные суммы выключены, сохраненное значение не имеет смысла.

page_checksum(page bytea, blkno bigint) returns smallint

Функция page_checksum вычисляет контрольную сумму для страницы так, будто эта страница находилась в заданном блоке.

В качестве аргумента ей следует передавать образ страницы, полученный с помощью get_raw_page. Например:

test=# SELECT page_checksum(get_raw_page('pg_class', 0), 0);
 page_checksum
---------------
         13443

Обратите внимание, что вычисление контрольной суммы зависит от номера блока, поэтому обеим функциям следует передавать одинаковые номера блоков (за исключением случаев эзотерической отладки).

Контрольные суммы, вычисленные этой функцией, можно сравнить с результирующим полем checksum функции page_header. Если в данном экземпляре СУБД контрольные суммы включены, эти значения должны быть равны.

fsm_page_contents(page bytea) returns text

Функция fsm_page_contents показывает внутреннюю структуру узла на странице FSM (Free Space Map, карта свободного пространства). Например:

test=# SELECT fsm_page_contents(get_raw_page('pg_class', 'fsm', 0));

Вывод состоит из нескольких текстовых строк, по одной строке для каждого узла двоичного дерева на заданной странице. Выводятся только ненулевые узлы. Также выводится так называемый указатель «вперед», которые указывает на следующий слот, возвращаемый с этой страницы.

Функции для исследования кучи

heap_page_items(page bytea) returns setof record

Функция heap_page_items показывает все указатели строк на странице кучи. Для используемых указателей также показываются заголовки и исходные данные кортежей. Показываются все кортежи, независимо от того, были ли они видны в снимке MVCC в момент копирования исходной страницы.

В качестве аргумента ей следует передавать образ страницы кучи, полученный с помощью get_raw_page. Например:

test=# SELECT * FROM heap_page_items(get_raw_page('pg_class', 0));

Распаковать биты флагов t_infomask и t_infomask2 для кортежей кучи можно с помощью функции heap_tuple_infomask_flags.

tuple_data_split(rel_oid oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text [, do_detoast bool]) returns bytea[]

Функция tuple_data_split разделяет данные кортежа на атрибуты так же, как это происходит внутри сервера.

test=# SELECT tuple_data_split('pg_class'::regclass, t_data, t_infomask, t_infomask2, t_bits) FROM heap_page_items(get_raw_page('pg_class', 0));

Вызывая эту функцию, ей следует передавать в аргументах те атрибуты, которые возвращает функция heap_page_items.

Если параметр do_detoast равен true, атрибуты будут распаковываться по мере необходимости. Значение по умолчанию — false.

heap_page_item_attrs(page bytea, rel_oid regclass [, do_detoast bool]) returns setof record

Функция heap_page_item_attrs равнозначна функции heap_page_items, за исключением того, что она возвращает исходные данные кортежа в виде массива атрибутов, которые при необходимости могут быть распакованы, если установлен флаг do_detoast (по умолчанию false, т. е. они не распаковываются).

В качестве аргумента ей следует передавать образ страницы кучи, полученный с помощью get_raw_page. Например:

test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0), 'pg_class'::regclass);

heap_tuple_infomask_flags(t_infomask integer, t_infomask2 integer) returns record

Функция heap_tuple_infomask_flags декодирует значения t_infomask и t_infomask2, возвращаемые функцией heap_page_items, в множество удобочитаемых массивов с именами флагов, где в одном столбце перечислены все флаги по отдельности, а в другом — комбинированные флаги. Например:

test=# SELECT t_ctid, raw_flags, combined_flags
         FROM heap_page_items(get_raw_page('pg_class', 0)),
           LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2)
         WHERE t_infomask IS NOT NULL OR t_infomask2 IS NOT NULL;

Вызывая эту функцию, ей следует передавать в аргументах те атрибуты, которые возвращает функция heap_page_items.

Комбинированные флаги отображаются для тех макросов на уровне исходного кода, которые учитывают значения более чем одного исходного бита, например HEAP_XMIN_FROZEN.

Функции для индексов B-деревьев

bt_metap(relname text) returns record

Функция bt_metap возвращает информацию о метастранице индекса B-дерева. Например:

test=# SELECT * FROM bt_metap('pg_cast_oid_index');
-[ RECORD 1 ]-------------+-------
magic                     | 340322
version                   | 4
root                      | 1
level                     | 0
fastroot                  | 1
fastlevel                 | 0
last_cleanup_num_delpages | 0
last_cleanup_num_tuples   | 230
allequalimage             | f

bt_page_stats(relname text, blkno bigint) returns record

Функция bt_page_stats возвращает сводную информацию по единичным страницам индексов B-деревьев. Например:

test=# SELECT * FROM bt_page_stats('pg_cast_oid_index', 1);
-[ RECORD 1 ]-+-----
blkno         | 1
type          | l
live_items    | 224
dead_items    | 0
avg_item_size | 16
page_size     | 8192
free_size     | 3668
btpo_prev     | 0
btpo_next     | 0
btpo_level    | 0
btpo_flags    | 3

bt_page_items(relname text, blkno bigint) returns setof record

Функция bt_page_items возвращает подробную информацию обо всех элементах на странице индекса B-дерева. Например:

test=# SELECT itemoffset, ctid, itemlen, nulls, vars, data, dead, htid, tids[0:2] AS some_tids
        FROM bt_page_items('tenk2_hundred', 5);
 itemoffset |   ctid    | itemlen | nulls | vars |          data           | dead |  htid  |      some_tids
------------+-----------+---------+-------+------+-------------------------+------+--------+---------------------
          1 | (16,1)    |      16 | f     | f    | 30 00 00 00 00 00 00 00 |      |        |
          2 | (16,8292) |     616 | f     | f    | 24 00 00 00 00 00 00 00 | f    | (1,6)  | {"(1,6)","(10,22)"}
          3 | (16,8292) |     616 | f     | f    | 25 00 00 00 00 00 00 00 | f    | (1,18) | {"(1,18)","(4,22)"}
          4 | (16,8292) |     616 | f     | f    | 26 00 00 00 00 00 00 00 | f    | (4,18) | {"(4,18)","(6,17)"}
          5 | (16,8292) |     616 | f     | f    | 27 00 00 00 00 00 00 00 | f    | (1,2)  | {"(1,2)","(1,19)"}
          6 | (16,8292) |     616 | f     | f    | 28 00 00 00 00 00 00 00 | f    | (2,24) | {"(2,24)","(4,11)"}
          7 | (16,8292) |     616 | f     | f    | 29 00 00 00 00 00 00 00 | f    | (2,17) | {"(2,17)","(11,2)"}
          8 | (16,8292) |     616 | f     | f    | 2a 00 00 00 00 00 00 00 | f    | (0,25) | {"(0,25)","(3,20)"}
          9 | (16,8292) |     616 | f     | f    | 2b 00 00 00 00 00 00 00 | f    | (0,10) | {"(0,10)","(0,14)"}
         10 | (16,8292) |     616 | f     | f    | 2c 00 00 00 00 00 00 00 | f    | (1,3)  | {"(1,3)","(3,9)"}
         11 | (16,8292) |     616 | f     | f    | 2d 00 00 00 00 00 00 00 | f    | (6,28) | {"(6,28)","(11,1)"}
         12 | (16,8292) |     616 | f     | f    | 2e 00 00 00 00 00 00 00 | f    | (0,27) | {"(0,27)","(1,13)"}
         13 | (16,8292) |     616 | f     | f    | 2f 00 00 00 00 00 00 00 | f    | (4,17) | {"(4,17)","(4,21)"}
(13 rows)

Это листовая страница B-дерева. Все кортежи, указывающие на таблицу, оказались кортежами со списками идентификаторов (в каждом из которых хранится в сумме 100 шестибайтных TID (идентификаторов кортежей)). Кроме того, по смещению itemoffset 1 находится кортеж «верхний ключ». В данном примере ctid используется для хранения закодированной информации о каждом кортеже, хотя на листовой странице кортежи часто содержат в поле ctid непосредственно TID кучи. Поле tids содержит список TID в виде списка идентификаторов.

На внутренней странице (здесь не показана) часть номера блока в ctid является «ссылкой вниз», то есть номером блока другой страницы в самом индексе. Часть смещения (второе число) ctid содержит закодированную информацию о кортеже, в частности, количество столбцов в нем (при отсечении суффикса могут быть удалены ненужные заключительные столбцы). Опустошенные столбцы воспринимаются как содержащие значение «минус бесконечность».

В поле htid показывается TID кортежа в куче, независимо от его нижележащего представления. Это значение может совпадать со ctid или может быть декодировано из альтернативных представлений, используемых в кортежах со списками идентификаторов и кортежами со внутренних страниц. В кортежах на внутренних страницах столбец с TID кучи на уровне реализации обычно опустошается, что выглядит как значение NULL в столбце htid.

Обратите внимание, что первый элемент в любой, кроме самой правой, странице (т. е. в любой странице с ненулевым значением в поле btpo_next) является «верхним ключом», то есть его поле data служит верхней границей для всех элементов, находящихся на этой странице, а поле ctid не указывает на другой блок. Кроме того, на внутренних страницах в первом действительном элементе с данными (в первом элементе после верхнего ключа) обязательно будут опустошены все столбцы, не оставив в поле data фактического значения. Однако такой элемент содержит в своем поле ctid корректную ссылку вниз.

Дополнительную информацию о структуре индексов B-деревьев см. в подразделе Структура В-дерева. Дополнительную информацию об исключении дубликатов и списках идентификаторов см. в подразделе Дедупликация.

bt_page_items(page bytea) returns setof record

Функции bt_page_items также можно передать страницу в виде значения bytea. Вызывая эту функцию, ей следует передавать в аргументе образ страницы, полученный с помощью функции get_raw_page. Так, последний пример можно переписать следующим образом:

test=# SELECT itemoffset, ctid, itemlen, nulls, vars, data, dead, htid, tids[0:2] AS some_tids
        FROM bt_page_items(get_raw_page('tenk2_hundred', 5));
 itemoffset |   ctid    | itemlen | nulls | vars |          data           | dead |  htid  |      some_tids
------------+-----------+---------+-------+------+-------------------------+------+--------+---------------------
          1 | (16,1)    |      16 | f     | f    | 30 00 00 00 00 00 00 00 |      |        |
          2 | (16,8292) |     616 | f     | f    | 24 00 00 00 00 00 00 00 | f    | (1,6)  | {"(1,6)","(10,22)"}
          3 | (16,8292) |     616 | f     | f    | 25 00 00 00 00 00 00 00 | f    | (1,18) | {"(1,18)","(4,22)"}
          4 | (16,8292) |     616 | f     | f    | 26 00 00 00 00 00 00 00 | f    | (4,18) | {"(4,18)","(6,17)"}
          5 | (16,8292) |     616 | f     | f    | 27 00 00 00 00 00 00 00 | f    | (1,2)  | {"(1,2)","(1,19)"}
          6 | (16,8292) |     616 | f     | f    | 28 00 00 00 00 00 00 00 | f    | (2,24) | {"(2,24)","(4,11)"}
          7 | (16,8292) |     616 | f     | f    | 29 00 00 00 00 00 00 00 | f    | (2,17) | {"(2,17)","(11,2)"}
          8 | (16,8292) |     616 | f     | f    | 2a 00 00 00 00 00 00 00 | f    | (0,25) | {"(0,25)","(3,20)"}
          9 | (16,8292) |     616 | f     | f    | 2b 00 00 00 00 00 00 00 | f    | (0,10) | {"(0,10)","(0,14)"}
         10 | (16,8292) |     616 | f     | f    | 2c 00 00 00 00 00 00 00 | f    | (1,3)  | {"(1,3)","(3,9)"}
         11 | (16,8292) |     616 | f     | f    | 2d 00 00 00 00 00 00 00 | f    | (6,28) | {"(6,28)","(11,1)"}
         12 | (16,8292) |     616 | f     | f    | 2e 00 00 00 00 00 00 00 | f    | (0,27) | {"(0,27)","(1,13)"}
         13 | (16,8292) |     616 | f     | f    | 2f 00 00 00 00 00 00 00 | f    | (4,17) | {"(4,17)","(4,21)"}
(13 rows)

В остальном данная функция работает так, как описано выше.

Функции для индексов BRIN

brin_page_type(page bytea) returns text

Функция brin_page_type возвращает тип страницы для заданной страницы индекса BRIN или выдает ошибку, если эта страница не является корректной страницей BRIN. Например:

test=# SELECT brin_page_type(get_raw_page('brinidx', 0));
 brin_page_type
----------------
 meta

brin_metapage_info(page bytea) returns record

Функция brin_metapage_info возвращает разнообразную информацию о метастранице индекса BRIN. Например:

test=# SELECT * FROM brin_metapage_info(get_raw_page('brinidx', 0));
   magic    | version | pagesperrange | lastrevmappage
------------+---------+---------------+----------------
 0xA8109CFA |       1 |             4 |              2

brin_revmap_data(page bytea) returns setof tid

Функция brin_revmap_data возвращает список идентификаторов кортежей со страницы сопоставлений диапазонов индекса BRIN. Например:

test=# SELECT * FROM brin_revmap_data(get_raw_page('brinidx', 2)) LIMIT 5;
  pages
---------
 (6,137)
 (6,138)
 (6,139)
 (6,140)
 (6,141)

brin_page_items(page bytea, index oid) returns setof record

Функция brin_page_items возвращает данные, хранящиеся на странице данных BRIN. Например:

test=# SELECT * FROM brin_page_items(get_raw_page('brinidx', 5),
                                     'brinidx')
       ORDER BY blknum, attnum LIMIT 6;
 itemoffset | blknum | attnum | allnulls | hasnulls | placeholder |    value
------------+--------+--------+----------+----------+-------------+--------------
        137 |      0 |      1 | t        | f        | f           |
        137 |      0 |      2 | f        | f        | f           | {1 .. 88}
        138 |      4 |      1 | t        | f        | f           |
        138 |      4 |      2 | f        | f        | f           | {89 .. 176}
        139 |      8 |      1 | t        | f        | f           |
        139 |      8 |      2 | f        | f        | f           | {177 .. 264}

Возвращаемые столбцы соответствуют полям в структурах BrinMemTuple и BrinValues.

Функции для индексов GIN

gin_metapage_info(page bytea) returns record

Функция gin_metapage_info возвращает информацию о метастранице индекса GIN. Например:

test=# SELECT * FROM gin_metapage_info(get_raw_page('gin_index', 0));
-[ RECORD 1 ]----+-----------
pending_head     | 4294967295
pending_tail     | 4294967295
tail_free_size   | 0
n_pending_pages  | 0
n_pending_tuples | 0
n_total_pages    | 7
n_entry_pages    | 6
n_data_pages     | 0
n_entries        | 693
version          | 2

gin_page_opaque_info(page bytea) returns record

Функция gin_page_opaque_info возвращает информацию о непрозрачной области индекса GIN, например тип страницы. Например:

test=# SELECT * FROM gin_page_opaque_info(get_raw_page('gin_index', 2));
 rightlink | maxoff |         flags
-----------+--------+------------------------
         5 |      0 | {data,leaf,compressed}
(1 row)

gin_leafpage_items(page bytea) returns setof record

Функция gin_leafpage_items возвращает информацию о данных, хранящихся на листовой странице GIN. Например:

test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
        FROM gin_leafpage_items(get_raw_page('gin_test_idx', 2));
 first_tid | nbytes |                        some_tids
-----------+--------+----------------------------------------------------------
 (8,41)    |    244 | {"(8,41)","(8,43)","(8,44)","(8,45)","(8,46)"}
 (10,45)   |    248 | {"(10,45)","(10,46)","(10,47)","(10,48)","(10,49)"}
 (12,52)   |    248 | {"(12,52)","(12,53)","(12,54)","(12,55)","(12,56)"}
 (14,59)   |    320 | {"(14,59)","(14,60)","(14,61)","(14,62)","(14,63)"}
 (167,16)  |    376 | {"(167,16)","(167,17)","(167,18)","(167,19)","(167,20)"}
 (170,30)  |    376 | {"(170,30)","(170,31)","(170,32)","(170,33)","(170,34)"}
 (173,44)  |    197 | {"(173,44)","(173,45)","(173,46)","(173,47)","(173,48)"}
(7 rows)

Функции для индексов GiST

gist_page_opaque_info(page bytea) returns record

Функция gist_page_opaque_info возвращает информацию из непрозрачной области страницы индекса GiST, например, значения NSN, rightlink и тип страницы. Например:

test=# SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist_idx', 2));
 lsn | nsn | rightlink | flags
-----+-----+-----------+--------
 0/1 | 0/0 |         1 | {leaf}
(1 row)

gist_page_items(page bytea, index_oid regclass) returns setof record

Функция gist_page_items возвращает информацию о данных, хранящихся на странице индекса GiST. Например:

test=# SELECT * FROM gist_page_items(get_raw_page('test_gist_idx', 0), 'test_gist_idx');
 itemoffset |   ctid    | itemlen | dead |       keys
------------+-----------+---------+------+-------------------
          1 | (1,65535) |      40 | f    | (p)=((166,166))
          2 | (2,65535) |      40 | f    | (p)=((332,332))
          3 | (3,65535) |      40 | f    | (p)=((498,498))
          4 | (4,65535) |      40 | f    | (p)=((664,664))
          5 | (5,65535) |      40 | f    | (p)=((830,830))
          6 | (6,65535) |      40 | f    | (p)=((996,996))
          7 | (7,65535) |      40 | f    | (p)=((1000,1000))
(7 rows)

gist_page_items_bytea(page bytea) returns setof record

Функция схожа с gist_page_items, но возвращает данные ключа в виде исходного двоичного объекта bytea. Так как она не пытается декодировать ключ, ей не требуется знать, какой индекс задействован. Например:

test=# SELECT * FROM gist_page_items_bytea(get_raw_page('test_gist_idx', 0));
 itemoffset |   ctid    | itemlen | dead |                                      key_data
------------+-----------+---------+------+-----------------------------------------​-------------------------------------------
          1 | (1,65535) |      40 | f    | \x00000100ffff28000000000000c0644000000000​00c06440000000000000f03f000000000000f03f
          2 | (2,65535) |      40 | f    | \x00000200ffff28000000000000c0744000000000​00c074400000000000e064400000000000e06440
          3 | (3,65535) |      40 | f    | \x00000300ffff28000000000000207f4000000000​00207f400000000000d074400000000000d07440
          4 | (4,65535) |      40 | f    | \x00000400ffff28000000000000c0844000000000​00c084400000000000307f400000000000307f40
          5 | (5,65535) |      40 | f    | \x00000500ffff28000000000000f0894000000000​00f089400000000000c884400000000000c88440
          6 | (6,65535) |      40 | f    | \x00000600ffff28000000000000208f4000000000​00208f400000000000f889400000000000f88940
          7 | (7,65535) |      40 | f    | \x00000700ffff28000000000000408f4000000000​00408f400000000000288f400000000000288f40
(7 rows)

Функции для хеш-индексов

hash_page_type(page bytea) returns text

Функция hash_page_type возвращает тип страницы для заданной страницы хеш-индекса. Например:

test=# SELECT hash_page_type(get_raw_page('con_hash_index', 0));
 hash_page_type
----------------
 metapage

hash_page_stats(page bytea) returns setof record

Функция hash_page_stats возвращает информацию о странице ячейки или переполнения хеш-индекса. Например:

test=# SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1));
-[ RECORD 1 ]---+-----------
live_items      | 407
dead_items      | 0
page_size       | 8192
free_size       | 8
hasho_prevblkno | 4096
hasho_nextblkno | 8474
hasho_bucket    | 0
hasho_flag      | 66
hasho_page_id   | 65408

hash_page_items(page bytea) returns setof record

Функция hash_page_items возвращает информацию о данных, хранящихся на странице ячейки или переполнения хеш-индекса. Например:

test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)) LIMIT 5;
 itemoffset |   ctid    |    data
------------+-----------+------------
          1 | (899,77)  | 1053474816
          2 | (897,29)  | 1053474816
          3 | (894,207) | 1053474816
          4 | (892,159) | 1053474816
          5 | (890,111) | 1053474816

hash_bitmap_info(index oid, blkno bigint) returns record

Функция hash_bitmap_info показывает состояние бита в странице битовой карты для определенной страницы переполнения хеш-индекса. Например:

test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2052);
 bitmapblkno | bitmapbit | bitstatus
-------------+-----------+-----------
          65 |         3 | t

hash_metapage_info(page bytea) returns record

Функция hash_metapage_info возвращает информацию, хранящуюся в метастранице хеш-индекса. Например:

test=# SELECT magic, version, ntuples, ffactor, bsize, bmsize, bmshift,
test-#     maxbucket, highmask, lowmask, ovflpoint, firstfree, nmaps, procid,
test-#     regexp_replace(spares::text, '(,0)*}', '}') as spares,
test-#     regexp_replace(mapp::text, '(,0)*}', '}') as mapp
test-# FROM hash_metapage_info(get_raw_page('con_hash_index', 0));
-[ RECORD 1 ]-------------------------------------------------​------------------------------
magic     | 105121344
version   | 4
ntuples   | 500500
ffactor   | 40
bsize     | 8152
bmsize    | 4096
bmshift   | 15
maxbucket | 12512
highmask  | 16383
lowmask   | 8191
ovflpoint | 28
firstfree | 1204
nmaps     | 1
procid    | 450
spares    | {0,0,0,0,0,0,1,1,1,1,1,1,1,1,3,4,4,4,45,55,58,59,​508,567,628,704,1193,1202,1204}
mapp      | {65}

passwordcheck

Модуль passwordcheck проверяет пароли пользователей, задаваемые командами CREATE ROLE или ALTER ROLE. Если пароль признается слишком слабым, он не принимается, и команда завершается ошибкой.

Чтобы загрузить этот модуль, добавьте строку '$libdir/passwordcheck' в переменную shared_preload_libraries в qhb.conf, а затем перезапустите сервер.

Вы можете приспособить этот модуль к своим нуждам, изменив исходный код. Например, для проверки паролей можно использовать библиотеку CrackLib — для этого нужно только раскомментировать две строки в Makefile и пересобрать модуль. (Мы не можем включить CrackLib по умолчанию из-за лицензии.) Без CrackLib этот модуль проверяет надежность пароля по простым правилам, которые вы можете модифицировать или расширить по своему усмотрению.

ВНИМАНИЕ!
Чтобы незашифрованные пароли не передавались по сети, не записывались в журнал сервера и не стали каким-либо иным образом известны администратору базы данных, QHB позволяет пользователю передавать предварительно зашифрованные пароли. Многие клиентские программы могут воспользоваться этой функциональной возможностью и шифровать пароль, прежде чем передавать его серверу.
Это ограничивает полезность модуля passwordcheck, так как в этом случае он может лишь попытаться угадать пароль. По этой причине не рекомендуется использовать passwordcheck, если требуется высокий уровень безопасности. Более безопасно будет применить внешний метод проверки подлинности, например GSSAPI (см. главу Аутентификация клиентского приложения), чем полагаться на пароли в базе данных.
Как вариант, можно модифицировать passwordcheck, чтобы предварительно зашифрованные пароли не принимались, но если вынудить пользователей задавать пароли открытым текстом, это повлечет свои риски безопасности.

pg_buffercache

Модуль pg_buffercache предоставляет возможность проверить, что происходит в общем кэше буферов в реальном времени.

Этот модуль предоставляет функцию на C/RUST pg_buffercache_pages, возвращающую набор записей плюс представление pg_buffercache, которое является удобной оберткой этой функции.

По умолчанию его использование разрешено только суперпользователям и членам роли pg_monitor. Дать доступ другим можно с помощью команды GRANT.

Представление pg_buffercache

Определения столбцов, содержащихся в представлении, показаны в Таблице 15.

Таблица 15. Столбцы pg_buffercache

Столбец Тип

Описание

bufferid integer

Идентификатор, в диапазоне 1..shared_buffers

relfilenode oid (ссылается на pg_class.relfilenode)

Номер файлового узла для отношения

reltablespace oid (ссылается на pg_tablespace.oid)

OID табличного пространства, содержащего отношение

reldatabase oid (ссылается на pg_database.oid)

OID базы данных, содержащей отношение

relforknumber smallint

Номер ветви в отношении; см. common/relpath.h

relblocknumber bigint

Номер страницы в отношении

isdirty boolean

Эта страница загрязнена?

usagecount smallint

Счетчик обращений по часовой стрелке

pinning_backends integer

Количество обслуживающих процессов, закрепивших этот буфер

Для каждого буфера в общем кэше выдается одна строка. Для неиспользуемых буферов все поля равны NULL, за исключением bufferid. Общие системные каталоги показываются как относящиеся к базе данных номер ноль.

Поскольку кэш используется совместно всеми базами данных, обычно в нем находятся и страницы отношений, не принадлежащих текущей базе данных. Это означает, что для некоторых строк при соединении с pg_class не будет соответствующих строк, или даже могут образоваться некорректные соединения. Если вы пытаетесь выполнить соединение с pg_class, имеет смысл ограничить соединение строками, в которых reldatabase содержит OID текущей базы данных или ноль.

Поскольку для копирования данных состояния буфера, которые будет отображать представление, блокировки в менеджере буферов не устанавливаются, обращения к представлению pg_buffercache оказывает меньшее влияние на нормальную работу буферов, но при этом не обеспечивает согласованный набор результатов для всех буферов. Однако внутри каждого отдельного буфера согласованность информации гарантируется.

Пример вывода

regression=# SELECT n.nspname, c.relname, count(*) AS buffers
             FROM pg_buffercache b JOIN pg_class c
             ON b.relfilenode = pg_relation_filenode(c.oid) AND
                b.reldatabase IN (0, (SELECT oid FROM pg_database
                                      WHERE datname = current_database()))
             JOIN pg_namespace n ON n.oid = c.relnamespace
             GROUP BY n.nspname, c.relname
             ORDER BY 3 DESC
             LIMIT 10;

  nspname   |        relname         | buffers
------------+------------------------+---------
 public     | delete_test_table      |     593
 public     | delete_test_table_pkey |     494
 pg_catalog | pg_attribute           |     472
 public     | quad_poly_tbl          |     353
 public     | tenk2                  |     349
 public     | tenk1                  |     349
 public     | gin_test_idx           |     306
 pg_catalog | pg_largeobject         |     206
 public     | gin_test_tbl           |     188
 public     | spgist_text_tbl        |     182
(10 rows)

Авторы

Марк Кирквуд (Mark Kirkwood), markir@paradise.net.nz

Предложения по дизайну: Нейл Конвей (Neil Conway), neilc@samurai.com

Советы по отладке: Том Лейн (Tom Lane), tgl@sss.pgh.pa.us

pgcrypto

Модуль pgcrypto предоставляет криптографические функции для QHB.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Стандартные функции хеширования

digest()

digest(data text, type text) returns bytea
digest(data bytea, type text) returns bytea

Вычисляет двойной хеш указанных данных (параметр data). Параметр type выбирает используемый алгоритм. Стандартные алгоритмы: md5, sha1, sha224, sha256, sha384 и sha512. Если модуль pgcrypto собирался с OpenSSL, будут доступны и другие алгоритмы, как описано в Таблице 19.

Если вам нужен дайджест в виде шестнадцатеричной строки, примените к результату encode(). Например:

CREATE OR REPLACE FUNCTION sha1(bytea) returns text AS $$
    SELECT encode(digest($1, 'sha1'), 'hex')
$$ LANGUAGE SQL STRICT IMMUTABLE;

hmac()

hmac(data text, key text, type text) returns bytea
hmac(data bytea, key bytea, type text) returns bytea

Вычисляет код аутентификации сообщения на основе хеша для данных data с ключом key. Параметр type имеет то же значение, что и для digest().

Эта функция похожа на digest(), но с ней хеш можно перерассчитать, только зная ключ. Это предотвращает сценарий подмены данных и изменения хеша (для соответствия).

Если размер ключа больше размера блока хеша, он сначала хешируется, а уже результат используется в качестве ключа.

Функции хеширования пароля

Функции crypt() и gen_salt() разработаны специально для хеширования паролей. Функция crypt() выполняет хеширование, а gen_salt() подготавливает параметры алгоритма для нее.

Алгоритмы в crypt() отличаются от обычных алгоритмов хеширования MD5 или SHA1 в следующих аспектах:

Они медленные. Поскольку объем данных очень мал, это единственный способ усложнить перебор паролей.

Они используют случайное значение, называемое солью, чтобы у пользователей с одинаковыми паролями зашифрованные пароли были разными. Это также является дополнительной защитой от инвертирования алгоритма.

Они включают в результат тип алгоритма, вследствие чего могут сосуществовать пароли, хешированные разными алгоритмами.

Некоторые из них являются адаптируемыми — то есть с ростом производительности компьютеров алгоритм можно настроить так, чтобы он стал медленнее, при этом сохраняя совместимость с существующими паролями.

В Таблице 16 перечислены алгоритмы, поддерживаемые функцией crypt().

Таблица 16. Алгоритмы, поддерживаемые crypt()

Алгоритм | Макс. длина пароля | Адаптивный? | Размер соли (бит) | Длина вывода | Описание
bf | 72 | да | 128 | 60 | На основе Blowfish, вариант 2a
md5 | без ограничений | нет | 48 | 34 | crypt на основе MD5
xdes | 8 | да | 24 | 20 | Расширенный DES
des | 8 | нет | 12 | 13 | Оригинальный crypt из UNIX

crypt()

crypt(password text, salt text) returns text

Вычисляет хеш пароля (password) в стиле crypt(3). При сохранении нового пароля следует вызвать gen_salt(), чтобы сгенерировать новое значение соли. Для проверки пароля нужно передать сохраненное значение хеша в параметре salt и проверить, соответствует ли результат сохраненному значению.

Пример установки нового пароля:

UPDATE ... SET pswhash = crypt('new password', gen_salt('md5'));

Пример проверки подлинности пароля:

SELECT (pswhash = crypt('entered password', pswhash)) AS pswmatch FROM ... ;

Этот запрос возвращает true, если введенный пароль верен.

gen_salt()

gen_salt(type text [, iter_count integer ]) returns text

Генерирует новую случайную строку соли для использования в crypt(). Строка соли также сообщает crypt(), какой алгоритм применять.

Параметр type задает алгоритм хеширования. Допустимые типы: des, xdes, md5 и bf.

Параметр iter_count позволяет пользователю задать счетчик итераций для алгоритма, у которого он есть. Чем выше это число, тем больше времени уйдет на хеширование пароля и, как следствие, тем больше времени понадобится, чтобы его взломать. Хотя при слишком большим значением время вычисления хеша может вырасти до нескольких лет — что довольно непрактично. Если параметр iter_count опущен, используется количество итераций по умолчанию. Допустимые значения для iter_count зависят от алгоритма, как показано в Таблице 17.

Таблица 17. Счетчики итераций для crypt()

Алгоритм | По умолчанию | Минимум | Максимум
xdes | 725 | 1 | 16777215
bf | 6 | 4 | 31

Для xdes есть дополнительное ограничение: счетчик итераций должен быть нечетным числом.

При выборе подходящего количества итераций учтите, что оригинальный алгоритм DES crypt был разработан так, чтобы выдавать 4 хеша в секунду на оборудовании того времени. Скорость менее 4 хешей в секунду, скорее всего, снизит удобство применения. А скорость больше, чем 100 хешей в секунду, вероятно, будет слишком высокой.

В Таблице 18 дан обзор относительной скорости различных алгоритмов хеширования. В таблице показано, сколько времени займет попытка перебрать все комбинации символов в 8-символьном пароле, в предположении, что пароль содержит только строчные буквы, либо строчные и прописные буквы, а также цифры. В записях crypt-bf числа после косой черты представляют собой значение параметра iter_count функции gen_salt.

Таблица 18. Скорости алгоритмов хеширования

Алгоритм | Хешей/сек. | Для [a-z] | Для [A-Za-z0-9] | Длительность относительно хеша md5
crypt-bf/8 | 1792 | 4 года | 3927 лет | 100 000
crypt-bf/7 | 3648 | 2 года | 1929 лет | 50 000
crypt-bf/6 | 7168 | 1 год | 982 года | 25 000
crypt-bf/5 | 13504 | 188 дней | 521 год | 12 500
crypt-md5 | 171584 | 15 дней | 41 год | 1000
crypt-des | 23221568 | 157,5 минут | 108 дней | 7
sha1 | 37774272 | 90 минут | 68 дней | 4
md5 (хеш) | 150085504 | 22,5 минуты | 17 дней | 1

Примечания:

Для расчетов использовался процессор Intel Mobile Core i3.

Показатели алгоритмов crypt-des и crypt-md5 взяты из вывода -test программы John the Ripper v1.6.38.

Показатели хеша md5 получены программой mdcrack 1.2.

Показатели sha1 получены программой lcrack-20031130-beta.

Показатели crypt-bf получены при помощи простой программы, обрабатывающей в цикле 1000 8-символьных паролей. Таким способом можно показать скорость с разным количеством итераций. Для справки: john -test показывает 13 506 циклов/с для crypt-bf/5. (Это очень малое различие в результатах согласуется с тем фактом, что реализация crypt-bf в pgcrypto та же, что применяется в программе John the Ripper.)

Обратите внимание, что вариант «перебрать все комбинации» не особо приближен к реальности. Обычно взлом паролей производится с помощью словарей, содержащих как обычные слова, так и их различные видоизменения. Поэтому даже некоторым образом похожие на слова пароли можно подобрать быстрее, чем за вышеуказанное время, тогда как 6-символьный несловесный пароль может избежать взлома. А может и не избежать.

Функции шифрования на основе PGP

Описанные здесь функции реализуют часть стандарта OpenPGP (RFC 4880), касающуюся шифрования. Они поддерживают шифрование как с симметричным, так и с открытым ключом.

Зашифрованное сообщение PGP состоит из 2 частей, или пакетов:

Пакет, содержащий сеансовый ключ — либо симметричный, либо открытый (в зашифрованном виде).

Пакет, содержащий данные, зашифрованные сеансовым ключом.

При шифровании с симметричным ключом (т. е. паролем):

Заданный пароль хешируется по алгоритму String2Key (S2K). Этот алгоритм отчасти схож с алгоритмами crypt() — нарочно замедлен и добавляет случайную соль — но создает двоичный ключ полной длины.

Если требуется отдельный сеансовый ключ, будет сгенерирован новый случайный ключ. В противном случае в качестве сеансового будет использован непосредственно ключ S2K.

Если используется непосредственно ключ S2K, то в пакет сеансового ключа будут помещены только параметры S2K. В противном случае сеансовый ключ будет зашифрован ключом S2K и помещен в пакет сеансового ключа.

При шифровании с открытым ключом:

Генерируется новый случайный ключ.

Он зашифровывается открытым ключом и помещается в пакет сеансового ключа.

В любом случае данные, подлежащие шифрованию, обрабатываются следующим образом:

Необязательная обработка данных: сжатие, перекодировка в UTF-8 и/или преобразование окончаний строк.

Перед данными добавляется блок случайных байтов. Это равнозначно использованию случайного вектора инициализации.

В конце добавляется хеш SHA1 случайного префикса и данных.

Все это шифруется сеансовым ключом и помещается в пакет данных.

pgp_sym_encrypt()

pgp_sym_encrypt(data text, psw text [, options text ]) returns bytea
pgp_sym_encrypt_bytea(data bytea, psw text [, options text ]) returns bytea

Шифрует данные (data) симметричным ключом PGP psw. Параметр options может содержать криптографические параметры, описанные ниже.

pgp_sym_decrypt()

pgp_sym_decrypt(msg bytea, psw text [, options text ]) returns text
pgp_sym_decrypt_bytea(msg bytea, psw text [, options text ]) returns bytea

Дешифрует сообщение, зашифрованное симметричным ключом PGP.

Дешифровать данные bytea функцией pgp_sym_decrypt запрещено. Это сделано для того, чтобы избежать вывода некорректных символьных данных. Дешифровать изначально текстовые данные с помощью pgp_sym_decrypt_bytea разрешается.

Параметр options может содержать криптографические параметры, описанные ниже.

pgp_pub_encrypt()

pgp_pub_encrypt(data text, key bytea [, options text ]) returns bytea
pgp_pub_encrypt_bytea(data bytea, key bytea [, options text ]) returns bytea

Зашифровывает данные (data) открытым ключом PGP key. Если передать этой функции закрытый ключ, она выдаст ошибку.

Параметр options может содержать криптографические параметры, описанные ниже.

pgp_pub_decrypt()

pgp_pub_decrypt(msg bytea, key bytea [, psw text [, options text ]]) returns text
pgp_pub_decrypt_bytea(msg bytea, key bytea [, psw text [, options text ]]) returns bytea

Дешифрует сообщение, зашифрованное открытым ключом. В key следует передавать секретный ключ, соответствующий открытому ключу, использованному при шифровании. Если секретный ключ защищен паролем, этот пароль нужно передать в параметре psw. Если пароля нет, но вы хотите задать криптографические параметры, следует передать пустой пароль.

Дешифровать данные bytea функцией pgp_pub_decrypt запрещено. Это сделано для того, чтобы избежать вывода некорректных символьных данных. Дешифровать изначально текстовые данные с помощью pgp_pub_decrypt_bytea разрешается.

Параметр options может содержать криптографические параметры, описанные ниже.

pgp_key_id()

pgp_key_id(bytea) returns text

Функция pgp_key_id извлекает ID ключа из открытого или секретного ключа PGP. Либо выдает ID ключа, который использовался для шифрования данных, если ей передается зашифрованное сообщение.

Она может возвращать 2 специальных ID ключа:

SYMKEY
Сообщение зашифровано симметричным ключом.

ANYKEY
Сообщение зашифровано открытым ключом, но ID ключа был удален. Это означает, что вам надо будет перепробовать все ваши секретные ключи, чтобы подобрать тот, который дешифрует это сообщение. Сам pgcrypto не создает такие сообщения.

Обратите внимание, что у разных ключей может быть одинаковый ID. Это редкое, но обычное явление. При этом клиентское приложение должно пытаться дешифровать данные с каждым ключом, чтобы найти подходящий — как в случае с ANYKEY.

armor(), dearmor()

armor(data bytea [ , keys text[], values text[] ]) returns text
dearmor(data text) returns bytea

Эти функции преобразуют двоичные данные в/из формата PGP «ASCII-armor», который по сути представляет собой кодировку Base64 с контрольными суммами и дополнительным форматированием.

Если задаются массивы keys и values, для каждой пары ключ/значение в формате Armor добавляется заголовок Armor. Оба массива должны быть одномерными и иметь одинаковую длину. Ключи и значения могут содержать только символы ASCII.

pgp_armor_headers

pgp_armor_headers(data text, key out text, value out text) returns setof record

Функция pgp_armor_headers() извлекает заголовки Armor из данных (data). Возвращаемое значение представляет собой набор строк с двумя столбцами, key (ключ) и value (значение). Если ключи или значения содержат какие-либо символы, отличные от ASCII, они воспринимаются как UTF-8.

Параметры функций PGP

Имена параметрам подобны таковым в GnuPG. Значение параметра следует задавать после знака равенства; разделяются параметры запятыми. Например:

pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')

Все эти параметры, кроме convert-crlf, применяются только к функциям шифрования. Функции дешифрования получают параметры из данных PGP.

Вероятно, самые интересные параметры — это compress-algo и unicode-mode. Остальные должны иметь приемлемые значения по умолчанию.

cipher-algo
Выбирает алгоритм шифрования.
Значения: bf, aes128, aes192, aes256 (только OpenSSL: 3des, cast5)
По умолчанию: aes128
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

compress-algo
Выбирает алгоритм сжатия. Доступен, только если QHB была собрана с zlib.
Значения:
0 - без сжатия
1 - сжатие ZIP
2 - сжатие ZLIB (= ZIP плюс метаданные и контрольные суммы блоков)
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

compress-level
Определяет уровень сжатия. При более высоких уровнях на выходе получается меньший объем, но сам процесс замедляется. Значение 0 выключает сжатие.
Значения: 0, 1-9
По умолчанию: 6
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

convert-crlf
Определяет, преобразовывать ли \n в \r\n при шифровании и \r\n в \n при дешифровании. В RFC 4880 указано, что текстовые данные должны храниться с переводами строк в виде \r\n. Используйте этот параметр, чтобы добиться поведения, полностью соответствующего RFC.
Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt

disable-mdc
Не защищать данные хешем SHA-1. Единственная веская причина использовать этот параметр — добиться совместимости с древними программами PGP, вышедшими до того, как в RFC 4880 была добавлена защита пакетов с SHA-1. Недавние программы с gnupg.org и pgp.com прекрасно ее поддерживают.
Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

sess-key
Использовать отдельные сеансовый ключ. Для шифрования с открытым ключом всегда используется отдельный сеансовый ключ; этот параметр предназначен для шифрования с симметричным ключом, которое по умолчанию использует непосредственно ключ S2K.
Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt

s2k-mode
Выбирает режим алгоритма S2K.
Значения:
0 - Без соли. Опасно!
1 - С солью, но с фиксированным числом итераций.
3 - С переменным числом итераций.
По умолчанию: 3
Применим к: pgp_sym_encrypt

s2k-count
Выбирает число итераций для алгоритма S2K. Значение должно быть от 1024 до 65011712 включительно.
По умолчанию: Случайное значение от 65536 до 253952
Применим к: pgp_sym_encrypt, только с s2k-mode=3

s2k-digest-algo
Выбирает алгоритм хеширования, который будет использоваться для вычисления S2K.
Значения: md5, sha1
По умолчанию: sha1
Применим к: pgp_sym_encrypt

s2k-cipher-algo
Выбирает шифр, который будет использоваться для шифрования отдельного сеансового ключа.
Значения: bf, aes, aes128, aes192, aes256
По умолчанию: используется cipher-algo
Применим к: pgp_sym_encrypt

unicode-mode
Определяет, преобразовывать ли текстовые данные из внутренней кодировки базы данных в UTF-8 и обратно. Если кодировка базы уже UTF-8, преобразование не производится, но сообщение будет помечено как UTF-8. Без этого параметра этого не произойдет.
Значения: 0, 1
По умолчанию: 0
Применим к: pgp_sym_encrypt, pgp_pub_encrypt

Генерирование ключей PGP с помощью GnuPG

Генерирование нового ключа:

gpg --gen-key

Предпочитаемый тип ключей — «DSA and Elgamal».

Для шифрования RSA необходимо создать в качестве главного либо DSA, либо RSA ключ только для подписания, а затем добавить подключ для шифрования RSA, выполнив команду gpg --edit-key.

Вывод списка ключей:

gpg --list-secret-keys

Экспорт открытого ключа в формате ASCII Armor:

gpg -a --export KEYID > public.key

Экспорт секретного ключа в формате ASCII Armor:

gpg -a --export-secret-keys KEYID > secret.key

Прежде чем передать эти ключи функциям PGP, необходимо применить к ним функцию dearmor(). Либо, если есть возможность обработать двоичные данные, можно убрать -a из этой команды.

Дополнительную информацию см. в руководстве man gpg, The GNU Privacy Handbook (Руководство GNU по обеспечению конфиденциальности) и в другой документации на сайте https://www.gnupg.org/.

Ограничения кода PGP

Нет поддержки подписывания. Это также означает, что принадлежность подключа шифрования главному ключу не проверяется.

Нет поддержки использования ключа шифрования в качестве главного ключа. Поскольку такая практика в целом не приветствуется, это не должно быть проблемой.

Нет поддержки нескольких подключей. Это может показаться проблемой, так как подобная практика распространена. С другой стороны, не стоит использовать обычные ключи GPG/PGP с pgcrypto — следует создавать новые, поскольку их сценарий использования отличается.

Низкоуровневые функции шифрования

Эти функции выполняют только шифрование данных; у них нет расширенных возможностей шифрования PGP. Поэтому с ними связано несколько существенных проблем:

Они используют ключ пользователя непосредственно в качестве ключа шифрования.

Они не обеспечивают проверку целостности для выявления изменений в зашифрованных данных.

Они рассчитывают, что пользователи будут управлять всеми параметрами шифрования самостоятельно, даже вектором инициализации.

Они не обрабатывают текст.

Поэтому с введением шифрования PGP использование низкоуровневых функций шифрования не приветствуется.

encrypt(data bytea, key bytea, type text) returns bytea
decrypt(data bytea, key bytea, type text) returns bytea

encrypt_iv(data bytea, key bytea, iv bytea, type text) returns bytea
decrypt_iv(data bytea, key bytea, iv bytea, type text) returns bytea

Шифрование/дешифрование данных, применяя метод шифрования, заданный параметром type. Синтаксис строки type:

алгоритм [ - режим ] [ /pad: дозаполнение ]

где допустимый алгоритм:

bf — Blowfish

aes — AES (Rijndael-128, -192 or -256)

допустимый режим:

cbc — следующий блок зависит от предыдущего (по умолчанию)

ecb — каждый блок шифруется отдельно (только для тестирования)

и допустимое дозаполнение:

pkcs — данные могут быть любой длины (по умолчанию)

none — размер данных должен быть кратным размеру блока шифра

Так что, например, эти строки равнозначны:

encrypt(data, 'fooz', 'bf')
encrypt(data, 'fooz', 'bf-cbc/pad:pkcs')

Для функций encrypt_iv и decrypt_iv параметр iv является начальным значением для режима CBC; для ECB он игнорируется. Если размер значения не равен ровно размеру блока, оно обрезается или дополняется нулями. В функциях без этого параметра оно по умолчанию заполняется нулями.

Функции получения случайных данных

gen_random_bytes(count integer) returns bytea

Возвращает заданное количество (count) криптографически стойких случайных байтов. За один вызов можно извлечь не более 1024 байт. Это ограничение предотвращает исчерпание пула генератора случайных чисел.

gen_random_uuid() returns uuid

Возвращает UUID версии 4 (случайный). (В данном модуле эту функцию можно счесть устаревшей, поскольку теперь такая функция включена в ядро QHB.)

Примечания

Конфигурирование

Модуль pgcrypto настраивается в соответствии с данными, полученными в главном скрипте configure QHB. На его конфигурацию влияют аргументы --with-zlib и --with-ssl=openssl.

При компиляции с zlib шифрующие функции PGP способны сжимать данные перед шифрованием.

При компиляции с OpenSSL будут доступны дополнительные алгоритмы. Кроме того, функции шифрования с открытым ключом будут работать быстрее, поскольку OpenSSL содержит более оптимизированные функции для работы с большими числами (BIGNUM).

Таблица 19. Обзор функциональности с OpenSSL и без него

Функциональность | Встроенная | С OpenSSL
MD5 | да | да
SHA1 | да | да
SHA224/256/384/512 | да | да
Другие алгоритмы выборки сообщений | нет | да (см. Примечание)
Blowfish | да | да
AES | да | да
DES/3DES/CAST5 | нет | да
Низкоуровневое шифрование | да | да
Симметричное шифрование PGP | да | да
Шифрование PGP с открытым ключом | да | да

Чтобы использовать более старые шифры, такие как DES или Blowfish, при компиляции с OpenSSL версии 3.0.0 и новее, нужно активировать поставщика ранней версии в файле конфигурации openssl.cnf.

Примечание:

Автоматически выбирается любой алгоритм выборки сообщений, который поддерживает OpenSSL. Это невозможно с шифрами, которые должны поддерживаться явно.

Обработка NULL

В соответствии со стандартом SQL, все функции возвращают NULL, если любой из аргументов — NULL. Это может угрожать безопасности при небрежном применении.

Ограничения безопасности

Все функции pgcrypto выполняются внутри сервера баз данных. Это означает, что все данные и пароли передаются между pgcrypto и клиентскими приложениями открытым текстом. Поэтому вы должны:

Подключаться локально или использовать подключения SSL.

Доверять и системе, и администратору баз данных.

Если это невозможно, лучше провести шифрование внутри клиентского приложения.

Эта реализация не противостоит атакам по сторонним каналам. Например, время, требующееся для выполнения функции дешифрования pgcrypto, будет разным для разного шифротекста заданного размера.

Дополнительная литература

https://www.gnupg.org/gph/en/manual.html

The GNU Privacy Handbook (Руководство GNU по обеспечению конфиденциальности).

https://www.openwall.com/crypt/

Описывает алгоритм crypt-blowfish.

https://www.iusmentis.com/security/passphrasefaq/

Как выбрать хороший пароль.

https://theworld.com/~reinhold/diceware.html

Интересный способ выбора паролей.

http://www.interhack.net/people/cmcurtin/snake-oil-faq.html

Описывает хорошую и плохую криптографию.

Техническая справка

https://tools.ietf.org/html/rfc4880

Формат сообщений OpenPGP.

https://tools.ietf.org/html/rfc1321

Алгоритм выборки сообщений MD5.

https://tools.ietf.org/html/rfc2104

HMAC: Хеширование по ключу для аутентификации сообщений.

https://www.usenix.org/legacy/events/usenix99/provos.html

Сравнение алгоритмов crypt-des, crypt-md5 и bcrypt.

https://en.wikipedia.org/wiki/Fortuna_(PRNG)

Описание Fortuna CSPRNG.

https://jlcooke.ca/random/

Драйвер /dev/random для Linux на базе Fortuna, написанный Жан-Люком Куком (Jean-Luc Cooke).

Автор

Марко Крин (Marko Kreen), markokr@gmail.com

Модуль pgcrypto использует код из следующих источников:

Алгоритм | Автор | Источник исходного кода
Шифрование DES | Дэвид Вуррен (David Burren) и др. | FreeBSD, libcrypt
Шифрование MD5 | Пол-Хеннинг Камп (Poul-Henning Kamp) | FreeBSD, libcrypt
Шифрование Blowfish | Solar Designer | www.openwall.com
Шифр Blowfish | Саймон Тэтем (Simon Tatham) | PuTTY
Шифр Rijndael | Брайан Глэдмен (Brian Gladman) | OpenBSD, sys/crypto
Хеш MD5 и SHA1 | Проект WIDE | KAME, kame/sys/crypto
SHA256/384/512 | Аарон Д. Гиффорд (Aaron D. Gifford) | OpenBSD, sys/crypto
Математика BIGNUM | Майкл Дж. Фромбергер (Michael J. Fromberger) | dartmouth.edu/~sting/sw/imath

pg_freespacemap

Модуль pg_freespacemap обеспечивает возможность исследовать карту свободного пространства (free space map, FSM). Он предоставляет функцию pg_freespace, или, точнее, две перегруженных функции. Эти функции показывают значение, записанное в карте свободного пространства для данной страницы либо для всех страниц в отношении.

По умолчанию его использование разрешено только суперпользователям и членам роли pg_stat_scan_tables. Дать доступ всем остальным можно с помощью команды GRANT.

Функции

pg_freespace(rel regclass IN, blkno bigint IN) returns int2

Возвращает объем свободного пространства на странице отношения, заданного параметром blkno, согласно FSM.

pg_freespace(rel regclass IN, blkno OUT bigint, avail OUT int2)

Показывает объем свободного пространства на каждой странице отношения, согласно FSM. Возвращается набор кортежей (blkno bigint, avail int2), по одному кортежу для каждой страницы в отношении.

Значения, хранимые в карте свободного пространства, не являются точными. Они округляются до 1/256 BLCKSZ (до 32 байт со значением BLCKSZ по умолчанию) и не в полной мере поддерживаются в актуальном состоянии при каждом добавлении и изменении кортежей.

Для индексов отслеживаются полностью неиспользованные страницы, а не свободное пространство в страницах. Как следствие, эти значения не имеют смысла, а лишь показывают, занята страница или свободна.

Пример вывода

qhb=# SELECT * FROM pg_freespace('foo');
 blkno | avail
-------+-------
     0 |     0
     1 |     0
     2 |     0
     3 |    32
     4 |   704
     5 |   704
     6 |   704
     7 |  1216
     8 |   704
     9 |   704
    10 |   704
    11 |   704
    12 |   704
    13 |   704
    14 |   704
    15 |   704
    16 |   704
    17 |   704
    18 |   704
    19 |  3648
(20 rows)

qhb=# SELECT * FROM pg_freespace('foo', 7);
 pg_freespace
--------------
         1216
(1 row)

Автор

Исходную версию разработал Марк Кирквуд (Mark Kirkwood), markir@paradise.net.nz. Для версии с новой реализацией FSM код адаптировал Хейкки Линнакангас (Heikki Linnakangas), heikki@enterprisedb.com.

pg_prewarm

Модуль pg_prewarm предоставляет оптимальный способ загружать данные отношений в кэш буферов операционной системы или в кэш буферов QHB. Предварительную загрузку можно выполнить вручную при помощи функции pg_prewarm или автоматически, добавив pg_prewarm в shared_preload_libraries. В последнем случае система запустит фоновый процесс, который будет периодически записывать содержимое общих буферов в файл autoprewarm.blocks, а после перезапуска сервера подгружать эти блоки в память, используя 2 фоновых процесса.

Функции

pg_prewarm(regclass, mode text default 'buffer', fork text default 'main',
           first_block int8 default null,
           last_block int8 default null) RETURNS int8

Первый аргумент задает отношение, которое будет предварительно загружено. Второй аргумент задает метод этой загрузки, из описанных ниже; третий задает ветвь отношения для загрузки, обычно main. Четвертый аргумент задает номер первого загружаемого блока (NULL принимается как синоним нуля). Пятый аргумент задает номер последнего загружаемого блока (NULL означает загрузку до последнего блока отношения включительно). Возвращаемое значение — количество предварительно загруженных блоков.

В этой функции доступны три метода предварительной загрузки. При prefetch выдаются асинхронные запросы предвыборки данных операционной системы, если они поддерживаются, либо происходит ошибка. При read считывается заданный диапазон блоков; в отличие от prefetch это происходит синхронно и поддерживается на всех платформах и сборках, но может быть медленнее. При buffer запрошенный диапазон блоков считывается в кэш буферов базы данных.

Обратите внимание, что с любым из этих методов попытка предварительно загрузить больше блоков, чем может поместиться в кэше, — в кэше ОС методом prefetch или read или в кэше QHB методом buffer — скорее всего приведет к тому, что блоки с меньшими номерами будут вытеснены из кэша при чтении последующих блоков. Кроме того, предварительно загруженные данные не имеют никакой специальной защиты от вытеснения из кэша, в связи с чем возможна ситуация, когда из-за другой системной активности только что загруженные блоки будут вытеснены вскоре после чтения, и, наоборот, при предварительной загрузке из кэша могут быть вытеснены другие данные. По этим причинам предварительная загрузка обычно наиболее полезна при запуске сервера, когда кэши в основном пусты.

autoprewarm_start_worker() RETURNS void

Запустить основной рабочий процесс автозагрузки. Обычно он запускается автоматически, но эта функция полезна, если автоматическая предварительная загрузка не была настроена при запуске сервера и вы хотите запустить этот рабочий процесс позже.

autoprewarm_dump_now() RETURNS int8

Обновить autoprewarm.blocks немедленно. Это может быть полезно, если рабочий процесс автозагрузки не работает, но вы хотите, чтобы он сработал после следующего перезапуска. Возвращаемое значение — количество записей, внесенных в autoprewarm.blocks.

Параметры конфигурации

pg_prewarm.autoprewarm (boolean)
Определяет, должен ли сервер запускать рабочий процесс автозагрузки. По умолчанию от включен. Этот параметр можно задать только при запуске сервера.

pg_prewarm.autoprewarm_interval (int)
Это интервал между обновлениями файла autoprewarm.blocks. Значение по умолчанию — 300 секунд. При значении 0 файл будет сохраняться не периодически, а только при отключении сервера.

Эти параметры нужно установить в qhb.conf. Их обычное использование может выглядеть так:

# qhb.conf
shared_preload_libraries = 'pg_prewarm'

pg_prewarm.autoprewarm = true
pg_prewarm.autoprewarm_interval = 300s

Автор

Роберт Хаас (Robert Haas), rhaas@postgresql.org

pgrowlocks

Модуль pgrowlocks предоставляет функцию, показывающую информацию о блокировке строк для заданной таблицы.

По умолчанию его использование разрешено суперпользователям, членам роли pg_stat_scan_tables и пользователям с правом SELECT в заданной таблице.

Обзор

pgrowlocks(text) returns setof record

В параметре передается имя таблицы. Возвращаемый результат представляет собой набор записей, в котором каждая строка соответствует заблокированной строке в таблице. Столбцы вывода перечислены в Таблице 20.

Таблица 20. Столбцы вывода pgrowlocks

Параметр | Тип | Описание
locked_row | tid | Идентификатор кортежа (TID) блокированной строки
locker | xid | Идентификатор блокировочной транзакции или идентификатор мультитранзакции, если это мультитранзакция
multi | boolean | True, если блокиратор — мультитранзакция
xids | xid[] | Идентификаторы блокирующих транзакций (больше одного, если это мультитранзакция)
modes | text[] | Режим блокирования (больше одного, если это мультитранзакция), массив со значениями Key Share, Share, For No Key Update, No Key Update, For Update, Update.
pids | integer[] | Идентификаторы блокирующих обслуживающих процессов (больше одного, если это мультитранзакция)

Функция pgrowlocks запрашивает блокировку AccessShareLock для целевой таблицы и считывает строки одну за другой для сбора информации о блокировке строк. Для большой таблицы это происходит небыстро. Обратите внимание, что:

Если таблица заблокирована в режиме ACCESS EXCLUSIVE, функция pgrowlocks будет блокироваться.

Функция pgrowlocks не гарантирует выдачу внутренне согласованных снимков. В ходе ее выполнения могут быть установлены новые блокировки строк или сняты старые.

Функция pgrowlocks не показывает содержимое заблокированных строк. Если вы хотите одновременно взглянуть на содержимое строк, можно написать что-то вроде этого:

SELECT * FROM accounts AS a, pgrowlocks('accounts') AS p
  WHERE p.locked_row = a.ctid;

Однако учтите, что такой запрос будет крайне неэффективным.

Пример вывода

=# SELECT * FROM pgrowlocks('t1');
 locked_row | locker | multi | xids  |     modes      |  pids
------------+--------+-------+-------+----------------+--------
 (0,1)      |    609 | f     | {609} | {"For Share"}  | {3161}
 (0,2)      |    609 | f     | {609} | {"For Share"}  | {3161}
 (0,3)      |    607 | f     | {607} | {"For Update"} | {3107}
 (0,4)      |    607 | f     | {607} | {"For Update"} | {3107}
(4 rows)

Автор

Тацуо Исии (Tatsuo Ishii)

pg_stat_statements

Модуль pg_stat_statements предоставляет возможность отслеживать статистику планирования и выполнения сервером всех операторов SQL.

Этот модуль следует загружать посредством добавления pg_stat_statements в shared_preload_libraries в файле qhb.conf, так как ему требуется дополнительная разделяемая память. Это означает, что для добавления или удаления этого модуля необходимо перезапустить сервер. Кроме того, для функционирования этого модуля требуется включить вычисление идентификаторов запросов, что происходит автоматически, если задать в параметре compute_query_id значение auto или on или загрузить сторонний модуль, вычисляющий идентификаторы запросов.

Когда модуль pg_stat_statements активен, он отслеживает статистику по всем базам данных на сервере. Для доступа к этой статистике и ее обработки модуль предоставляет представления pg_stat_statements и pg_stat_statements_info, а также вспомогательные функции pg_stat_statements_reset и pg_stat_statements. Эти объекты не доступны глобально, но их можно установить в определенной базе данных посредством команды CREATE EXTENSION pg_stat_statements.

Представление pg_stat_statements

Статистика, собираемая модулем, выдается через представление с именем pg_stat_statements. Это представление содержит по одной строке для каждой отдельной комбинации ID базы данных, ID пользователя, ID запроса и указания, относится ли данный оператор к верхнему уровню или нет (в количестве, не превышающем максимальное число отдельных операторов, которые может отслеживать модуль). Столбцы представления показаны в Таблице 21.

Таблица 21. Столбцы pg_stat_statements

Столбец Тип

Описание

userid oid (ссылается на pg_authid.oid)

OID пользователя, выполнявшего оператор

dbid oid (ссылается на pg_database.oid)

OID базы данных, в которой выполнялся оператор

toplevel bool

True, если этот запрос выполнялся как оператор верхнего уровня (всегда true, если для параметра pg_stat_statements.track установлено значение top)

queryid bigint

Хеш-код для идентификации одинаковых нормализованных запросов

query text

Текст, представляющий оператор

plans bigint

Число операций планирования этого оператора (если включен параметр pg_stat_statements.track_planning; иначе 0

total_plan_time double precision

Общее время, затраченное на планирование этого оператора, в миллисекундах (если включен параметр pg_stat_statements.track_planning; иначе 0)

min_plan_time double precision

Минимальное время, затраченное на планирование этого оператора, в миллисекундах (если включен параметр pg_stat_statements.track_planning; иначе 0)

max_plan_time double precision

Максимальное время, затраченное на планирование этого оператора, в миллисекундах (если включен параметр pg_stat_statements.track_planning; иначе 0)

mean_plan_time double precision

Среднее время, затраченное на планирование этого оператора, в миллисекундах (если включен параметр pg_stat_statements.track_planning; иначе 0)

stddev_plan_time double precision

Стандартное отклонение времени, затраченного на планирование этого оператора, в миллисекундах (если включен параметр pg_stat_statements.track_planning; иначе 0)

calls bigint

Число выполнений этого оператора

total_exec_time double precision

Общее время, затраченное на выполнение этого оператора, в миллисекундах

min_exec_time double precision

Минимальное время, затраченное на выполнение этого оператора, в миллисекундах

max_exec_time double precision

Максимальное время, затраченное на выполнение этого оператора, в миллисекундах

mean_exec_time double precision

Среднее время, затраченное на выполнение этого оператора, в миллисекундах

stddev_exec_time double precision

Стандартное отклонение времени, затраченного на выполнение этого оператора, в миллисекундах

rows bigint

Общее количество строк, полученных или затронутых этим оператором

shared_blks_hit bigint

Общее количество попаданий в разделяемый кэш блоков для этого оператора

shared_blks_read bigint

Общее количество разделяемых блоков, прочитанных этим оператором

shared_blks_dirtied bigint

Общее количество разделяемых блоков, «загрязненных» этим оператором

shared_blks_written bigint

Общее количество разделяемых блоков, записанных этим оператором

local_blks_hit bigint

Общее количество попаданий в локальный кэш блоков для этого оператора

local_blks_read bigint

Общее количество локальных блоков, прочитанных этим оператором

local_blks_dirtied bigint

Общее количество локальных блоков, «загрязненных» этим оператором

local_blks_written bigint

Общее количество локальных блоков, записанных этим оператором

temp_blks_read bigint

Общее количество временных блоков, прочитанных этим оператором

temp_blks_written bigint

Общее количество временных блоков, записанных этим оператором

blk_read_time double precision

Общее время, затраченное оператором на чтение блоков, в миллисекундах (если включен track_io_timing; иначе 0)

blk_write_time double precision

Общее время, затраченное оператором на запись блоков, в миллисекундах (если включен track_io_timing; иначе 0)

wal_records bigint

Общее количество записей WAL, сгенерированных этим оператором

wal_fpi bigint

Общее количество образов полных страниц WAL, сгенерированных этим оператором

wal_bytes numeric

Общий объем WAL, сгенерированный этим оператором, в байтах

Из соображений безопасности только суперпользователям и членам роли pg_read_all_stats разрешено видеть текст SQL и queryid запросов, выполняемых другими пользователями. Однако другие пользователи могут видеть статистику, если это представление было установлено в их базу данных.

Планируемые запросы (то есть SELECT, INSERT, UPDATE и DELETE) объединяются в одну запись в pg_stat_statements, когда они имеют идентичные структуры запросов согласно внутреннему вычислению хеша. Обычно в таком случае два запроса буду считаться равными, если они семантически равнозначны, не считая значений литеральных констант, находящихся в запросе. Однако служебные команды (то есть все остальные команды) сравниваются строго по текстовым строкам запросов.

Примечание
Приведенные ниже сведения о замене констант и queryid применимы, только когда включен параметр compute_query_id. Если же вы используете для вычисления queryid внешний модуль, обратитесь за информацией к его документации.

Когда значение константы игнорируется в целях сравнения запроса с другими запросами, эта константа заменяется в выводе pg_stat_statements символом параметра, например $1. В остальном этот вывод содержит текст первого запроса с определенным значением хеша queryid, связанным с записью в pg_stat_statements.

В некоторых случаях запросы с визуально различными тексами могут быть объединены в одну запись pg_stat_statements. Обычно это происходит только для семантически равнозначных запросов, но есть небольшой шанс, что из-за наложений хеша несвязанные запросы окажутся объединенными в одной записи. (Однако это невозможно для запросов, принадлежащих разным пользователям или базам данных.)

Поскольку значение хеша queryid вычисляется по представлениям запроса на стадии после синтаксического анализа, возможна и обратная ситуация: запросы с идентичным текстом могут оказаться в разных записях, если в результате каких-либо факторов, например разных настроек search_path, у них будет разное значение.

Потребители статистики pg_stat_statements могут пожелать использовать в качестве более стабильного и надежного идентификатора для каждой записи не текст запроса, а queryid (возможно, в сочетании с dbid и userid). Однако важно понимать, что стабильность значения хеша queryid гарантируется с ограничениями. Так как этот идентификатор получается из дерева запроса после анализа, его значение будет, помимо прочего, зависеть от внутренних идентификаторов объектов, находящихся в этом представлении. С этим связано несколько неинтуитивных последствий. Например, pg_stat_statements будет считать два одинаковых внешне запроса разными, если они обращаются к таблице, которая была удалена и воссоздана между выполнением этих запросов. Процесс хеширования также чувствителен к различиям в машинной архитектуре и другим особенностям платформы. Более того, опасно предполагать, что queryid будет оставаться неизменным при обновлении основных версий QHB.

На практике выходит, что значения queryid можно считать надежными и сравнимыми только до тех пор, пока версия базового сервера и детали метаданных каталога остаются неизменными. Можно ожидать, что два сервера, участвующих в репликации на основе воспроизведения физического WAL, будут иметь одинаковые значения queryid для одного. Однако схемы с логической репликацией не гарантируют идентичности реплик во всех значимых деталях, поэтому queryid не будет полезным идентификатором для накопления показателей стоимости по всему набору логических реплик. В случае сомнений рекомендуется провести прямое тестирование.

Символы параметров, используемые для замены констант в представляющих запросы текстах, начинаются с номера, следующего за наибольшим параметром $n в исходном текста запроса, или с $1, если параметров нет. Стоит отметить, что в некоторых случаях на эту нумерацию могут влиять скрытые символы параметров. Например, PL/pgSQL применяет такие символы для добавления в запросы значений локальных переменных функции, поэтому оператор PL/pgSQL вида SELECT i + 1 INTO j будет представлен в тексте как SELECT i + $2.

Тексты, представляющие запросы, сохраняются во внешнем файле на диске и не занимают разделяемую память. Поэтому даже очень громоздкие тексты запросов можно успешно сохранить. Однако если во внешнем файле накапливается много длинных текстов запросов, он может вырасти до неуправляемого размера. В таком случае в качестве метода исправления этой проблемы pg_stat_statements может решить стереть тексты запросов, в результате чего во всех существующих записях в представлении pg_stat_statements в полях query окажутся значения NULL, хотя статистика, связанная с каждым queryid, сохранится. Если это происходит, возможно, стоит уменьшить pg_stat_statements.max, чтобы предотвратить повторение таких ситуаций.

Показатели plans и calls не всегда должны совпадать, потому что статистика планирования и выполнения обновляется в конце соответствующей фазы и только при успешном завершении операции. Например, если оператор был успешно запланирован, но во время фазы выполнения произошла ошибка, изменится только статистика планирования. Если же планирование пропускается по причине использования кэшированного плана, изменится только статистика выполнения.

Представление pg_stat_statements_info

Статистика самого модуля pg_stat_statements отслеживается и выдается через представление с именем pg_stat_statements_info. Это представление содержит только одну строку. Столбцы представления показаны в Таблице 22.

Таблица 22. Столбцы pg_stat_statements_info

Столбец Тип

Описание

dealloc bigint

Сколько всего раз в pg_stat_statements отбрасывались записи о наиболее редко выполняемых операторах, поскольку было выявлено больше различных операторов, чем задано в pg_stat_statements.max

stats_reset timestamp with time zone

Время последнего сброса всех статистических данных в представлении pg_stat_statements

Функции

pg_stat_statements_reset(userid Oid, dbid Oid, queryid bigint) returns void

Функция pg_stat_statements_reset сбрасывает всю статистику, собранную к этому времени модулем pg_stat_statements для заданного пользователя (userid), базы данных (dbid) и запроса (queryid). В случае отсутствия одного из параметров для него по умолчанию используется значение 0(неприменимо) и очищается статистика, соответствующая другим параметрам. Если никакой параметр не задан или все параметры имеют значение 0(неприменимо), очищается вся статистика. Если сбрасывается вся статистика в представлении pg_stat_statements, вместе с ней также очищается статистика в представлении pg_stat_statements_info. По умолчанию эту функцию могут выполнять только суперпользователи. Другим пользователям можно дать к ней доступ с помощью команды GRANT.

pg_stat_statements(showtext boolean) returns setof record

Представление pg_stat_statements определено на базе функции, которая также называется pg_stat_statements. Клиенты могут вызвать функцию pg_stat_statements напрямую, а также, указав showtext := false, получить результат без текста запроса (то есть выходной аргумент (OUT), соответствующий столбцу представления query, вернет NULL). Эта возможность предназначена для поддержки внешних инструментов, для которых может быть желательно избежать издержек, связанных с постоянным получением текстов запросов неопределенной длины. Такие инструменты могут кэшировать текст первого запроса, который они выявят для каждой записи самостоятельно, поскольку именно это и делает сама pg_stat_statements, а затем запрашивать тексты запросов только при необходимости. Так как сервер сохраняет тексты запросов в файле, этот подход может сократить объем физического ввода/вывода, порождаемого постоянной проверкой данных pg_stat_statements.

Параметры конфигурации

pg_stat_statements.max (integer)
Этот параметр задает максимальное количество операторов, отслеживаемых модулем (т. е. максимальное количество строк в представлении pg_stat_statements). Если выявляется больше различных операторов, чем заданное число, информация о наиболее редко выполняемых операторах сбрасывается. В представлении pg_stat_statements_info можно увидеть, сколько раз это происходило. Значение по умолчанию — 5000. Этот параметр можно задать только при запуске сервера.

pg_stat_statements.track (enum)
Этот параметр определяет, какие операторы будут подсчитываться модулем. Со значением top отслеживаются операторы верхнего уровня (те, которые выполняются непосредственно клиентами), со значением all также отслеживаются вложенные операторы (например операторы, вызываемые внутри функций), а со значением none сбор статистики по операторам выключается. Значение по умолчанию — top. Изменять этот параметр могут только суперпользователи.

pg_stat_statements.track_utility (boolean)
Этот параметр определяет, будет ли модуль отслеживать служебные команды. Служебными командами являются все команды, отличные от SELECT, INSERT, UPDATE и DELETE. Значение по умолчанию — on (включен). Изменять этот параметр могут только суперпользователи.

pg_stat_statements.track_planning (boolean)
Этот параметр определяет, будет ли модуль отслеживать операции планирования и их длительность. Включение этого параметра может вызвать значительное снижение производительности, особенно когда операторы с идентичной структурой запроса выполняются во множестве одновременных подключений, которые в итоге пытаются одновременно изменить одни и те же записи в pg_stat_statements. Значение по умолчанию — off (выключен). Изменять этот параметр могут только суперпользователи.

pg_stat_statements.save (boolean)
Этот параметр указывает, должна ли статистика операторов сохраняться после перезагрузок сервера. При значении off (выключен) статистика не сохраняется при остановке сервера и не перезагружается при его запуске. Значение по умолчанию — on (включен). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.

Этому модулю требуется дополнительная разделяемая память в объеме, пропорциональном pg_stat_statements.max. Обратите внимание, что эта память будет занята при загрузке модуля, даже если pg_stat_statements.track имеет значение none.

Эти параметры следует задавать в qhb.conf. Обычно использование может выглядеть так:

# qhb.conf
shared_preload_libraries = 'pg_stat_statements'

compute_query_id = on
pg_stat_statements.max = 10000
pg_stat_statements.track = all

Пример вывода

bench=# SELECT pg_stat_statements_reset();

$ pgbench -i bench
$ pgbench -c10 -t300 bench

bench=# \x
bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
               nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
          FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2
calls           | 3000
total_exec_time | 25565.855387
rows            | 3000
hit_percent     | 100.0000000000000000
-[ RECORD 2 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls           | 3000
total_exec_time | 20756.669379
rows            | 3000
hit_percent     | 100.0000000000000000
-[ RECORD 3 ]---+--------------------------------------------------​------------------
query           | copy pgbench_accounts from stdin
calls           | 1
total_exec_time | 291.865911
rows            | 100000
hit_percent     | 100.0000000000000000
-[ RECORD 4 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls           | 3000
total_exec_time | 271.232977
rows            | 3000
hit_percent     | 98.8454011741682975
-[ RECORD 5 ]---+--------------------------------------------------​------------------
query           | alter table pgbench_accounts add primary key (aid)
calls           | 1
total_exec_time | 160.588563
rows            | 0
hit_percent     | 100.0000000000000000

bench=# SELECT pg_stat_statements_reset(0,0,s.queryid) FROM pg_stat_statements AS s
            WHERE s.query = 'UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2';

bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
               nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
          FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls           | 3000
total_exec_time | 20756.669379
rows            | 3000
hit_percent     | 100.0000000000000000
-[ RECORD 2 ]---+--------------------------------------------------​------------------
query           | copy pgbench_accounts from stdin
calls           | 1
total_exec_time | 291.865911
rows            | 100000
hit_percent     | 100.0000000000000000
-[ RECORD 3 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls           | 3000
total_exec_time | 271.232977
rows            | 3000
hit_percent     | 98.8454011741682975
-[ RECORD 4 ]---+--------------------------------------------------​------------------
query           | alter table pgbench_accounts add primary key (aid)
calls           | 1
total_exec_time | 160.588563
rows            | 0
hit_percent     | 100.0000000000000000
-[ RECORD 5 ]---+--------------------------------------------------​------------------
query           | vacuum analyze pgbench_accounts
calls           | 1
total_exec_time | 136.448116
rows            | 0
hit_percent     | 99.9201915403032721

bench=# SELECT pg_stat_statements_reset(0,0,0);

bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
               nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
          FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------​---------------------------
query           | SELECT pg_stat_statements_reset(0,0,0)
calls           | 1
total_exec_time | 0.189497
rows            | 1
hit_percent     |
-[ RECORD 2 ]---+--------------------------------------------------​---------------------------
query           | SELECT query, calls, total_exec_time, rows, $1 * shared_blks_hit /          +
                |                nullif(shared_blks_hit + shared_blks_read, $2) AS hit_percent+
                |           FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT $3
calls           | 0
total_exec_time | 0
rows            | 0
hit_percent     |

Авторы

Такахиро Итагаки (Takahiro Itagaki), itagaki.takahiro@oss.ntt.co.jp. Нормализацию запросов добавил Питер Гейган (Peter Geoghegan), peter@2ndquadrant.com.

pgstattuple

Модуль pgstattuple предоставляет различные функции для получения статистики на уровне кортежей.

Поскольку эти функции возвращают подробную информацию на уровне страницы, доступ к ним по умолчанию ограничен. По умолчанию право EXECUTE для них имеет только роль pg_stat_scan_tables. Разумеется, суперпользователи могут обойти это ограничение. После установки этого расширения пользователи могут с помощью команд GRANT поменять права доступа к этим функциям и разрешить их выполнение другим пользователям. Однако вместо этого предпочтительнее будет добавить этих пользователей в роль pg_stat_scan_tables.

Функции

pgstattuple(regclass) returns record

Функция pgstattuple возвращает физическую длину отношения, процент неиспользуемых кортежей и другую информацию. Это может помочь пользователям принять решение о необходимости очистки. В аргументе передается имя целевого отношения (при необходимости дополненное схемой) или его OID. Например:

test=> SELECT * FROM pgstattuple('pg_catalog.pg_proc');
-[ RECORD 1 ]------+-------
table_len          | 458752
tuple_count        | 1470
tuple_len          | 438896
tuple_percent      | 95.67
dead_tuple_count   | 11
dead_tuple_len     | 3157
dead_tuple_percent | 0.69
free_space         | 8932
free_percent       | 1.95

Выходные столбцы описаны в Таблице 23.

Таблица 23. Выходные столбцы pgstattuple

Столбец | Тип | Описание
table_len | bigint | Физическая длина отношения в байтах
tuple_count | bigint | Количество активных кортежей
tuple_len | bigint | Общая длина активных кортежей в байтах
tuple_percent | float8 | Процент активных кортежей
dead_tuple_count | bigint | Количество неиспользуемых кортежей
dead_tuple_len | bigint | Общая длина неиспользуемых кортежей в байтах
dead_tuple_percent | float8 | Процент неиспользуемых кортежей
free_space | bigint | Общий объем свободного пространства в байтах
free_percent | float8 | Процент свободного пространства

Примечание
Значение table_len всегда будет больше суммы tuple_len, dead_tuple_len и free_space. Разница объясняется фиксированными издержками на страницу, внутристраничной таблицей указателей на кортежи и пропусками, добавляемыми для корректного выравнивания кортежей.

Функция pgstattuple получает блокировку отношения только для чтения. Поэтому ее результаты не отражают мгновенный снимок; на них будут влиять параллельные изменения.

Функция pgstattuple считает кортеж неиспользуемым, если HeapTupleSatisfiesDirty возвращает false.

pgstattuple(text) returns record

Равнозначна функции pgstattuple(regclass) за исключением того, что целевое отношение задается в текстовом виде. Эта функция пока что оставлена для обратной совместимости и в последующих выпусках будет считаться устаревшей.

pgstatindex(regclass) returns record

Функция pgstatindex возвращает запись, показывающую информацию об индексе B-дереве. Например:

test=> SELECT * FROM pgstatindex('pg_cast_oid_index');
-[ RECORD 1 ]------+------
version            | 2
tree_level         | 0
index_size         | 16384
root_block_no      | 1
internal_pages     | 0
leaf_pages         | 1
empty_pages        | 0
deleted_pages      | 0
avg_leaf_density   | 54.27
leaf_fragmentation | 0

Выходные столбцы:

Столбец | Тип | Описание
version | integer | Номер версии B-дерева
tree_level | integer | Уровень корневой страницы в дереве
index_size | bigint | Общий размер индекса в байтах
root_block_no | bigint | Расположение корневой страницы (0, если таковой нет)
internal_pages | bigint | Количество «внутренних» страниц (верхнего уровня)
leaf_pages | bigint | Количество страниц на уровне листьев
empty_pages | bigint | Количество пустых страниц
deleted_pages | bigint | Количество удаленных страниц
avg_leaf_density | float8 | Средняя плотность страниц на уровне листьев
leaf_fragmentation | float8 | Фрагментация страниц на уровне листьев

Сообщаемый размер индекса (index_size) обычно равен сумме internal_pages + leaf_pages + empty_pages + deleted_pages плюс одна страница, поскольку в него также включается метастраница индекса.

Как и в случае с pgstattuple, результаты собираются страница за страницей, и не следует ожидать, что они представляют мгновенный снимок всего индекса.

pgstatindex(text) returns record

Равнозначна функции pgstatindex(regclass) за исключением того, что целевой индекс задается в текстовом виде. Эта функция пока что оставлена для обратной совместимости и в последующих выпусках будет считаться устаревшей.

pgstatginindex(regclass) returns record

Функция pgstatginindex возвращает запись, показывающую информацию об индексе GIN. Например:

test=> SELECT * FROM pgstatginindex('test_gin_index');
-[ RECORD 1 ]--+--
version        | 1
pending_pages  | 0
pending_tuples | 0

Выходные столбцы:

Столбец | Тип | Описание
version | integer | Номер версии GIN
pending_pages | integer | Количество страниц в списке ждущих обработки
pending_tuples | bigint | Количество кортежей в списке ждущих обработки

pgstathashindex(regclass) returns record

Функция pgstathashindex возвращает запись, показывающую информацию о хеш-индексе. Например:

test=> SELECT * FROM pgstathashindex('con_hash_index');
-[ RECORD 1 ]--+-----------------
version        | 4
bucket_pages   | 33081
overflow_pages | 0
bitmap_pages   | 1
unused_pages   | 32455
live_items     | 10204006
dead_items     | 0
free_percent   | 61.8005949100872

Выходные столбцы:

Столбец | Тип | Описание
version | integer | Номер версии HASH
bucket_pages | bigint | Количество страниц с ячейками хеша
overflow_pages | bigint | Количество страниц переполнения
bitmap_pages | bigint | Количество страниц битовой карты
unused_pages | bigint | Количество неиспользованных страниц
live_items | bigint | Количество активных кортежей
dead_tuples | bigint | Количество неиспользуемых кортежей
free_percent | float | Процент свободного пространства

pg_relpages(regclass) returns bigint

Функция pg_relpages возвращает количество страниц в отношении.

pg_relpages(text) returns bigint

Равнозначна pg_relpages(regclass) за исключением того, что целевое отношение задается в текстовом виде. Эта функция пока что оставлена для обратной совместимости и в последующих выпусках будет считаться устаревшей.

pgstattuple_approx(regclass) returns record

Функция pgstattuple_approx представляет собой более быструю альтернативу pgstattuple, возвращающую приблизительные результаты В аргументе передается имя или OID целевого отношения. Например:

test=> SELECT * FROM pgstattuple_approx('pg_catalog.pg_proc'::regclass);
-[ RECORD 1 ]--------+-------
table_len            | 573440
scanned_percent      | 2
approx_tuple_count   | 2740
approx_tuple_len     | 561210
approx_tuple_percent | 97.87
dead_tuple_count     | 0
dead_tuple_len       | 0
dead_tuple_percent   | 0
approx_free_space    | 11996
approx_free_percent  | 2.09

Выходные столбцы описаны в Таблице 24.

В то время как pgstattuple всегда производит полное сканирование таблицы и возвращает точное количество активных и неиспользуемых кортежей (и их размер), а также точный объем свободного пространства, pgstattuple_approx пытается избежать полного сканирования таблицы и возвращает точную статистику только по неиспользуемым кортежам, а количество и размер активных кортежей и объем свободного пространства выдает приблизительные.

pgstattuple_approx делает это, пропуская страницы, в которых, согласно карте видимости, есть только видимые кортежи (если для страницы установлен соответствующий бит карты видимости, предполагается, что она не содержит неиспользуемые кортежи). Для таких страниц эта функция узнает объем свободного пространства из карты свободного пространства и предполагает, что остальное пространство на странице занято активными кортежами.

На страницах, которые нельзя пропустить, она сканирует каждый кортеж, записывая его наличие и размер в соответствующих счетчиках и подсчитывая свободное пространство на странице. В конце она оценивает общее количество активных кортежей, исходя из количества просканированных страниц и кортежей (тем же способом, каким VACUUM рассчитывает значение столбца pg_class.reltuples).

Таблица 24. Выходные столбцы pgstattuple_approx

Столбец | Тип | Описание
table_len | bigint | Физическая длина отношения в байтах (точная)
scanned_percent | float8 | Просканированный процент таблицы
approx_tuple_count | bigint | Количество активных кортежей (примерное)
approx_tuple_len | bigint | Общая длина активных кортежей в байтах (примерная)
approx_tuple_percent | float8 | Процент активных кортежей
dead_tuple_count | bigint | Количество неиспользуемых кортежей (точное)
dead_tuple_len | bigint | Общая длина неиспользуемых кортежей в байтах (точная)
dead_tuple_percent | float8 | Процент неиспользуемых кортежей
approx_free_space | bigint | Общий объем свободного пространства в байтах (примерный)
approx_free_percent | float8 | Процент свободного пространства

В показанном выше выводе показатели свободного пространства могут не соответствовать в точности выводу pgstattuple, потому что карта свободного пространства выдает правильное значение, но не гарантирует, что оно будет точным до байта.

Авторы

Тацуо Ишии (Tatsuo Ishii), Сатоши Нагаясу (Satoshi Nagayasu) и Абхиджит Менон-Сен (Abhijit Menon-Sen)

pg_surgery

Модуль pg_surgery предоставляет различные функции для проведения операций с поврежденными отношениями. Эти функции по своему замыслу небезопасны, и их применение может повредить вашу базу данных (или усугубить имеющиеся повреждения). Например, эти функции с легкостью могут быть использованы, чтобы нарушить соответствие таблицы и ее индексов, повредить ограничения UNIQUE или FOREIGN KEY или даже сделать видимыми кортежи, чтение которых вызовет отказ сервера баз данных. Эти функции следует использовать с большой осторожностью и только в крайнем случае.

Функции

heap_force_kill(regclass, tid[]) returns void

Функция heap_force_kill помечает «израсходованные» указатели строк как «мертвые», не проверяя кортежи. Эта функция предназначена для принудительного удаления кортежей, к которым нельзя обратиться. Например:

test=> SELECT * FROM t1 where ctid = '(0, 1)';
ERROR:  could not access status of transaction 4007513275
DETAIL:  Could not open file "pg_xact/0EED": No such file or directory.
-- ОШИБКА: не удалось получить доступ к состоянию транзакции 4007513275
-- ДЕТАЛИЗАЦИЯ: Не удалось открыть файл "pg_xact/0EED": нет такого файла или каталога

test=# SELECT heap_force_kill('t1'::regclass, ARRAY['(0, 1)']::tid[]);
 heap_force_kill
-----------------

(1 row)

test=# SELECT * FROM t1 where ctid = '(0, 1)';
(0 rows)

heap_force_freeze(regclass, tid[]) returns void

Функция heap_force_freeze помечает кортежи как замороженные, не проверяя их данные. Эта функция предназначена для получения доступа к кортежам, которые оказались недоступными из-за повреждения информации о видимости или не дают успешно очистить таблицу из-за повреждения этой информации. Например:

test=> VACUUM t1;
ERROR:  found xmin 507 from before relfrozenxid 515
CONTEXT:  while scanning block 0 of relation "public.t1"
-- ОШИБКА: xmin 507 обнаружен до relfrozenxid 515
-- КОНТЕКСТ: при сканировании блока 0 отношения "public.t1"

test=# SELECT ctid FROM t1 WHRER xmin = 507;
 ctid
-------
 (0,3)
(1 row)

test=# SELECT heap_force_freeze('t1'::regclass, ARRAY['(0, 3)']::tid[]);
 heap_force_freeze
-------------------

(1 row)

test=# SELECT ctid FROM t1 WHERE xmin = 2;
 ctid
-------
 (0,3)
(1 row)

Авторы

Ашутош Шарма (Ashutosh Sharma), ashu.coek88@gmail.com

pg_trgm

Модуль pg_trgm предоставляет функции и операторы для определения схожести буквенно-цифрового текста на основе соответствия триграмм, а также классы операторов индексов, поддерживающие быстрый поиск схожих строк.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Общее представление о триграммах (или триграфах)

Триграмма — это группа из трех последовательных символов, взятых из одной строки. Схожесть двух строк можно измерить, подсчитав количество триграмм, которые есть в обеих. Эта простая идея оказывается очень эффективной для измерения схожести слов на многих естественных языках.

Примечание
Извлекая триграммы из строки, pg_trgm игнорирует символы, не относящиеся к словам (не буквенно-цифровые). При определении набора триграмм, содержащихся в строке, считается, что перед каждым словом находятся два пробела, а после — один. Например, из строки «cat» выделяется набор триграмм: « c», « ca», «cat» и «at ». Набор триграмм из строки «foo|bar»: « f», « fo», «foo», «oo », « b», « ba», «bar» и «ar ».

Функции и операторы

Функции, предоставляемые модулем pg_trgm, перечислены в Таблице 25, а операторы — в Таблице 26.

Таблица 25. Функции pg_trgm

Функция

Описание

similarity ( text, text ) → real

Возвращает число, показывающее, насколько схожи два аргумента. Диапазон результата — от нуля (показывающего, что две строки полностью различны) до единицы (показывающей, что две строки идентичны).

show_trgm ( text ) → text[]

Возвращает массив всех триграмм в заданной строке. (На практике это редко бывает полезно, разве что для отладки.)

word_similarity ( text, text ) → real

Возвращает число, показывающее наибольшую степень схожести между набором триграмм в первой строке и любой непрерывной последовательностью упорядоченного набора триграмм во второй строке. Подробнее об этом рассказывается ниже.

strict_word_similarity ( text, text ) → real

Похожа на word_similarity, но подгоняет границы последовательности к границам слов. Поскольку триграммы не пересекают слова, эта функция фактически возвращает наибольшую степень схожести между первой строкой и любой непрерывной последовательностью слов во второй строке.

show_limit () → real

Возвращает текущий порог схожести, используемый оператором %. Это значение задает минимальную схожесть между двумя словами, при которой они считаются достаточно схожими, чтобы, к примеру, одно было ошибочным написанием другого. (Устаревшая функция; используйте вместо нее SHOW pg_trgm.similarity_threshold.)

set_limit ( real ) → real

Задает текущий порог схожести, используемый оператором %. Порог должен быть в диапазоне от 0 до 1 (по умолчанию 0.3). Возвращает то же значение, что было передано на вход. (Устаревшая функция; используйте вместо нее SET pg_trgm.similarity_threshold.)

Рассмотрим следующий пример:

# SELECT word_similarity('word', 'two words');
 word_similarity
-----------------
             0.8
(1 row)

Набор триграмм в первой строке: {" w"," wo","wor","ord","rd "}. Во второй строке упорядоченный набор триграмм: {" t"," tw","two","wo "," w"," wo","wor","ord","rds","ds "}. Наиболее схожая последовательность в упорядоченном наборе триграмм: {" w"," wo","wor","ord"}, а коэффициент схожести равен 0.8.

Эта функция возвращает значение, которое можно примерно воспринимать как наивысшую схожесть между первой строкой и любой подстрокой второй строки. Однако эта функция не добавляет пробелы к границам последовательности. Поэтому число дополнительных символов, находящихся во второй строке, не рассматривается, разве что для несоответствующих границ слов.

В то же время strict_word_similarity выбирает последовательность слов во второй строке. В показанном выше примере strict_word_similarity выбрала бы последовательность из одного слова 'words', которой соответствует набор триграмм {" w"," wo","wor","ord","rds","ds "}.

# SELECT strict_word_similarity('word', 'two words'), similarity('word', 'words');
 strict_word_similarity | similarity
------------------------+------------
               0.571429 |   0.571429
(1 row)

Таким образом, функция strict_word_similarity полезна для выявления схожести целых слов, тогда как word_similarity больше подходит для выявления схожести частей слов.

Таблица 26. Операторы pg_trgm

Оператор

Описание

text % text → boolean

Возвращает true, если схожесть его аргументов выше текущего порога, заданного параметром pg_trgm.similarity_threshold.

text <% text → boolean

Возвращает true, если схожесть между набором триграмм в первом аргументе и непрерывной последовательностью упорядоченного набора триграмм во втором аргументе выше текущего порога схожести слов, заданного параметром pg_trgm.word_similarity_threshold.

text %> text → boolean

Коммутатор оператора <%.

text <<% text → boolean

Возвращает true, если в его втором аргументе имеется непрерывная последовательность упорядоченного набора триграмм, соответствующего границам слов, и его схожесть с набором триграмм первого аргумента выше текущего порога схожести строго слов, заданного параметром pg_trgm.strict_word_similarity_threshold.

text %>> text → boolean

Коммутатор оператора <<%.

text <-> text → real

Возвращает «расстояние» между аргументами, то есть один минус значение similarity().

text <<-> text → real

Возвращает «расстояние» между аргументами, то есть один минус значение word_similarity().

text <->> text → real

Коммутатор оператора <<->.

text <<<-> text → real

Возвращает «расстояние» между аргументами, то есть один минус значение strict_word_similarity().

text <->>> text → real

Коммутатор оператора <<<->.

Параметры GUC

pg_trgm.similarity_threshold (real)
Задает текущий порог схожести, используемый оператором %. Порог должен быть в диапазоне от 0 до 1 (по умолчанию 0.3).

pg_trgm.word_similarity_threshold (real)
Задает текущий порог схожести слов, используемый операторами <% и %>. Порог должен быть в диапазоне от 0 до 1 (по умолчанию 0.6).

pg_trgm.strict_word_similarity_threshold (real)
Задает текущий порог схожести строго слов, используемый операторами <<% и %>>. Порог должен быть в диапазоне от 0 до 1 (по умолчанию 0.5).

Поддержка индексов

Модуль pg_trgm предоставляет классы операторов индексов GiST и GIN, которые позволяют создавать индекс по текстовым столбцам для очень быстрого поиска по схожести. Эти типы индексов поддерживают вышеописанные операторы схожести и дополнительно поддерживают поиск по индексу на основе триграмм для запросов с LIKE, ILIKE, ~, ~* и =. Операторы неравенства не поддерживаются. Обратите внимание, что для операторов равенства эти индексы могут быть не так эффективны, как обычные индексы B-деревья.

Пример:

CREATE TABLE test_trgm (t text);
CREATE INDEX trgm_idx ON test_trgm USING GIST (t gist_trgm_ops);

или

CREATE INDEX trgm_idx ON test_trgm USING GIN (t gin_trgm_ops);

Класс операторов GiST gist_trgm_ops аппроксимирует набор триграмм в виде сигнатуры битовой карты. Его необязательный целочисленный параметр siglen определяет длину сигнатуры в байтах. Значение по умолчанию — 12 байт. Допустима длина сигнатуры в пределах от 1 до 2024 байт. При увеличении размера сигнатуры поиск работает точнее (сканируется меньшая область индекса и меньше страниц кучи), но сам индекс становится больше.

Пример создания такого индекса с длиной сигнатуры 32 байта:

CREATE INDEX trgm_idx ON test_trgm USING GIST (t gist_trgm_ops(siglen=32));

На этот момент у вас будет индекс по столбцу t, который можно использовать для поиска по схожести. Типичный запрос:

SELECT t, similarity(t, 'слово') AS sml
  FROM test_trgm
  WHERE t % 'слово'
  ORDER BY sml DESC, t;

Он возвратит все значения в текстовом столбце, которые достаточно схожи со строкой слово, отсортированные от наиболее к наименее схожим. Благодаря использованию индекса эта операция будет быстрой даже с очень большими наборами данных.

Другой вариант предыдущего запроса:

SELECT t, t <-> 'слово' AS dist
  FROM test_trgm
  ORDER BY dist LIMIT 10;

Он может быть довольно эффективно выполнен с индексами GiST, но не GIN. Обычно он выигрышнее первого варианта, когда требуется получить лишь небольшое число близких совпадений.

Также можно использовать индекс по столбцу t для оценки условной и строгой схожести слов. Типичные запросы:

SELECT t, word_similarity('слово', t) AS sml
  FROM test_trgm
  WHERE 'слово' <% t
  ORDER BY sml DESC, t;

и

SELECT t, strict_word_similarity('слово', t) AS sml
  FROM test_trgm
  WHERE 'слово' <<% t
  ORDER BY sml DESC, t;

Он возвратит все значения в текстовом столбце, для которых существует непрерывная последовательность в упорядоченном наборе триграмм, достаточно схожая с набором триграмм строки слово, отсортированные от наиболее к наименее схожим. Благодаря использованию индекса эта операция будет быстрой даже с очень большими наборами данных.

Другие возможные варианты предыдущих запросов:

SELECT t, 'слово' <<-> t AS dist
  FROM test_trgm
  ORDER BY dist LIMIT 10;

и

SELECT t, 'слово' <<<-> t AS dist
  FROM test_trgm
  ORDER BY dist LIMIT 10;

Они могут быть довольно эффективно выполнены с индексами GiST, но не GIN.

Эти типы индексов также поддерживают поиск с операторами LIKE и ILIKE, например:

SELECT * FROM test_trgm WHERE t LIKE '%foo%bar';

При таком поиске по индексу сначала из искомой строки извлекаются триграммы, а затем они ищутся в индексе. Чем больше триграмм оказывается в искомой строке, тем более эффективен поиск по индексу. В отличие от поиска по B-дереву, искомой строке не обязательно быть привязанной к левому краю.

Кроме того, эти типы индексов поддерживают индексный поиск по совпадению регулярных выражений (операторы ~ и ~*), например:

SELECT * FROM test_trgm WHERE t ~ '(foo|bar)';

При таком поиске по индексу сначала из регулярного выражения извлекаются триграммы, а затем они ищутся в индексе. Чем больше триграмм можно извлечь из регулярного выражения, тем более эффективен поиск по индексу. В отличие от поиска по B-дереву, искомой строке не обязательно быть привязанной к левому краю.

Относительно поиска с LIKE или по регулярному выражению, имейте в виду, что при отсутствии триграмм в шаблоне поиск сводится к полному сканированию индекса.

Выбор между индексами GiST и GIN зависит от относительных характеристик производительности GiST и GIN, которые здесь не рассматриваются.

Интеграция с текстовым поиском

Сопоставление триграмм — очень полезный инструмент при использовании в сочетании с полнотекстовым индексом. В частности, это может помочь распознать слова, написанные неправильно, которые нельзя будет сопоставить непосредственно механизмом полнотекстового поиска.

Первый этап — создание вспомогательной таблицы, содержащей все уникальные слова в документах:

CREATE TABLE words AS SELECT word FROM
        ts_stat('SELECT to_tsvector(''simple'', bodytext) FROM documents');

где documents — это таблица с текстовым полем bodytext, по которому нам нужно выполнить поиск. Причина использования с функцией to_tsvector конфигурации simple вместо конфигурации для определенного языка состоит в том, что нам нужен список исходных (необработанных стеммером) слов.

Затем создадим индекс триграмм по столбцу со словами:

CREATE INDEX words_idx ON words USING GIN (word gin_trgm_ops);

Теперь можно использовать запрос SELECT, подобный показанному в предыдущем примере, чтобы предлагать варианты исправлений слов, введенных пользователем с ошибками. Будет полезно дополнительно проверить, что выбранные слова также имеют длину, схожую с длиной неправильно написанного слова.

Примечание
Поскольку таблица words была создана как отдельная статическая таблица, ее нужно периодически пересоздавать, чтобы она достаточно хорошо соответствовала коллекции документов. Постоянно поддерживать ее в полностью актуальном состоянии обычно нет необходимости.

Ссылки

Сайт разработки GiST http://www.sai.msu.su/~megera/postgres/gist/

Сайт разработки Tsearch2 http://www.sai.msu.su/~megera/postgres/gist/tsearch/V2/

Авторы

Олег Бартунов (oleg@sai.msu.su), Москва, Московский Государственный Университет им. М. В. Ломоносова, Россия

Федор Сигаев (teodor@sigaev.ru), Москва, ООО «Дельта-Софт», Россия

Александр Коротков (a.korotkov@postgrespro.ru), Москва, Postgres Professional, Россия

Документация: Кристофер Кингс-Линн (Christopher Kings-Lynne)

Разработку этого модуля спонсировало ООО «Дельта-Софт», г. Москва, Россия.

pg_visibility

Модуль pg_visibility предоставляет средства для исследования карты видимости (visibility map, VM) и информации о видимости на уровне страниц для таблицы. Кроме того, он предоставляет функции для проверки целостности карты видимости и ее принудительного пересоздания.

Для хранения информации о видимости на уровне страниц используется три различных бита. Бит полной видимости в карте видимости означает, что все кортежи в соответствующей странице отношения являются видимыми для всех текущих и будущих транзакций. Бит полной заморозки в карте видимости означает, что все кортежи в этой странице являются замороженными; то есть никакой будущей операции очистки не понадобится модифицировать эту страницу, пока в ней не будет добавлен, изменен, удален или заблокирован кортеж. Бит PD_ALL_VISIBLE в заголовке страницы имеет то же значение, что и бит полной видимости в карте видимости, но он хранится в самой странице данных, а не в отдельной структуре данных. В большинстве случаев эти два бита будут согласованы, но бит полной видимости в странице иногда может быть задан, тогда как в карте видимости он сбрасывается при восстановлении после сбоя. Полученные значения также могут расходиться вследствие изменения, происходящего в промежутке между проверкой pg_visibility карты видимости и страницы данных. Кроме того, эти биты могут различаться из-за событий, приводящих к повреждению данных.

Функции, показывающие информацию о битах PD_ALL_VISIBLE, куда более затратны, нежели те, что обращаются только к карте видимости, так как они должны читать блоки данных отношения, а не только карту видимости (которая гораздо меньше). Функции, которые проверяют блоки данных отношения, тоже затратны.

Функции

pg_visibility_map(relation regclass, blkno bigint, all_visible OUT boolean, all_frozen OUT boolean) returns record

Возвращает биты полной видимости и полной заморозки в карте видимости для указанного блока указанного отношения.

pg_visibility(relation regclass, blkno bigint, all_visible OUT boolean, all_frozen OUT boolean, pd_all_visible OUT boolean) returns record

Возвращает биты полной видимости и полной заморозки в карте видимости для указанного блока указанного отношения, а также бит PD_ALL_VISIBLE этого блока.

pg_visibility_map(relation regclass, blkno OUT bigint, all_visible OUT boolean, all_frozen OUT boolean) returns setof record

Возвращает биты полной видимости и полной заморозки в карте видимости для всех блоков указанного отношения.

pg_visibility(relation regclass, blkno OUT bigint, all_visible OUT boolean, all_frozen OUT boolean, pd_all_visible OUT boolean) returns setof record

Возвращает биты полной видимости и полной заморозки в карте видимости для всех блоков указанного отношения, а также бит PD_ALL_VISIBLE каждого блока.

pg_visibility_map_summary(relation regclass, all_visible OUT bigint, all_frozen OUT bigint) returns record

Возвращает количество полностью видимых страниц и полностью замороженных страниц в отношении, согласно карте видимости.

pg_check_frozen(relation regclass, t_ctid OUT tid) returns setof tid

Возвращает идентификаторы TID незамороженных кортежей, хранящихся в страницах, помеченных как полностью замороженные в карте видимости. Если эти функции возвращают непустое множество TID, карта видимости повреждена.

pg_check_visible(relation regclass, t_ctid OUT tid) returns setof tid

Возвращает идентификаторы TID не полностью видимых кортежей, хранящихся в страницах, помеченных как полностью видимые в карте видимости. Если эти функции возвращают непустое множество TID, карта видимости повреждена.

pg_truncate_visibility_map(relation regclass) returns void

Опустошает карту видимости для указанного отношения. Эта функция полезна, если вы полагаете, что карта видимости для этого отношения повреждена, и хотите принудительно ее восстановить. Первая же команда VACUUM, выполненная с заданным отношением после выполнения этой функции, просканирует все страницы в этом отношении и воссоздаст карту видимости. (Пока это не произойдет, запросы будут обрабатывать карту видимости как содержащую одни нули.)

По умолчанию эти функции разрешено выполнять только суперпользователям и членам роли pg_stat_scan_tables, за исключением pg_truncate_visibility_map(relation regclass), которую могут выполнять только суперпользователи.

Автор

Роберт Хаас (Robert Haas), rhaas@postgresql.org

postgres_fdw

Модуль postgres_fdw предоставляет обертку сторонних данных postgres_fdw, которую можно использовать для обращения к данным, хранящимся на внешних серверах QHB.

Функциональность, обеспечиваемая этим модулем, во многом пересекается с функциональностью старого модуля dblink. Однако postgres_fdw предоставляет более прозрачный и стандартизированный синтаксис для обращения к удаленным таблицам и во многих случаях может дать лучшую производительность.

Для подготовки к удаленному обращению через postgres_fdw:

Установите расширение postgres_fdw с помощью команды CREATE EXTENSION.

Создайте объект стороннего сервера с помощью команды CREATE SERVER, чтобы он представлял удаленную базу данных, к которой вы хотите подключиться (по одному для каждой). Задайте свойства подключения, за исключением user и password, в параметрах объекта сервера.

Создайте сопоставление пользователей с помощью команды CREATE USER MAPPING для каждого пользователя базы данных, которому нужен доступ к какому-либо удаленному серверу. Укажите имя и пароль удаленного пользователя в параметрах user и password сопоставления.

Создайте стороннюю таблицу с помощью команды CREATE FOREIGN TABLE или IMPORT FOREIGN SCHEMA для каждой удаленной таблицы, к которой вы хотите обращаться. Столбцы сторонней таблицы должны соответствовать столбцам ссылочной удаленной таблицы. Однако можно использовать имена таблиц и/или столбцов, отличные от удаленных, если указать правильные удаленные имена в параметрах объекта сторонней таблицы.

Теперь для обращения к данным, хранящимся в нижележащей удаленной таблице, вам нужно только выполнять SELECT из сторонней таблицы. Вы также можете изменять удаленную таблицу, используя INSERT, UPDATE, DELETE или TRUNCATE. (Естественно, удаленный пользователь, которого вы указали в сопоставлении, должен иметь необходимые права для этого.)

Обратите внимание, что при чтении или изменении удаленной таблицы указание параметра ONLY в командах SELECT, UPDATE, DELETE или TRUNCATE не действует.

Обратите внимание, что в настоящее время в postgres_fdw не поддерживаются операторы INSERT с предложением ON CONFLICT DO UPDATE. Однако предложение ON CONFLICT DO NOTHING поддерживается при опущенном указании для извлечения уникального индекса. Также обратите внимание, что postgres_fdw поддерживает перемещение строк, вызываемое командами UPDATE, выполненными для партиционированных таблиц, однако в настоящее время это не работает в случаях, когда удаленная партиция, выбранная для добавления перемещаемой строки, также является целевой партицией для UPDATE и будет изменена в другой части той же команды.

Как правило рекомендуется объявлять столбцы сторонней таблицы с точно такими же типами данных и правилами сортировки (если применимо), как у ссылочных столбцов удаленной таблицы. Хотя postgres_fdw в настоящее время довольно лоялен к преобразованиям типов данных при необходимости, но когда типы или правила сортировки не совпадают, возможны неожиданные семантические аномалии из-за того, что удаленный сервер обрабатывает условия запроса не так, как локальный.

Обратите внимание, что стороннюю таблицу можно объявить с меньшим количеством или с другим порядком столбцов, чем в нижележащей удаленной таблице. Сопоставление столбцов удаленной таблицы производится по имени, а не по позиции.

Параметры обертки сторонних данных для postgres_fdw

Параметры подключения

Для стороннего сервера, настраиваемого с помощью обертки сторонних данных postgres_fdw, можно задать те же параметры, что принимает libpq в строках подключения, как описано в подразделе Ключевые слова-параметры, за исключением следующих параметров, которые не допускаются или обрабатываются по-другому:

user, password и sslpassword (их следует задавать в сопоставлениях пользователей или использовать файл служб)

client_encoding (автоматически принимается равной кодировке локального сервера)

fallback_application_name (значение всегда postgres_fdw)

sslkey и sslcert — могут находиться в свойствах соединения и/или сопоставления пользователя. Если задаются и там, и там, сопоставление пользователя переопределяет свойства соединения.

Создавать или изменять сопоставления пользователей с установленным параметром sslcert или sslkey могут только суперпользователи.

Подключаться к сторонним серверам без аутентификации по паролю могут только суперпользователи, поэтому в сопоставлениях для обычных пользователей всегда нужно задавать пароль (параметр password).

Суперпользователь может переопределить эту проверку на уровне сопоставления пользователя, установив параметр password_required 'false', например:

ALTER USER MAPPING FOR some_non_superuser SERVER loopback_nopw
OPTIONS (ADD password_required 'false');

Чтобы не позволить недоверенным пользователям злоупотреблять правами аутентификации пользователя, от имени которого работает сервер qhb, чтобы повысить свои права до суперпользователя, изменять этот параметр в сопоставлении пользователя может только суперпользователь.

Необходимо позаботиться о том, чтобы через установленное сопоставление пользователь не мог подключиться к другой базе как суперпользователь, воспользовавшись уязвимостями CVE-2007-3278 и CVE-2007-6601. Не задавайте password_required=false для роли public. Учтите, что сопоставляемый пользователь имеет возможность использовать любые клиентские сертификаты, файлы .pgpass, .pg_service.conf и т. п. в домашнем каталоге системного пользователя, от имени которого работает сервер qhb. Они также могут использовать любое отношение доверия, установленное режимами аутентификации, например peer или ident.

Параметры имени объекта

Эти параметры можно применять для управления тем, как на удаленный сервер QHB будут передаваться имена, использующиеся в операторах SQL. Эти параметры нужны, когда сторонняя таблица создается с именами, отличными от имен нижележащей удаленной таблицы.

schema_name
Этот параметр, который можно указывать для сторонней таблицы, задает имя схемы для обращения к этой таблице на удаленном сервере. Если этот параметр опущен, используется схема сторонней таблицы.

table_name
Этот параметр, который можно указывать для сторонней таблицы, задает имя таблицы для обращения к этой таблице на удаленном сервере. Если этот параметр опущен, используется имя сторонней таблицы.

column_name
Этот параметр, который можно указывать для столбца сторонней таблицы, задает имя столбца для обращения к этому столбцу на удаленном сервере. Если этот параметр опущен, используется исходное имя столбца.

Параметры оценки стоимости

Обертка postgres_fdw получает удаленные данные, выполняя запросы на удаленных серверах, поэтому в идеале оценочная стоимость сканирования сторонней таблицы должна равняться стоимости выполнения на удаленном сервере плюс некоторые издержки на обмен данными. Самый надежный способ получить такую оценку — запросить стоимость у удаленного сервера, а затем добавить издержки — но для простых запросов может быть невыгодно передавать дополнительный запрос, чтобы просто получить оценку стоимости. Поэтому postgres_fdw предоставляет следующие параметры, позволяющие управлять вычислением оценки стоимости:

use_remote_estimate
Этот параметр, который можно указывать для сторонней таблицы или стороннего сервера, определяет, будет ли postgres_fdw выполнять удаленно команды EXPLAIN для получения оценок стоимости. Параметр, заданный для сторонней таблицы, переопределяет параметр сервера, но только для этой таблицы. Значение по умолчанию — false (выключен).

fdw_startup_cost
Этот параметр, который можно указывать для стороннего сервера, является значением с плавающей запятой, которое добавляется к оценочной стоимости запуска для любого сканирования сторонней таблицы на этом сервере. Он отображает дополнительные издержки на установление подключения, разбор и планирование запроса на удаленной стороне и т. п. Значение по умолчанию — 100.

fdw_tuple_cost
Этот параметр, который можно указывать для стороннего сервера, является значением с плавающей запятой, которое используется как дополнительная стоимость чтения одного кортежа при сканировании сторонней таблицы на этом сервере. Он отображает дополнительные издержки на передачу данных между серверами. Это число можно увеличить или уменьшить, чтобы отразить большую или меньшую сетевую задержку при взаимодействии с удаленным сервером. Значение по умолчанию — 0.01.

Когда параметр use_remote_estimate равен true (включен), postgres_fdw получает количество строк и оценки стоимости с удаленного сервера, а затем добавляет к оценке стоимости fdw_startup_cost и fdw_tuple_cost. Когда параметр use_remote_estimate равен false, postgres_fdw рассчитывает количество строк и оценку стоимости локально, а затем добавляет к оценке стоимости fdw_startup_cost и fdw_tuple_cost. Скорее всего, эта локальная оценка будет достаточно точной только при наличии локальных копий статистики удаленных таблиц. Обновить локальную статистику можно, выполнив для сторонней таблицы команду ANALYZE; при этом будет просканирована удаленная таблица, а затем по результату будет вычислена и сохранена статистика как для локальной таблицы. Локальное хранение статистики может быть полезно для сокращения издержек планирования запросов для удаленной таблицы — но если удаленная таблица часто меняется, локальная статистика быстро устареет.

Параметры удаленного выполнения

По умолчанию на удаленном сервере будут обрабатываться только предложения WHERE, использующие встроенные операторы и функции. Предложения, содержащие вызовы не встроенных функций, проверяются локально после извлечения строк. Если же такие функции доступны на удаленном сервере и можно рассчитывать, что они дадут те же результаты, что и локально, производительность можно увеличить, передавая и такие предложения WHERE для удаленного выполнения. Этим поведением позволяет управлять следующий параметр:

extensions
Этот параметр является списком имен расширений QHB через запятую, которые установлены и имеют совместимые версии как на локальном, так и на удаленном сервере. Относящиеся к перечисленным расширениям и при этом неизменяемые (immutable) функции и операторы будут считаться поддающимися передаче удаленному серверу. Этот параметр можно задать только для сторонних серверов, но не для таблиц.
При использовании параметра extensions пользователь сам отвечает за то, чтобы перечисленные расширения существовали и вели себя одинаково на локальном и удаленном серверах. Иначе удаленные запросы могут выдавать ошибки или неожиданное поведение.

fetch_size
Этот параметр задает число строк, которое должна получить postgres_fdw в каждой операции выборки. Его можно задать для сторонней таблицы или стороннего сервера. Параметр, указанный для таблицы, переопределяет параметр сервера. Значение по умолчанию — 100.

batch_size
Этот параметр задает число строк, которое должна вставлять postgres_fdw в каждой операции вставки. Его можно задать для сторонней таблицы или стороннего сервера. Параметр, указанный для таблицы, переопределяет параметр сервера. Значение по умолчанию — 1.
Обратите внимание, что фактическое число строк, вставляемых postgres_fdw в одной операции, зависит от числа столбцов и предоставленного значения batch_size. Вставка пакета выполняется в одном запросе, а протокол libpq (который postgres_fdw использует для подключения к удаленному серверу) ограничивает количество параметров одного запроса числом 65535. Когда количество столбцов, умноженное на batch_size, превышает этот предел, batch_size корректируется во избежание ошибки.

Параметры асинхронного выполнения

Модуль postgres_fdw поддерживает асинхронное выполнение, когда для улучшения производительности несколько частей узла Append выполняются одновременно, а не последовательно. Таким выполнением управляет следующий параметр:

async_capable
Этот параметр определяет, будет ли postgres_fdw допускать одновременное сканирование сторонних таблиц для асинхронного выполнения. Его можно задать для сторонней таблицы или стороннего сервера. Параметр, определенный на уровне таблицы, переопределяет параметр уровня сервера. Значение по умолчанию — false (запрещено).
Для гарантии того, что возвращаемые со стороннего сервера данные являются согласованными, postgres_fdw открывает только одно соединение для каждого стороннего сервера и выполняет все запросы к нему последовательно, даже если задействовано несколько сторонних таблиц (несколько соединений будет открыто, только если для этих таблиц используются разные сопоставления пользователей). Возможно, в таком случае для увеличения производительности стоит выключить этот параметр, чтобы убрать издержки, связанные с асинхронным выполнением запросов.
Асинхронное выполнение применяется, даже когда узел Append содержит как подпланы, выполняемые синхронно, так и подпланы, выполняемые асинхронно. В таких случаях, если асинхронные подпланы обрабатываются postgres_fdw, кортежи этих подпланов не возвращаются, пока хотя бы один синхронный подплан не вернет все кортежи, так как этот подплан выполняется, пока асинхронные подпланы ожидают результатов асинхронных запросов, переданных сторонним серверам. Это поведение может измениться в будущих версиях.

Параметры изменения данных

По умолчанию все сторонние таблицы, доступные через обертку postgres_fdw, считаются изменяемыми. Это можно переопределить с помощью следующего параметра:

updatable
Этот параметр, определяет, будет ли postgres_fdw допускать изменения в сторонних таблицах посредством команд INSERT, UPDATE и DELETE. Его можно задать для сторонней таблицы или стороннего сервера. Параметр, определенный на уровне таблицы, переопределяет параметр уровня сервера. Значение по умолчанию — true (изменения разрешены).
Разумеется, если удаленная таблица на самом деле не изменяемая, все равно произойдет ошибка. Использование этого параметра в первую очередь позволяет выдать ошибку локально, не посылая запрос удаленному серверу. Однако обратите внимание, что представление information_schema будет показывать, что сторонняя таблица postgres_fdw является изменяемой (или нет), согласно значению этого параметра, не проверяя это на удаленном сервере.

Параметры опустошения таблиц

По умолчанию все сторонние таблицы, доступные через обертку postgres_fdw, считаются опустошаемыми. Это можно переопределить с помощью следующего параметра:

truncatable Этот параметр, определяет, будет ли postgres_fdw допускать опустошение сторонних таблиц посредством команды TRUNCATE. Его можно задать для сторонней таблицы или стороннего сервера. Параметр, определенный на уровне таблицы, переопределяет параметр уровня сервера. Значение по умолчанию — true (опустошение разрешено).
Разумеется, если удаленная таблица на самом деле не опустошаемая, все равно произойдет ошибка. Использование этого параметра в первую очередь позволяет выдать ошибку локально, не посылая запрос удаленному серверу.

Параметры импорта

Обертка postgres_fdw способна импортировать определения сторонних таблиц с помощью команды IMPORT FOREIGN SCHEMA. Эта команда создает на локальном сервере определения сторонних таблиц, которые соответствуют таблицам или представлениям, находящимся на удаленном сервере. Если импортируемые удаленные таблицы содержат столбцы пользовательских типов данных, на локальном сервере должны быть совместимые типы с теми же именами.

Поведение операции импорта можно настроить посредством следующих параметров (задаваемых в команде IMPORT FOREIGN SCHEMA):

import_collate
Этот параметр определяет, будут ли в определениях сторонних таблиц, импортируемых со стороннего сервера, включаться правила сортировки столбцов (параметр COLLATE). Значение по умолчанию — true (включен). Вам может понадобиться выключить его, если на удаленном сервере набор имен правил сортировки отличается от локального, что скорее всего произойдет, если удаленный сервер работает в другой операционной системе. Однако при отключении крайне велик риск, что правила сортировки в столбцах импортируемых таблиц не совпадут с правилами для нижележащих данных, что приведет к аномальному поведению запросов.
Даже когда этот параметр равен true, импортировать столбцы с правилом сортировки, выбираемом на удаленном сервере по умолчанию, может быть рискованно. Эти столбцы будут импортироваться со свойством COLLATE "default", в результате чего будет применяться правило сортировки, выбираемое по умолчанию на локальном сервере, которое может отличаться от выбираемого на удаленном.

import_default
Этот параметр определяет, будут ли в определениях сторонних таблиц, импортируемых со стороннего сервера, включаться заданные для столбцов выражения по умолчанию (DEFAULT). Значение по умолчанию — false (выключен). Если вы включите этот параметр, опасайтесь выражений по умолчанию, которые могут вычисляться на локальном сервере не так, как на удаленном; к примеру, nextval() является частым источником проблем. Если в импортируемом выражении по умолчанию используются функции или операторы, несуществующие локально, команда IMPORT в целом завершится неудачно.

import_generated
Этот параметр определяет, будут ли в определениях сторонних таблиц, импортируемых со стороннего сервера, включаться заданные для столбцов генерирующие выражения (GENERATED). Значение по умолчанию — true (включен). Если в импортируемом генерирующем выражении используются функции или операторы, несуществующие локально, команда IMPORT в целом завершится неудачно.

import_not_null
Этот параметр определяет, будут ли в определениях сторонних таблиц, импортируемых со стороннего сервера, включаться заданные для столбцов ограничения NOT NULL. Значение по умолчанию — true (включен).

Обратите внимание, что ограничения, отличные от NOT NULL, из удаленных таблиц никогда импортироваться не будут. Хотя QHB и поддерживает проверочные ограничения для сторонних таблиц, средства для их автоматического импорта отсутствуют из-за риска, что выражение ограничения на локальном и удаленном серверах может вычисляться по-разному. Любая такая несогласованность в поведении проверочных ограничений может привести к сложно выявляемым ошибкам в оптимизации запросов. Поэтому если вы хотите импортировать проверочные ограничения, вам следует сделать это вручную и нужно будет внимательно проверить семантику каждого. Более подробную информацию об интерпретации проверочных ограничений для сторонних таблиц см. в CREATE FOREIGN TABLE.

Таблицы или сторонние таблицы, являющиеся партициями некоторой другой таблицы, импортируются, только когда они явно указаны в предложении LIMIT TO. В противном случае они автоматически исключаются из команды IMPORT FOREIGN SCHEMA. Поскольку все данные могут быть доступны через корневую партиционированную таблицу в иерархии партиционирования, должно быть достаточно импортировать только партиционированные таблицы, чтобы получить доступ ко всем данным, не создавая лишние объекты.

Параметры управления соединением

По умолчанию все соединения, устанавливаемые postgres_fdw со сторонними серверами, остаются открытыми в локальном сеансе для повторного использования.

keep_connections
Этот параметр определяет, будет ли postgres_fdw оставлять открытыми соединения со сторонним сервером, чтобы последующие запросы могли повторно их использовать. Этот параметр можно задать только для стороннего сервера. Значение по умолчанию — on (включен). При значении off все соединения к этому стороннему серверу будут закрываться в конце каждой транзакции.

Функции

postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record

Эта функция возвращает имена всех сторонних серверов, с которыми postgres_fdw установила соединения в локальном сеансе. Также она возвращает признак допустимого соединения. Признак false возвращается, если это соединение к стороннему серверу используется в текущей локальной транзакции, но определение стороннего сервера или сопоставление пользователя были изменены или удалены (обратите внимание, что если определение сервера удалено, имя сервера в записи недопустимого соединения будет NULL). Такое недопустимое соединение будет закрыто в конце этой транзакции. Во всех остальных случаях возвращается true (допустимое соединение). Если открытых соединений нет, никакая запись не возвращается. Пример использования этой функции:

qhb=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
 server_name | valid
-------------+-------
 loopback1   | t
 loopback2   | f

postgres_fdw_disconnect(server_name text) returns boolean

Эта функция закрывает открытые соединения, установленные postgres_fdw со сторонним сервером с заданным именем в локальном сеансе. Обратите внимание, что с данным сервером может быть установлено несколько соединений, использующих разные сопоставления пользователей. Если какие-либо соединения используются в текущей локальной транзакции, они не разрываются, о чем выдаются предупреждающие сообщения. Эта функция возвращает true, если закрывает хотя бы одно соединение; в противном случае возвращается false. Если сторонний сервер с заданным именем не найден, выдается ошибка. Пример использования этой функции:

qhb=# SELECT postgres_fdw_disconnect('loopback1');
 postgres_fdw_disconnect
-------------------------
 t

postgres_fdw_disconnect_all() returns boolean

Эта функция закрывает все открытые соединения, установленные postgres_fdw со сторонними серверами в локальном сеансе. Если какие-либо соединения используются в текущей локальной транзакции, они не разрываются, о чем выдаются предупреждающие сообщения. Эта функция возвращает true, если закрывает хотя бы одно соединение; в противном случае возвращается false. Пример использования этой функции:

qhb=# SELECT postgres_fdw_disconnect_all();
 postgres_fdw_disconnect_all
-----------------------------
 t

Управление соединением

Модуль postgres_fdw устанавливает соединение со сторонним сервером при первом запросе, использующем стороннюю таблицу, связанную со сторонним сервером. По умолчанию это соединение сохраняется и повторно используется для последующих запросов в том же сеансе. Этим поведением можно управлять с помощью параметра стороннего сервера keep_connections. Если для обращения к стороннему серверу задействуется несколько пользователей (сопоставлений пользователей), для каждого сопоставления пользователей устанавливается отдельное соединение.

При изменении или удалении определения стороннего сервера или сопоставления пользователя, связанные с ними соединения закрываются. Но обратите внимание, что если какие-либо соединения используются в текущей локальной транзакции, они сохраняются до конца этой транзакции. Когда закрытые соединения понадобятся для будущих запросов, использующих сторонние таблицы, они будут установлены заново.

Установленное соединение со сторонним сервером по умолчанию сохраняется до конца локального или соответствующего удаленного сеанса. Чтобы явно закрыть соединение, можно отключить параметр стороннего сервера keep_connections или воспользоваться функциями postgres_fdw_disconnect и postgres_fdw_disconnect_all. Например, эти функции полезны тем, что закрывают уже ненужные соединения, тем самым освобождая подключения на стороннем сервере.

Управление транзакциями

В процессе выполнения запроса, содержащего ссылки на какие-либо удаленные таблицы на стороннем сервере, postgres_fdw открывает транзакцию на удаленном сервере, если еще не была открыта такая транзакция, соответствующая текущей локальной транзакции. Удаленная транзакция фиксируется или прерывается, когда фиксируется или прерывается локальная транзакция. Подобным образом (созданием соответствующих удаленных точек) реализуется и управление точками сохранения.

Для удаленной транзакции используется уровень изоляции SERIALIZABLE, когда локальная транзакция открыта с уровнем SERIALIZABLE; в противном случае применяется уровень REPEATABLE READ. Такой выбор гарантирует, что если запрос сканирует несколько таблиц на удаленном сервере, он будет получать согласованные со снимком результаты для всех сканирований. Как следствие, последовательные запросы в рамках одной транзакции будут видеть одинаковые данные удаленного сервера, даже если на нем происходят параллельные изменения, вызванные другими действиями. Это поведение в любом случае ожидаемо, если для локальной транзакции используется уровень изоляции SERIALIZABLE или REPEATABLE READ, но для локальной транзакции с уровнем READ COMMITTED оно может быть неожиданным. В будущих выпусках QHB эти правила могут быть изменены.

Обратите внимание, что подготовку удаленной транзакции для двухфазной фиксации postgres_fdw в настоящее время не поддерживает.

Оптимизация удаленных запросов

Обертка postgres_fdw пытается оптимизировать удаленные запросы, чтобы уменьшить объем данных, пересылаемых сторонними серверами. Для этого она передает на выполнение удаленному серверу предложения WHERE и не получает столбцы таблицы, которые не требуются для текущего запроса. Чтобы снизить риск неправильного выполнения запросов, предложения WHERE передаются удаленному серверу, только если в них используются типы данных, операторы и функции, встроенные в ядро или принадлежащие расширениям, перечисленным в параметре extensions удаленного сервера. Операторы и функции в таких предложениях также должны быть неизменяемыми (IMMUTABLE). Для запросов UPDATE или DELETE postgres_fdw пытается оптимизировать выполнение, передавая удаленному серверу весь запрос, если в нем нет предложений WHERE, которые нельзя передать удаленному серверу, для запроса не выполняется локальное соединение, в целевой таблице отсутствуют локальные триггеры BEFORE или AFTER уровня строки или хранимые генерируемые столбцы, а в родительских представлениях нет ограничения CHECK OPTION. Кроме того, в запросах UPDATE выражения, присваиваемые целевым столбцам, должны использовать только встроенные типы данных и неизменяемые (IMMUTABLE) операторы и функции, чтобы снизить риск неправильного выполнения запроса.

Когда postgres_fdw обнаруживает соединение сторонних таблиц на одном стороннем сервере, она передает все это соединение стороннему серверу, если только по какой-то причине не решит, что будет эффективнее выбирать строки из каждой таблицы по отдельности, или если задействованные ссылки на таблицы относятся к разным сопоставлениям пользователей. При передаче предложений JOIN она принимает те же меры предосторожности, что и описанные выше для предложений WHERE.

Запрос, фактически передаваемый стороннему серверу для выполнения, можно проверить с помощью команды EXPLAIN VERBOSE.

Среда удаленного выполнения запросов

В удаленных сеансах, установленных postgres_fdw, в параметре search_path задается только pg_catalog, поэтому без указания схемы видны только встроенные объекты. Это не проблема для запросов, генерируемых самой postgres_fdw, поскольку она всегда добавляет такое указание. Однако это может представлять опасность для функций, которые выполняются на удаленном сервере при срабатывании триггеров или правил для удаленных таблиц. К примеру, если удаленная таблица на самом деле является представлением, все функции, используемые в этом представлении, будут выполняться с таким ограниченным путем поиска. Поэтому в таких функциях рекомендуется дополнять все имена схемой или добавлять параметры SET search_path (см. CREATE FUNCTION), чтобы установить ожидаемую ими среду пути поиска.

Подобным же образом postgres_fdw устанавливает для удаленного сеанса значения различных параметров:

TimeZone — UTC

DateStyle — ISO

IntervalStyle — postgres

extra_float_digits — 3

С ними проблемы менее вероятны, чем с search_path, но если они возникнут, их можно решить, задав нужные значения в параметре SET.

Это поведение не рекомендуется переопределять, меняя значения этих параметров на уровне сеанса; скорее всего, это приведет к нарушениям в работе postgres_fdw.

Совместимость с разными версиями

Модуль postgres_fdw полагает, что неизменяемые встроенные функции и операторы могут безопасно передаваться на удаленный сервер для выполнения, если они фигурируют в предложении WHERE для сторонней таблицы. Как следствие, на выполнение может быть отправлена встроенная функция, добавленная в более новой версии, чем на удаленном сервере, что в результате приведет к ошибке «функция не существует» или ей подобной. Сбои такого типа можно предотвратить, переписав запрос, например поместив ссылку на стороннюю таблицу в подзапрос SELECT с OFFSET 0 в качестве защиты от оптимизации и применив проблематичную функцию или оператор снаружи этого подзапроса SELECT.

Примеры

Ниже приведен пример создания сторонней таблицы при помощи postgres_fdw. Сначала установите расширение:

CREATE EXTENSION postgres_fdw;

Затем создайте сторонний сервер командой CREATE SERVER. В данном примере мы хотим подключиться к серверу QHB на хосте 192.83.123.89, порт 5432. База данных, с которой устанавливается соединение, на удаленном сервере называется foreign_db:

CREATE SERVER foreign_server
        FOREIGN DATA WRAPPER postgres_fdw
        OPTIONS (host '192.83.123.89', port '5432', dbname 'foreign_db');

Также для определения роли, которая будет задействована на удаленном сервере, требуется сопоставление пользователей, задаваемое командой CREATE USER MAPPING:

CREATE USER MAPPING FOR local_user
        SERVER foreign_server
        OPTIONS (user 'foreign_user', password 'password');

Теперь можно создать стороннюю таблицу с помощью команды CREATE FOREIGN TABLE. В данном примере мы ходим обратиться к таблице, которая на удаленном сервере называется some_schema.some_table. Ее локальным именем будет foreign_table:

CREATE FOREIGN TABLE foreign_table (
        id integer NOT NULL,
        data text
)
        SERVER foreign_server
        OPTIONS (schema_name 'some_schema', table_name 'some_table');

Важно, чтобы типы данных и другие свойства столбцов, объявленных в CREATE FOREIGN TABLE, соответствовали фактической удаленной таблице. Также должны соответствовать имена столбцов, если только вы не добавите параметры column_name для отдельных столбцов, показывающие их имена в удаленной таблице. Во многих случаях использовать IMPORT FOREIGN SCHEMA предпочтительнее, чем конструировать определения сторонних таблиц вручную.

Автор

Шигеру Ханада (Shigeru Hanada), shigeru.hanada@gmail.com

seg

Этот модуль реализует тип данных seg для представления отрезков или интервалов с плавающей запятой. Тип seg может отображать неопределенность концов интервала, что делает его особенно полезным для представления лабораторных измерений.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Обоснование

Геометрия измерений обычно более сложна, чем точка в числовом континууме. Как правило, измерение представляет собой отрезок этого континуума с несколько размытыми границами. Эти измерения выводятся в виде отрезков вследствие неопределенности и случайности, а также того, что измеряемое значение может по своей природе быть интервалом, указывающим на некое условие, например диапазон температур стабильности белка.

Исходя только из здравого смысла, кажется более удобным хранить такие данные в виде интервалов, а не пары чисел. На практике это оказывается еще эффективнее в большинстве приложений.

Если и далее рассуждать здраво, размытость границ предполагает, что использование традиционных числовых типов данных приводит к определенной потере информации. Рассмотрим такой пример: ваш инструмент выдает 6.50, и вы вводите это значение в базу данных. Что вы получите, выбрав его из базы? Смотрите:

test=> SELECT 6.50 :: float8 AS "pH";
 pH
---
6.5
(1 row)

В мире измерений 6.50 — это не то же самое, что 6.5. Разница между этими измерениями иногда может быть критической. Экспериментаторы обычно записывают (и публикуют) цифры, в которых уверены. Запись 6.50 на самом деле представляет собой нечеткий интервал, содержащийся внутри большего и еще более нечеткого интервала 6.5, и единственное, что у них может быть общего, — это их центральные точки. Мы определенно не хотим, чтобы такие разные элементы данных выглядели одинаково.

Вывод? Удобно иметь специальный тип данных, в котором можно сохранить границы интервала с произвольной переменной точностью. (Здесь под переменной точностью подразумевается, что для каждого элемента данных она записывается индивидуально.)

Проверьте это:

test=> SELECT '6.25 .. 6.50'::seg AS "pH";
          pH
------------
6.25 .. 6.50
(1 row)

Синтаксис

Внешнее представление интервала образуется одним или двумя числами с плавающей запятой, соединенными оператором диапазона (.. или ...). Как вариант, интервал можно задать центральной точкой плюс/минус отклонение. Также этот тип позволяет сохранить дополнительные индикаторы достоверности (<, > или ~). (Однако индикаторы достоверности игнорируются всеми встроенными операторами.) В Таблице 27 дается обзор допустимых представлений; в Таблице 28 приводятся некоторые примеры.

В Таблице 27 символы x, y и delta обозначают числа с плавающей запятой. Перед значениями x и y, но не delta, может быть добавлен индикатор достоверности.

Таблица 27. Внешние представления seg

Представление | Описание
x | Одно значение (интервал нулевой длины)
x .. y | Интервал от x до y
x (+-) delta | Интервал от x - delta до x + delta
x .. | Открытый интервал с нижней границей x
.. x | Открытый интервал с верхней границей x

Таблица 28. Примеры допустимых вводимых значений seg

Пример | Что делает
5.0 | Создает сегмент нулевой длины (или точку, если хотите).
~5.0 | Создает сегмент нулевой длины и записывает ~ в данные. Знак ~ игнорируется при операциях с seg, но сохраняется как комментарий.
<5.0 | Создает точку с координатой 5.0. Знак < игнорируется, но сохраняется как комментарий.
>5.0 | Создает точку с координатой 5.0. Знак > игнорируется, но сохраняется как комментарий.
5(+-)0.3 | Создает интервал 4.7 .. 5.3. Обратите внимание, что запись (+-) не сохраняется.
50 .. | Все, что больше или равно 50.
.. 0 | Все, что меньше или равно 0.
1.5e-2 .. 2E-2 | Создает интервал 0.015 .. 0.02.
1 ... 2 | То же, что и 1...2, или 1 .. 2, или 1..2 (пробелы вокруг оператора диапазона игнорируются).

Поскольку оператор ... широко используется в источниках данных, он принимается в качестве альтернативного написания оператора ... К сожалению, это порождает неоднозначность при разборе: неясно, какая верхняя граница подразумевается в записи 0...23 — 23 или 0.23. Для разрешения этой неоднозначности во входных числах seg перед десятичной точкой всегда должна быть как минимум одна цифра.

В целях контроля корректности seg не принимает интервалы с нижней границей, превышающей верхнюю, например 5 .. 2.

Точность

Значения seg хранятся внутри как пары 32-битных чисел с плавающей запятой. Это значит, что числа с более чем 7 значащими цифрами будут усекаться.

Числа, содержащие 7 значащих цифр и меньше, сохраняют изначальную точность. То есть, если запрос возвращает 0.00, вы можете быть уверены, что конечные нули не являются артефактами форматирования: они отражают точность исходных данных. Количество ведущих нулей не влияет на точность: значение 0.0067 считается имеющим только 2 значащие цифры.

Применение

Модуль seg включает класс операторов индекса GiST для значений seg. Операторы, поддерживаемые этим классом операторов, перечислены в Таблице 29.

Таблица 29. Операторы seg для GiST

Оператор

Описание

seg << seg → boolean

Первый seg полностью находится левее второго? [a, b] << [c, d] — true, если b < c.

seg >> seg → boolean

Первый seg полностью находится правее второго? [a, b] >> [c, d] — true, если a > d.

seg &< seg → boolean

Первый seg не простирается правее второго? [a, b] &< [c, d] — true, если b <= d.

seg &> seg → boolean

Первый seg не простирается левее второго? [a, b] &> [c, d] — true, если a >= c.

seg = seg → boolean

Два интервала seg равны?

seg && seg → boolean

Два интервала seg пересекаются?

seg @> seg → boolean

Первый интервал seg содержит второй?

seg <@ seg → boolean

Первый интервал seg содержится во втором?

Помимо вышеописанных операторов для типа seg доступны обычные операторы сравнения, показанные в таблице Операторы сравнения. Эти операторы сначала сравнивают (a) с (c), и если они равны, сравнивают (b) с (d). В большинстве случаев это приводит к адекватному упорядочиванию, что полезно, если нужно применить ORDER BY с этим типом.

Примечания

Примеры использования можно увидеть в регрессионном тесте sql/seg.sql.

Механизм, преобразующий (+-) в обычные диапазоны, не вполне точно определяет число значащих цифр для границ. К примеру, он добавляет дополнительную цифру к нижней границе, если результирующий интервал включает степень десяти:

qhb=> SELECT '10(+-)1'::seg AS seg;
      seg
---------
9.0 .. 11             -- должно быть: 9 .. 11

Производительность индекса R-дерева может значительно зависеть от начального порядка вводимых значений. Может быть очень полезно отсортировать входную таблицу по столбцу seg; пример можно найти в скрипте sort-segments.pl.

Благодарности

Оригинальный автор: Джин Селков мл. (Gene Selkov, Jr.), selkovjr@mcs.anl.gov, Аргоннская национальная лаборатория, Отдел математики и компьютерных наук.

Я благодарен в первую очередь профессору Джо Геллерштейну (Joe Hellerstein) (https://dsf.berkeley.edu/jmh/) за пояснение сути GiST (http://gist.cs.berkeley.edu/). Я также признателен всем разработчикам Postgres в настоящем и прошлом за возможность создать мой собственный мир и спокойно жить в нем. Еще я хотел бы выразить признательность Аргоннской лаборатории и Министерству энергетики США за годы постоянной поддержки моих исследований в области баз данных.

sepgsql

sepgsql — это загружаемый модуль, который поддерживает мандатное управление доступом (Mandatory access control, MAC) с использованием меток, работающее на основе политик безопасности SELinux.

ВНИМАНИЕ!
Текущая реализация имеет существенные ограничения и применяет мандатное управление доступом не для всех действий. См. параграф Ограничения.

Обзор

Этот модуль интегрируется в SELinux, чтобы обеспечить дополнительный уровень проверок безопасности, превышающий те, что предоставляются обычными средствами QHB. С точки зрения SELinux, этот модуль позволяет QHB выполнять роль менеджера объектов в пространстве пользователя. Обращение к каждой таблице или функции, инициируемое запросом DML, будет проверено согласно системной политике безопасности. Эта проверка дополняет обычную проверку разрешений SQL, осуществляемую QHB.

SELinux принимает решения о разрешении доступа на основе меток безопасности, представляемых строками вида system_u:object_r:sepgsql_table_t:s0. В каждом решении учитываются две метки: метка субъекта, пытающегося выполнить действие, и метка объекта, над которым должна совершиться эта операция. Поскольку эти метки могут применяться к объекту любого вида, решения о разрешении доступа к объектам внутри базы данных могут подчиняться (и с этим модулем подчиняются) тем же общим критериям, которые применяются к объектам любого другого типа, например к файлам. Эта конструкция призвана дать возможность организовать централизованную политику безопасности для защиты информационных активов, не зависящую от того, как именно хранятся эти активы.

Назначить метку безопасности объекту базы данных позволяет оператор SECURITY LABEL.

Установка

Модуль sepgsql можно использовать только в Linux 2.6.28 или новее с включенным SELinux. На остальных платформах он недоступен. Также вам понадобится libselinux 2.1.10 или новее и selinux-policy 3.9.13 или новее (хотя в некоторых дистрибутивах можно бэкпортировать необходимые правила к политике старой версии).

Команда sestatus позволяет проверить состояние SELinux. Типичное ее отображение выглядит так:

$ sestatus
SELinux status:                 enabled
SELinuxfs mount:                /selinux
Current mode:                   enforcing
Mode from config file:          enforcing
Policy version:                 24
Policy from config file:        targeted

Если SELinux выключен или не установлен, его необходимо настроить, прежде чем устанавливать этот модуль.

Чтобы собрать этот модуль, добавьте параметр --with-selinux в команду QHB configure. Убедитесь в том, что на момент сборки установлен RPM-пакет libselinux-devel.

Для использования этого модуля следует добавить sepgsql в параметр shared_preload_libraries в qhb.conf. Этот модуль не будет корректно работать, если загрузить его каким-либо иным способом. Когда он загрузится, нужно выполнить sepgsql.sql в каждой базе данных. Этот скрипт установит функции, необходимые для управления метками безопасности, и назначит начальные метки безопасности.

Ниже приведен пример, показывающий, как инициализировать новый кластер баз данных с установленными функциями и метками безопасности sepgsql. Измените указанные пути в соответствии размещением вашей установки:

$ export PGDATA=/путь/к/каталогу/данных
$ qhb_bootstrap
$ vi $PGDATA/qhb.conf
  изменить
    #shared_preload_libraries = ''                # (после изменения требуется перезапуск)
  на
    shared_preload_libraries = 'sepgsql'          # (после изменения требуется перезапуск)
$ for DBNAME in template0 template1 qhb; do
    qhb --single -F -c exit_on_error=true $DBNAME \
      </var/lib/qhb/data/share/contrib/sepgsql.sql >/dev/null
  done

Пожалуйста, обратите внимание, что вы можете увидеть следующие уведомления, в зависимости от конкретных установленных версий libselinux и selinux-policy:

/etc/selinux/targeted/contexts/sepgsql_contexts:  line 33 has invalid object type db_blobs
/etc/selinux/targeted/contexts/sepgsql_contexts:  line 36 has invalid object type db_language
/etc/selinux/targeted/contexts/sepgsql_contexts:  line 37 has invalid object type db_language
/etc/selinux/targeted/contexts/sepgsql_contexts:  line 38 has invalid object type db_language
/etc/selinux/targeted/contexts/sepgsql_contexts:  line 39 has invalid object type db_language
/etc/selinux/targeted/contexts/sepgsql_contexts:  line 40 has invalid object type db_language

Эти сообщения безвредны и их следует игнорировать.

Если процесс установки завершается без ошибок, можно запустить сервер как обычно.

Регрессионные тесты

Учитывая природу SELinux, для проведения регрессионных тестов sepgsql требуется несколько дополнительных этапов конфигурирования, некоторые из которых должен выполнять root. Эти регрессионные тесты не будут запускаться обычной командой make check или make installcheck; необходимо настроить конфигурацию, а затем вызвать тестовый скрипт вручную. Тесты должны запускаться в каталоге contrib/sepgsql сконфигурированного дерева сборки QHB. Несмотря на то, что им требуется дерево сборки, эти тесты рассчитаны на выполнение на установленном сервере, то есть они сравнимы с make installcheck, но не с make check.

Сначала установите sepgsql в рабочую базу данных согласно инструкциям, приведенным в разделе Установка. Обратите внимание, что для этого текущий пользователь операционной системы должен иметь возможность подключаться к базе данных как суперпользователь без аутентификации по паролю.

На втором этапе соберите и установите пакет политики для регрессионного теста. Политика sepgsql-regtest представляет собой политику особого назначения, которая предоставляет набор правил, включаемых во время регрессионных тестов. Ее нужно собрать из исходного файла политики sepgsql-regtest.te, что осуществляется с помощью команды make со скриптом Makefile, предоставляемым SELinux. Вам понадобится найти подходящий Makefile в своей системе; путь, показанный ниже, является всего лишь примером. (Этот Makefile обычно поставляется в пакете RPM selinux-policy-devel или selinux-policy.) Собрав пакет политики, установите его с помощью команды semodule, которая загружает предоставленные пакеты в ядро. Если пакет установлен корректно, команда semodule -l должна показать sepgsql-regtest в списке доступных пакетов политик:

$ cd .../contrib/sepgsql
$ make -f /usr/share/selinux/devel/Makefile
$ sudo semodule -u sepgsql-regtest.pp
$ sudo semodule -l | grep sepgsql
sepgsql-regtest 1.07

На третьем этапе включите параметр sepgsql_regression_test_mode. По соображениям безопасности правила в sepgsql-regtest по умолчанию выключены; параметр sepgsql_regression_test_mode включает правила, необходимые для запуска регрессионных тестов. Включить этот параметр можно командой setsebool:

$ sudo setsebool sepgsql_regression_test_mode on
$ getsebool sepgsql_regression_test_mode
sepgsql_regression_test_mode --> on

На четвертом этапе убедитесь, что ваша оболочка работает в домене unconfined_t:

$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Если необходимо сменить рабочий домен, подробную информацию об этом см. в параграфе Внешние ресурсы.

Наконец, запустите скрипт регрессионного теста:

$ ./test_sepgsql

Этот скрипт попытается проверить, все ли этапы конфигурирования были выполнены корректно, а затем запустит регрессионные тесты для модуля sepgsql.

После завершения тестов рекомендуется выключить параметр sepgsql_regression_test_mode:

$ sudo setsebool sepgsql_regression_test_mode off

Возможно, более предпочтительным будет полностью удалить политику sepgsql-regtest:

$ sudo semodule -r sepgsql-regtest

Параметры GUC

sepgsql.permissive (boolean)
Этот параметр позволяет sepgsql работать в разрешительном режиме, независимо от настроек системы. Значение по умолчанию — off (выключен). Этот параметр можно задать только в файле qhb.conf или в командной строке сервера.
Когда этот параметр включен, sepgsql функционирует в разрешительном режиме, даже если SELinux в целом работает в ограничительном режиме. Этот параметр полезен в первую очередь для тестирования.

sepgsql.debug_audit (boolean)
Этот параметр включает вывод сообщений аудита, независимо от настроек системной политики. Значение по умолчанию — off, означающее, что сообщения будут выводиться согласно настройкам системы.
Политика безопасности SELinux также содержит правила, определяющие, будут ли протоколироваться в журнал определенные обращения. По умолчанию фиксируются нарушения доступа, а успешный доступ — нет.
Этот параметр принудительно включает протоколирование в журнале всех возможных событий, независимо от системной политики.

Функциональность

Управляемые классы объектов

Модель безопасности SELinux описывает все правила управления доступом как отношения между сущностью субъекта (обычно это клиент базы данных) и сущностью объекта (например объектом базы данных), каждая из которых определяется меткой безопасности. Если предпринимается попытка доступа к непомеченному объекту, он обрабатывается как объект с меткой unlabeled_t.

В настоящее время sepgsql позволяет назначать метки безопасности схемам, таблицам, столбцам, последовательностям, представлениям и функциям. При использовании sepgsql метки безопасности автоматически назначаются поддерживаемым объектом базы данных в момент создания. Такая метка называется меткой безопасности по умолчанию и устанавливается согласно системной политике безопасности, которая принимает на вход метку создателя, метку, назначенную родительскому объекту нового объекта и, при необходимости, имя создаваемого объекта.

Новый объект базы данных обычно наследует метку безопасности родительского объекта, за исключением случаев, когда в политике безопасности установлены специальные правила, известные как правила перехода типов, — тогда может быть назначена другая метка. Для схем родительским объектом является текущая база данных; для таблиц, последовательностей, представлений и функций — схемы, содержащие эти объекты; для столбцов — содержащие их таблицы.

Разрешения для DML

Для всех ссылочных целевых таблиц проверяются разрешения db_table:select, db_table:insert, db_table:update или db_table:delete, в зависимости от вида оператора; кроме того, для всех таблиц, содержащих столбцы, фигурирующие в предложении WHERE или RETURNING, или служащих источником данных для UPDATE и т. п., также проверяется разрешение db_table:select.

Для всех ссылочных столбцов также проверяются разрешения уровня столбцов. Разрешение db_column:select проверяется не только для столбцов, которые считываются оператором SELECT, но и для тех, к которым обращаются другие операторы DML; для столбцов, изменяемых оператором UPDATE или INSERT, также проверяется разрешение db_column:update или db_column:insert соответственно.

Например, рассмотрим запрос:

UPDATE t1 SET x = 2, y = func1(y) WHERE z = 100;

В данном примере разрешение db_column:update будет проверяться для столбца t1.x, так как он изменяется, db_column:{select update} — для t1.y, так как он и изменяется, и считывается, а db_column:select — для t1.z, так как он только считывается. Также на уровне таблицы будет проверяться разрешение db_table:{select update}.

Для последовательностей проверяется разрешение db_sequence:get_value при обращении к объекту последовательности с помощью оператора SELECT; однако обратите внимание, что в настоящее время разрешения на выполнение связанных функций, например lastval(), не проверяются.

Для представлений будет проверяться разрешение db_view:expand, а затем все остальные требуемые разрешения для объектов, развернутых из этого представления, по отдельности.

Для функций будет проверяться разрешение db_procedure:{execute}, когда пользователь попытается выполнить функцию в составе запроса, или при вызове по быстрому пути. Если эта функция является доверенной процедурой, также проверяется разрешение db_procedure:{entrypoint}, чтобы определить, может ли эта функция выступать точкой входа в доверенную процедуру.

Для обращения к любому объекту схемы необходимо разрешение db_schema:search для содержащей его схемы. Когда имя ссылочного объекта не дополняется схемой, схемы, для которых это разрешение отсутствует, не будут просматриваться (то же самое происходит, если у пользователя нет права USAGE для этой схемы). Если имеется явное указание схемы, но у пользователя нет требуемого разрешения для именованной схемы, произойдет ошибка.

Клиенту должен быть разрешен доступ ко всем ссылочным таблицам и столбцам, даже если они появились в запросе из развернутых тогда же представлений, поэтому правила управления доступом применяются согласованно, независимо от способа обращения к содержимому таблицы.

Система прав базы данных по умолчанию позволяет суперпользователям этой базы изменять системные каталоги с помощью команд DML и обращаться к таблицам TOAST или модифицировать их. Когда модуль sepgsql активен, эти операции запрещаются.

Разрешения для DDL

SELinux определяет несколько разрешений для управления стандартными операциями для каждого типа объекта, в частности, созданием, изменением определения, удалением и сменой метки безопасности. Кроме того, для некоторых типов объектов имеются специальные разрешения, управляющие их специфическими операциями, в частности добавлением или удалением элементов в определенной схеме.

Для создания нового объекта базы данных требуется разрешение create. SELinux выдает или отказывает в выдаче этого разрешения в зависимости от метки безопасности клиента и предполагаемой метки безопасности нового объекта. В некоторых случаях требуются дополнительные разрешения:

Для CREATE DATABASE дополнительно требуется разрешение getattr в исходной или шаблонной базе данных.

Для создания объекта схемы дополнительно требуется разрешение add_name в родительской схеме.

Для создания таблицы дополнительно требуется разрешение на создание каждого отдельного ее столбца, как если бы каждый столбец был отдельным объектом верхнего уровня.

Для создания функции с атрибутом LEAKPROOF дополнительно требуется разрешение install. (Это разрешение также проверяется, когда атрибут LEAKPROOF устанавливается для существующей функции.)

Когда выполняется команда DROP, для удаляемого объекта будет проверяться разрешение drop. Также разрешения будут проверяться для объектов, удаляемых неявно посредством указания CASCADE. Для удаления объектов, содержащихся в определенной схеме (таблиц, представлений, последовательностей и процедур) дополнительно требуется разрешение remove_name в этой схеме.

Когда выполняется команда ALTER, для каждого типа модифицируемого объекта будет проверяться разрешение setattr, за исключением подчиненных объектов, таких как индексы или триггеры таблицы, для которых вместо этого проверяется разрешение родительского объекта. В некоторых случаях требуются дополнительные разрешения:

Для перемещения объекта в новую схему дополнительно требуется разрешение remove_name в старой схеме и add_name в новой.

Для установки атрибута LEAKPROOF для функции требуется разрешение install.

Для использования SECURITY LABEL на объекте дополнительно требуется разрешение relabelfrom для этого объекта в связке со старой меткой безопасности и relabelto для этого же объекта в связке с новой меткой безопасности. (В тех случаях, когда установлено несколько поставщиков меток и пользователь пытается задать метку безопасности, неподконтрольную SELinux, должно проверяться только разрешение setattr. В настоящее время этого не происходит из-за ограничений реализации.)

Доверенные процедуры

Доверенные процедуры схожи с функциями, определяющими контекст безопасности, или командами setuid. SELinux предоставляет возможность запускать доверенный код с меткой безопасности, отличной от метки клиента, обычно с целью предоставления тщательно контролируемого доступа к важным данным (при этом, к примеру, могут отбрасываться строки или может быть снижена точность хранимых значений). Будет ли функция действовать как доверенная процедура, определяется ее меткой безопасности и политикой безопасности операционной системы. Например:

qhb=# CREATE TABLE customer (
               cid     int primary key,
               cname   text,
               credit  text
           );
CREATE TABLE
qhb=# SECURITY LABEL ON COLUMN customer.credit
               IS 'system_u:object_r:sepgsql_secret_table_t:s0';
SECURITY LABEL
qhb=# CREATE FUNCTION show_credit(int) RETURNS text
             AS 'SELECT regexp_replace(credit, ''-[0-9]+$'', ''-xxxx'', ''g'')
                        FROM customer WHERE cid = $1'
           LANGUAGE sql;
CREATE FUNCTION
qhb=# SECURITY LABEL ON FUNCTION show_credit(int)
               IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0';
SECURITY LABEL

Показанные выше операции должен выполнять пользователь с правами администратора.

qhb=# SELECT * FROM customer;
ERROR:  SELinux: security policy violation
-- ОШИБКА:  SELinux: нарушение политики безопасности
qhb=# SELECT cid, cname, show_credit(cid) FROM customer;
 cid | cname  |     show_credit
-----+--------+---------------------
   1 | taro   | 1111-2222-3333-xxxx
   2 | hanako | 5555-6666-7777-xxxx
(2 rows)

В данном случае обычный пользователь не может обращаться к customer.credit напрямую, но доверенная процедура show_credit позволяет ему получить номера кредитных карт клиентов, в которых скрыты некоторые цифры.

Динамические переключения домена

Функциональную возможность динамического переключения между доменами SELinux можно использовать для перевода метки безопасности клиентского процесса, клиентский домен, в новый контекст, если это допускается политикой безопасности. Для этого клиентскому домену нужно разрешение setcurrent, а также разрешение dyntransition для перехода из старого в новый домен.

Динамические переключения домена следует тщательно продумывать, поскольку они позволяют пользователям менять свои метки, а значит и права, по собственному желанию, а не согласно требованиям системы (как в случае с доверенными процедурами). Поэтому разрешение dyntransition считается безопасным, только когда используется для перехода в домен с меньшим набором прав, чем исходный. Например:

regression=# select sepgsql_getcon();
                    sepgsql_getcon
-------------------------------------------------------
 unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
(1 row)

regression=# SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0-s0:c1.c4');
 sepgsql_setcon
----------------
 t
(1 row)

regression=# SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0-s0:c1.c1023');
ERROR:  SELinux: security policy violation
-- ОШИБКА:  SELinux: нарушение политики безопасности

В этом примере нам позволили переключиться из более широкого диапазона MCS c1.c1023 в более узкий c1.c4, но в обратном переключении было отказано.

Комбинация динамического переключение домена и доверенной процедуры открывает интересный сценарий использования, который подходит жизненному циклу типичных процессов менеджера пула соединений. Даже если менеджеру пула соединений не разрешено запускать большинство команд SQL, ему можно разрешить сменить метку безопасности клиента с помощью функции sepgsql_setcon() из доверенной процедуры; для авторизации этого запроса наверняка понадобится удостоверение защиты. После этого сеанс получит права целевого пользователя, а не пользователя пула соединений. Позднее пользователь пула соединений может отменить смену метки безопасности, снова воспользовавшись функцией sepgsql_setcon() с аргументом NULL, также вызванной из доверенной процедуры с требуемыми проверками разрешений. Смысл здесь в том, что только у этой доверенной процедуры есть разрешение менять действующую метку безопасности и только тогда, когда получает правильные подтверждения авторизации. Естественно, чтобы такая операция была безопасной, хранилище удостоверений защиты (таблица, определение процедуры или что-то другое) должно быть защищено от несанкционированного доступа.

Прочее

Выполнение команды LOAD полностью запрещено, так как любой загруженный модуль может легко обойти установки политики безопасности.

Функции sepgsql

В Таблице 30 перечислены доступные функции.

Таблица 30. Функции sepgsql

Функция

Описание

sepgsql_getcon () → text

Возвращает клиентский домен, текущую метку безопасности клиента.

sepgsql_setcon ( text ) → boolean

Переключает клиентский домен текущего сеанса в новый домен, если это допускает политика безопасности. Также принимает в аргументе NULL в качестве запроса на переход в исходный домен клиента.

sepgsql_mcstrans_in ( text ) → text

Переводит заданный диапазон MLS/MCS из полной записи в низкоуровневый формат, если работает демон mcstrans.

sepgsql_mcstrans_out ( text ) → text

Переводит заданный диапазон MLS/MCS из низкоуровневого формата в полную запись, если работает демон mcstrans.

sepgsql_restorecon ( text ) → boolean

Устанавливает начальные метки безопасности для всех объектов в текущей базе данных. В аргументе может передаваться NULL или имя файла спецификации для использования вместо стандартного системного.

Ограничения

Разрешения для языка определения данных (Data Definition Language, DDL)

Из-за ограничений реализации для некоторых операций DDL разрешения не проверяются.

Разрешения для языка управления данными (Data Control Language, DCL)

Из-за ограничений реализации для некоторых операций DCL разрешения не проверяются.

Управление доступом на уровне строк

QHB поддерживает доступ на уровне строк, а sepgsql — нет.

Скрытые каналы

Модуль sepgsql не пытается скрыть существование определенного объекта, даже если пользователю не разрешено к нему обращаться. Например, можно сделать логический вывод о существовании невидимого объекта по конфликтам первичного ключа, нарушениям внешних ключей и т. д., даже если нельзя получить содержимое этого объекта. Существование совершенно секретной таблицы невозможно скрыть; можно лишь надеяться утаить ее содержимое.

Внешние ресурсы

SE-PostgreSQL Introduction, Введение в SE-PostgreSQL

На этой вики-странице представлен краткий обзор и описание архитектуры и проекта безопасности, администрирования и ожидаемых в будущем возможностей расширения SE-PostgreSQL.

SELinux User's and Administrator's Guide, Руководство пользователя и администратора SELinux

В этом документе представлен широкий спектр знаний по администрированию SELinux в операционных системах. В первую очередь он ориентирован на системы Red Hat, но не ограничивается только ими.

Fedora SELinux FAQ, FAQ по SELinux в ОС Fedora

В этом документе даются ответы на часто задаваемые вопросы по SELinux. В первую очередь он ориентирован на ОС Fedora, но не ограничивается только ей.

Автор

КайГай Кохэй (KaiGai Kohei), kaigai@ak.jp.nec.com

spi

Модуль spi предоставляет несколько рабочих примеров использования Интерфейса программирования сервера (Server Programming Interface, SPI) и триггеров. Хотя эти функции имеют некоторую ценность и сами по себе, они еще более полезны в качестве образцов, которые можно модифицировать под собственные нужды. Эти функции достаточно общие, чтобы применяться с любой таблицей, но при создании триггера следует явно указать имена таблицы и полей (как описано ниже).

Каждая описанная ниже группа функций представлена в виде отдельно устанавливаемого расширения.

refint — функции для реализации ссылочной целостности

Функции check_primary_key() и check_foreign_key() используются для проверки ограничений внешних ключей. (Эта функциональность уже давно вытеснена встроенным механизмом внешних ключей, но этот модуль все еще полезен в качестве примера.)

Функция check_primary_key() проверяет ссылающуюся таблицу. Чтобы воспользоваться ей, создайте с помощью этой функции триггер BEFORE INSERT OR UPDATE для таблицы, ссылающейся на другую. Задайте в аргументах триггера: имена столбцов ссылающейся таблицы, образующих внешний ключ, имя ссылочной таблицы и имена столбцов в ней, образующих первичный/уникальный ключ. Чтобы управлять несколькими внешними ключами, создайте триггер для каждой такой ссылки.

Функция check_foreign_key() проверяет ссылочную таблицу. Чтобы воспользоваться ей, создайте с помощью этой функции триггер BEFORE DELETE OR UPDATE для таблицы, на которую ссылаются другие таблицы. Задайте в аргументах триггера: количество ссылающихся таблиц, для которых функция должна выполнить проверки, действие в случае обнаружения ссылающегося ключа (cascade — удалить ссылающуюся строку, restrict — прервать транзакцию, если ссылающийся ключ существует, setnull — установить в полях ссылающихся ключей значения NULL), имена столбцов ссылочной таблицы, образующих первичный/уникальный ключ, а затем имя ссылающейся таблицы и имена ее столбцов (повторить для стольких ссылающихся таблиц, сколько их указано в первом аргументе). Обратите внимание, что столбцы первичного/уникального ключа должны быть помечены NOT NULL и по ним должен быть создан уникальный индекс.

Примеры приведены в refint.example.

autoinc — функции для автоувеличения полей

Функция autoinc() является триггером, сохраняющим следующее значение последовательности в целочисленном поле. Это некоторым образом пересекается со встроенной функциональностью столбцов типа serial, но все же не одно и то же: autoinc() будет переопределять попытки вставить другое значение поля при добавлении строк, а также при необходимости может использоваться для увеличения значения поля при изменениях.

Чтобы воспользоваться этой функцией, создайте с ее помощью триггер BEFORE INSERT (или BEFORE INSERT OR UPDATE). Задайте в двух аргументах триггера имя целочисленного столбца, подлежащего изменению, и имя объекта последовательности, который будет поставлять значения. (На самом деле можно задать любое количество пар таких имен, если хотите изменять несколько автоувеличивающихся столбцов.)

Пример приведен в autoinc.example.

insert_username — функции для отслеживания пользователя, меняющего таблицу

Функция insert_username() является триггером, сохраняющим имя текущего пользователя в текстовом поле. Это может быть полезно для отслеживания пользователя, последним изменившего конкретную строку в таблице.

Чтобы воспользоваться этой функцией, создайте с ее помощью триггер BEFORE INSERT и/или UPDATE. Задайте в единственном аргументе триггера имя текстового столбца, подлежащего изменению.

Пример приведен в insert_username.example.

moddatetime — функции для отслеживания времени последнего изменения

Функция moddatetime() является триггером, сохраняющим текущее время в поле типа timestamp. Это может быть полезно для отслеживания времени последнего изменения конкретной строки в таблице.

Чтобы воспользоваться этой функцией, создайте с ее помощью триггер BEFORE UPDATE. Задайте в единственном аргументе триггера имя столбца, подлежащего изменению. Столбец должен иметь тип timestamp или timestamp with time zone.

Пример приведен в moddatetime.example.

sslinfo

Модуль sslinfo предоставляет информацию о SSL-сертификате, который был предоставлен текущим клиентом при подключении к QHB. Этот модуль бесполезен (большинство функций возвратят NULL), если для текущего подключения не используется SSL.

Часть информации, доступной благодаря этому модулю, также можно получить через встроенное системное представление pg_stat_ssl.

Это расширение не будет собираться, если установка была произведена без ключа --with-ssl=openssl.

Предоставляемые функции

ssl_is_used() returns boolean

Возвращает true, если текущее подключение к серверу использует SSL, и false в противном случае.

ssl_version() returns text

Возвращает имя протокола, используемого для SSL-подключения (например TLSv1.0, TLSv1.1, TLSv1.2 или TLSv1.3).

ssl_cipher() returns text

Возвращает имя шифра, используемого для SSL-подключения (например DHE-RSA-AES256-SHA).

ssl_client_cert_present() returns boolean

Возвращает true, если текущий клиент предоставил серверу действительный клиентский SSL-сертификат, и false в противном случае. (В зависимости от своей конфигурации, сервер может требовать, а может и не требовать предоставления клиентского сертификата.)

ssl_client_serial() returns numeric

Возвращает серийный номер текущего клиентского сертификата. Сочетание серийного номера сертификата с выдавшим его издателем (центром сертификации, ЦС) однозначно гарантирует идентификацию сертификата (но не его владелец — владелец должен регулярно менять свои ключи и получать новые сертификаты в ЦС).

Поэтому, если вы используете собственный ЦС и серверу разрешено принимать сертификаты только от этого ЦС, серийный номер будет наиболее надежным (хотя и не очень запоминающимся) средством идентификации пользователя.

ssl_client_dn() returns text

Возвращает полное имя субъекта текущего клиентского сертификата, преобразуя символьные данные в текущую кодировку базы данных. Предполагается, что если в именах сертификатов используются символы вне ASCII, база данных тоже может представить эти символы. Если в базе данных используется кодировка SQL_ASCII, символы вне ASCII в имени будут представлены последовательностями UTF-8.

Результат выглядит так: /CN=Некто /C=Некая страна/O=Некая организация.

ssl_issuer_dn() returns text

Возвращает полное имя издателя текущего клиентского сертификата, преобразуя символьные данные в текущую кодировку базы данных. Преобразования кодировки осуществляются так же, как и в ssl_client_dn.

Сочетание возвращаемого значения этой функции с серийным номером сертификата однозначно идентифицирует сертификат.

Фактически эта функция полезна, только если в установленном на сервере файле ЦС содержатся сертификаты нескольких доверенных ЦС или если этот ЦС выдает сертификаты для промежуточных центров сертификации.

ssl_client_dn_field(fieldname text) returns text

Эта функция возвращает значение заданного поля в субъекте сертификата или NULL, если это поле отсутствует. Имена полей являются строковыми константами, которые преобразуются в идентификаторы объектов ASN1, используя базу данных объектов OpenSSL. Принимаются следующие значения:

commonName (alias CN)
surname (alias SN)
name
givenName (alias GN)
countryName (alias C)
localityName (alias L)
stateOrProvinceName (alias ST)
organizationName (alias O)
organizationalUnitName (alias OU)
title
description
initials
postalCode
streetAddress
generationQualifier
description
dnQualifier
x500UniqueIdentifier
pseudonym
role
emailAddress

Все эти поля являются необязательными, за исключением commonName. Какие из них будут включены в сертификат, а какие нет, полностью зависит от политики ЦС. Однако значение этих полей строго определено стандартами X.500 и X.509, поэтому им нельзя присваивать произвольное значение.

ssl_issuer_field(fieldname text) returns text

То же, что и ssl_client_dn_field, но для издателя, а не для субъекта сертификата.

ssl_extension_info() returns setof record

Предоставляет информацию о расширениях клиентского сертификата: имя расширения, значение расширения и является ли это расширение критическим.

Автор

Виктор Вагнер (vitus@cryptocom.ru), ООО «Криптоком»

Дмитрий Воронин (carriingfate92@yandex.ru)

Адрес электронной почты группы разработчиков OpenSSL в ООО «Криптоком»: (openssl@cryptocom.ru)

tablefunc

Модуль tablefunc содержит ряд функций, возвращающих таблицы (то есть множества строк). Эти функции полезны и сами по себе, и как примеры написания функций на C/RUST, возвращающих множество строк.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Предоставляемые функции

Функции, предоставляемые модулем tablefunc, перечислены в Таблице 31.

Таблица 31. Функции tablefunc

Функция

Описание

normal_rand ( numvals integer, mean float8, stddev float8 ) → setof float8

Выдает набор случайных значений с нормальным распределением.

crosstab ( sql text ) → setof record

Выдает «повернутую таблицу», содержащую имена строк плюс N столбцов значений, где N определяется типом строк, заданным в вызывающем запросе.

crosstabN ( sql text ) → setof table_crosstab_N

Выдает «повернутую таблицу», содержащую имена строк плюс N столбцов значений. Функции crosstab2, crosstab3 и crosstab4 предопределены, но можно создать дополнительные функции crosstabN, как описано ниже.

crosstab ( source_sql text, category_sql text ) → setof record

Выдает «повернутую таблицу» со столбцами значений, заданными вторым запросом.

crosstab ( sql text, N integer ) → setof record

Устаревшая версия crosstab(text). Параметр N теперь игнорируется, поскольку число столбцов всегда определяется вызывающим запросом.

connectby ( relname text, keyid_fld text, parent_keyid_fld text [, orderby_fld text ], start_with text, max_depth integer [, branch_delim text ] ) → setof record

Выдает представление структуры дерева иерархии.

normal_rand

normal_rand(int numvals, float8 mean, float8 stddev) returns setof float8

Функция normal_rand выдает набор случайных значений с нормальным распределением (распределение Гаусса).

Параметр numvals представляет собой количество значений, возвращаемых этой функцией. Параметр mean — это среднее (математическое ожидание) нормального распределения значений, а параметр stddev — стандартное отклонение нормального распределения значений.

Например, этот вызов запрашивает 1000 значений со средним 5 и стандартным отклонением 3:

test=# SELECT * FROM normal_rand(1000, 5, 3);
     normal_rand
----------------------
     1.56556322244898
     9.10040991424657
     5.36957140345079
   -0.369151492880995
    0.283600703686639
       .
       .
       .
     4.82992125404908
     9.71308014517282
     2.49639286969028
(1000 rows)

crosstab(text)

crosstab(text sql)
crosstab(text sql, int N)

Функция crosstab используется для создания «повернутых» отображений, в которых данные записываются вдоль строк, а не сверху вниз. Например, у нас могут быть данные вроде таких:

row1    val11
row1    val12
row1    val13
...
row2    val21
row2    val22
row2    val23
...

которые мы хотим отобразить как

row1    val11   val12   val13   ...
row2    val21   val22   val23   ...
...

Функция crosstab принимает в текстовом параметре запрос SQL, выдающий исходные данные, отформатированные первым способом, и выдает таблицу, отформатированную вторым способом.

Параметр sql представляет собой оператор SQL, выдающий исходный набор данных. Этот оператор должен возвращать один столбец row_name, один столбец category и один столбец value. Параметр N является устаревшим и игнорируется, если передается при вызове (раньше он должен был соответствовать числу выходных столбцов значений, но теперь это число определяется вызывающим запросом).

Например, приведенный запрос может выдавать результат вроде этого:

 row_name    cat    value
----------+-------+-------
  row1      cat1    val1
  row1      cat2    val2
  row1      cat3    val3
  row1      cat4    val4
  row2      cat1    val5
  row2      cat2    val6
  row2      cat3    val7
  row2      cat4    val8

Функция crosstab объявлена как возвращающая setof record, поэтому фактические имена и типы выходных столбцов должны определяться в предложении FROM вызывающего оператора SELECT, например так:

SELECT * FROM crosstab('...') AS ct(row_name text, category_1 text, category_2 text);

В этом примере выдается приблизительно такой результат:

           <== value  columns  ==>
 row_name   category_1   category_2
----------+------------+------------
  row1        val1         val2
  row2        val5         val6

Предложение FROM должно определять выводимые данные как один столбец row_name (с тем же типом данных, что и у первого результирующего столбца запроса SQL), за которым следуют N столбцов значений (все с тем же типом данных, что и третий результирующий столбец запроса SQL). Можно задать любое желаемое количество выходных столбцов значений, и имена выходных столбцов также определяете вы сами.

Функция crosstab выдает одну выходную строку для каждой последовательной группы входных строк с одним значением row_name. Она заполняет выходные столбцы значений слева направо полями value из этих строк. Если в группе оказывается меньше строк, чем выходных столбцов значений, лишние столбцы заполняются значениями NULL; если же строк оказывается больше, лишние строки игнорируются.

На практике в запросе SQL всегда следует указывать ORDER BY 1,2, чтобы входные строки были отсортированы должным образом, то есть чтобы значения с одинаковым row_name собирались вместе и правильно упорядочивались в строке. Заметьте, что сама crosstab не обращает никакого внимания на второй столбец в результате запроса; он присутствует только для упорядочивания, чтобы определять порядок, в котором значения третьего столбца будут находиться в строке.

Вот полный пример:

CREATE TABLE ct(id SERIAL, rowid TEXT, attribute TEXT, value TEXT);
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att1','val1');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att2','val2');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att3','val3');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att4','val4');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att1','val5');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att2','val6');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att3','val7');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att4','val8');

SELECT *
FROM crosstab(
  'select rowid, attribute, value
   from ct
   where attribute = ''att2'' or attribute = ''att3''
   order by 1,2')
AS ct(row_name text, category_1 text, category_2 text, category_3 text);

 row_name | category_1 | category_2 | category_3
----------+------------+------------+------------
 test1    | val2       | val3       |
 test2    | val6       | val7       |
(2 rows)

Можно избежать необходимости постоянно писать предложение FROM для определения выходных столбцов, собрав функцию crosstab под себя и зашив в ее определение желаемый тип выходной строки. Это описывается в следующем подразделе. Еще один вариант — включить требуемое предложение FROM в определение представления.

Примечание
Также изучите команду \crosstabview в psql, предоставляющую функциональность, схожую с crosstab().

crosstabN(text)

crosstabN(text sql)

Функции crosstabN являются примерами того, как можно создать собственные обертки универсальной функции crosstab, чтобы не приходилось писать имена и типы столбцов в вызывающем запросе SELECT. Модуль tablefunc содержит функции crosstab2, crosstab3 и crosstab4, у которых типы выходных строк определяются как

CREATE TYPE tablefunc_crosstab_N AS (
    row_name TEXT,
    category_1 TEXT,
    category_2 TEXT,
        .
        .
        .
    category_N TEXT
);

Таким образом, эти функции могут применяться непосредственно, когда входной запрос выдает столбцы row_name и value типа text и хочется получить 2, 3 или 4 выходных столбца значений. В остальном эти функции ведут себя точно так же, как описанная выше универсальная функция crosstab.

Так, пример, приведенный в предыдущем подразделе, сработает и в таком виде:

SELECT *
FROM crosstab3(
  'select rowid, attribute, value
   from ct
   where attribute = ''att2'' or attribute = ''att3''
   order by 1,2');

Эти функции предоставлены в основном в демонстрационных целях. Вы можете создать свои типы возвращаемых данных и функции на базе нижележащей функции crosstab(). Существует два способа это сделать:

Создать составной тип, описывающий желаемые выходные столбцы, подобно тому, как это делается в примерах в contrib/tablefunc/tablefunc--1.0.sql/. Затем определить уникальное имя для функции, принимающей один параметр text и возвращающей setof имя_вашего_типа, но связать его с той же нижележащей функцией crosstab на C/RUST. Например, если ваши исходные данные выдают имена строк типа text и значения типа float8, и вы хотите получить 5 столбцов значений:

CREATE TYPE my_crosstab_float8_5_cols AS (
    my_row_name text,
    my_category_1 float8,
    my_category_2 float8,
    my_category_3 float8,
    my_category_4 float8,
    my_category_5 float8
);

CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(text)
    RETURNS setof my_crosstab_float8_5_cols
    AS '$libdir/tablefunc','crosstab' LANGUAGE C STABLE STRICT;

Использовать выходные параметры (OUT), чтобы явно определить возвращаемый тип. Тот же пример можно реализовать и таким способом:

CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(
    IN text,
    OUT my_row_name text,
    OUT my_category_1 float8,
    OUT my_category_2 float8,
    OUT my_category_3 float8,
    OUT my_category_4 float8,
    OUT my_category_5 float8)
  RETURNS setof record
  AS '$libdir/tablefunc','crosstab' LANGUAGE C STABLE STRICT;

crosstab(text, text)

crosstab(text source_sql, text category_sql)

Главное ограничение формы crosstab с одним параметром состоит в том, что она интерпретирует все значения в группе одинаково, вставляя каждое значение в первый свободный столбец. Если вы хотите, чтобы столбцы значений соответствовали определенным категориям данных, а некоторые группы могли содержать данные не для всех категорий, этот подход не будет нормально работать. Форма crosstab с двумя параметрами решает эту задачу, предоставляя явный список категорий, соответствующих выходным столбцам.

Параметр source_sql — это оператор SQL, выдающий исходный набор данных. Этот оператор должен возвращать один столбец row_name, один столбец category и один столбец value. Также он может выдавать один и более «дополнительных» столбцов. Столбец row_name должен быть первым. Столбцы category и value должны быть последними двумя и именно в таком порядке. Все столбцы между row_name и category воспринимаются «дополнительные». Ожидается, что «дополнительные» столбцы будут содержать одинаковые значения для всех строк с одним значением row_name.

Например, source_sql может выдавать набор данных вроде этого:

SELECT row_name, extra_col, cat, value FROM foo ORDER BY 1;

 row_name    extra_col   cat    value
----------+------------+-----+---------
  row1         extra1    cat1    val1
  row1         extra1    cat2    val2
  row1         extra1    cat4    val4
  row2         extra2    cat1    val5
  row2         extra2    cat2    val6
  row2         extra2    cat3    val7
  row2         extra2    cat4    val8

Параметр category_sql — это оператор SQL, выдающий набор категорий. Этот оператор должен возвращать только один столбец. Он должен выдать как минимум одну строку, иначе произойдет ошибка. Кроме того, он не должен выдавать повторяющиеся значения, так как это тоже вызовет ошибку. Например, category_sql может выглядеть так:

SELECT DISTINCT cat FROM foo ORDER BY 1;
    cat
  -------
    cat1
    cat2
    cat3
    cat4

Функция crosstab объявлена как возвращающая тип setof record, поэтому фактические имена и типы выходных столбцов должны определяться в предложении FROM вызывающего оператора SELECT, например:

SELECT * FROM crosstab('...', '...')
    AS ct(row_name text, extra text, cat1 text, cat2 text, cat3 text, cat4 text);

При этом будет получен примерно такой результат:

                  <==  value  columns   ==>
row_name   extra   cat1   cat2   cat3   cat4
---------+-------+------+------+------+------
  row1     extra1  val1   val2          val4
  row2     extra2  val5   val6   val7   val8

В предложении FROM должно определяться требуемое количество выходных столбцов надлежащих типов данных. Если в результате запроса source_sql содержится N столбцов, первые N-2 из них должны соответствовать первым N-2 выходным столбцам. Оставшиеся выходные столбцы должны иметь тип последнего столбца результата запроса source_sql и их должно быть ровно столько, сколько строк содержится в результате запроса category_sql.

Функция crosstab выдает по одной выходной строке для каждой последовательной группы входных строк с одинаковым значением row_name. Выходной столбец row_name плюс все «дополнительные» столбцы копируются из первой строки группы. Выходные столбцы значений заполняются содержимым полей value из строк с соответствующими значениями category. Если значение поля category не соответствует ни одному выходному значению запроса category_sql, содержимое поля value в этой строке игнорируется. Выходные столбцы, для которых соответствующая категория не представлена ни в одной входной строке группы, заполняются значениями NULL.

На практике в запросе source_sql всегда следует указывать ORDER BY 1, чтобы все значения с одинаковым row_name гарантированно выводились вместе. При этом порядок категорий внутри группы не важен. Кроме того, крайне важно, чтобы порядок выходных данных запроса category_sql соответствовал заданному порядку выходных столбцов.

Ниже приводится два законченных примера:

create table sales(year int, month int, qty int);
insert into sales values(2007, 1, 1000);
insert into sales values(2007, 2, 1500);
insert into sales values(2007, 7, 500);
insert into sales values(2007, 11, 1500);
insert into sales values(2007, 12, 2000);
insert into sales values(2008, 1, 1000);

select * from crosstab(
  'select year, month, qty from sales order by 1',
  'select m from generate_series(1,12) m'
) as (
  year int,
  "Jan" int,
  "Feb" int,
  "Mar" int,
  "Apr" int,
  "May" int,
  "Jun" int,
  "Jul" int,
  "Aug" int,
  "Sep" int,
  "Oct" int,
  "Nov" int,
  "Dec" int
);
 year | Jan  | Feb  | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov  | Dec
------+------+------+-----+-----+-----+-----+-----+-----+-----+-----+------+------
 2007 | 1000 | 1500 |     |     |     |     | 500 |     |     |     | 1500 | 2000
 2008 | 1000 |      |     |     |     |     |     |     |     |     |      |
(2 rows)

CREATE TABLE cth(rowid text, rowdt timestamp, attribute text, val text);
INSERT INTO cth VALUES('test1','01 March 2003','temperature','42');
INSERT INTO cth VALUES('test1','01 March 2003','test_result','PASS');
INSERT INTO cth VALUES('test1','01 March 2003','volts','2.6987');
INSERT INTO cth VALUES('test2','02 March 2003','temperature','53');
INSERT INTO cth VALUES('test2','02 March 2003','test_result','FAIL');
INSERT INTO cth VALUES('test2','02 March 2003','test_startdate','01 March 2003');
INSERT INTO cth VALUES('test2','02 March 2003','volts','3.1234');

SELECT * FROM crosstab
(
  'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
  'SELECT DISTINCT attribute FROM cth ORDER BY 1'
)
AS
(
       rowid text,
       rowdt timestamp,
       temperature int4,
       test_result text,
       test_startdate timestamp,
       volts float8
);
 rowid |          rowdt           | temperature | test_result |      test_startdate      | volts
-------+--------------------------+-------------+-------------+--------------------------+--------
 test1 | Sat Mar 01 00:00:00 2003 |          42 | PASS        |                          | 2.6987
 test2 | Sun Mar 02 00:00:00 2003 |          53 | FAIL        | Sat Mar 01 00:00:00 2003 | 3.1234
(2 rows)

Чтобы не выписывать в каждом запросе имена и типы результирующих столбцов, можно создать предопределенные функции. См. примеры в предыдущем подразделе. Нижележащая функция на C/RUST для этой формы crosstab называется crosstab_hash.

connectby

connectby(text relname, text keyid_fld, text parent_keyid_fld
          [, text orderby_fld ], text start_with, int max_depth
          [, text branch_delim ])

Функция connectby выдает отображение хранящихся в таблице данных иерархической структуры. Эта таблица должна содержать поле ключа, однозначно идентифицирующее строки, и поле ключа родителя, ссылающееся на родителя каждой строки (если он есть). Функция connectby может отобразить вложенное дерево, начиная с любой строки.

Параметры функции описаны в Таблице 29.

Таблица 32. Параметры connectby

Параметр | Описание
relname | Имя исходного отношения
keyid_fld | Имя поля ключа
parent_keyid_fld | Имя поля ключа родителя
orderby_fld | Имя поля, по которому сортируются потомки (необязательно)
start_with | Значение ключа начальной строки
max_depth | Максимальная глубина, на которую можно погрузиться, или ноль для неограниченной глубины
branch_delim | Строка, разделяющая ключи в выводе ветви (необязательно)

Поля ключа и ключа родителя могут быть любого типа, но он у них должен быть одинаковым. Обратите внимание, что значение start_with должно задаваться текстовой строкой, независимо от типа поля ключа.

Функция connectby объявлена как возвращающая setof record, поэтому фактические имена и типы выходных столбцов должны определяться в предложении FROM вызывающего оператора SELECT, например:

SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~')
    AS t(keyid text, parent_keyid text, level int, branch text, pos int);

Первые два выходных столбца используются для вывода ключа текущей строки и ключа ее родительской строки; их тип должен соответствовать типу поля ключа таблицы. В третьем выходном столбце выводится глубина в дереве, и он должен иметь тип integer. Если передается параметр branch_delim, в следующем выходном столбце отображаются ветви, и он должен иметь тип text. Наконец, если передается параметр orderby_fld, в последнем выходном столбце выводится порядковый номер, и он должен иметь тип integer.

В выходном столбце «ветви» показывается путь, приведший ключи к текущей строке. Ключи разделяются заданной строкой branch_delim. Если выводить ветви не требуется, опустите параметр branch_delim и столбец ветви в списке выходных столбцов.

Если порядок потомков одного родителя важен, добавьте параметр orderby_fld для указания поля, по которому будут упорядочены потомки. Это поле может иметь любой сортируемый тип данных. Последним в списке выходных столбцов должен идти целочисленный столбец с порядковыми номерами, если и только если передается параметр orderby_fld.

Параметры, представляющие имена таблицы и полей, копируются как есть в запросы SQL, которые connectby генерирует внутри. Поэтому их следует заключить в кавычки, если они содержат буквы в разном регистре или специальные символы. Также может понадобиться дополнить имя таблицы схемой.

С большими таблицами производительность будет низкой, если не создать индекс по полю с ключом родителя.

Важно, чтобы строка branch_delim не содержалась в значениях ключа, иначе connectby может некорректно сообщить об ошибке бесконечной вложенности. Обратите внимание, что если branch_delim не передается, для выявления рекурсии используется символ по умолчанию ~.

Пример:

CREATE TABLE connectby_tree(keyid text, parent_keyid text, pos int);

INSERT INTO connectby_tree VALUES('row1',NULL, 0);
INSERT INTO connectby_tree VALUES('row2','row1', 0);
INSERT INTO connectby_tree VALUES('row3','row1', 0);
INSERT INTO connectby_tree VALUES('row4','row2', 1);
INSERT INTO connectby_tree VALUES('row5','row2', 0);
INSERT INTO connectby_tree VALUES('row6','row4', 0);
INSERT INTO connectby_tree VALUES('row7','row3', 0);
INSERT INTO connectby_tree VALUES('row8','row6', 0);
INSERT INTO connectby_tree VALUES('row9','row5', 0);

-- с ветвями, без orderby_fld (порядок результатов не гарантируется)
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0, '~')
 AS t(keyid text, parent_keyid text, level int, branch text);
 keyid | parent_keyid | level |       branch
-------+--------------+-------+---------------------
 row2  |              |     0 | row2
 row4  | row2         |     1 | row2~row4
 row6  | row4         |     2 | row2~row4~row6
 row8  | row6         |     3 | row2~row4~row6~row8
 row5  | row2         |     1 | row2~row5
 row9  | row5         |     2 | row2~row5~row9
(6 rows)

-- без ветвей и без orderby_fld (порядок результатов не гарантируется)
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0)
 AS t(keyid text, parent_keyid text, level int);
 keyid | parent_keyid | level
-------+--------------+-------
 row2  |              |     0
 row4  | row2         |     1
 row6  | row4         |     2
 row8  | row6         |     3
 row5  | row2         |     1
 row9  | row5         |     2
(6 rows)

-- с ветвями и с orderby_fld (обратите внимание, что row5 идет перед row4)
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~')
 AS t(keyid text, parent_keyid text, level int, branch text, pos int);
 keyid | parent_keyid | level |       branch        | pos
-------+--------------+-------+---------------------+-----
 row2  |              |     0 | row2                |   1
 row5  | row2         |     1 | row2~row5           |   2
 row9  | row5         |     2 | row2~row5~row9      |   3
 row4  | row2         |     1 | row2~row4           |   4
 row6  | row4         |     2 | row2~row4~row6      |   5
 row8  | row6         |     3 | row2~row4~row6~row8 |   6
(6 rows)

-- без ветвей, с orderby_fld (обратите внимание, что row5 идет перед row4)
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0)
 AS t(keyid text, parent_keyid text, level int, pos int);
 keyid | parent_keyid | level | pos
-------+--------------+-------+-----
 row2  |              |     0 |   1
 row5  | row2         |     1 |   2
 row9  | row5         |     2 |   3
 row4  | row2         |     1 |   4
 row6  | row4         |     2 |   5
 row8  | row6         |     3 |   6
(6 rows)

Автор

Джо Конвэй (Joe Conway)

tcn

Модуль tcn предоставляет триггерную функцию, уведомляющую блоки прослушивания об изменениях в любой таблице, к которой она привязана. Она должна использоваться в качестве триггера AFTER вида FOR EACH ROW.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Этой функции в операторе CREATE TRIGGER может передаваться только один параметр, и он является необязательным. Если этот параметр присутствует, он будет использован для указания имени канала для уведомлений. Если он отсутствует, именем канала будет tcn.

Информационное наполнение уведомлений состоит из имени таблицы, буквы, обозначающей тип выполняемой операции, и пар имя столбца/значение для столбцов первичного ключа. Каждая часть отделяется от следующей запятой. Для упрощения синтаксического анализа сообщения регулярными выражениями имена таблицы и столбцов всегда заключаются в кавычки, а значения данных — в апострофы. Внутренние кавычки и апострофы дублируются.

Далее следует краткий пример использования расширения.

test=# create table tcndata
test-#   (
test(#     a int not null,
test(#     b date not null,
test(#     c text,
test(#     primary key (a, b)
test(#   );
CREATE TABLE
test=# create trigger tcndata_tcn_trigger
test-#   after insert or update or delete on tcndata
test-#   for each row execute function triggered_change_notification();
CREATE TRIGGER
test=# listen tcn;
LISTEN
test=# insert into tcndata values (1, date '2012-12-22', 'one'),
test-#                            (1, date '2012-12-23', 'another'),
test-#                            (2, date '2012-12-23', 'two');
INSERT 0 3
Asynchronous notification "tcn" with payload ""tcndata",I,"a"='1',"b"='2012-12-22'" received from server process with PID 22770.
Asynchronous notification "tcn" with payload ""tcndata",I,"a"='1',"b"='2012-12-23'" received from server process with PID 22770.
Asynchronous notification "tcn" with payload ""tcndata",I,"a"='2',"b"='2012-12-23'" received from server process with PID 22770.
test=# update tcndata set c = 'uno' where a = 1;
UPDATE 2
Asynchronous notification "tcn" with payload ""tcndata",U,"a"='1',"b"='2012-12-22'" received from server process with PID 22770.
Asynchronous notification "tcn" with payload ""tcndata",U,"a"='1',"b"='2012-12-23'" received from server process with PID 22770.
test=# delete from tcndata where a = 1 and b = date '2012-12-22';
DELETE 1
Asynchronous notification "tcn" with payload ""tcndata",D,"a"='1',"b"='2012-12-22'" received from server process with PID 22770.

test_decoding

test_decoding представляет собой пример плагина вывода логического декодирования. Он не делает ничего особенно полезного, но может служить отправной точкой для разработки собственного плагина вывода.

test_decoding получает WAL через механизм логического декодирования и дешифрует его в текстовые представления выполняемых операций.

Типичный вывод этого плагина, используемого через интерфейс логического декодирования SQL, может выглядеть так:

qhb=# SELECT * FROM pg_logical_slot_get_changes('test_slot', NULL, NULL, 'include-xids', '0');
   lsn     | xid |                       data
-----------+-----+--------------------------------------------------
 0/16D30F8 | 691 | BEGIN
 0/16D32A0 | 691 | table public.data: INSERT: id[int4]:2 data[text]:'arg'
 0/16D32A0 | 691 | table public.data: INSERT: id[int4]:3 data[text]:'demo'
 0/16D32A0 | 691 | COMMIT
 0/16D32D8 | 692 | BEGIN
 0/16D3398 | 692 | table public.data: DELETE: id[int4]:2
 0/16D3398 | 692 | table public.data: DELETE: id[int4]:3
 0/16D3398 | 692 | COMMIT
(8 rows)

Также можно получить информацию об изменениях в текущей транзакции, и типичный вывод может выглядеть так:

qhb[33712]=#* SELECT * FROM pg_logical_slot_get_changes('test_slot', NULL, NULL, 'stream-changes', '1');
    lsn    | xid |                       data
-----------+-----+--------------------------------------------------
 0/16B21F8 | 503 | opening a streamed block for transaction TXN 503
 0/16B21F8 | 503 | streaming change for TXN 503
 0/16B2300 | 503 | streaming change for TXN 503
 0/16B2408 | 503 | streaming change for TXN 503
 0/16BEBA0 | 503 | closing a streamed block for transaction TXN 503
 0/16B21F8 | 503 | opening a streamed block for transaction TXN 503
 0/16BECA8 | 503 | streaming change for TXN 503
 0/16BEDB0 | 503 | streaming change for TXN 503
 0/16BEEB8 | 503 | streaming change for TXN 503
 0/16BEBA0 | 503 | closing a streamed block for transaction TXN 503
(10 rows)

tsm_system_rows

Модуль tsm_system_rows предоставляет метод взятия выборки из таблицы SYSTEM_ROWS, который можно использовать в предложении TABLESAMPLE команды SELECT.

Этот метод взятия выборки из таблицы принимает один целочисленный аргумент, задающий максимальное количество строк для прочтения. Результирующая выборка всегда будет содержать ровно столько строк, если только в таблице не окажется недостаточно строк — в таком случае выбирается вся таблица.

Как и встроенный метод взятия выборки SYSTEM, SYSTEM_ROWS производит взятие выборки на уровне блоков, так что выборка не будет полностью случайной, а может подвергаться эффектам кластеризации, особенно если запрашивается небольшое количество строк.

SYSTEM_ROWS не поддерживает предложение REPEATABLE.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Примеры

Ниже приведен пример отбора выборки из таблицы с помощью SYSTEM_ROWS. Сначала установим расширение:

CREATE EXTENSION tsm_system_rows;

Затем его можно использовать в команде SELECT, например так:

SELECT * FROM my_table TABLESAMPLE SYSTEM_ROWS(100);

Эта команда возвратит выборку из 100 строк из таблицы my_table (а если в таблице не окажется 100 видимых строк, будут возвращены все строки).

tsm_system_time

Модуль tsm_system_time предоставляет метод взятия выборки из таблицы SYSTEM_TIME, который можно использовать в предложении TABLESAMPLE команды SELECT.

Этот метод взятия выборки из таблицы принимает один целочисленный аргумент, задающий максимальное время в миллисекундах, которое отводится на чтение таблицы. Это обеспечивает возможность непосредственно управлять продолжительностью выполнения запроса за счет того, что размер выборки становится трудно предсказать. Результирующая выборка будет содержать столько строк, сколько удастся прочитать за заданное время, если только раньше не будет прочитана вся таблица.

Как и встроенный метод взятия выборки SYSTEM, SYSTEM_TIME производит взятие выборки на уровне блоков, так что выборка не будет полностью случайной, а может подвергаться эффектам кластеризации, особенно если запрашивается небольшое количество строк.

SYSTEM_TIME не поддерживает предложение REPEATABLE.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Примеры

Ниже приведен пример отбора выборки из таблицы с помощью SYSTEM_TIME. Сначала установим расширение:

CREATE EXTENSION tsm_system_time;

Затем его можно использовать в команде SELECT, например так:

SELECT * FROM my_table TABLESAMPLE SYSTEM_TIME(1000);

Эта команда возвратит такой объем выборки из my_table, какой удастся прочитать за 1 секунду (1000 миллисекунд). Конечно, если за 1 секунду можно прочитать всю таблицу, будут возвращены все ее строки.

unaccent

Модуль unaccent представляет собой словарь текстового поиска, убирающий надбуквенные (диакритические) знаки из лексем. Это фильтрующий словарь, что означает, что его выходные данные всегда передаются следующему словарю (если таковой имеется), что отличается от обычного поведения словарей. Это позволяет выполнять полнотекстовый поиск без учета знаков ударения (диакритик).

Текущую реализацию unaccent нельзя использовать в качестве нормализующего словаря для словаря thesaurus.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Конфигурация

Словарь unaccent принимает следующие параметры:

Параметр RULES задает базовое имя файла, содержащего список правил преобразования. Этот файл должен храниться в каталоге $SHAREDIR/tsearch_data/ (где под $SHAREDIR подразумевается каталог с общими данными установки QHB). Имя файла должно заканчиваться расширением .rules (которое не нужно указывать в параметре RULES).

Файл правил имеет следующий формат:

Каждая строка представляет одно правило преобразования, состоящее из символа с диакритикой и следующим за ним символом без диакритики. Первый символ преобразуется во второй. Например,

À        A
Á        A
Â        A
Ã        A
Ä        A
Å        A
Æ        AE

Эти два символа должны разделяться пробелом, а любые начальные или конечные пробелы в строке игнорируются.

Как вариант, если в строке задан всего один символ, вхождения этого символа удаляются; это полезно в языках, где диакритики представляются отдельными символами.

Фактически «символом» может быть любая строка, не содержащая пробелов, поэтому, помимо удаления диакритик, словари unaccent можно использовать и для других видов замены подстрок.

Как и другие файлы конфигурации текстового поиска в QHB, файл правил должен храниться в кодировке UTF-8. При загрузке данные из него будут автоматически преобразованы в кодировку текущей базы данных. Все строки, содержащие непереводимые символы, молча игнорируются, поэтому файлы правил могут содержать правила, неприменимые в текущей кодировке.

Более полный экземпляр правил, непосредственно пригодный для большинства европейских языков, можно найти в файле unaccent.rules, который при установке модуля unaccent помещается в каталог $SHAREDIR/tsearch_data/. Этот файл правил преобразует символы с диакритиками в те же символы без диакритик, а также разворачивает лигатуры в равнозначные последовательности простых символов (например Æ в AE).

Применение

При установке расширения unaccent создается шаблон текстового поиска unaccent и словарь unaccent на его основе. Для словаря unaccent по умолчанию задается параметр RULES='unaccent', благодаря чему его можно сразу использовать со стандартным файлом unaccent.rules. При желании этот параметр можно изменить, например так:

mydb=# ALTER TEXT SEARCH DICTIONARY unaccent (RULES='my_rules');

или создать новые словари на базе этого шаблона.

Чтобы протестировать этот словарь можно попробовать следующее:

mydb=# select ts_lexize('unaccent','Hôtel');
 ts_lexize
-----------
 {Hotel}
(1 row)

Приведенный ниже пример показывает, как добавить словарь unaccent в конфигурацию текстового поиска:

mydb=# CREATE TEXT SEARCH CONFIGURATION fr ( COPY = french );
mydb=# ALTER TEXT SEARCH CONFIGURATION fr
        ALTER MAPPING FOR hword, hword_part, word
        WITH unaccent, french_stem;
mydb=# select to_tsvector('fr','Hôtels de la Mer');
    to_tsvector
-------------------
 'hotel':1 'mer':4
(1 row)

mydb=# select to_tsvector('fr','Hôtel de la Mer') @@ to_tsquery('fr','Hotels');
 ?column?
----------
 t
(1 row)

mydb=# select ts_headline('fr','Hôtel de la Mer',to_tsquery('fr','Hotels'));
      ts_headline
------------------------
 <b>Hôtel</b> de la Mer
(1 row)

Функции

Функция unaccent() удаляет надбуквенные (диакритические) знаки из заданной строки. По сути это обертка вокруг словарей типа unaccent, но ее можно использовать и вне обычного контекста текстового поиска.

unaccent([словарь regdictionary, ] строка text) returns text

Если аргумент словарь опущен, применяется словарь текстового поиска с именем unaccent, находящийся в той же схеме, что и сама функция unaccent().

Например:

SELECT unaccent('unaccent', 'Hôtel');
SELECT unaccent('Hôtel');

uuid-ossp

Модуль uuid-ossp предоставляет функции для генерирования универсальных уникальных идентификаторов (UUID) по одному из нескольких стандартных алгоритмов. Также в нем есть функции, выдающие определенные специальные константы UUID. Этот модуль необходим только в случае специальных требований module за пределами функциональности, доступной в ядре QHB. Встроенные в ядро способы генерирования UUID см. в разделе Функции генерирования UUID.

Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.

Функции uuid-ossp

В Таблице 33 показаны имеющиеся в модуле функции для генерирования UUID. В соответствующих стандартах ITU-T Rec. X.667, ISO/IEC 9834-8:2005 и RFC 4122 указаны четыре алгоритма для генерирования UUID, обозначаемые номерами версий 1, 3, 4 и 5. (Алгоритма версии 2 не существует.) Каждый из этих алгоритмов предназначен для различных групп приложений.

Таблица 33. Функции для генерирования UUID

Функция

Описание

uuid_generate_v1 () → uuid

Генерирует UUID версии 1. Этот UUID включает в себя MAC-адрес компьютера и метку времени. Обратите внимание, что UUID такого типа раскрывают реквизиты компьютера, создавшего идентификатор, и время создания, что может быть неприемлемым для определенных приложений, где требуется повышенная безопасность данных.

uuid_generate_v1mc () → uuid

Генерирует UUID версии 1, но вместо реального MAC-адреса компьютера использует случайный групповой MAC-адрес.

uuid_generate_v3 ( namespace uuid, name text ) → uuid

Генерирует UUID версии 3 в заданном пространстве имен, используя указанное имя. Пространство имен должно задаваться одной из специальных констант, которые выдаются функциями uuid_ns_*(), перечисленными в Таблице 34. (Хотя теоретически это может быть любой UUID.) Имя задает идентификатор в выбранном пространстве имен.
Например:
SELECT uuid_generate_v3(uuid_ns_url(), 'http://www.postgresql.org');
Из параметра name будет получен хеш MD5, так что из сгенерированного UUID нельзя будет извлечь открытый текст. В генерировании UUID таким методом нет элемента случайности или зависимости от среды, поэтому его можно воспроизвести.

uuid_generate_v4 () → uuid

Генерирует UUID версии 4, который полностью выводится из случайных чисел.

uuid_generate_v5 ( namespace uuid, name text ) → uuid

Генерирует UUID версии 5, который похож на версию 3, но хеширование проводится по алгоритму SHA-1. Версия 5 предпочтительнее версии 3, так как SHA-1 считается более безопасным, чем MD5.

Таблица 34. Функции, возвращающие константы UUID

Функция

Описание

uuid_nil () → uuid

Возвращает «нулевую» константу UUID, которая не считается настоящим UUID.

uuid_ns_dns () → uuid

Возвращает константу, обозначающую пространство имен DNS для UUID.

uuid_ns_url () → uuid

Возвращает константу, обозначающую пространство имен URL для UUID.

uuid_ns_oid () → uuid

Возвращает константу, обозначающую пространство имен идентификаторов объектов ISO (OID, object identifier) для UUID. (Здесь имеются в виду OID ASN.1, которые не имеют отношения к OID, используемым в QHB.)

uuid_ns_x500 () → uuid

Возвращает константу, обозначающую пространство имен с различаемыми именами (DN, distinguished name) X.500 для UUID.

Сборка uuid-ossp

Исторически этот модуль зависел от библиотеки OSSP UUID, что отразилось в его имени. Хотя библиотеку OSSP UUID все еще можно найти по адресу http://www.ossp.org/pkg/lib/uuid/, она плохо поддерживается и ее становится все сложнее портировать на более новые платформы. Поэтому теперь на некоторых платформах модуль uuid-ossp можно собирать без библиотеки OSSP. Во FreeBSD, NetBSD и некоторых других платформах на базе BSD подходящие функции создания UUID включены в корневую библиотеку libc. В Linux, macOS и некоторых других платформах подходящие функции предоставляются библиотекой libuuid, которая изначально пришла из проекта e2fsprogs (хотя в современном Linux она считается частью util-linux-g). Вызывая configure, укажите ключ --with-uuid=bsd, чтобы использовать функции BSD, или --with-uuid=e2fs, чтобы использовать libuuid из e2fsprogs, или --with-uuid=ossp, чтобы использовать библиотеку OSSP UUID. На конкретной платформе может быть установлено сразу несколько библиотек, поэтому configure не выбирает библиотеку автоматически.

Автор

Питер Эйзентраут (Peter Eisentraut), peter_e@gmx.net

xml2

Модуль xml2 предоставляет функциональные возможности для выполнения запросов XPath и преобразований XSLT.

Уведомление об исключении из числа рекомендованных

В QHB функциональность, связанная с XML, основана на стандарте SQL/XML и находится в ядре сервера. Эта функциональность охватывает проверку синтаксиса XML и запросы XPath, что как раз и делает этот модуль, но он имеет совершенно несовместимый API. Этот модуль планируется удалить в будущей версии QHB в пользу более нового стандартного API, поэтому мы рекомендуем вам попробовать перевести свои приложения на новый API.

Описание функций

Функции, предоставляемые этим модулем, описаны в Таблице 35. Эти функции обеспечивают выполнение простого разбора XML и запросов XPath.

Таблица 35. Функции xml2

Функция

Описание

xml_valid ( document text ) → boolean

Разбирает переданный документ и возвращает true, если это правильно сформированный XML. (Примечание: это псевдоним стандартной функции QHB xml_is_well_formed(). Имя xml_valid() технически некорректно, поскольку понятия допустимости (valid) и правильности формата (well-formed) в XML имеют разное значение.)

xpath_string ( document text, query text ) → text

Анализирует запрос XPath для предоставленного документа и приводит результат к типу text.

xpath_number ( document text, query text ) → real

Анализирует запрос XPath для предоставленного документа и приводит результат к типу real.

xpath_bool ( document text, query text ) → boolean

Анализирует запрос XPath для предоставленного документа и приводит результат к типу boolean.

xpath_nodeset ( document text, query text, toptag text, itemtag text ) → text

Анализирует запрос для документа и обрамляет результат XML-тегами. Если результат содержит несколько значений, выходные данные будут выглядеть так:

\
\Value 1 which could be an XML fragment\
\Value 2....\
\

Если toptag или itemtag — пустая строка, соответствующий тег опускается.

xpath_nodeset ( document text, query text, itemtag text ) → text

Подобна xpath_nodeset(document, query, toptag, itemtag), но выводит результат без toptag.

xpath_nodeset ( document text, query text ) → text

Подобна xpath_nodeset(document, query, toptag, itemtag), но выводит результат без обоих тегов.

xpath_list ( document text, query text, separator text ) → text

Анализирует запрос для документа и возвращает несколько значений, вставляя между ними заданный разделитель, например: Значение 1,Значение 2,Значение 3, если separator равен знаку «,».

xpath_list ( document text, query text ) → text

Это обертка предыдущей функции, устанавливающая в качестве разделителя знак «,».

xpath_table

xpath_table(text key, text document, text relation, text xpaths, text criteria) returns setof record

xpath_table — это табличная функция, обрабатывающая набор запросов XPath для каждого из набора документов и возвращающая результаты в виде таблицы. Поле первичного ключа из таблицы оригинального документа возвращается в виде первого столбца результата, поэтому результирующий набор можно сразу использовать в соединениях. Параметры функции описаны в Таблице 36.

Таблица 36. Параметры xpath_table

Параметр | Описание
key | имя «ключевого» поля — это просто поле, содержимым которого будет заполнен первый столбец выходной таблицы, т. е. оно указывает на запись, из которой была получена каждая выходная строка (см. примечание о нескольких значениях ниже)
document | имя поля, содержащего XML-документ
relation | имя таблицы (или представления), содержащей документы
xpaths | одно или несколько выражений XPath, разделенных символом |
criteria | содержимое предложения WHERE. Этот параметр нельзя опустить, поэтому если хотите обработать все строки в отношении, напишите true или 1=1

Эти параметры (за исключением строк XPath) просто подставляются в обычный оператор SQL SELECT, так что у вас есть некоторая гибкость — оператор выглядит так:

SELECT <ключ>, <документ> FROM <отношение> WHERE <критерий>

поэтому в этих параметрах можно передать всё, что будет допустимо в этих позициях. Этот SELECT должен возвращать в результате ровно два столбца (что он и будет делать, если только вы не попытаетесь перечислить несколько полей для ключа или документа). Учтите, что при таком примитивном подходе вам обязательно нужно проверять все значения, получаемые от пользователя, во избежание атак с внедрением SQL-кода.

Эта функция предназначена для использования в выражении FROM, с предложением AS, задающим выходные столбцы; например:

SELECT * FROM
xpath_table('article_id',
            'article_xml',
            'articles',
            '/article/author|/article/pages|/article/title',
            'date_entered > ''2003-01-01'' ')
AS t(article_id integer, author text, page_count integer, title text);

Предложение AS определяет имена и типы столбцов в выходной таблице. Первым идет «ключевое» поле, а за ним поля, соответствующие запросам XPath. Если запросов XPath больше, чем результирующих столбцов, лишние запросы будут игнорироваться. Если же результирующих столбцов больше, чем запросов XPath, лишние столбцы будут заполнены значением NULL.

Обратите внимание, что в этом примере результирующий столбец page_count определен как целочисленный. Эта функция внутри имеет дело со стоковыми представлениями, поэтому когда вы указываете, что в выходных данных хотите получить целое число, она берет строковое представление результата XPath и использует функции ввода QHB, чтобы преобразовать его в целое число (или в тот тип, который указан в предложении AS). Если она не сможет это сделать, произойдет ошибка — например, если результат пустой, — поэтому если вы считаете, что с данными могут возникнуть проблемы, возможно, стоит просто оставить для такого столбца тип text.

Вызывающий оператор SELECT необязательно должен быть простым SELECT * — он может обращаться к выходным столбцам по именам или соединять их с другими таблицами. Эта функция выдает виртуальную таблицу, с которой вы можете выполнять любые операции, какие пожелаете (например агрегацию, соединение, сортировку данных и т. д.). Поэтому возможен и такой запрос:

SELECT t.title, p.fullname, p.email
FROM xpath_table('article_id', 'article_xml', 'articles',
                 '/article/title|/article/author/@id',
                 'xpath_string(article_xml,''/article/@date'') > ''2003-03-20'' ')
       AS t(article_id integer, title text, author_id integer),
     tblPeopleInfo AS p
WHERE t.author_id = p.person_id;

в качестве более сложного примера. Разумеется, для удобства весь этот запрос можно завернуть в представление.

Результаты с несколькими значениями

Функция xpath_table предполагает, что результаты каждого запроса XPath могут состоять из нескольких значений, поэтому количество возвращенных этой функцией строк может не совпадать с количеством входных документов. Первая возвращенная строка содержит первый результат каждого запроса, вторая — второй результат и т. д. Если один запросов возвращает меньше значений, чем другие, вместо недостающих значений будет возвращаться NULL.

В некоторых случаях пользователь будет знать, что данный запрос XPath возвратить только один результат (возможно, уникальный идентификатор документа), — если он используется рядом с запросом XPath, возвращающим несколько результатов, результат с одним значением появится только в первой строке результата. Чтобы решить эту проблему, можно использовать поле ключа и соединить результат с более простым запросом XPath. Например:

CREATE TABLE test (
    id int PRIMARY KEY,
    xml text
);

INSERT INTO test VALUES (1, '<doc num="C1">
<line num="L1"><a>1</a><b>2</b><c>3</c></line>
<line num="L2"><a>11</a><b>22</b><c>33</c></line>
</doc>');

INSERT INTO test VALUES (2, '<doc num="C2">
<line num="L1"><a>111</a><b>222</b><c>333</c></line>
<line num="L2"><a>111</a><b>222</b><c>333</c></line>
</doc>');

SELECT * FROM
  xpath_table('id','xml','test',
              '/doc/@num|/doc/line/@num|/doc/line/a|/doc/line/b|/doc/line/c',
              'true')
  AS t(id int, doc_num varchar(10), line_num varchar(10), val1 int, val2 int, val3 int)
WHERE id = 1 ORDER BY doc_num, line_num

 id | doc_num | line_num | val1 | val2 | val3
----+---------+----------+------+------+------
  1 | C1      | L1       |    1 |    2 |    3
  1 |         | L2       |   11 |   22 |   33

Чтобы получить doc_num в каждой строке, можно вызвать xpath_table дважды и соединить результаты:

SELECT t.*,i.doc_num FROM
  xpath_table('id', 'xml', 'test',
              '/doc/line/@num|/doc/line/a|/doc/line/b|/doc/line/c',
              'true')
    AS t(id int, line_num varchar(10), val1 int, val2 int, val3 int),
  xpath_table('id', 'xml', 'test', '/doc/@num', 'true')
    AS i(id int, doc_num varchar(10))
WHERE i.id=t.id AND i.id=1
ORDER BY doc_num, line_num;

 id | line_num | val1 | val2 | val3 | doc_num
----+----------+------+------+------+---------
  1 | L1       |    1 |    2 |    3 | C1
  1 | L2       |   11 |   22 |   33 | C1
(2 rows)

Функции XSLT

Если установлена libxslt, доступны следующие функции:

xslt_process

xslt_process(text document, text stylesheet, text paramlist) returns text

Эта функция применяет стилевое оформление XSL к документу и возвращает результат преобразования. Параметр paramlist — это список присваиваний значений параметрам, которые будут использоваться в преобразовании, заданных в форме a=1,b=2. Обратите внимание, что разбор параметров выполнен очень просто: значения параметров не могут содержать запятые!

Также есть версия xslt_process с двумя параметрами, которая не передает преобразованию никакие параметры.

Автор

Джон Грей (John Gray), jgray@azuli.co.uk

Разработку этого модуля спонсировала компания Torchbox Ltd. (www.torchbox.com). Этот модуль выпускается под той же лицензией BSD, что и PostgreSQL.

Клиентские приложения

В этом разделе рассматриваются клиентские приложения QHB, размещенные в share/extension. Их можно запускать откуда угодно, независимо от того, где находится сервер баз данных. Информацию о клиентских приложениях, являющихся частью ядра дистрибутива QHB, см. в разделе Клиентские приложения QHB.

oid2name — разрешить OID и номера файловых узлов в каталоге данных QHB

vacuumlo — удалить потерянные большие объекты из базы данных QHB

oid2name

oid2name — разрешить OID и номера файловых узлов в каталоге данных QHB

Синтаксис

oid2name [параметр..]

Описание

oid2name — это программа-утилита, помогающая администраторам изучать файловую структуру QHB. Чтобы использовать ее, необходимо понимать файловую структуру базы данных, описанную в главе Физическое хранилище базы данных.

Примечание
Название «oid2name» является историческим и на самом деле вводит в заблуждение, поскольку при ее использовании вы по большей части будете иметь дело с номерами файловых узлов таблиц (которые представляют собой имена файлов, видимые в каталогах баз данных). Необходимо точно понимать разницу между OID таблиц и номерами файловых узлов таблиц!

Программа oid2name подключается к целевой базе данных и извлекает информацию об OID, номерах файловых узлов и/или именах таблиц. С ее помощью можно также просмотреть OID базы данных или табличного пространства.

Параметры

oid2name принимает следующие аргументы командной строки:

-f файловый_узел
--filenode=файловый_узел
Показать информацию о таблице, к которой относится файловый_узел.

-i
--indexes
Включить в распечатку индексы и последовательности.

-o oid
--oid=oid
Показать информацию о таблице с OID, равным oid.

-q
--quiet
Не выводить заголовки (полезно для скриптов).

-s
--tablespaces
Показать OID табличных пространств.

-S
--system-objects
Включить в распечатку системные объекты (те, что находятся в схемах information_schema, pg_toast и pg_catalog).

-t шаблон_имен_таблиц
--table=шаблон_имен_таблиц
Показать информацию о таблицах, соответствующих шаблону_имен_таблиц.

-V
--version
Вывести версию oid2name и завершиться.

-x
--extended
Отобразить более подробную информацию о каждом показываемом объекте: имя табличного пространства, имя схемы и OID.

-?
--help
Показать справку об аргументах командной строки oid2name и завершиться.

Кроме того, в качестве параметров подключения oid2name принимает следующие аргументы командной строки:

-d база_данных
--dbname=база_данных
База данных для подключения.

-h хост
--host=хост
Имя хост-компьютера, на котором работает сервер баз данных.

-H хост
Имя хост-компьютера, на котором работает сервер баз данных. Этот параметр считается устаревшим.

-p порт
--port=порт
Порт сервера баз данных.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

Для отображения информации об определенных таблицах выберите их с аргументом -o, -f и/или -t. Параметр -o принимает OID, -f — файловый узел, а -t — имя таблицы (на самом деле это шаблон LIKE, поэтому в нем можно задать что-то вроде foo%). Эти аргументы можно использовать в любом количестве; в распечатку будут включены все объекты, соответствующие любым из этих указаний. Но учтите, что эти параметры будут выбирать только объекты в базе данных, заданной ключом -d.

Если вы не задали -o, -f или -t, но передали -d, будут перечислены все таблицы в базе данных с именем, заданным в -d. В этом режиме набором выводимых сведений управляют параметры -S и -i.

Если вы не задали и аргумент -d, будет показан список OID баз данных. Как вариант, можно передать аргумент -s, чтобы получить список табличных пространств.

Переменные среды

PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию.

Эта утилита, как и большинство других утилит QHB, также использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Переменная среды PG_COLOR указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Программе oid2name требуется работающий сервер с исправными системными каталогами. Таким образом, в ситуациях восстановления после катастрофического повреждения базы данных ее использование ограничено.

Примеры

$ # Что вообще есть на этом сервере баз данных?
$ oid2name
All databases:
    Oid  Database Name  Tablespace
----------------------------------
  17228       alvherre  pg_default
  17255     regression  pg_default
  17227      template0  pg_default
      1      template1  pg_default

$ oid2name -s
All tablespaces:
     Oid  Tablespace Name
-------------------------
    1663       pg_default
    1664        pg_global
  155151         fastdisk
  155152          bigdisk

$ # Ладно, давайте взглянем на базу данных alvherre
$ cd $PGDATA/base/17228

$ # Получим первые 10 объектов БД (отсортированных по размеру) в табличном пространстве по умолчанию
$ ls -lS * | head -10
-rw-------  1 alvherre alvherre 136536064 sep 14 09:51 155173
-rw-------  1 alvherre alvherre  17965056 sep 14 09:51 1155291
-rw-------  1 alvherre alvherre   1204224 sep 14 09:51 16717
-rw-------  1 alvherre alvherre    581632 sep  6 17:51 1255
-rw-------  1 alvherre alvherre    237568 sep 14 09:50 16674
-rw-------  1 alvherre alvherre    212992 sep 14 09:51 1249
-rw-------  1 alvherre alvherre    204800 sep 14 09:51 16684
-rw-------  1 alvherre alvherre    196608 sep 14 09:50 16700
-rw-------  1 alvherre alvherre    163840 sep 14 09:50 16699
-rw-------  1 alvherre alvherre    122880 sep  6 17:51 16751

$ # Поинтересуемся, что за файл 155173...
$ oid2name -d alvherre -f 155173
From database "alvherre":
  Filenode  Table Name
----------------------
    155173    accounts

$ # Можно запросить информацию о нескольких объектах
$ oid2name -d alvherre -f 155173 -f 1155291
From database "alvherre":
  Filenode     Table Name
-------------------------
    155173       accounts
   1155291  accounts_pkey

$ # Можно добавить другие параметры и получить более подробную информацию с -x
$ oid2name -d alvherre -t accounts -f 1155291 -x
From database "alvherre":
  Filenode     Table Name      Oid  Schema  Tablespace
------------------------------------------------------
    155173       accounts   155173  public  pg_default
   1155291  accounts_pkey  1155291  public  pg_default

$ # Показать объем, который занимает на диске каждый объект БД
$ du [0-9]* |
> while read SIZE FILENODE
> do
>   echo "$SIZE       `oid2name -q -d alvherre -i -f $FILENODE`"
> done
16            1155287  branches_pkey
16            1155289  tellers_pkey
17561            1155291  accounts_pkey
...

$ # То же самое, но отсортированное по размеру
$ du [0-9]* | sort -rn | while read SIZE FN
> do
>   echo "$SIZE   `oid2name -q -d alvherre -f $FN`"
> done
133466             155173    accounts
17561            1155291  accounts_pkey
1177              16717  pg_proc_proname_args_nsp_index
...

$ # Если хотите просмотреть содержимое табличных пространств, воспользуйтесь каталогом pg_tblspc
$ cd $PGDATA/pg_tblspc
$ oid2name -s
All tablespaces:
     Oid  Tablespace Name
-------------------------
    1663       pg_default
    1664        pg_global
  155151         fastdisk
  155152          bigdisk

$ # Объекты каких баз данных находятся в табличном пространстве «fastdisk»?
$ ls -d 155151/*
155151/17228/  155151/PG_VERSION

$ # И что это еще за база данных 17228?
$ oid2name
All databases:
    Oid  Database Name  Tablespace
----------------------------------
  17228       alvherre  pg_default
  17255     regression  pg_default
  17227      template0  pg_default
      1      template1  pg_default

$ # Давайте посмотрим, какие объекты этой базы данных находятся в этом табличном пространстве.
$ cd 155151/17228
$ ls -l
total 0
-rw-------  1 postgres postgres 0 sep 13 23:20 155156

$ # Ладно, это довольно маленькая таблица... но что это за таблица?
$ oid2name -d alvherre -f 155156
From database "alvherre":
  Filenode  Table Name
----------------------
    155156         foo

Автор

Б. Палмер (B. Palmer), bpalmer@crimelabs.net

vacuumlo

vacuumlo — удалить потерянные большие объекты из базы данных QHB

Синтаксис

vacuumlo [параметр...] имя_бд...

Описание

vacuumlo — это простая программа-утилита, которая удалит все «потерянные» большие объекты из базы данных QHB. Потерянным большим объектом (БО) считается любой БО, OID которого не фигурирует ни в одном столбце данных oid или lo в базе данных.

Если вы пользуетесь этой утилитой, вас также может заинтересовать триггер lo_manage в модуле lo. lo_manage полезен тем, что пытается предотвратить образование потерянных БО в принципе.

Обрабатываются все базы данных, перечисленные в командной строке.

Параметры

vacuumlo принимает следующие аргументы командной строки:

-l предел
--limit=предел
Удалять в одной транзакции не более заданного предела больших объектов (количество по умолчанию — 1000). Поскольку сервер запрашивает блокировку для каждого удаляемого БО, удаление слишком большого количества БО в одной транзакции чревато превышением лимита max_locks_per_transaction. Если вы хотите удалить все в одной транзакции, установите этот предел равным нулю.

-n
--dry-run
Не удалять ничего, просто показать предполагаемые операции.

-v
--verbose
Выводить множество сообщений о прогрессе.

-V
--version
Вывести версию vacuumlo и завершиться.

-?
--help
Показать справку об аргументах командной строки vacuumlo и завершиться.

Кроме того, в качестве параметров подключения vacuumlo принимает следующие аргументы командной строки:

-h хост
--host=хост
Имя хост-компьютера, на котором работает сервер баз данных.

-p порт
--port=порт
Порт сервера баз данных.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.

Это несущественный параметр, так как vacuumlo автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, vacuumlo потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

Переменные среды

PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию.

Эта утилита, как и большинство других утилит QHB, также использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Переменная среды PG_COLOR указывает, использовать ли цвет в диагностических сообщениях. Возможные значения: always (всегда), auto (автоматически) и never (никогда).

Примечания

Утилита vacuumlo работает следующим образом: сначала она создает временную таблицу, содержащую все OID больших объектов в выбранной базе данных. Затем она сканирует все столбцы в базе данных, имеющие тип oid или lo, и удаляет соответствующие записи из временной таблицы. (Примечание: рассматриваются только типы именно с этими именами; в частности, домены на их базе не рассматриваются.) Оставшиеся записи во временной таблице указывают на потерянные БО, которые и удаляются.

Автор

Питер Маунт (Peter Mount), peter@retep.org.uk

Сторонние расширения

В данном разделе представлены внешние расширения, проверенные на совместимость с QHB.

Дефекты, требующие изменения кода расширения, устраняются сообществом (автором расширения) и не могут служить объектом претензий к QHB или к поддерживающей QHB организации.

PostGIS — расширение к PostgreSQL с открытым исходным кодом, позволяющее хранить в базе географические и пространственные данные

Установка пакета PostGIS

Patroni — Python-приложение для создания высокодоступных кластеров QHB на основе потоковой репликации

Установка компонентов

Настройка компонентов

Запуск компонентов

Работа, переключение

См. также

mysql_fdw — обертка сторонних данных MySQL для QHB

Установка

Применение

Примеры

Лицензия

См. также

Orafce — функции совместимости и пакеты Oracle

Установка

Функции и пакеты Oracle

pg_hint_plan — модуль настройки планов выполнения с помощью фраз-подсказок

Установка

Применение

Таблица подсказок

Подробное описание подсказок

Подзапросы

Ошибки

Функциональные ограничения

Список подсказок

См. также

pg_repack — модуль реорганизации таблиц с минимальными блокировками

Синтаксис

Описание

Требования

Установка

Параметры

Переменные среды

Примеры

Диагностика

Ограничения

Детализация

См. также

pg_store_plans — модуль для отслеживания статистики плана выполнения операторов SQL

Описание

Установка

Представление pg_store_plans

Представление pg_store_plans_info

Функции pg_store_plans

Параметры конфигурации

Настройки plan_storage

Пример вывода

См. также

pgtt — модуль управления глобальными временными таблицами

Описание

Установка

Конфигурация

Использование расширения

Как работает расширение

См. также

plpgsql_check — модуль расширенной проверки plpgsql

Описание

Установка

Функциональные возможности

Параметры

Триггеры

Параметры в комментариях

Ограничения

Список зависимостей

Профилировщик

Трассировщик

Директивы

Лицензия

См. также

sr_plan — модуль управления хранением планов запросов в QHB

Описание

Установка

Применение

EXPLAIN для сохраненных планов

Интеграция pg_stat_statements

См. также

PostGIS

Расширение PostGIS увеличивает возможности реляционной базы данных QHB, добавляя поддержку хранения, индексирования и запроса географических данных.

Возможности PostGIS включают в себя:

Хранилище пространственных данных: хранение различных типов пространственных данных, таких как точки, линии, полигоны и мультигеометрии, как в 2D, так и в 3D данных.

Пространственное индексирование: быстрый поиск и извлечение пространственных данных на основе их местоположения.

Пространственные функции: широкий спектр пространственных функций, которые позволяют фильтровать и анализировать пространственные данные, измерять расстояния и площади, находить пересекающиеся геометрические объекты, буферизовать и многое другое.

Обработка геометрии: использование инструментов для обработки геометрических данных и управления ими, такие как упрощение, преобразование и обобщение.

Поддержка растровых данных: хранение и обработка растровых данных, таких как данные о высоте и данные о погоде.

Геокодирование и обратное геокодирование: использование функций для геокодирования и обратного геокодирования.

Интеграция: доступ и работа PostGIS с использованием сторонних инструментов, таких как QGIS, GeoServer, MapServer, ArcGIS, Tableau.

Установка пакета PostGIS

Расширение PostGIS поставляется в виде пакета qhb-1.5.2-postgis.

Для установки PostGIS вам потребуется установленный QHB с инициализированным кластером БД. Для получения информации по установке и настройке QHB обратитесь в соответствующий раздел документации (см. Пошаговая инструкция по начальной загрузке, установке и запуску).

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

Установка расширения PostGIS на кластере QHB

После установки пакета PostGIS возможно выполнить установку расширения для используемой базы данных.

Для установки расширения подключитесь к базе данных любым инструментом и выполните:

CREAE EXTENSION postgis;

Для проверки успешной установки PostGIS запустите команду:

SELECT postgis_full_version();

Оптимизация QHB для PostGIS

QHB предназначен для работы с различными базами данных. Объекты базы данных PostGIS велики по сравнению с обычными текстовыми данными. Поэтому желательно выполнить оптимизацию QHB, для лучшей работы с объектами PostGIS.

Вы можете оптимизировать сервер QHB, отредактировав файл qhb.conf:

Измените следующие параметры в зависимости от оперативной памяти вашей системы:

#Это значение должно составлять примерно 75% от RAM вашего сервера.
shared_buffers = 1500M
#Это позволяет QHB кэшировать в памяти больше данных при выполнении сортировки
work_mem = 64MB
#Контрольная точка — это периодически выполняемая операция, сохраняющее информацию о вашей системе
checkpoint_segments = 6
#По сути этот параметр представляет собой количество времени, которое ваш оптимизатор должен провести, считывая память, прежде чем обратится к вашему диску.
random_page_cost = 2.0

Сохраните и закройте файл, когда закончите. Затем перезапустите службу QHB, чтобы изменения вступили в силу.

systemctl restart qhb

Примечание
Подробную информацию по работе с расширением PostGIS, а также документацию к нему можно найти на официальном сайте продукта.

Patroni

Patroni — это Python-приложение для создания высокодоступных кластеров QHB на основе потоковой репликации. Оно используется такими компаниями, как Red Hat, IBM Compose, Zalando и многие другие. С его помощью можно преобразовать систему из основного и резервного серверов в высокодоступный кластер с поддержкой автоматического контролируемого и аварийного переключения. Patroni позволяет легко добавлять новые реплики в существующий кластер, поддерживает динамическое изменение конфигурации QHB одновременно на всех серверах кластера, а также множество других возможностей, таких как синхронная репликация, настраиваемые действия при переключении узлов, REST API, возможность запуска пользовательских команд для создания реплики.

Одна из возможных комбинаций стека технологий для организации высокодоступных кластеров:

QHB в качестве СУБД;

Patroni в качестве решения для кластеризации;

etcd или consul в качестве распределенного хранилища для Patroni.

Patroni является приложением с открытым исходным кодом, поэтому для удобства использования и для совместимости с QHB мы вносим изменения в исходные пакеты. Совместимые пакеты Patroni находятся в репозиториях QHB.

Установка компонентов

Установка etcd

Пример установки на CentOS 7:

yum install etcd

Также возможна установка через пакеты или исходный код по документации на установку etcd.

Рекомендуется устанавливать etcd на отдельных машинах, где не будут установлены узлы Patroni и QHB, так как для etcd очень важна нагрузка на диски. Для штатной работы кластера etcd минимальное количество узлов — три. Для разработки и тестирования можно использовать один узел.

Установка Consul

В связи с ограничениями Hashicorp на данный момент установка возможна только с использованием VPN.

Установка выполняется через пакеты с ресурса pkgs.org, либо по документации на установку Consul.

Серверы Consul — это место, где хранятся и реплицируются данные Patroni. Поэтому рекомендованное минимальное количество узлов — три. Для разработки и тестирования можно использовать один узел. Для продуктивных высоконагруженных систем следует разворачивать сервисы Consul на отдельных от Patroni и QHB машинах.

Клиент Consul также является членом системы и может подключаться к кластеру серверов Consul для получения информации об инфраструктуре. Клиента можно расположить на сервере с Patroni и QHB для подключения к кластеру серверов Consul через локального клиента Consul.

Установка QHB

Для установки обратитесь к документации по установке QHB.

Создание и запуск сервиса QHB, а также инициализация кластера базы данных не требуется. Все управление кластером СУБД осуществляется через Patroni.

Установка Patroni

Patroni требуется установить на тех же машинах, где уже установлены серверы QHB.

Имя пакета для установки qhb-patroni

Для установки обратитесь к документации по установке QHB.

Обращаем внимание, что для работы с QHB выпущен специализированный пакет qhb-patroni. Стандартный Patroni для PostgreSQL не может использоваться совместно с QHB.

Проверьте версию qhb-patroni:

patroni --version

Результат: Patroni 3.0.2

Настройка компонентов

Настройка etcd

Выполняется независимо от Patroni и QHB, может быть выполнена предварительно (подробнее см. в официальной документации etcd). Для конфигурации каждого узла etcd нужно внести необходимые для запуска параметры в файл /etc/etcd/etcd.conf. Также предварительно на каждом из узлов необходимо создать каталог под данные etcd, например /var/lib/etcd/default.etcd. Ниже представлен пример минимальной конфигурации узлов на 3 машинах с адресами: X.X.X.101, X.X.X.102, X.X.X.103.

Для первого узла:

ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://X.X.X.101:2380"
ETCD_LISTEN_CLIENT_URLS="http://X.X.X.101:2379,http://127.0.0.1:2379"
ETCD_NAME="etcd0"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://X.X.X.101:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://X.X.X.101:2379"
ETCD_INITIAL_CLUSTER="etcd0=http://X.X.X.101:2380,etcd1=http://X.X.X.102:2380,etcd2=http://X.X.X.103:2380"
ETCD_INITIAL_CLUSTER_TOKEN="qhb_token"

Для второго узла:

ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://X.X.X.102:2380"
ETCD_LISTEN_CLIENT_URLS="http://X.X.X.102:2379,http://127.0.0.1:2379"
ETCD_NAME="etcd1"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://X.X.X.102:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://X.X.X..102:2379"
ETCD_INITIAL_CLUSTER="etcd0=http://X.X.X.101:2380,etcd1=http://X.X.X.102:2380,etcd2=http://X.X.X.103:2380"
ETCD_INITIAL_CLUSTER_TOKEN="qhb_token"

Для третьего узла:

ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://X.X.X.103:2380"
ETCD_LISTEN_CLIENT_URLS="http://X.X.X.103:2379,http://127.0.0.1:2379"
ETCD_NAME="etcd2"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://X.X.X.103:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://X.X.X.103:2379"
ETCD_INITIAL_CLUSTER="etcd0=http://X.X.X.101:2380,etcd1=http://X.X.X.102:2380,etcd2=http://X.X.X.103:2380"
ETCD_INITIAL_CLUSTER_TOKEN="qhb_token"

Базу данных etcd при пересоздании кластера нужно переинициализировать, удаляя данные в каталоге /var/lib/etcd/default.etcd.

Создайте сервис etcd на каждом из узлов для удобного управления кластером:

[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
WorkingDirectory=/var/lib/etcd/
EnvironmentFile=-/etc/etcd/etcd.conf
User=etcd
# установить в GOMAXPROCS число процессоров
ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd --name=\"${ETCD_NAME}\" --data-dir=\"${ETCD_DATA_DIR}\" --listen-client-urls=\"${ETCD_LISTEN_CLIENT_URLS}\""
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Настройка Consul

Выполняется независимо от Patroni и QHB, может быть выполнена предварительно (подробнее см. в официальной документации Consul). Для конфигурации каждого узла кластера серверов consul нужно внести необходимые для запуска параметры в файл /etc/consul.d/config.json.

Ниже представлен пример минимальной конфигурации узлов на 3 машинах. Адреса и доменные имена:

X.X.X.101, qhb-srv-01.quantom.local;
X.X.X.102, qhb-srv-02.quantom.local;
X.X.X.103, qhb-srv-03.quantom.local.

Для первого узла кластера серверов consul (для второго и третьего узла изменить bind_addr и client_addr):

{
  "bind_addr": "X.X.X.101",
  "bootstrap_expect": 3,
  "client_addr": "X.X.X.101",
  "datacenter": "dc1",
  "data_dir": "/opt/consul",
  "domain": "consul",
  "enable_script_checks": true,
  "dns_config": {
    "enable_truncate": true,
    "only_passing": true
  },
  "enable_syslog": true,
  "encrypt": "v6+HtK6JQ6kX2XgYkSFQM9KFXF1YeGyFHcRo6hWZbjI=",
  "leave_on_terminate": true,
  "log_level": "INFO",
  "rejoin_after_leave": true,
  "retry_join": [
    "qhb-srv-01.quantom.local",
    "qhb-srv-02.quantom.local",
    "qhb-srv-03.quantom.local"
  ],
  "server": true,
  "start_join": [
    "qhb-srv-01.quantom.local",
    "qhb-srv-02.quantom.local",
    "qhb-srv-03.quantom.local"
  ],
  "ui_config": { "enabled": true }
}

Для подключения к кластеру серверов consul можно создать клиента consul (например, разместить на серверах с Patroni). При такой конфигурации Patroni подключается к локальному клиенту consul, который обращается ко всему кластеру серверов consul.

Для конфигурации клиента consul необходимо внести необходимые для запуска параметры в файл /etc/consul.d/config.json.

Пример минимальной конфигурации клиента:

{
  "bind_addr": "X.X.X.104",
  "client_addr": "X.X.X.104",
  "datacenter": "dc1",
  "node_name": "client01",
  "data_dir": "/opt/consul",
  "domain": "consul",
  "enable_script_checks": true,
  "dns_config": {
    "enable_truncate": true,
    "only_passing": true
  },
  "enable_syslog": true,
  "encrypt": "v6+HtK6JQ6kX2XgYkSFQM9KFXF1YeGyFHcRo6hWZbjI=",
  "leave_on_terminate": true,
  "log_level": "INFO",
  "rejoin_after_leave": true,
  "retry_join": [
    "qhb-srv-01.quantom.local",
    "qhb-srv-02.quantom.local",
    "qhb-srv-03.quantom.local"
  ],
  "server": false,
  "start_join": [
    "qhb-srv-01.quantom.local",
    "qhb-srv-02.quantom.local",
    "qhb-srv-03.quantom.local"
  ],
  "ui_config": { "enabled": true }
}

Каждую конфигурацию необходимо проверить на допустимость:

consul validate /etc/consul.d/config.json

Создайте сервис consul на каждом из узлов для удобного управления кластером:

[Unit]
Description=Consul Service Discovery Agent
Documentation=https://www.consul.io/
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=consul
Group=consul
ExecStart=/usr/bin/consul agent \
    -node=qhb-srv-01.quantom.local \
    -config-dir=/etc/consul.d
ExecReload=/bin/kill -HUP $MAINPID
KillSignal=SIGINT
TimeoutStopSec=5
Restart=on-failure
SyslogIdentifier=consul

[Install]
WantedBy=multi-user.target

Настройка Patroni

На каждом хосте с qhb-patroni необходимо создать директорию под кластер баз данных QHB и раздать необходимые права пользователю qhb, например, /mnt/patroni:

mkdir -p /mnt/patroni
chown qhb:qhb /mnt/patroni
chmod 700 /mnt/patroni

Создайте конфигурационный файл с расширением .yml на каждом хосте с qhb-patroni. Можете скопировать примеры конфигурационных файлов в rpm системах /usr/share/doc/patroni-3.0.2/qhb1.yml (qhb2.yml) и в deb системах /usr/share/doc/patroni/qhb1.yml (qhb2.yml) Подробнее параметры конфигурации описаны в документации Patroni.

Кроме основных параметров конфигурации, указанных в документации Patroni, в реализации qhb-patroni добавлены дополнительные параметры для управления QHB:

# параметры QHB
  hba_file: qhb_hba.conf
  ident_file: qhb_ident.conf
  database: qhb
  config_base_name: qhb

Ниже представлен пример минимальной конфигурации кластера qhb-patroni на 2 хостах с адресами: X.X.X.104, X.X.X.105.

Примечание
В примере указаны параметры подключения к etcd, для работы с consul раскомментируйте и добавьте необходимые строки.

qhb0.yml для первого узла кластера qhb-patroni:

scope: cluster
# пространство имен: /служба/
name: qhb0

restapi:
  listen: 127.0.0.1:8008
  connect_address: 127.0.0.1:8008

etcd:
  # Использовать «хосты» для предоставления нескольких рабочих станций
  hosts:
    - X.X.X.101:2379
    - X.X.X.102:2379
    - X.X.X.103:2379

  #consul:
  #хост: X.X.X.104:8500

bootstrap:
  # этот раздел будет записан в etcd/Consul:/<пространство_имен>/<область_применения>/config
  # после инициализации нового кластера, и все остальные члены кластера будут
  # использовать его в качестве «общих настроек»
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
  # некоторые желаемые параметры для 'initdb'
  initdb:  # Примечание: Это должен быть список (для некоторых параметров
    # нужны значения, а другие являются флагами)
    - encoding: UTF8
    - data-checksums

  pg_hba:  # Добавить следующие строки в qhb_hba.conf после запуска 'initdb'
    - host replication replicator 127.0.0.1/32 md5
    - host replication replicator X.X.X.0/24 md5
    - host all all 0.0.0.0/0 md5

  # Некоторые дополнительные пользователи, которых нужно создать после
  # инициализации нового кластера
  users:
    admin:
      password: admin
      options:
        - createrole
        - createdb
postgresql:
  listen: 127.0.0.1,X.X.X.104,::1:5432
  connect_address: X.X.X.104:5432
  data_dir: /mnt/patroni
  bin_dir: /usr/local/qhb/bin
  pgpass: /tmp/pgpass0
  authentication:
    replication:
      username: replicator
      password: rep-pass
    superuser:
      username: qhb
      password: qhb
    rewind:
      username: rewind_user
      password: rewind_password
  parameters:
    unix_socket_directories: '.'
  # параметры QHB
  hba_file: qhb_hba.conf
  ident_file: qhb_ident.conf
  database: qhb
  config_base_name: qhb

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false

qhb1.yml для второго узла кластера qhb-patroni:

scope: cluster
# пространство имен: /служба/
name: qhb1

restapi:
  listen: 127.0.0.1:8008
  connect_address: 127.0.0.1:8008

etcd:
  # Использовать «хосты» для предоставления нескольких рабочих станций
  hosts:
    - X.X.X.101:2379
    - X.X.X.102:2379
    - X.X.X.103:2379

  #consul:
  #хост: X.X.X.105:8500

bootstrap:
  # этот раздел будет записан в etcd/Consul:/<пространство_имен>/<область_применения>/config
  # после инициализации нового кластера, и все остальные члены кластера будут
  # использовать его в качестве «общих настроек»
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
  # некоторые желаемые параметры для 'initdb'
  initdb:  # Примечание: Это должен быть список (для некоторых параметров
    # нужны значения, а другие являются флагами)
    - encoding: UTF8
    - data-checksums

  pg_hba:  # Добавить следующие строки в qhb_hba.conf после запуска 'initdb'
    - host replication replicator 127.0.0.1/32 md5
    - host replication replicator X.X.X.0/24 md5
    - host all all 0.0.0.0/0 md5

  # Некоторые дополнительные пользователи, которых нужно создать после инициализации нового кластера
  users:
    admin:
      password: admin
      options:
        - createrole
        - createdb
postgresql:
  listen: 127.0.0.1,X.X.X.105,::1:5432
  connect_address: X.X.X.105:5432
  data_dir: /mnt/patroni
  bin_dir: /usr/local/qhb/bin
  pgpass: /tmp/pgpass0
  authentication:
    replication:
      username: replicator
      password: rep-pass
    superuser:
      username: qhb
      password: qhb
    rewind:
      username: rewind_user
      password: rewind_password
  parameters:
    unix_socket_directories: '.'
  # параметры QHB
  hba_file: qhb_hba.conf
  ident_file: qhb_ident.conf
  database: qhb
  config_base_name: qhb

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false

Для проверки работоспособности необходимо провести проверку конфигурации и при необходимости исправить все ошибки, например, если конфигурация первого узла кластера расположена в /opt/patroni/:

patroni --validate-config /opt/patroni/qhb0.yml

При необходимости создайте сервис qhb-patroni на каждом из узлов для удобного управления кластером qhb-patroni. Сервис располагается в /usr/lib/systemd/system/qhb-patroni.service.

# Это пример файла конфигурации systemd для Patroni
# Вы можете скопировать его в "/etc/systemd/system/patroni.service",

[Unit]
Description=Runners to orchestrate a high-availability QHB
After=syslog.target network.target

[Service]
Type=simple

User=qhb
Group=qhb

# Прочитать в файле конфигурации, если таковой существует, иначе продолжить
EnvironmentFile=-/etc/patroni_env.conf

# WorkingDirectory = /var/lib/pgsql

# Куда отправлять сообщения о раннем запуске от сервера
# Обычно это определяется общей настройкой в systemd
#StandardOutput=syslog

# Прекоманды для запуска устройства наблюдения
# Конвертировать комментарий, если устройство наблюдения является частью вашей установки patroni
#ExecStartPre=-/usr/bin/sudo /sbin/modprobe softdog
#ExecStartPre=-/usr/bin/sudo /bin/chown qhb /dev/watchdog

# Запустить процесс patroni
# См. примеры файлов конфигурации для QHB в /usr/share/doc/patroni-3.0.2/qhb*.yml
ExecStart=/usr/bin/patroni /opt/qhb1.yml

# Переслать HUP для загрузки с patroni.yml
ExecReload=/usr/bin/kill -s HUP $MAINPID

# приостановить процесс patroni, но не его дочерние процессы, чтобы он постепенно остановил qhb
KillMode=process

# Выждать достаточное время, чтобы сервер запустился/остановился
TimeoutSec=30

# Не перезапускать службу в случае сбоя; мы хотим вручную проверить базу данных на ошибки
Restart=no

[Install]
WantedBy=multi-user.target

Запуск компонентов

Запуск etcd

Предварительно убедитесь, что у вас добавлены необходимые правила в firewall. Для запуска etcd через сервис на всех узлах необходимо выполнить systemctl start etcd. Команда на всех узлах завершится, когда узлы найдут друг друга, т. е. когда команда будет выполнена на втором и третьем хосте. При пересоздании кластера нужно предварительно очистить базы etcd.

Состав кластера etcd можно посмотреть командой etcdctl member list.

Запуск Consul

Предварительно убедитесь, что у вас добавлены необходимые правила в firewall.

Для запуска etcd через сервис на всех узлах серверов и клиентах необходимо выполнить systemctl start consul.

По адресу http(s) любого участника кластера, например http://X.X.X.101:8500, можно посмотреть состав кластера (сервера и клиенты) и текущего лидера. Подробнее в официальной документации Consul.

Запуск Patroni

Предварительно убедитесь, что у вас добавлены необходимые правила в firewall.

Запуск необходимо производить только после запуска распределенного хранилища — etcd или consul.

Для запуска первого узла qhb-patroni необходимо из под пользователя qhb выполнить patroni /opt/qhb0.yml. При успешном запуске первого узла также запустите остальные узлы, указав соответствующие файлы конфигурации.

Для запуска qhb-patroni через сервис на всех узлах необходимо выполнить systemctl start qhb-patroni.

Состояние и состав кластера qhb-patroni можно посмотреть на любом узле, например patronictl -c /opt/qhb0.yml list.

При штатной работе вы увидите State — running у всех участников кластера.

Работа, переключение

При остановке работы СУБД на хосте 104 СУБД на хосте 105 становится основной.

При включении в работу СУБД на хосте 104 она становится резервной.

Если останавливается работа мастера на хосте 105 основным становится хост 104.

См. также

Документация по etcd

Документация по Consul

Документация по Patroni

mysql_fdw

Это расширение QHB реализует обертку сторонних данных (FDW) для MySQL.

Обратите внимание, что эта версия mysql_fdw работает с QHB версии 1.5.1 и выше.

Установка

Обертка сторонних данных MySQL для QHB поставляется в виде пакета qhb-1.5.2-mysql-fdw.

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

Примечание
Для работы расширения требуется установка клиентских библиотек MariaDB или MySQL (MySQL's C client library is needed. This library can be downloaded from the official MySQL website), например, для CentOS 7 и 8: yum install mariadb-devel.

Применение

Следующие параметры могут быть установлены для объекта внешнего сервера MySQL:

host: Адрес или имя хоста сервера MySQL. Значение по умолчанию 127.0.0.1.

port: номер порта сервера MySQL. Значение по умолчанию 3306.

secure_auth: Включить или отключить безопасную аутентификацию. Значение по умолчанию true.

init_command: Оператор SQL для выполнения при подключении к серверу MySQL.

use_remote_estimate: Определяет, выдает ли mysql_fdw удаленные команды EXPLAIN для получения оценки стоимости. Значение по умолчанию false.

reconnect: Включить или отключить автоматическое переподключение к серверу MySQL, если обнаружится, что существующее соединение было потеряно. Значение по умолчанию false.

sql_mode: Установите MySQL sql_mode для установленного соединения. Значение по умолчанию ANSI_QUOTES.

ssl_key: Путь к файлу закрытого ключа клиента.

ssl_cert: Путь к файлу сертификата открытого ключа клиента.

ssl_ca: Путь к файлу сертификата центра сертификации (ЦС). Этот параметр, если он используется, должен указывать тот же сертификат, который используется сервером.

ssl_capath: Путь к каталогу, содержащему файлы доверенных сертификатов ЦС SSL.

ssl_cipher: список допустимых шифров для SSL-шифрования.

fetch_size: Этот параметр указывает количество строк, которые mysql_fdw должна получить при каждой операции выборки. Его можно указать для внешней таблицы или внешнего сервера. Параметр, указанный в таблице, переопределяет параметр, указанный для сервера. Значение по умолчанию 100.

character_set: Набор символов для подключения к MySQL. Значение по умолчанию auto означает автоматическое определение на основе настроек операционной системы.

Следующие параметры могут быть установлены для объекта внешней таблицы MySQL:

dbname: имя базы данных MySQL для запроса. Это обязательный параметр.

table_name: Имя таблицы MySQL, по умолчанию такое же, как у внешней таблицы.

max_blob_size: максимальный размер большого двоичного объекта для чтения без усечения.

fetch_size: То же, что и параметр fetch_size для стороннего сервера.

Следующие параметры необходимо указать при создании сопоставления пользователей:

username: Имя пользователя для использования при подключении к MySQL.

password: Пароль для аутентификации на сервере MySQL.

В команде IMPORT FOREIGN SCHEMA можно задать следующие параметры:

import_default: Этот параметр определяет, включаются ли выражения столбца DEFAULT в определения внешних таблиц, импортированных с внешнего сервера. Значение по умолчанию false.

import_not_null: Этот параметр определяет, включаются ли ограничения столбца NOT NULL в определения внешних таблиц, импортированных с внешнего сервера. Значение по умолчанию true.

import_enum_as_text: Этот параметр можно использовать для сопоставления типа MySQL enum с типом text в определениях внешних таблиц, в противном случае выдается предупреждение о создаваемом типе. Значение по умолчанию false.

import_generated: Этот параметр определяет, будут ли выражения столбцов GENERATED включены в определения внешних таблиц, импортированных со стороннего сервера, или нет. Значение по умолчанию true. Импорт полностью завершится ошибкой, если импортированное сгенерированное выражение использует функцию или оператор, которых нет в QHB.

Примеры

-- Первая загрузка расширения после установки
CREATE EXTENSION mysql_fdw;

-- создание объекта сервера
CREATE SERVER mysql_server
	FOREIGN DATA WRAPPER mysql_fdw
	OPTIONS (host '127.0.0.1', port '3306');

-- создание сопоставления пользователей
CREATE USER MAPPING FOR qhb
	SERVER mysql_server
	OPTIONS (username 'foo', password 'bar');

-- создание сторонней таблицы
CREATE FOREIGN TABLE warehouse
	(
		warehouse_id int,
		warehouse_name text,
		warehouse_created timestamp
	)
	SERVER mysql_server
	OPTIONS (dbname 'db', table_name 'warehouse');

-- добавление новых строк в таблицу
INSERT INTO warehouse values (1, 'UPS', current_date);
INSERT INTO warehouse values (2, 'TV', current_date);
INSERT INTO warehouse values (3, 'Table', current_date);

-- выборка из таблицы
SELECT * FROM warehouse ORDER BY 1;

warehouse_id | warehouse_name | warehouse_created
-------------+----------------+-------------------
           1 | UPS            | 10-JUL-20 00:00:00
           2 | TV             | 10-JUL-20 00:00:00
           3 | Table          | 10-JUL-20 00:00:00

-- удаление строки из таблицы
DELETE FROM warehouse where warehouse_id = 3;

-- изменение строки в таблице
UPDATE warehouse set warehouse_name = 'UPS_NEW' where warehouse_id = 1;

-- показать план выполнения запроса для таблицы с параметром verbose
EXPLAIN VERBOSE SELECT warehouse_id, warehouse_name FROM warehouse WHERE warehouse_name LIKE 'TV' limit 1;

                                   QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Limit  (cost=10.00..11.00 rows=1 width=36)
	Output: warehouse_id, warehouse_name
	->  Foreign Scan on public.warehouse  (cost=10.00..1010.00 rows=1000 width=36)
		Output: warehouse_id, warehouse_name
		Local server startup cost: 10
		Remote query: SELECT `warehouse_id`, `warehouse_name` FROM `db`.`warehouse` WHERE ((`warehouse_name` LIKE BINARY 'TV'))

Лицензия

Авторское право (c) 2011-2023, EnterpriseDB Corporation.

Разрешено использование, копирование, изменение и распространение данного программного обеспечения и его документация для любых целей без оплаты и без письменного соглашения при условии включения вышеуказанной отметки об авторских правах, этого параграфа и следующих двух абзацев во все копии.

НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ ENTERPRISEDB CORPORATION НЕ НЕСЕТ ОТВЕТСТВЕННОСТИ ПЕРЕД ЛЮБОЙ СТОРОНОЙ ЗА ПРЯМОЙ, КОСВЕННЫЙ, СПЕЦИАЛЬНЫЙ, СЛУЧАЙНЫЙ ИЛИ СПРОВОЦИРОВАННЫЙ УЩЕРБ, ВКЛЮЧАЯ ПОТЕРЮ ПРИБЫЛИ, ПОЛУЧЕННОЙ ОТ ИСПОЛЬЗОВАНИЯ ЭТОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ И ЕГО ДОКУМЕНТАЦИИ, ДАЖЕ ЕСЛИ ENTERPRISEDB CORPORATION БЫЛА УВЕДОМЛЕНА О ВОЗМОЖНОСТИ ТАКОГО УЩЕРБА.

ENTERPRISEDB CORPORATION ПРЯМО ОТКАЗЫВАЕТСЯ ОТ ВЫПОЛНЕНИЯ ЛЮБЫХ ДРУГИХ ГАРАНТИЙНЫХ ОБЯЗАТЕЛЬСТВ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ​​ПОДРАЗУМЕВАЕМЫМИ ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ И СООТВЕТСТВИЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ, ПРЕДОСТАВЛЯЕМОЕ ПО НАСТОЯЩЕМУ СОГЛАШЕНИЮ, ПРЕДОСТАВЛЯЕТСЯ ПО ПРИНЦИПУ «КАК ЕСТЬ», И ENTERPRISEDB CORPORATION НЕ ОБЯЗАНА ОБЕСПЕЧИВАТЬ ЕГО ОБСЛУЖИВАНИЕ, ПОДДЕРЖКУ, ОБНОВЛЕНИЯ, УЛУЧШЕНИЯ ИЛИ МОДИФИКАЦИИ.

См. также

Документация по mysql_fdw на Github

Orafce

Это расширение QHB реализует функции и операторы, которые эмулируют подмножество функций и пакетов из Oracle RDBMS.

Обратите внимание, что эта версия orafce работает с QHB версии 1.5.1 и выше.

Установка

Функции совместимости и пакеты Oracle для QHB поставляется в виде пакета qhb-1.5.2-orafce.

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

Функции и пакеты Oracle

Этот модуль содержит несколько полезных функций, которые могут помочь при переносе приложения Oracle на QHB или могут быть полезными в целом.

Встроенные функции даты Oracle были протестированы на совместимость с Oracle 10. Диапазоны дат с 1960 по 2070 год работают корректно. Даты до 1100-03-01 не могут быть проверены из-за ошибки в Oracle.

Все функции полностью совместимы с Oracle и учитывают все известные строки формата.

Список строк формата для функций trunc, round

Y,YY,YYY,YYYY,SYYY,SYEAR      год
I,IY,IYY,IYYY			            недельный год по ISO 8601
Q				                      квартал
WW				                    неделя, начиная с первого дня года
IW				                    неделя, начиная с понедельника
W				                      неделя, начиная с первого дня месяца
DAY,DY,D			                первый день недели, воскресенье
MONTH,MON,MM,RM			          месяц
CC,SCC				                век
DDD,DD,J			                день
HH,HH12,HH24			            час
MI				                    минута

Функции округляются. То есть дата 1 июля будет округлена до следующего года. 16 июля округляется до августа.

Функции даты

Функция

Описание
Пример(ы)

add_months ( date, integer ) → date

Возвращает дату плюс n месяцев.
add_months(date '2005-05-31',1) → 2005-06-30

last_day ( date ) → date

Возвращает последний день месяца на основе значения даты.
last_day(date '2005-05-24') → 2005-05-31

next_day ( date, text ) → date

Возвращает первый день недели, который больше значения даты.
next_day(date '2005-05-24', 'monday') → 2005-05-30

next_day ( date, integer ) → date

То же, что и выше. Второй аргумент должен быть 1..7 и интерпретироваться как воскресенье..суббота.
next_day(date '2005-05-24', 1) → 2005-05-30

months_between ( date, date ) → numeric

Возвращает количество месяцев между датой1 и датой2. Если вычисляется дробный месяц, функция months_between вычисляет дробь на основе 31-дневного месяца.
months_between(date '1995-02-02', date '1995-01-01') → 1.0322580645161

trunc ( date, text ) → date

Обрезает дату в соответствии с указанным форматом.
trunc(date '2005-07-12', 'iw') → 2005-07-11

round ( date, text ) → date

Округляет дату в соответствии с указанным форматом.
round(date '2005-07-12', 'yyyy') → 2006-01-01

to_date ( text ) → timestamp

Приводит вводимый текста к отметке времени. GUC orafce.nls_date_format используется для указания формата входного текста для этой функции. Если значение оставлено пустым или установлено как default (формат по умолчанию), тогда введите текстовый формат в соответствии с настройкой стиля даты GUC QHB.

orafce.nls_date_format value to DEFAULT

to_date('2014-05-19 17:23:53+5:30') -> 2014-05-19 17:23:53

orafce.nls_date_format='YYYY-MMDD HH24:MI:SS'

to_date('2014-0519 17:23:53+5:30')  -> 2014-05-19 17:23:53

Тип данных oracle.date

Этот модуль содержит реализацию совместимого с Oracle типа данных date, «oracle.date», и функций, использующих тип данных date, таких как oracle.add_months, oracle.last_day(), oracle.next_day(), oracle.months_between() и т. д.

Пример:

SET search_path TO oracle,"$user", public, pg_catalog;
CREATE TABLE oracle_date(col1 date);
INSERT INTO oracle_date values('2014-06-24 12:12:11'::date);
SELECT * FROM oracle_date;
        col1
---------------------
 2014-06-24 12:12:11
(1 row)

Функции oracle.date

Функция

Описание
Пример(ы)

oracle.add_months ( timestamp with time zone, integer ) → timestamp with time zone

Возвращает дату и время плюс n месяцев.
oracle.add_months(oracle.date'2005-05-31 10:12:12',1) → 2005-06-30 10:12:12

oracle.last_day ( timestamp with time zone ) → timestamp with time zone

Возвращает последний день месяца на основе значения даты.
oracle.last_day(oracle.date '2005-05-24 11:12:12') → 2005-05-31 11:12:12

oracle.next_day ( timestamp with time zone, text ) → timestamp with time zone

Возвращает первый день недели, который больше значения даты.
oracle.next_day(oracle.date '2005-05-24 10:12:12', 'monday') → 2005-05-30 10:12:12

oracle.next_day ( timestamp with time zone, integer ) → timestamp with time zone

То же, что и выше. Второй аргумент должен быть 1..7 и интерпретироваться как воскресенье..суббота.
oracle.next_day(oracle.date '2005-05-24 11:21:12', 1) → 2005-05-29 11:21:12

oracle.months_between ( timestamp with time zone, timestamp with time zone ) → numeric

Возвращает количество месяцев между меткой времени1 и меткой времени2. Если вычисляется дробный месяц, функция months_between вычисляет дробь на основе 31-дневного месяца.
oracle.months_between(oracle.date '1995-02-02 10:00:00', oracle.date '1995-01-01 10:21:11') → 1.03225806451613

oracle.to_date ( text, text ) → timestamp without time zone

Возвращает метку времени без часового пояса.
oracle.to_date('02/16/09 04:12:12', 'MM/DD/YY HH24:MI:SS') → 2009-02-16 04:12:12

oracle.to_date ( text ) → oracle.date

Возвращает oracle.date.
oracle.to_date('16.02.09 04:12:12') → 2009-02-16 04:12:12

oracle.sysdate() → timestamp with time zone

Возвращает временную метку оператора в часовом поясе сервера (orafce.timezone).
oracle.sysdate() → 2015-12-09 17:47:56

oracle.dbtimezone() → timezone

Возвращает часовой пояс сервера — эмулируется через orafce.timezone.
oracle.dbtimezone() → GMT

oracle.sessiontimezone() → timezone

Возвращает часовой пояс сеанса — текущий часовой пояс QHB.
oracle.sessiontimezone() → Europe/Prague

oracle.to_char ( timestamp ) → nls_date_format

Возвращает временную метку в формате nls_date_format.

orafce.nls_date_format='YY-MonDD HH24:MI:SS'

oracle.to_char(to_date('14-Jan08 11:44:49+05:30')) -> 14-Jan08 11:44:49

orafce.nls_date_format='YY-MonDD HH24:MI:SS'

oracle.to_char(oracle.to_date('21052014 12:13:44+05:30','DDMMYYYY HH24:MI:SS')) -> 14-May21 12:13:44

Операторы oracle.date

Оператор

Описание
Пример(ы)

oracle. + ( oracle.date, smallint ) → oracle.date

Возвращает oracle.date.
oracle.to_date('2014-07-02 10:08:55','YYYY-MM-DD HH:MI:SS') + 9::smallint → 2014-07-11 10:08:55

oracle. + ( oracle.date, integer ) → oracle.date

Возвращает oracle.date.
oracle.to_date('2014-07-02 10:08:55','YYYY-MM-DD HH:MI:SS') + 9::integer → 2014-07-11 10:08:55

oracle. + ( oracle.date, bigint ) → oracle.date

Возвращает oracle.date.
oracle.to_date('2014-07-02 10:08:55','YYYY-MM-DD HH:MI:SS') + 9::bigint → 2014-07-11 10:08:55

oracle. + ( oracle.date, numeric ) → oracle.date

Возвращает oracle.date.
oracle.to_date('2014-07-02 10:08:55','YYYY-MM-DD HH:MI:SS') + 9::numeric → 2014-07-11 10:08:55

oracle. - ( oracle.date, smallint ) → oracle.date

Возвращает oracle.date.
oracle.to_date('2014-07-02 10:08:55','YYYY-MM-DD HH:MI:SS') - 9::smallint → 2014-06-23 10:08:55

oracle. - ( oracle.date, integer ) → oracle.date

Возвращает oracle.date.
oracle.to_date('2014-07-02 10:08:55','YYYY-MM-DD HH:MI:SS') - 9::integer → 2014-06-23 10:08:55

oracle. - ( oracle.date, bigint ) → oracle.date

Возвращает oracle.date.
oracle.to_date('2014-07-02 10:08:55','YYYY-MM-DD HH:MI:SS') - 9::bigint → 2014-06-23 10:08:55

oracle. - ( oracle.date, numeric ) → oracle.date

Возвращает oracle.date.
oracle.to_date('2014-07-02 10:08:55','YYYY-MM-DD HH:MI:SS') - 9::numeric -> 2014-06-23 10:08:55

oracle. - ( oracle.date, oracle.date ) → double precision

Возвращает двойную точность.
oracle.to_date('2014-07-17 11:10:15', 'yyyy-mm-dd hh24:mi:ss') - oracle.to_date('2014-02-01 10:00:00', 'yyyy-mm-dd hh24:mi:ss') → 166.048785

Вам нужно установить search_path TO oracle,"$user", public, pg_catalog, потому что такие функции, как oracle.add_months, oracle.last_day, oracle.next_day и oracle.months_between, устанавливаются бок о бок с функциями pg_catalog.add_months, pg_catalog.last_day, pg_catalog.next_day и pg_catalog.months_between.

«Двойная» таблица

QHB не нуждается в «двойной» таблице Oracle, но поскольку она интенсивно используется пользователями Oracle, она была добавлена ​​в orafce. Эта таблица находится в схеме oracle. Обычно имеет смысл разрешить к ней широкий доступ, поэтому следует добавить эту схему в конфигурацию search_path (как search_path = 'oracle, pg_catalog, "$user", public' в qhb.conf).

Пакет dbms_output

QHB отправляет информацию клиенту через RAISE NOTICE. Oracle использует dbms_output.put_line(). Это работает иначе, чем RAISE NOTICE. В Oracle имеется очередь сеанса, функция put_line() добавляет строку в очередь, а функция get_line() читает из очереди. Если установлен флаг serveroutput, то клиент по всем операторам sql читает очередь. Вы можете использовать:

SELECT dbms_output.enable();
SELECT dbms_output.put_line('first_line');
SELECT dbms_output.put_line('next_line');
SELECT * FROM dbms_output.get_lines(0);

или

SELECT dbms_output.enable();
SELECT dbms_output.serveroutput('t');
SELECT dbms_output.put_line('first_line');

Этот пакет содержит следующие функции: enable(), disable(), serveroutput(), put(), put_line(), new_line(), get_line(), get_lines(). Очередь пакетов реализована в локальной памяти сеанса.

Пакет utl_file

Этот пакет позволяет программам PL/pgSQL читать и записывать любые файлы, доступные с сервера. Каждый сеанс может открыть максимум десять файлов, а максимальный размер строки составляет 32 КБ. Этот пакет содержит следующие функции:

Функция

Описание

utl_file.fclose ( file utl_file.file_type)

Закрывает файл.

utl_file.fclose_all()

Закрывает все файлы.

utl_file.fcopy ( src_location text, src_filename text, dest_location text, dest_filename text [, start_line text ] [, end_line text ] )

Копирует текстовый файл.

utl_file.fflush ( file utl_file.file_type )

Сбрасывает все данные из буферов.

utl_file.fgetattr ( location text, filename text )

Выводит атрибуты файла.

utl_file.fopen ( location text, filename text, file_mode text [, maxlinesize int ] [, encoding name ] ) → utl_file.file_type

Открывает файл.

utl_file.fremove ( location text, filename text )

Удаляет файл.

utl_file.frename ( location text, filename text, dest_dir text, dest_file text [, overwrite ])

Переименовывает файл.

utl_file.get_line ( file utl_file.file_type) → text

Считывает одну строку из файла.

utl_file.get_nextline ( file utl_file.file_type) → text

Считывает одну строку из файла или возвращает NULL.

utl_file.is_open ( file utl_file.file_type) → bool

Возвращает true, если файл открыт.

utl_file.new_line ( file utl_file.file_type [, rows int ])

Помещает в файл несколько новых строковых символов.

utl_file.put ( file utl_file.file_type, buffer text )

Помещает буфер в файл.

utl_file.put_line ( file utl_file.file_type, buffer text )

Помещает строку из буфера в файл.

utl_file.putf ( file utl_file.file_type, buffer format [, arg1 text ] [, arg2 text ] [..] [, arg5 text ])

Помещает форматированный текст в файл.

utl_file.tmpdir()

Выводит путь к временному каталогу.

Поскольку QHB не поддерживает вызов по ссылке, некоторые функции немного отличаются: fclose и get_line.

do $$
declare f utl_file.file_type;
begin
  f := utl_file.fopen('/tmp', 'sample.txt', 'r');
  <<readl>>
  loop
    begin
      raise notice '%', utl_file.get_line(f);
    exception
      when no_data_found then
        exit readl;
    end;
  end loop;
  f := utl_file.fclose(f);
end;
$$;

или второй (со специальной функцией QHB get_nextline)

do $$
declare
  f utl_file.file_type;
  line text;
begin
  f := utl_file.fopen('/tmp', 'sample.txt', 'r');
  loop
    line := utl_file.get_nextline(f);
    exit when line is NULL;
    raise notice '%', line;
  end loop
exception
  when others then
  perform utl_file.fclose_all();
end;
$$;

Перед использованием пакета вы должны установить таблицу utl_file.utl_file_dir. Она содержит все разрешенные каталоги без завершающего символа ('/' или '\'). На платформе WinNT пути должны каждый раз заканчиваться символом '\'.

Записи каталога могут быть названы (второй столбец в таблице utl_file.utl_file_dir). Параметр location может быть либо именем каталога, либо путем к словарю. Расположение сначала интерпретируется и проверяется как имя каталога. Если не найден (во 2-м столбце), то расположение интерпретируется и проверяется как путь.

Функции из пакета utl_file (схема в QHB) требуют доступа к таблице utl_file.utl_file_dir. Этот факт можно использовать для контроля того, могут ли пользователи использовать эти функции или нет. Значение по умолчанию — READ для public. INSERT и UPDATE может делать только привилегированный пользователь (суперпользователь). Таким образом, непривилегированный пользователь может использовать функции из этого пакета, но не может изменять список безопасных каталогов (содержимое таблицы utl_file.utl_file_dir). Содержимое этой таблицы видно для public (или должно быть видно для пользователей, использующих функции из этого пакета).

Пакет dbms_sql

Данный пакет — реализация Oracle API пакета DBMS_SQL.

Пакет не обеспечивает полной совместимости, но должен уменьшить работу, необходимую для успешной миграции.

Внимание: архитектура QHB отличается от архитектуры Oracle. PL/pgSQL выполняется в том же контексте, что и механизм SQL. Тогда нет никакой причины использовать шаблоны Oracle, такие как массовый сбор и итерация по сбору в QHB, для получения хорошей производительности. Этот код предназначен для сокращения работы, связанной с переносом некоторых приложений с Oracle на QHB, и может работать хорошо. Но не будет никакого преимущества в производительности по сравнению со встроенными операторами PL/pgSQL. Эмуляция API Oracle требует дополнительных ресурсов памяти и ЦП, которые могут быть значительными при больших объемах данных.

Функциональность

Это расширение реализует подмножество интерфейса Oracle dbms_sql. Целью этого расширения является не совместимость с Oracle, оно предназначено для сокращения объема работы, связанной с миграцией приложений Oracle на QHB. Поддерживаются некоторые базовые функции массового DML:

do $$
declare
  c int;
  a int[];
  b varchar[];
  ca numeric[];
begin
  c := dbms_sql.open_cursor();
  call dbms_sql.parse(c, 'insert into foo values(:a, :b, :c)');
  a := ARRAY[1, 2, 3, 4, 5];
  b := ARRAY['Ahoj', 'Nazdar', 'Bazar'];
  ca := ARRAY[3.14, 2.22, 3.8, 4];

  call dbms_sql.bind_array(c, 'a', a, 2, 3);
  call dbms_sql.bind_array(c, 'b', b, 3, 4);
  call dbms_sql.bind_array(c, 'c', ca);
  raise notice 'inserted rows %d', dbms_sql.execute(c);
end;
$$;

do $$
declare
  c int;
  a int[];
  b varchar[];
  ca numeric[];
begin
  c := dbms_sql.open_cursor();
  call dbms_sql.parse(c, 'select i, ''Ahoj'' || i, i + 0.003 from generate_series(1, 35) g(i)');
  call dbms_sql.define_array(c, 1, a, 10, 1);
  call dbms_sql.define_array(c, 2, b, 10, 1);
  call dbms_sql.define_array(c, 3, ca, 10, 1);

  perform dbms_sql.execute(c);
  while dbms_sql.fetch_rows(c) > 0
  loop
    call dbms_sql.column_value(c, 1, a);
    call dbms_sql.column_value(c, 2, b);
    call dbms_sql.column_value(c, 3, ca);
    raise notice 'a = %', a;
    raise notice 'b = %', b;
    raise notice 'c = %', ca;
  end loop;
  call dbms_sql.close_cursor(c);
end;
$$;

Имеется функция dbms_sql.describe_columns_f, то есть процедура dbms_sql.describe_columns. Обратите внимание, что идентификаторы типов относятся к системе типов QHB. Значения не конвертируются в числа Oracle.

do $$
declare
  c int;
  r record;
  d dbms_sql.desc_rec;
begin
  c := dbms_sql.open_cursor();
  call dbms_sql.parse(c, 'select * from pg_class');
  r := dbms_sql.describe_columns(c);
  raise notice '%', r.col_cnt;

  foreach d in array r.desc_t
  loop
    raise notice '% %', d.col_name, d.col_type::regtype;
  end loop;

  call dbms_sql.close_cursor(c);
end;
$$;

do $$
declare
  c int;
  n int;
  d dbms_sql.desc_rec;
  da dbms_sql.desc_rec[];
begin
  c := dbms_sql.open_cursor();
  call dbms_sql.parse(c, 'select * from pg_class');
  call dbms_sql.describe_columns(c, n, da);
  raise notice '%', n;

  foreach d in array da
  loop
    raise notice '% %', d.col_name, d.col_type::regtype;
  end loop;

  call dbms_sql.close_cursor(c);
end;
$$;

Пакет dbms_pipe

Этот пакет является эмуляцией пакета Oracle dbms_pipe. Обеспечивает межсеансовую связь. Вы можете отправлять и читать любое сообщение с ожиданием или без него, просмотреть список активных каналов, установить канал как частный или общедоступный и использовать явные или неявные каналы.

Максимальное количество каналов: 50.

Разделяемая память используется для отправки сообщений.

Ниже приведен пример:

-- Сеанс A
SELECT dbms_pipe.create_pipe('my_pipe',10,true); -- явное создание канала
SELECT dbms_pipe.pack_message('neco je jinak');
SELECT dbms_pipe.pack_message('anything is else');
SELECT dbms_pipe.send_message('my_pipe',20,0); -- изменение предела и передача без ожидания
SELECT * FROM dbms_pipe.db_pipes; -- список текущих каналов

-- Сеанс B
SELECT dbms_pipe.receive_message('my_pipe',1); -- ожидание для сообщения максимум 1 секунда
SELECT dbms_pipe.next_item_type(); -- -> 11, текст
SELECT dbms_pipe.unpack_message_text();
SELECT dbms_pipe.next_item_type(); -- -> 11, текст
SELECT dbms_pipe.unpack_message_text();
SELECT dbms_pipe.next_item_type(); -- -> 0, больше элементов нет
SELECT dbms_pipe.remove_pipe('my_pipe');

Однако есть некоторые отличия от Oracle:

предел для каналов не в байтах, а в элементах в канале

вы можете отправить сообщение без ожидания

вы можете отправлять пустые сообщения

next_item_type знает о timestamp (тип 13)

QHB не знает о типе raw, вместо этого используйте bytea

Пакет dbms_alert

Еще одно средство межпроцессного взаимодействия.

-- Сеанс A
SELECT dbms_alert.register('boo');
SELECT * FROM dbms_alert.waitany(10);

-- Сеанс B
SELECT dbms_alert.register('boo');
SELECT * FROM dbms_alert.waitany(10);

-- Сеанс C
SELECT dbms_alert.signal('boo','Nice day');

Пакет PLVdate

Этот модуль содержит некоторые функции для работы с рабочими днями из пакета PLVdate. Подробную документацию можно найти в библиотеке PLVision. Этот пакет является мультикультурным, но конфигурации по умолчанию предназначены только для европейских стран.

Вы должны определить свои собственные нерабочие дни (максимум 50 дней) и собственные праздники (максимум 30 дней). Выходной день — это любой нерабочий день, который одинаков каждый год. Например, Рождество в западных странах.

Функции

Функция

Описание

plvdate.add_bizdays ( day date, days int ) → date

Выводит дату, созданную путем добавления n рабочих дней к заданной.

plvdate.nearest_bizday ( day date ) → date

Выводит рабочую дату, ближайшую к заданной дате.

plvdate.next_bizday ( day date ) → date

Выводит следующую рабочую дату с заданной даты.

plvdate.bizdays_between ( day1 date, day2 date ) → int

Выводит количество рабочих дней между двумя заданными датами.

plvdate.prev_bizday ( day date ) → date

Выводит предыдущую рабочую дату с заданной даты.

plvdate_isbizday ( date ) → bool

Возвращает true, если заданная дата является рабочим днем.

plvdate.set_nonbizday ( dow varchar )

Устанавливает заданный день недели как нерабочий день.

plvdate.unset_nonbizday ( dow varchar )

Отменяет установку заданного дня недели как нерабочего.

plvdate.set_nonbizday ( day date )

Устанавливает заданный день как нерабочий.

plvdate.unset_nonbizday ( day date)

Отменяет установку заданного дня как нерабочего.

plvdate.set_nonbizday ( day date, repeat bool )

Устанавливает заданный день как нерабочий; если repeat равно true, то день будет нерабочим каждый год.

plvdate.unset_nonbizday ( day date, repeat bool )

Отменяет установку заданного дня как нерабочего; если repeat равно true, то установка отменяется и на последующие годы.

plvdate.use_easter()

Пасхальное воскресенье и Пасхальный понедельник будут выходными.

plvdate.unuse_easter()

Пасхальное воскресенье и Пасхальный понедельник не будут выходными.

plvdate.use_easter ( use bool )

Если true, то Пасхальное воскресенье и Пасхальный понедельник будут выходными.

plvdate.using_easter() → bool

Возвращает true, если Пасхальное воскресенье и Пасхальный понедельник являются выходными.

plvdate.use_great_friday()

Страстная пятница будет выходным.

plvdate.unuse_easter()

Страстная пятница не будет выходным.

plvdate.use_easter ( use bool )

Если true, то Страстная пятница будет выходным.

plvdate.using_easter() → bool

Возвращает true, если Страстная пятница является выходным.

plvdate.include_start()

Включает в расчет bizdays_between дату начала.

plvdate.noinclude_start()

Исключает из расчета bizdays_between дату начала.

plvdate.include_start (use bool)

Если true, то в расчет bizdays_between включается дата начала.

plvdate.include_start() → bool

Возвращает true, если в расчет bizdays_between включена дата начала.

plvdate.default_holidays ( varchar )

Загружает конфигурации по умолчанию. На данный момент доступны следующие конфигурации: Чехия, Германия, Австрия, Польша, Словакия, Россия, Великобритания и США. Конфигурация содержит только общие праздники для всех регионов. Свой региональный праздник можно добавить с помощью plvdate.set_nonbizday(nonbizday, true).

Пример:

qhb=# select plvdate.default_holidays('czech');
 default_holidays
 -----------------

(1 row)
qhb=# select to_char(current_date, 'day'),
           plvdate.next_bizday(current_date),
	   to_char(plvdate.next_bizday(current_date),'day');
  to_char  | next_bizday |  to_char
 ----------+-------------+-----------
 saturday  | 2006-03-13  | monday
(1 row)

Изменение для неевропейской среды:

SELECT plvdate.unset_nonbizday('saturday');
SELECT plvdate.unset_nonbizday('sunday');
SELECT plvdate.set_nonbizday('friday');
SELECT plvdate.set_nonbizday('2006-05-19', true);
SELECT plvdate.unuse_easter();

Пакет PLVstr и PLVchr

Этот пакет содержит несколько полезных строковых и символьных функций. Каждая функция поддерживает положительные и отрицательные смещения, т. е. смещение как от начала, так и от конца строки. Например:

plvstr.left('abcdef',2) -> ab
plvstr.left('abcdef',-2) -> abcd
plvstr.substr('abcdef',1,1) -> a
plvstr.substr('abcdef',-1,1) -> f
plvstr.substr('abcde',-2,1) -> d

Список функций:

Функция

Описание

plvstr.normalize ( str text )

Нормализует заданную строку: заменяет пустые символы на пробел и несколько пробелов на один.

plvstr.is_prefix ( str text, prefix text, cs bool ) → bool

Возвращает true, если заданный префикс является префиксом указанной строки.

plvstr.is_prefix ( str text, prefix ) → bool

Возвращает true, если заданный префикс является префиксом указанной строки.

plvstr.is_prefix ( str int, prefix int ) → bool

Возвращает true, если заданный префикс является префиксом указанной строки.

plvstr.is_prefix ( str bigint, prefix bigint ) → bool

Возвращает true, если заданный префикс является префиксом указанной строки.

plvstr.substr ( str text, start int, len int ) → text

Извлекает из заданной строки подстроку, начинающуюся с символа на позиции start и длиной в len символов.

plvstr.substr ( str text, start ) → text

Извлекает из заданной строки подстроку, начинающуюся с символа на позиции start.

plvstr.instr ( str text, patt text, start int, n int ) → text

Ищет n-й заданный шаблон в указанной строке, начиная с символа на позиции start.

plvstr.instr ( str text, patt text, start int ) → text

Ищет заданный шаблон в указанной строке, начиная с символа на позиции start.

plvstr.instr ( str text, patt text ) → text

Ищет заданный шаблон в указанной строке.

plvstr.lpart ( str text, div text, start int, nth int, all_if_notfound bool ) → text

Вызовите эту функцию, чтобы вернуть левую часть строки. Если параметр all_if_notfound равен true, то в случае отсутствия искомого возвращается вся строка.

plvstr.lpart ( str text, div text, start int, nth int ) → text

Вызовите эту функцию, чтобы вернуть левую часть строки.

plvstr.lpart ( str text, div text, start int ) → text

Вызовите эту функцию, чтобы вернуть левую часть строки.

plvstr.lpart ( str text, div text ) → text

Вызовите эту функцию, чтобы вернуть левую часть строки.

plvstr.rpart ( str text, div text, start int, nth int, all_if_notfound bool ) → text

Вызовите эту функцию, чтобы вернуть правую часть строки. Если параметр all_if_notfound равен true, то в случае отсутствия искомого возвращается вся строка.

plvstr.rpart ( str text, div text, start int, nth int ) → text

Вызовите эту функцию, чтобы вернуть правую часть строки.

plvstr.rpart ( str text, div text, start int ) → text

Вызовите эту функцию, чтобы вернуть правую часть строки.

plvstr.rpart ( str text, div text, ) → text

Вызовите эту функцию, чтобы вернуть правую часть строки.

plvstr.lstrip ( str text, substr text, num int ) → text

Вызовите эту функцию, чтобы удалить символы с начала строки.

plvstr.lstrip ( str text, substr text ) → text

Вызовите эту функцию, чтобы удалить символы с начала строки.

plvstr.rstrip ( str text, substr text, num int ) → text

Вызовите эту функцию, чтобы удалить символы с конца строки.

plvstr.rstrip ( str text, substr text ) → text

Вызовите эту функцию, чтобы удалить символы с конца строки.

plvstr.rvrs ( str text, start int, _end int ) → text

Реверсирует строку или часть строки.

plvstr.rvrs ( str text, start int ) → text

Реверсирует строку или часть строки.

plvstr.rvrs ( str text ) → text

Реверсирует строку или часть строки.

plvstr.left ( str text, n int ) → text

Возвращает первые num_in символов. Можно использовать отрицательный num_in.

plvstr.right ( str text, n int ) → text

Возвращает последние num_in символов. Можно использовать отрицательный num_in.

plvstr.swap ( str text, replace text, start int, length int ) → text

Заменяет заданной строкой подстроку в указанной строке, начиная с символа на позиции start и длиной в length символов.

plvstr.swap ( str text, replace text ) → text

Заменяет заданной строкой подстроку в указанной строке.

plvstr.betwn ( str text, start int, _end int, inclusive bool ) → text

Ищет строку между заданными начальным и конечным символами. Если параметр inclusive равен true, граничные символы включаются в поиск.

plvstr.betwn( str text, start int, _end text, startnth int, endnth int, inclusive bool, gotoend bool ) → text

Ищет строку между заданными начальным и конечным символами. Если параметр inclusive равен true, граничные символы включаются в поиск. Если параметр gotoend равен true, поиск ведется до конца строки.

plvstr.betwn ( str text, start int, _end int ) → text

Ищет строку между заданными начальным и конечным символами.

plvstr.betwn( str text, start int, _end text, startnth int, endnth int ) → text

Ищет строку между заданными начальным и конечным символами.

plvchr.nth ( str text, n int ) → text

Вызовите эту функцию, чтобы вернуть N-й символ в строке.

plvchr.first ( str text ) → text

Вызовите эту функцию, чтобы вернуть первый символ в строке.

plvchr.last ( str text ) → text

Вызовите эту функцию, чтобы вернуть последний символ в строке.

plvchr.is_blank ( c int )

Это пустой символ.

plvchr.is_blank ( c text )

Это пустой символ.

plvchr.is_digit ( c int )

Это цифра.

plvchr.is_digit ( c text )

Это цифра.

plvchr.is_quote ( c int )

Это кавычка.

plvchr.is_quote ( c text )

Это кавычка.

plvchr.is_other ( c int )

Это другое.

plvchr.is_other ( c text )

Это другое.

plvchr.is_letter ( c int )

Это буква.

plvchr.is_letter ( c text )

Это буква.

plvchr.char_name ( c text )

Возвращает имя символа в код ASCII как VARCHAR.

plvchr.quoted1 ( str text )

Текст в кавычках вида '''.

plvchr.quoted2 ( str int )

Текст в кавычках вида '"'.

plvchr.stripped ( str text, char_in text )

Очищает строку от всех вхождений заданных символов.

Пакет PLVsubst

Пакет PLVsubst выполняет замену строк на основе замещающего ключевого слова.

Функция

Описание

plvsubst.string ( template_in text, vals_in text[] )

Сканирует строку на предмет всех вхождений замещающего ключевого слова и заменяет его следующим значением из списка замещающих значений.

plvsubst.string ( template_in text, vals_in text[], subst_in text )

Сканирует строку на предмет всех вхождений замещающего ключевого слова и заменяет его следующим значением из списка замещающих значений.

plvsubst.string ( template_in text, vals_in text[], delim_in text )

Сканирует строку на предмет всех вхождений замещающего ключевого слова и заменяет его следующим значением из списка замещающих значений.

plvsubst.string ( template_in text, vals_in text[], delim_in text subst_in text )

Сканирует строку на предмет всех вхождений замещающего ключевого слова и заменяет его следующим значением из списка замещающих значений.

plvsubst.setsubst ( str text )

Устанавливает замещающее ключевое слово в значение по умолчанию ('%s').

plvsubst.subst()

Выводит замещающее ключевое слово.

Примеры:

SELECT plvsubst.string('My name is %s %s.', ARRAY['Pavel','Stěhule']);
          string
 --------------------------
 My name is Pavel Stěhule.
(1 row)

SELECT plvsubst.string('My name is %s %s.', 'Pavel,Stěhule');
          string
 --------------------------
 My name is Pavel Stěhule.
(1 row)

SELECT plvsubst.string('My name is $$ $$.', 'Pavel|Stěhule','|','$$');
          string
 --------------------------
 My name is Pavel Stěhule.
(1 row)

Пакет DBMS_utility

dms_utility.format_call_stack()

Возвращает отформатированную строку с содержимым стека вызовов.

qhb=# select foo2();
               foo2
 ---------------------------------
 -----  Call Stack  -----
   line             object
 number  statement  name
      1  return     function foo
      1  return     function foo1
      1  return     function foo2
(1 row)

Пакет PLVlex

Этот пакет несовместим с оригинальным PLVlex.

plvlex.tokens(str text, skip_spaces bool, qualified_names bool)

Возвращает таблицу лексических элементов в строке.

qhb=# select * from
	plvlex.tokens('select * from a.b.c join d ON x=y', true, true);

 pos | token  | code |  class  | separator | mod
 ----+--------+------+---------+-----------+------
   0 | select |  527 | KEYWORD |           |
   7 | *      |   42 | OTHERS  |           | self
   9 | from   |  377 | KEYWORD |           |
  25 | a.b.c  |      | IDENT   |           |
  20 | join   |  418 | KEYWORD |           |
  25 | d      |      | IDENT   |           |
  27 | on     |  473 | KEYWORD |           |
  30 | x      |      | IDENT   |           |
  31 | =      |   61 | OTHERS  |           | self
  32 | y      |      | IDENT   |           |
(10 rows)

ВНИМАНИЕ!
Коды ключевых слов могут меняться от версии к версии QHB!

Пакет DBMS_ASSERT

Этот пакет защищает пользовательский ввод от SQL-инъекций.

Функция

Описание

dbms_assert.enquote_literal(varchar) → varchar

Добавляет открывающие и закрывающие кавычки, проверяет, что все апострофы сопоставлены с соседними апострофами.

dbms_assert.enquote_name(varchar [, boolean]) → varchar

Заключает имя в двойные кавычки. Необязательный второй параметр переводит имя в нижний регистр. Внимание — в Oracle второй параметр переводит имя в верхний регистр.

dbms_assert.noop(varchar) → varchar

Возвращает значение без какой-либо проверки.

dbms_assert.qualified_sql_name(varchar) → varchar

Эта функция удостоверяется, что входная строка является уточненным именем SQL.

dbms_assert.schema_name(varchar) → varchar

Эта функция удостоверяется, что входная строка является существующим именем схемы.

dbms_assert.simple_sql_name(varchar) → varchar

Эта функция удостоверяется, что входная строка является простым именем SQL.

dbms_assert.object_name(varchar) → varchar

Эта функция удостоверяется, что входная строка является уточненным идентификатором SQL для существующего объекта SQL.

Пакет PLUnit

Этот пакет содержит некоторые функции утверждения.

Функция

Описание

plunit.assert_true(bool [, varchar])

Утверждает, что условие истинно.

plunit.assert_false(bool [, varchar])

Утверждает, что условие ложно.

plunit.assert_null(anyelement [, varchar])

Утверждает, что фактическое значение является NULL.

plunit.assert_not_null(anyelement [, varchar])

Утверждает, что фактическое значение не является NULL.

plunit.assert_equals(anyelement, anyelement [, double precision] [, varchar])

Утверждает, что ожидаемое и фактическое значения равны.

plunit.assert_not_equals(anyelement, anyelement [, double precision] [, varchar])

Утверждает, что ожидаемое и фактическое значения не равны.

plunit.fail([varchar])

Эту функцию можно использовать, чтобы с помощью предоставленного сообщения заставить тестовую процедуру немедленно завершиться ошибкой.

Пакет DBMS_random

Функция

Описание

dbms_random.initialize(int)

Инициализирует пакет с величиной рандомизации.

dbms_random.normal()

Возвращает случайные числа со стандартным распределением Гаусса.

dbms_random.random()

Возвращает случайное число из диапазона -2³¹ .. 2³¹.

dbms_random.seed(int)

Возвращает величину рандомизации.

dbms_random.seed(text)

Сбрасывает величину рандомизации.

dbms_random.string(opt text(1), len int)

Создает случайную строку.

dbms_random.terminate()

Завершает пакет (ничего не делает в Pg).

dbms_random.value()

Возвращает случайное число из диапазона [0.0 - 1.0).

dbms_random.value ( low double precision, high double precision )

Возвращает случайное число из диапазона [low - high).

Другие функции

Этот пакет содержит реализацию функций: concat, nvl, nvl2, lnnvl, decode, greatest, least, bitand, nanvl, sinh, cosh, tanh, oracle.substr и oracle.mod.

Функция

Описание

oracle.substr ( str text, start int, len int )

Подстрока, совместимая с Oracle.

oracle.substr ( str text, start int )

Подстрока, совместимая с Oracle.

oracle.substr ( str numeric, start numeric )

Подстрока, совместимая с Oracle.

oracle.substr ( str numeric, start numeric, len numeric )

Подстрока, совместимая с Oracle.

oracle.substr ( str varchar, start numeric )

Подстрока, совместимая с Oracle.

oracle.substr ( str varchar, start numeric, len numeric )

Подстрока, совместимая с Oracle.

oracle.lpad ( string, length [, fill] )

Функция lpad, совместимая с Oracle.

oracle.rpad ( string, length [, fill] )

Функция rpad, совместимая с Oracle.

oracle.ltrim ( string text [, characters text] )

Функция ltrim, совместимая с Oracle.

oracle.rtrim ( string text [, characters text] )

Функция rtrim, совместимая с Oracle.

oracle.btrim ( string text [, characters text] )

Функция btrim, совместимая с Oracle.

oracle.length ( string char )

Функция length, совместимая с Oracle.

oracle.listagg ( str text [, separator text] )

Агрегирует значения в список.

oracle.wm_concat ( str text )

Агрегирует значения в список через запятую.

oracle.median(float4)

Вычисляет медиану.

oracle.median(float8)

Вычисляет медиану.

oracle.to_number(text)

Преобразует строку в число.

oracle.to_number(numeric)

Преобразует строку в число.

oracle.to_number(numeric,numeric)

Преобразует строку в число.

public.to_multi_byte(text)

Преобразует все однобайтовые символы в соответствующие им многобайтовые символы.

public.to_single_byte(text)

Преобразует все многобайтовые символы в соответствующие им однобайтовые символы.

oracle.greatest(anyelement, anyelement[])

Функция greatest, совместимая с Oracle; при вводе NULL возвращает NULL.

oracle.least(anyelement, anyelement[])

Функция least, совместимая с Oracle; при вводе NULL возвращает NULL.

oracle.mod(int, int)

Функция mod, совместимая с Oracle; если второй параметр равен нулю, возвращает первый параметр.

oracle.remainder(int, int)

Возвращает остаток от деления числа на другое число.

oracle.remainder(numeric, numeric)

Возвращает остаток от деления числа на другое число.

oracle.sys_guid() → bytea

Возвращает 16 байт глобального уникального идентификатора.

Возможно, вам потребуется установить для параметра search_path значение oracle, pg_catalog, "$user", public, поскольку функции oracle.substr, oracle.lpad, oracle.rpad, oracle.ltrim, oracle.rtrim, oracle.btrim и oracle.length устанавливаются бок о бок с функциями pg_catalog.substr, pg_catalog.lpad, pg_catalog.rpad, pg_catalog.ltrim, pg_catalog.rtrim, pg_catalog.btrim и pg_catalog.length соответственно.

Функции oracle.decode, oracle.greatest и oracle.least всегда должны начинаться с префикса имени схемы, даже если oracle стоит перед pg_catalog в search_path, потому что эти функции реализованы внутри парсера и анализатора QHB. Без имени схемы всегда будут использоваться внутренние функции.

Обратите внимание, что в случае lpad** и rpad параметры string и fill могут иметь типы char, varchar, text, varchar2 или nvarchar2 (обратите внимание, что последние два типа предоставляются orafce). Символ заполнения по умолчанию представляет собой пробел половинной ширины. То же самое верно для ltrim, rtrim и btrim.

Обратите внимание, что у функции oracle.length есть ограничение, заключающееся в том, что она работает только с единицами символов, поскольку в QHB тип char поддерживает только семантику символов.

Функция oracle.sys_guid()

Эта функция возвращает глобальный уникальный идентификатор. Она вызывает указанные функции из расширения uuid-ossp, а затем эта функция должна быть установлена ​​до использования функции sys_guid. По умолчанию эта функция использует функцию uuid_generate_v1, но также можно использовать функции uuid_generate_v1mc и uuid_generate_v4 (путем установки функции orafce.sys_guid_source). Кроме того, oracle.sys_guid может использовать встроенную функцию gen_random_uuid. В этом случае расширение uuid-ossp не требуется.

Поддержка varchar2 и nvarchar2

Тип varchar2 от orafce реализует части спецификации базы данных Oracle о varchar2:

Единица модификатора типа — 'байты' (семантику символов см. в описании nvarchar2).

В отличие от varchar QHB, неявное приведение к varchar2 не усекает пробелы сверх заявленной максимальной длины.

При включении orafce.varchar2_null_safe_concat TO true для этих типов можно использовать защищенный от NULL оператор ||. Это поведение очень похоже на Oracle.

ВНИМАНИЕ!
Если результатом является пустая строка, то результат равен NULL. По умолчанию это поведение отключено.

-- защищенная от NULL конкатенация (по умолчанию отключено)
SELECT NULL || 'hello'::varchar2 || NULL;

SET orafce.varchar2_null_safe_concat TO true;
SELECT NULL || 'hello'::varchar2 || NULL;

Обратите внимание, что QHB не позволяет динамически указывать, как мы интерпретируем строки varchar. Она всегда интерпретирует их как «символьные» строки в соответствии с кодировкой базы данных. Таким образом, мы не можем одновременно поддерживать семантику байтов и символов для данного типа varchar в одной и той же базе данных. Мы решили реализовать семантику байтов, так как она используется в Oracle по умолчанию. Семантику символов см. в описании nvarchar2, который по умолчанию всегда реализует семантику символов.

Будьте осторожны при использовании вышеуказанного типа для хранения строк, состоящих из многобайтовых закодированных символов, где каждый символ может состоять из произвольного количества байтов.

nvarchar2 реализует следующее:

Единица модификатора типа = 'символы' (с использованием набора символов/ кодировки базы данных)

Используйте этот тип, если предпочтительна семантика символов.

Обратите внимание, что, в отличие от Oracle, в orafce типы varchar2* и nvarchar2 не налагают ограничение в 4000 байт на «объявленный» размер. На самом деле это то же самое, что и у varchar в QHB, размер которого составляет около 10 МБ (хотя теоретически varchar может хранить значения размером до 1 ГБ).

Некоторые строковые функции на основе байтов для использования со строками varchar2:

substrb(varchar2, int [, int])

Извлекает подстроку заданной длины (в байтах), начиная с заданной позиции байта (считая с единицы); если третий аргумент не указан, то считается длина до конца строки.

strposb(varchar2, varchar2)

Возвращает расположение указанной подстроки в заданной строке (считая с единицы).

lengthb(varchar2)

Возвращает длину (в байтах) заданной строки.

Триггеры

Oracle не делает различий между NULL и пустой строкой (когда значение используется как текст). Для QHB NULL и пустая строка являются разными значениями. Для простоты рекомендуется обеспечить (в базе данных QHB) использование только NULL (и не использовать пустые строки) или использование только пустых строк (и не использовать NULL) для столбцов текстового типа. Оба варианта имеют некоторые преимущества и недостатки.

Это можно обеспечить с помощью триггерных функций:

oracle.replace_empty_strings([raise_warnings boolean])
oracle.replace_null_strings([raise_warnings boolean])

Необязательный логический аргумент используется в качестве индикатора, поэтому эти функции должны выдавать предупреждение, когда внутри них была изменена строка.

CREATE TABLE test(id serial, name varchar, surname varchar);
CREATE TRIGGER test_trg
  BEFORE INSERT OR UPDATE
  ON test
  FOR EACH ROW
  EXECUTE PROCEDURE oracle.replace_empty_strings();

INSERT INTO test(name, surname) VALUES('', 'Stehule');

-- имя будет заменено на NULL

Эмулированные представления

oracle.user_tab_columns

oracle.user_tables

oracle.user_cons_columns

oracle.user_constraints

oracle.product_component_version

oracle.user_objects

oracle.dba_segments

См. также

Документация по orafce на Github

pg_hint_plan

pg_hint_plan — управляет планом выполнения с помощью фраз-подсказок в комментариях особой формы.

QHB использует оптимизатор на основе стоимости, который использует статистику данных, а не статические правила. Планировщик (оптимизатор) оценивает стоимость каждого возможного плана выполнения для оператора SQL, после чего в итоге выполняется план с наименьшей стоимостью. Планировщик делает все возможное, чтобы выбрать наилучший план выполнения, но он не совершенен, поскольку не учитывает некоторые свойства данных, например корреляцию между столбцами.

pg_hint_plan позволяет корректировать планы выполнения с помощью так называемых «подсказок», представляющих собой простые описания в комментариях SQL особой формы.

Установка

Модуль pg_hint_plan для QHB поставляется в виде пакета qhb-1.5.2-pg-hint-plan.

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

Применение

pg_hint_plan считывает фразы-подсказки в комментарии особой формы, заданном вместе с целевым оператором SQL. Специальная форма начинается с последовательности символов /*+ и заканчивается последовательностью */. Фразы-подсказки состоят из имени подсказки и следующих за ним параметров, заключенных в круглые скобки и разделенных пробелами. Для удобства чтения каждая фраза-подсказка может быть разделена на несколько строк.

В приведенном ниже примере в качестве метода соединения выбрано хеш-соединение, а pgbench_accounts сканируется последовательным методом.

qhb=# /*+
qhb*#    <b>HashJoin(a b)</b>
qhb*#    <b>SeqScan(a)</b>
qhb*#  */
qhb-# EXPLAIN SELECT *
qhb-#    FROM pgbench_branches b
qhb-#    JOIN pgbench_accounts a ON b.bid = a.bid
qhb-#   ORDER BY a.aid;
                                        QUERY PLAN
---------------------------------------------------------------------------------------
    Sort  (cost=31465.84..31715.84 rows=100000 width=197)
    Sort Key: a.aid
    ->  <b>Hash Join</b>  (cost=1.02..4016.02 rows=100000 width=197)
            Hash Cond: (a.bid = b.bid)
            ->  <b>Seq Scan on pgbench_accounts a</b>  (cost=0.00..2640.00 rows=100000 width=97)
            ->  Hash  (cost=1.01..1.01 rows=1 width=100)
                ->  Seq Scan on pgbench_branches b  (cost=0.00..1.01 rows=1 width=100)
(7 rows)

qhb=#

Таблица подсказок

В предыдущем разделе говорилось, что подсказки даются в комментарии особой формы. Это неудобно, когда запросы нельзя редактировать. В таких случаях подсказки можно разместить в специальной таблице hint_plan.hints. Таблица состоит из следующих столбцов:

Столбец | Описание
id | Уникальный номер для идентификации строки с подсказкой. Этот столбец заполняется автоматически по порядку.
norm_query_string | Шаблон, соответствующий запросу, к которому будет относиться подсказка. Константы в запросе должны быть заменены на '?', как в следующем примере. Пробельные символы в шаблоне являются значимыми.
application_name | Значение application_name (имя приложения), выбирающее сеансы для применения подсказки. Подсказка в приведенном ниже примере применяется к сеансам, подключенным из psql. Пустая строка означает сеансы с любым значением application_name.
hints | Фраза-подсказка. Это должна быть серия подсказок без окружающих меток комментариев.

В следующем примере показано, как работать с таблицей подсказок.

qhb=# INSERT INTO hint_plan.hints(norm_query_string, application_name, hints)
qhb-#     VALUES (
qhb(#         'EXPLAIN (COSTS false) SELECT * FROM t1 WHERE t1.id = ?;',
qhb(#         '',
qhb(#         'SeqScan(t1)'
qhb(#     );
INSERT 0 1
qhb=# UPDATE hint_plan.hints
qhb-#    SET hints = 'IndexScan(t1)'
qhb-#  WHERE id = 1;
UPDATE 1
qhb=# DELETE FROM hint_plan.hints
qhb-#  WHERE id = 1;
DELETE 1
qhb=#

Таблица подсказок принадлежит пользователю-создателю и имеет права, установленные по умолчанию во время ее создания, при выполнении CREATE EXTENSION. Табличные подсказки имеют приоритет над подсказками в комментариях.

Типы подсказок

В зависимости от вида объекта и того, как они могут повлиять на планирование, фразы-подсказки подразделяются на шесть типов: методы сканирования, методы соединения, порядок соединения, корректировка количества строк, параллельный запрос и параметры GUC. Вы увидите списки фраз-подсказок каждого типа в соответствующем списке (см. раздел Список подсказок).

Подсказки для методов сканирования

Подсказки для методов сканирования принудительно задают определенный метод сканирования для целевой таблицы. pg_hint_plan распознает целевую таблицу по псевдонимам, если таковые имеются. К этому типу относятся подсказки «SeqScan», «IndexScan» и т. д.

Подсказки сканирования эффективны для обычных таблиц, таблиц наследования, нежурналируемых таблиц, временных таблиц и системных каталогов. Внешние (сторонние) таблицы, табличные функции, предложения VALUES, CTE, представления и подзапросы не затрагиваются.

qhb=# /*+
qhb*#     SeqScan(t1)
qhb*#     IndexScan(t2 t2_pkey)
qhb*#  */
qhb-# SELECT * FROM table1 t1 JOIN table table2 t2 ON (t1.key = t2.key);

Подсказки для методов соединения

Подсказки для методов соединения принудительно задают определенные методы для соединения заданных таблиц.

Это действует только на соединения обычных таблиц, таблиц наследования, нежурналируемых таблиц, временных таблиц, внешних (сторонних) таблиц, системных каталогов, табличных функций, результатов команды VALUES и CTE. Но соединения в представлениях и подзапросах не затрагиваются.

Подсказка для порядка соединений

Подсказка «Leading» принудительно задает порядок соединения двух или более таблиц. Есть два способа это сделать. Первый — установить определенный порядок соединения, но не ограничивать направление на каждом уровне соединения:

qhb=# /*+
qhb*#     NestLoop(t1 t2)
qhb*#     MergeJoin(t1 t2 t3)
qhb*#     Leading(t1 t2 t3)
qhb*#  */
qhb-# SELECT * FROM table1 t1
qhb-#     JOIN table table2 t2 ON (t1.key = t2.key)
qhb-#     JOIN table table3 t3 ON (t2.key = t3.key);

При другом способе дополнительно указывается направление соединения. Подробности можно увидеть в таблице списка подсказок. Когда задается показанный выше порядок соединения, его направление, выбранное планировщиком (внешняя таблица — внутренняя таблица), может отличиться от ожидаемого. Если в такой ситуации вы хотите изменить направление соединения, воспользуйтесь следующим форматом:

qhb=# / * + Leading ((t1 (t2 t3))) * / SELECT ...

В этом формате два элемента в скобках являются вложенными, при этом внутри одних скобок первый элемент будет внешней таблицей, а второй — внутренней.

Подробную информацию см. в разделе Список подсказок.

Подсказки для управления поведением соединения

Подсказка «Memoize» позволяет соединению запоминать внутренний результат, а подсказка «NoMemoize» это запрещает. В следующем примере подсказка «NoMemoize» запрещает самому верхнему хеш-соединению запоминать результат таблицы a.

qhb=# /*+ NoMemoize(a b) */
EXPLAIN SELECT * FROM a, b WHERE a.val = b.val;
                             QUERY PLAN
--------------------------------------------------------------------
 Hash Join  (cost=270.00..1412.50 rows=100000 width=16)
   Hash Cond: (b.val = a.val)
   ->  Seq Scan on b  (cost=0.00..15.00 rows=1000 width=8)
   ->  Hash  (cost=145.00..145.00 rows=10000 width=8)
         ->  Seq Scan on a  (cost=0.00..145.00 rows=10000 width=8)

Подсказка для корректировки количества строк

Подсказка «Rows» исправляет неправильную оценку количества строк соединений, возникающую из-за ограничений планировщика.

qhb=# /*+ Rows(a b #10) */ SELECT... ; Sets rows of join result to 10
qhb=# /*+ Rows(a b +10) */ SELECT... ; Increments row number by 10
qhb=# /*+ Rows(a b -10) */ SELECT... ; Subtracts 10 from the row number.
qhb=# /*+ Rows(a b *10) */ SELECT... ; Makes the number 10 times larger.

Подсказка для параллельного плана

Подсказка «Parallel» принудительно устанавливает конфигурацию параллельного выполнения при сканировании. Третий параметр определяет уровень изменений конфигурации. Уровень soft означает, что pg_hint_plan меняет только параметр max_parallel_worker_per_gather и оставляет все прочие параметры на усмотрение планировщика. На уровне hard изменяются и другие параметры планировщика, чтобы принудительно установить количество параллельных рабочих процессов. Эта подсказка может воздействовать на обычные таблицы, родительские таблицы в иерархии наследования, нежурналируемые таблицы и системные каталоги. Внешние таблицы, табличные функции, предложения VALUES, CTE, представления и подзапросы не затрагиваются. Внутренние таблицы представления в качестве целевого объекта можно указывать по реальному имени/псевдониму. В следующем примере показано, что запрос выполняется по-разному для каждой таблицы.

qhb=# explain /*+ Parallel(c1 3 hard) Parallel(c2 5 hard) */
       SELECT c2.a FROM c1 JOIN c2 ON (c1.a = c2.a);
                                  QUERY PLAN
-------------------------------------------------------------------------------
 Hash Join  (cost=2.86..11406.38 rows=101 width=4)
   Hash Cond: (c1.a = c2.a)
   ->  Gather  (cost=0.00..7652.13 rows=1000101 width=4)
         Workers Planned: 3
         ->  Parallel Seq Scan on c1  (cost=0.00..7652.13 rows=322613 width=4)
   ->  Hash  (cost=1.59..1.59 rows=101 width=4)
         ->  Gather  (cost=0.00..1.59 rows=101 width=4)
               Workers Planned: 5
               ->  Parallel Seq Scan on c2  (cost=0.00..1.59 rows=59 width=4)

qhb=# EXPLAIN /*+ Parallel(tl 5 hard) */ SELECT sum(a) FROM tl;
                                    QUERY PLAN
-----------------------------------------------------------------------------------
 Finalize Aggregate  (cost=693.02..693.03 rows=1 width=8)
   ->  Gather  (cost=693.00..693.01 rows=5 width=8)
         Workers Planned: 5
         ->  Partial Aggregate  (cost=693.00..693.01 rows=1 width=8)
               ->  Parallel Seq Scan on tl  (cost=0.00..643.00 rows=20000 width=4)

Временная установка параметров GUC

Подсказка «Set» подсказка изменяет параметры GUC прямо во время планирования. Параметр GUC, показанный в подразделе Константы стоимости для планировщика, может оказать ожидаемое влияние на планирование, если только какие-либо другие подсказки не конфликтуют с параметрами конфигурации метода планировщика. Из нескольких подсказок для одного параметра GUC в силу вступает последняя. С помощью этой подсказки можно установить и параметры GUC для pg_hint_plan, но она не будет работать так, как вы ожидаете.

qhb=# /*+ Set(random_page_cost 2.0) */
qhb-# SELECT * FROM table1 t1 WHERE key = 'value';

Параметры GUC для pg_hint_plan

Нижеприведенные параметры GUC влияют на поведение pg_hint_plan.

Имя параметра | Описание | Значение по умолчанию
pg_hint_plan.enable_hint | Значение true включает pg_hint_plan. | on (вкл.)
pg_hint_plan.enable_hint_table | Значение true включает использование подсказок из таблицы. | on (вкл.)
pg_hint_plan.parse_messages | Задает уровень записи в журнал ошибок синтаксического анализа подсказок. Допустимые значения: error (ошибка), warning (предупреждение), notice (уведомление), info (информация), log (протоколирование), debug (отладка). | INFO
pg_hint_plan.debug_print | Управляет выводом и детализацией информации об отладке. Допустимые значения: off, on, detailed (подробно) и verbose (очень подробно). | off (выкл.)
pg_hint_plan.message_level | Задает уровень вывода отладочных сообщений. Допустимые значения: error, warning, notice, info, log, debug. | LOG
pg_hint_plan.hints_anywhere | При значении on pg_hint_plan считывает подсказки, игнорируя синтаксис SQL. Это позволяет размещать подсказки в любом месте запроса, но учтите, что при этом подсказки могут считываться некорректно. | off (выкл.)

Подробное описание подсказок

Синтаксис и расположение

pg_hint_plan считывает подсказки только из первого блочного комментария и при обнаружении любых символов, кроме букв, цифр, пробелов, подчеркиваний, запятых и скобок, немедленно прекращает синтаксический анализ. В следующем примере HashJoin(a b) и SeqScan(a) воспринимаются как подсказки, а IndexScan(a) и MergeJoin(a b) — нет.

qhb=# /*+
qhb*#    HashJoin(a b)
qhb*#    SeqScan(a)
qhb*#  */
qhb-# /*+ IndexScan(a) */
qhb-# EXPLAIN SELECT /*+ MergeJoin(a b) */ *
qhb-#    FROM pgbench_branches b
qhb-#    JOIN pgbench_accounts a ON b.bid = a.bid
qhb-#   ORDER BY a.aid;
                                      QUERY PLAN
---------------------------------------------------------------------------------------
 Sort  (cost=31465.84..31715.84 rows=100000 width=197)
   Sort Key: a.aid
   ->  Hash Join  (cost=1.02..4016.02 rows=100000 width=197)
         Hash Cond: (a.bid = b.bid)
         ->  Seq Scan on pgbench_accounts a  (cost=0.00..2640.00 rows=100000 width=97)
         ->  Hash  (cost=1.01..1.01 rows=1 width=100)
               ->  Seq Scan on pgbench_branches b  (cost=0.00..1.01 rows=1 width=100)
(7 rows)

qhb=#

Однако когда в параметре hints_anywhere установлено значение on, pg_hint_plan считывает подсказки из любого места запроса, поэтому следующие варианты использования подсказки будут равнозначными:

EXPLAIN /*+ SeqScan(t1)*/
SELECT * FROM table1 t1 WHERE a < 10;
                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on table1 t1  (cost=0.00..17.50 rows=9 width=8)
   Filter: (a < 10)
(2 rows)

EXPLAIN
SELECT * FROM table1 t1 WHERE a < 10 AND '/*+SeqScan(t1)*/' <> '';
                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on table1 t1  (cost=0.00..17.50 rows=9 width=8)
   Filter: (a < 10)
(2 rows)

EXPLAIN
SELECT * FROM table1 t1 WHERE a < 10 /*+SeqScan(t1)*/;
                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on table1 t1  (cost=0.00..17.50 rows=9 width=8)
   Filter: (a < 10)
(2 rows)

Использование с PL/pgSQL

pg_hint_plan работает для запросов в скриптах PL/pgSQL с некоторыми ограничениями.

Подсказки действуют только на следующие виды запросов:

Запросы, возвращающие одну строку (SELECT, INSERT, UPDATE и DELETE)

Запросы, возвращающие множество строк (RETURN QUERY)

Динамические операторы SQL (EXECUTE)

Открытие курсора (OPEN)

Цикл по результату запроса (FOR)

Комментарий с подсказкой должен размещаться после первого слова запроса, как показано в следующем примере, поскольку комментарии, стоящие перед ним, в составе запроса не передаются.

qhb=# CREATE FUNCTION hints_func(integer) RETURNS integer AS $$
qhb$# DECLARE
qhb$#     id  integer;
qhb$#     cnt integer;
qhb$# BEGIN
qhb$#     SELECT /*+ NoIndexScan(a) */ aid
qhb$#         INTO id FROM pgbench_accounts a WHERE aid = $1;
qhb$#     SELECT /*+ SeqScan(a) */ count(*)
qhb$#         INTO cnt FROM pgbench_accounts a;
qhb$#     RETURN id + cnt;
qhb$# END;
qhb$# $$ LANGUAGE plpgsql;

Регистр букв в именах объектов

В отличие от того, как обрабатываются имена объектов в QHB, pg_hint_plan сравнивает непосредственно имена объектов в подсказках с внутренними именами объектов базы данных с учетом регистра. Таким образом, имени TBL в подсказке соответствует только «TBL» в базе данных, и не соответствуют имена без кавычек вроде TBL, tbl или Tbl.

Экранирование специальных символов в именах объектов

Если имена объектов в качестве параметра подсказки содержат скобки, двойные кавычки и пробелы, эти имена должны быть заключены в двойные кавычки. При этом действует то же правило экранирования, что и в QHB.

Разграничение между несколькими вхождениями таблицы

pg_hint_plan идентифицирует целевой объект с помощью псевдонимов, если они существуют. Это поведение позволяет нацелиться на какое-то конкретное вхождение среди нескольких вхождений одной таблицы.

qhb=# /*+ HashJoin(t1 t1) */
qhb-# EXPLAIN SELECT * FROM s1.t1
qhb-# JOIN public.t1 ON (s1.t1.id=public.t1.id);
INFO:  hint syntax error at or near "HashJoin(t1 t1)"
DETAIL:  Relation name "t1" is ambiguous.
...
qhb=# /*+ HashJoin(pt st) */
qhb-# EXPLAIN SELECT * FROM s1.t1 st
qhb-# JOIN public.t1 pt ON (st.id=pt.id);
                             QUERY PLAN
---------------------------------------------------------------------
 Hash Join  (cost=64.00..1112.00 rows=28800 width=8)
   Hash Cond: (st.id = pt.id)
   ->  Seq Scan on t1 st  (cost=0.00..34.00 rows=2400 width=4)
   ->  Hash  (cost=34.00..34.00 rows=2400 width=4)
         ->  Seq Scan on t1 pt  (cost=0.00..34.00 rows=2400 width=4)

Нижележащие таблицы представлений или правил

Подсказки неприменимы к самим представлениям, но могут воздействовать на запросы к ним, если имена объектов в подсказках соответствуют именам объектов в развернутом запросе представления. Псевдонимы, присвоенные таблицам в представлении, позволяют манипулировать этими таблицами снаружи представления.

qhb=# CREATE VIEW v1 AS SELECT * FROM t2;
qhb=# EXPLAIN /*+ HashJoin(t1 v1) */
          SELECT * FROM t1 JOIN v1 ON (c1.a = v1.a);
                            QUERY PLAN
------------------------------------------------------------------
 Hash Join  (cost=3.27..18181.67 rows=101 width=8)
   Hash Cond: (t1.a = t2.a)
   ->  Seq Scan on t1  (cost=0.00..14427.01 rows=1000101 width=4)
   ->  Hash  (cost=2.01..2.01 rows=101 width=4)
         ->  Seq Scan on t2  (cost=0.00..2.01 rows=101 width=4)

Таблицы в иерархии наследования

Подсказки могут быть нацелены только на родителя в иерархии наследования, но воздействуют при этом на всю иерархию. В то же время подсказки, нацеленные непосредственно на потомков, не действуют.

Подсказки для составных операторов

У одного составного оператора может быть ровно один комментарий с подсказкой, и эта подсказка воздействует на все отдельные операторы в этом составном. Обратите внимание, что кажущийся похожим составной оператор в интерактивном интерфейсе psql внутренне представляет собой последовательность одиночных операторов, поэтому подсказки воздействуют только на тот оператор, перед которым стоят.

Выражения VALUES

Выражения VALUES в предложении FROM внутренне называются «*VALUES*», поэтому к ним можно ставит подсказку, если это единственное VALUES в запросе. Два и более выражения VALUES в запросе могут казаться различными, если посмотреть на их результат в EXPLAIN. Но в действительности это всего лишь поверхностная разница, и на самом деле они неразличимы.

qhb=# /*+ MergeJoin(*VALUES*_1 *VALUES*) */
      EXPLAIN SELECT * FROM (VALUES (1, 1), (2, 2)) v (a, b)
      JOIN (VALUES (1, 5), (2, 8), (3, 4)) w (a, c) ON v.a = w.a;
INFO:  pg_hint_plan: hint syntax error at or near "MergeJoin(*VALUES*_1 *VALUES*) "
DETAIL:  Relation name "*VALUES*" is ambiguous.
                               QUERY PLAN
-------------------------------------------------------------------------
 Hash Join  (cost=0.05..0.12 rows=2 width=16)
   Hash Cond: ("*VALUES*_1".column1 = "*VALUES*".column1)
   ->  Values Scan on "*VALUES*_1"  (cost=0.00..0.04 rows=3 width=8)
   ->  Hash  (cost=0.03..0.03 rows=2 width=8)
         ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=8)

Подзапросы

В следующем контексте к подзапросам можно иногда давать подсказку, используя имя «ANY_subquery».

IN (SELECT ... {LIMIT | OFFSET ...} ...)
= ANY (SELECT ... {LIMIT | OFFSET ...} ...)
= SOME (SELECT ... {LIMIT | OFFSET ...} ...)

Для такого синтаксиса планировщик внутри присваивает это имя подзапросу, когда планирует соединения таблиц, включающих этот подзапрос, поэтому для таких соединений можно применять подсказки соединений, используя это неявное имя следующим образом:

qhb=# /*+HashJoin(a1 ANY_subquery)*/
qhb=# EXPLAIN SELECT *
qhb=#    FROM pgbench_accounts a1
qhb=#   WHERE aid IN (SELECT bid FROM pgbench_accounts a2 LIMIT 10);
                                          QUERY PLAN

 ---------------------------------------------------------------------------------------------
 Hash Semi Join  (cost=0.49..2903.00 rows=1 width=97)
    Hash Cond: (a1.aid = a2.bid)
    ->  Seq Scan on pgbench_accounts a1  (cost=0.00..2640.00 rows=100000 width=97)
    ->  Hash  (cost=0.36..0.36 rows=10 width=4)
          ->  Limit  (cost=0.00..0.26 rows=10 width=4)
                ->  Seq Scan on pgbench_accounts a2  (cost=0.00..2640.00 rows=100000 width=4)

Использование подсказки IndexOnlyScan

Вопреки ожиданиям, сканирование индекса может выполняться с другим индексом, когда с индексом, указанным в подсказке IndexOnlyScan, нельзя выполнить сканирование только по индексу.

Поведение NoIndexScan

Подсказка NoIndexScan подразумевает NoIndexOnlyScan.

Подсказка Parallel и UNION

Предложение UNION может выполняться в параллельном режиме, только когда все нижележащие подзапросы безопасны для параллельного выполнения. С другой стороны, принудительный выбор параллельного выполнения для любого из подзапросов позволяет параллельно выполняться предложению UNION, для которого это возможно. При этом подсказка Parallel с нулевым количеством рабочих процессов запрещает выполнение сканирования в параллельном режиме.

Установка параметров pg_hint_plan с помощью подсказок Set

Параметры pg_hint_plan меняют его собственное поведение, поэтому некоторые параметры работают не так, как ожидается.

Подсказки для изменения enable_hint, enable_hint_table игнорируются, даже несмотря на то, что в журналах отладки они записываются как «использованные подсказки».

Установка debug_print и message_level начинает действовать с середины процедуры обработки целевого запроса.

Ошибки

pg_hint_plan останавливает синтаксический анализ при любой ошибке и в большинстве случаев использует уже проанализированные подсказки. Ниже приведены типичные ошибки.

Синтаксические ошибки

Все синтаксические ошибки или неправильные имена подсказок протоколируются как синтаксические ошибки. Этих ошибки протоколируются в журнал сервера с уровнем сообщения, заданным в параметре pg_hint_plan.message_level, если параметр pg_hint_plan.debug_print имеет значение on (включен) и выше.

Неправильные спецификации объекта

Неправильные спецификации объекта приводят к молчаливому игнорированию подсказок. Ошибки такого типа протоколируются в журнал сервера как «неиспользованные подсказки» по тому же принципу, что и синтаксические ошибки.

Излишние или противоречивые подсказки

При избытке подсказок или конфликте подсказок друг с другом действовать будет последняя подсказка. Ошибки такого типа протоколируются в журнал сервера как «дублирующиеся подсказки» по тому же принципу, что и синтаксические ошибки.

Вложенные комментарии

Комментарий с подсказкой не может включать в себя другой блочный комментарий. Если pg_hint_plan находит такой комментарий, то, в отличие от других ошибок, останавливает синтаксический анализ и отбрасывает все уже проанализированные подсказки. Об этом типе ошибки сообщается так же, как и о других ошибках.

Функциональные ограничения

Влияние некоторых параметров планировщика GUC

Планировщик не пытается учитывать порядок соединения записей в предложении FROM, если их больше from_collapse_limit. При этом pg_hint_plan не может повлиять на порядок соединения, как ожидается в таких случаях.

Подсказки, пытающиеся принудительно выбрать невыполнимые планы

Если принудительно выбранный план нельзя выполнить, планировщик выбирает любые исполняемые планы.

Использовать для FULL OUTER JOIN вложенный цикл

Использовать индексы, в которых нет столбцов, задействованных в условиях

Выполнить сканирование TID для запросов без условий с ctid

Запросы в ECPG

ECPG удаляет комментарии в запросах, написанных в виде встроенного SQL, поэтому из этих запросов нельзя передать подсказки. Единственным исключением является то, что команда EXECUTE передает заданную строку без изменений. В этом случае стоит рассмотреть возможность использования таблицы подсказок.

Работа с cpg_stat_statements

pg_stat_statements генерирует идентификатор запроса, игнорируя комментарии. В результате одинаковые запросы с разными подсказками сводятся вместе как один и тот же запрос.

Список подсказок

Ниже перечислены доступные подсказки.

Группа

Формат
Описание

Метод сканирования

SeqScan(таблица)
Принудительно задает последовательное сканирование таблицы.

TidScan(таблица)
Принудительно задает сканирование таблицы по TID.

IndexScan(таблица[ индекс...])
Принудительное задает сканирование таблицы по индексу. Если задаются индексы, сканирование ограничивается ими.

IndexOnlyScan(таблица[ индекс...])
Принудительное задает сканирование таблицы только по индексу. Если задаются индексы, сканирование ограничивается ими. Если сканирование только по индексу недоступно, возможно использование обычного сканирования по индексу.

BitmapScan(таблица[ индекс...])
Принудительное задает сканирование таблицы по битовой карте. Если задаются индексы, сканирование ограничивается ими.

IndexScanRegexp(таблица[ регулярное выражение POSIX...])
Принудительное задает сканирование таблицы по индексу. Сканирование ограничивается индексами, соответствующими указанному регулярному выражению POSIX.

IndexOnlyScanRegexp(таблица[ регулярное выражение POSIX...])
Принудительное задает сканирование таблицы только по индексу. Сканирование ограничивается индексами, соответствующими указанному регулярному выражению POSIX.

BitmapScanRegexp(таблица[ регулярное выражение POSIX...])
Принудительное задает сканирование таблицы по битовой карте. Сканирование ограничивается индексами, соответствующими указанному регулярному выражению POSIX.

NoSeqScan(таблица)
Принудительно отключает выполнение последовательного сканирования таблицы.

NoTidScan(таблица)
Принудительно отключает выполнение сканирования таблицы по TID.

NoIndexScan(таблица)
Принудительно отключает выполнение обычного сканирования таблицы по индексу и сканирования таблицы только по индексу.

NoIndexOnlyScan(таблица)
Принудительно отключает выполнение сканирования таблицы только по индексу.

NoBitmapScan(таблица)
Принудительно отключает выполнение сканирования таблицы по битовой карте.

Метод соединения

NestLoop(таблица таблица[ таблица...])
Принудительно задает вложенный цикл для соединений с указанными таблицами.

HashJoin(таблица таблица[ таблица...])
Принудительно задает хеш-соединение для соединений с указанными таблицами.

MergeJoin(таблица таблица[ таблица...])
Принудительно задает соединение слиянием для соединений с указанными таблицами.

NoNestLoop(таблица таблица[ таблица...])
Принудительно отключает вложенный цикл для соединений с указанными таблицами.

NoHashJoin(таблица таблица[ таблица...])
Принудительно отключает выполнение хеш-соединения для соединений с указанными таблицами.

NoMergeJoin(таблица таблица[ таблица...])
Принудительно отключает выполнение соединения слиянием для соединений с указанными таблицами.

Порядок соединения

Leading(таблица таблица[ таблица...])
Принудительно задает указанный порядок соединения.

Leading(<соединяемая пара>)
Принудительно задает указанный порядок соединения. Соединяемая пара — это пара таблиц и/или других соединяемых пар, заключенная в круглые скобки, что позволяет образовывать вложенную структуру.

Управление поведением соединения

Memoize(таблица таблица[ таблица...])
Позволяет самому верхнему соединению среди соединений с указанными таблицами запоминать внутренний результат. (Обратите внимание, что запоминание не задействуется принудительно.)

NoMemoize(таблица таблица[ таблица...])
Запрещает самому верхнему соединению среди соединений с указанными таблицами запоминать внутренний результат.

Корректировка количества строк

Rows(таблица таблица[ таблица...] корректировка)
Корректирует количество строк в результате соединения с указанными таблицами. Доступные методы корректировки: абсолютное значение (#), сложение (+), вычитание (-) и умножение (*). Здесь должно быть строкой, которую может прочитать функция strtod().

Конфигурация параллельных запросов

Parallel(таблица <количество рабочих процессов> [soft|hard])
Принудительно включает или запрещает параллельное выполнение указанной таблицы. <количество рабочих процессов> — желаемое количество параллельных рабочих процессов, где ноль означает запрещение параллельного выполнения. Если третий параметр равен soft (по умолчанию), он просто изменяет параметр max_parallel_workers_per_gather, а все остальные параметры оставляет на усмотрение планировщика. Со значением hard заданное количество рабочих процессов устанавливается принудительно.

GUC

Set(параметр-GUC значение)
Устанавливает значение для параметра GUC на время работы планировщика.

См. также

EXPLAIN, SET, Конфигурация сервера, Параллельные планы, Документация по pg_hint_plan на Github

pg_store_plans

Описание

Модуль pg_store_plans предоставляет средства для отслеживания статистики плана выполнения всех операторов SQL, выполняемых сервером.

Этот модуль необходимо загрузить, добавив pg_store_plans в параметр shared_preload_libraries файла qhb.conf, поскольку ему требуется дополнительная разделяемая память. Это означает, что для добавления или удаления этого модуля понадобится перезагрузка сервера. pg_store_plans требует, чтобы переменная GUC compute_query_id имела значение on или auto. Если установлено значение no, pg_store_plans автоматически отключается.

Установка

Модуль для отслеживания статистики плана выполнения операторов SQL для QHB поставляется в виде пакета qhb-1.5.2-pg-store-plans.

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

Представление pg_store_plans

Статистика, собранная модулем, доступна через системное представление pg_store_plans. Это представление содержит по одной строке для каждого отдельного набора идентификаторов баз данных, идентификаторов пользователей и идентификаторов запросов. Столбцы представления описаны в Таблице 1.

Таблица 1. Столбцы pg_store_plans

Столбец Тип

Описание

userid oid (ссылается на pg_authid.oid)

OID пользователя, выполнившего оператор

dbid oid (ссылается на pg_database.oid)

OID базы данных, в которой был выполнен оператор

queryid bigint

Идентификатор запроса, сгенерированный ядром. Если для параметра calculate_query_id установлено значение no, pg_store_plan автоматически отключается. Его можно использовать в качестве ключа соединения с pg_stat_statements.

planid bigint

Хеш-код плана, вычисленный из нормализованного представления плана.

plan text

Текст репрезентативного плана. Формат задается параметром конфигурации pg_store_plans.plan_format.

calls bigint

Сколько раз выполнялся оператор

total_time double precision

Общее время, потраченное оператором, использующим план, в миллисекундах

min_time double precision

Минимальное время, потраченное оператором, использующим план, в миллисекундах

max_time double precision

Максимальное время, потраченное оператором, использующим план, в миллисекундах

mean_time double precision

Среднее время время, потраченное оператором, использующим план, в миллисекундах

stddev_time double precision

Сумма отклонений времени, потраченное оператором, использующим план, в миллисекундах

rows bigint

Общее количество строк, извлеченных или затронутых оператором, использующим план

shared_blks_hit bigint

Общее количество попаданий в кэш разделяемых блоков оператором, использующим план

shared_blks_read bigint

Общее количество разделяемых блоков, прочитанных оператором, использующим план

shared_blks_dirtied bigint

Общее количество разделяемых блоков, «загрязненных» оператором, использующим план

shared_blks_written bigint

Общее количество разделяемых блоков, записанных оператором, использующим план

local_blks_hit bigint

Общее количество попаданий в кэш локальных блоков оператором, использующим план

local_blks_read bigint

Общее количество локальных блоков, прочитанных оператором, использующим план

local_blks_dirtied bigint

Общее количество локальных блоков, «загрязненных» оператором, использующим план

local_blks_written bigint

Общее количество локальных блоков, записанных оператором, использующим план

temp_blks_read bigint

Общее количество временных блоков, прочитанных оператором, использующим план

temp_blks_written bigint

Общее количество временных блоков, записанных оператором, использующим план

blk_read_time double precision

Общее время, затраченное оператором, использующим план, на чтение блоков, в миллисекундах (если установлен параметр track_io_timing, в противном случае ноль)

blk_write_time double precision

Общее время, затраченное оператором, использующим план, на запись блоков, в миллисекундах (если установлен параметр track_io_timing, в противном случае ноль)

first_call timestamp with time zone

Метка времени первого вызова запроса с использованием этого плана.

last_call timestamp with time zone

Метка времени последнего вызова запроса с использованием этого плана.

pg_repack

Синтаксис

pg_repack [параметр]... [имя_бд]

Описание

pg_repack — это расширение QHB, которое позволяет избавляться от «разбухания» таблиц и индексов и при необходимости восстанавливать физический порядок кластеризованных индексов. В отличие от команд CLUSTER и VACUUM FULL, оно делает это в реальном времени, не удерживая эксклюзивную блокировку обрабатываемых таблиц во время обработки. Кроме того, pg_repack очень эффективно, его производительность сравнима с прямым использованием CLUSTER.

Для реорганизации таблиц можно выбрать один из следующих способов:

Выполнение неблокирующей CLUSTER (упорядочивание по кластерному индексу);

Упорядочивание по указанным столбцам;

Выполнение неблокирующей VACUUM FULL (упаковка только строк);

Перестройка или перемещение только индексов таблицы.

Примечание
Использовать утилиту могут только суперпользователи. Целевая таблица должна иметь первичный ключ (PRIMARY KEY) или хотя бы уникальный индекс (UNIQUE) для столбца NOT NULL.

Требования

Версия QHB 1.5.1 и выше.

Диски: Для полной перепаковки таблиц требуется свободное место на диске размером примерно в два раза больше, чем занимает целевая таблица (или таблицы) и ее индексы. Например, если общий размер реорганизуемых таблиц и индексов составляет 1 ГБ, потребуется дополнительно 2 ГБ дискового пространства.

Установка

Модуль реорганизации таблиц с минимальными блокировками для QHB поставляется в виде пакета qhb-1.5.2-pg-repack.

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

После установки загрузите расширение pg_repack в базу данных, которую необходимо обработать. pg_repack упакован как расширение, поэтому надо запустить команду:

$ psql -c "CREATE EXTENSION pg_repack" -d ваша_бд

Удалить pg_repack можно с помощью команды DROP EXTENSION pg_repack или просто удалив схему repack.

При обновлении с предыдущей версии pg_repack просто удалите старую версию из базы данных, как описано выше, и установите новую версию.

Параметры

Параметры реорганизации

-a
--all
Попытаться перепаковать все базы данных кластера. Базы данных, в которых не установлено расширение pg_repack, будут пропущены.

-t таблица
--table=таблица
Реорганизовать только указанные таблицы. Реорганизовать несколько таблиц можно, написав несколько ключей -t. По умолчанию реорганизуются все подходящие таблицы в целевых базах данных.

-I таблица
--parent-table=таблица Реорганизовать как указанные таблицы, так и их наследников. Реорганизовать несколько иерархий таблиц можно, написав несколько ключей -I.

-c схема
--schema=схема
Перепаковать таблицы только в указанной схеме (схемах). Перепаковать несколько схем можно, написав несколько ключей -c. Можно использовать вместе с --tablespace для перемещения таблиц в другое табличное пространство.

-o столбец [,...]
--order-by=столбец [,...]
Выполнить неблокирующую кластеризацию (CLUSTER), с упорядочиванием по указанным столбцам.

-n
--no-order
Выполнить неблокирующую полную очистку (VACUUM FULL). Это значение по умолчанию для некластеризованных таблиц.

-N
--dry-run
Показать, что будет перепаковано и завершиться.

-j
--jobs
Создать указанное количество дополнительных подключений к QHB и использовать эти дополнительные подключения для распараллеливания перестройки индексов для каждой таблицы. Параллельное построение индексов поддерживается только для полной перепаковки таблиц, без параметров --index или --only- indexes. Если на сервере QHB есть дополнительные ядра и дисковый ввод-вывод, это может быть полезным способом ускорить pg_repack.

-s табличное_пространство
--tablespace=табличное_пространство
Переместить перепакованные таблицы в указанное табличное пространство: по сути, это неблокирующая версия ALTER TABLE ... SET TABLESPACE. Индексы таблиц остаются в исходном табличном пространстве, если только дополнительно не указан параметр --moveidx.

-S
--moveidx
Переместить индексы перепакованных таблиц в табличное пространство, указанное параметром --tablespace.

-i индекс
--index=индекс
Перепаковать только указанный индекс (индексы). Перепаковать несколько индексов можно, написав несколько ключей -i. Может использоваться вместе с --tablespace для перемещения индекса(ов) в другое табличное пространство.

-x
--only-indexes
Перепаковать только индексы таблицы (таблиц), указанной в параметре --table или --parent-table.

-T сек
--wait-timeout=сек
Расширению pg_repack нужно получить эксклюзивную блокировку в конце реорганизации. Этот параметр определяет, сколько секунд pg_repack будет ждать, чтобы получить эту блокировку. Если блокировку не удастся получить по истечении этого времени и параметр --no-kill-backend не указан, pg_repack принудительно отменит конфликтующие запросы. pg_repack прибегнет к вызову pg_terminate_backend() для отключения всех оставшихся серверных процессов, после того как этот срок пройдет дважды. Значение по умолчанию — 60 секунд.

-D
--no-kill-backend
Если блокировка не может быть снята на время, указанное в --wait-timeout, вместо отмены конфликтующих запросов перейти к перепаковке таблицы. Значение по умолчанию — false.

-Z
--no-analyze
Не выполнять ANALYZE после полной реорганизации таблицы. Если этот ключ не указан, после реорганизации выполняется ANALYZE.

-k
--no-superuser-check
Пропустить проверки суперпользователя на стороне клиента. Этот параметр полезен для использования pg_repack на платформах, поддерживающих запуск от имени пользователя без прав суперпользователя.

-C
--exclude-extension
Не перепаковывать таблицы, принадлежащие конкретному расширению. Некоторые расширения могут сильно зависеть от таких таблиц во время планирования и т. д.

Параметры подключения

Примечание
Параметры -all и --dbname, или --table, или --parent-table нельзя использовать одновременно.

-a
--all
Реорганизовать все базы данных.

-d имя_бд
--dbname=имя_бд
Задает имя реорганизуемой базы данных. Если этот параметр не указан и не используется -a (или --all), имя базы данных считывается из переменной среды PGDATABASE. Если этот параметр не установлен, используется имя пользователя, указанное для подключения.

-h имя_хоста
--host=имя_хоста
Указывает имя хост-компьютера, на котором работает сервер. Если значение начинается со слэша, оно используется в качестве каталога для сокета домена Unix.

-p порт
--port=порт
Указывает TCP-порт или расширение файла локального сокета домена Unix, через который сервер принимает подключения.

-U имя_пользователя
--username=имя_пользователя
Имя пользователя, под которым производится подключение.

-w
--no-password
Никогда не запрашивать ввод пароля. Если серверу требуется аутентификация по паролю, и пароль недоступен с помощью иных средств, таких как файл .pgpass, попытка подключения завершится неудачно. Этот параметр может быть полезен в пакетных заданиях и скриптах, где нет пользователя, чтобы ввести пароль.

-W
--password
Принудительно запрашивать пароль перед подключением к базе данных.
Это несущественный параметр, так как pg_repack автоматически запросит пароль, если сервер требует аутентификацию по паролю. Однако чтобы выяснить это, pg_repack потребуется дополнительная попытка подключения к серверу. В некоторых случаях имеет смысл ввести -W, чтобы исключить эту лишнюю попытку.

Общие параметры

-e
--echo
Отобразить на экране команды, которые pg_repack генерирует и отправляет на сервер.

-E уровень
--elevel=уровень
Выбрать уровень выводимых сообщений: DEBUG, INFO, NOTICE, WARNING, ERROR, LOG, FATAL и PANIC. Уровень по умолчанию — INFO.

--help
Показать справку об аргументах командной строки pg_repack и завершиться.

--version
Вывести версию pg_repack и завершиться.

Переменные среды

PGDATABASE
PGHOST
PGPORT
PGUSER
Параметры подключения по умолчанию.

Эта утилита, как и большинство других утилит QHB, также использует переменные среды, поддерживаемые libpq (см. раздел Переменные среды).

Примеры

Выполнить неблокирующую команду CLUSTER всех кластеризованных таблиц в базе данных test и выполнить неблокирующую команду VACUUM FULL всех некластеризованных таблиц:

$ pg_repack test

Выполнить неблокирующую команду VACUUM FULL для таблиц foo и bar в базе данных test (кластерные индексы, если таковые имеются, игнорируется):

$ pg_repack --no-order --table foo --table bar test

Переместить все индексы таблицы foo в табличное пространство tbs:

$ pg_repack -d test --table foo --only-indexes --tablespace tbs

Переместить указанный индекс в табличное пространство tbs:

$ pg_repack -d test --index idx --tablespace tbs

Диагностика

Сообщения об ошибках выводятся при сбое pg_repack. В следующем списке показаны причины ошибок.

После фатальных ошибок потребуется очистка вручную. Для этого просто удалите pg_repack из базы данных и заново его установите. Выполните

DROP EXTENSION pg_repack CASCADE

в базе данных, где произошла ошибка, а затем выполните

CREATE EXTENSION pg_repack

INFO: database "db" skipped: pg_repack VER is not installed in the database
-- ИНФО: база данных "db" пропущена: в этой базе данных не установлено pg_repack VER

Если указан параметр --all, pg_repack в базе данных не устанавливается.

Создайте расширение pg_repack в базе данных.

ERROR: pg_repack VER is not installed in the database
-- ОШИБКА: в этой базе данных не установлено pg_repack VER

pg_repack не установлено в базе данных, указанной в параметре --dbname.

Создайте расширение pg_repack в базе данных.

ERROR: program 'pg_repack V1' does not match database library 'pg_repack V2'
-- ОШИБКА: программа 'pg_repack V1' не соответствует библиотеке базы данных 'pg_repack V2'

Существует несоответствие между двоичным файлом pg_repack и библиотекой базы данных (.so или .dll).

Несоответствие может быть связано с неправильным двоичным файлом в $PATH или обращением к неправильной базе данных. Проверьте каталог программы и базу данных; если они соответствуют ожиданиям, возможно, вам потребуется повторить установку pg_repack.

ERROR: extension 'pg_repack V1' required, found 'pg_repack V2'
-- ОШИБКА: требуется расширение 'pg_repack V1', найдено 'pg_repack V2'

Расширение SQL, найденное в базе данных, не соответствует версии, требуемой программой pg_repack.

Необходимо удалить расширение из базы данных и перезагрузить его, как описано в разделе Установка.

ERROR: relation "table" must have a primary key or not-null unique keys
-- ОШИБКА: отношение "table" должно иметь первичный ключ или уникальные ключи, отличные от NULL

Для целевой таблицы не определено ограничение PRIMARY KEY или какие-либо ограничения UNIQUE.

Определите для таблицы ограничение PRIMARY KEY или UNIQUE.

ERROR: query failed: ERROR: column "col" does not exist
-- ОШИБКА: запрос не выполнен: ОШИБКА: столбец "col" не существует

В целевой таблице нет столбцов, указанных в параметре --order-by.

Укажите существующие столбцы.

WARNING: the table "tbl" already has a trigger called repack_trigger
-- ПРЕДУПРЕЖДЕНИЕ: у таблицы "tbl" уже имеется триггер с именем repack_trigger

Этот триггер, вероятно, был установлен во время предыдущей попытки запуска pg_repack для таблицы, которая была прервана и по какой-то причине не смогла удалить временные объекты.

Удалить все временные объекты можно, удалив и заново создав расширение: подробную информацию см. в разделе Установка.

ERROR: Another pg_repack command may be running on the table. Please try again later.
-- ОШИБКА: Возможно, для этой таблицы уже выполняется другая команда pg_repack.
-- Пожалуйста, повторите попытку позднее.

Когда на одной таблице выполняются две параллельные команды pg_repack, существует вероятность взаимоблокировки. Поэтому попробуйте запустить команду через некоторое время.

WARNING: Cannot create index "schema"."index_xxxx", already exists
DETAIL: An invalid index may have been left behind by a previous pg_repack on the
table which was interrupted. Please use DROP INDEX "schema"."index_xxxxx" to remove
this index and try again.
-- ПРЕДУПРЕЖДЕНИЕ: Невозможно создать уже существующий индекс "schema"."index_xxxx"
-- ПОДРОБНЕЕ: Недопустимый индекс мог быть оставлен при предыдущем выполнении
-- pg_repack для таблицы, которое было прервано. Пожалуйста, воспользуйтесь
-- DROP INDEX "schema"."index_xxxxx", чтобы удалить этот индекс, и повторите попытку.

Был оставлен временный индекс, явно созданный при предыдущем выполнении pg_repack, и не стоит рисковать, удаляя его самостоятельно. Если этот индекс действительно был создан при предыдущем запуске pg_repack, после которого не была проведена очистка, следует просто выполнить DROP INDEX и повторить команду repack.

Ограничения

Расширение pg_repack имеет следующие ограничения:

pg_repack не может реорганизовать временные таблицы.

pg_repack не может кластеризовать таблицы по индексам GiST.

Пока выполняется pg_repack, вы не сможете выполнять команды DDL для целевых таблиц, за исключением VACUUM или ANALYZE. Чтобы обеспечить соблюдение этого ограничения, pg_repack будет удерживать блокировку ACCESS SHARE для целевой таблицы во время ее полной перепаковки.

Детализация

Полная перепаковка таблицы

Чтобы выполнить полную перепаковку таблицы, pg_repack:

создает таблицу журнала для записи изменений, внесенных в исходную таблицу;

добавляет триггер в исходную таблицу, для регистрации команд INSERT, UPDATE и DELETE в таблице журнала;

создает новую таблицу, содержащую все строки из старой таблицы;

строит индексы в этой новой таблице;

применяет все изменения, которые произошли в таблице журнала, к новой таблице;

меняет местами таблицы, включая индексы и всплывающие таблицы, используя системные каталоги;

удаляет исходную таблицу.

pg_repack будет удерживать блокировку ACCESS EXCLUSIVE только в течение короткого периода времени во время первоначальной настройки (шаги 1 и 2) и на заключительном этапе обмена и удаления (шаги 6 и 7). В остальное время pg_repack нужно удерживать только блокировку ACCESS SHARE для исходной таблицы, что означает, что операции INSERT, UPDATE и DELETE могут выполняться как обычно.

Перепаковка только индексов

Чтобы выполнить перепаковку только для индекса, pg_repack проводит следующие операции:

создает новые индексы в таблице, используя сопоставление CONCURRENTLY определений старых индексов;

заменяет в каталогах старые индексы на новые;

удаляет старые индексы.

Одновременное создание индексов осуществляется с несколькими оговорками; подробную информацию см. в параграфе Неблокирующее построение индексов на справочной странице команды CREATE INDEX.

См. также

Документация по pg_repack на Github, clusterdb, vacuumdb

pgtt

Описание

pgtt — это расширение QHB для создания, управления и использования глобальных временных таблиц (Global Temporary Table, GTT) в стиле Oracle и других реляционных СУБД.

Цель этого расширения — предложить возможности для предоставления функциональности глобальной временной таблицы, ожидающей реализации в ядре. Основной интерес этого расширения состоит в том, чтобы имитировать поведение Oracle с GTT, когда нет возможности или желания переписывать код приложения при переходе на QHB. Во всех остальных случаях лучше всего переписать код для использования стандартных временных таблиц QHB.

В этой версии расширения GTT в качестве «шаблонной» таблицы используется обычная нежурналируемая таблица и внутренняя переадресация на временную таблицу. Подробную информацию см. в параграфе Как работает расширение.

Встроенные временные таблицы QHB автоматически удаляются в конце сеанса или, при необходимости, в конце текущей транзакции. Глобальные временные таблицы постоянны, они создаются как обычные таблицы, видимые для всех пользователей, но их содержимое относится к текущему сеансу или транзакции. Даже если сама таблица сохраняемая, сеанс или транзакция не могут видеть строки, записанные другим сеансом.

Обычно это не является проблемой, поскольку вы уже знакомы с поведением временных таблиц в QHB, но трудности начинаются, когда вы переносите в QHB базу данных Oracle. Вам придется переписать код SQL и PlPgSQL, чтобы соответствовать логике этого приложения и использовать временные таблицы QHB, что подразумевает пересоздание временных таблиц везде, где они используются.

Другое преимущество объекта такого рода состоит в том, что когда ваше приложение создает и удаляет множество временных таблиц, каталоги QHB «разбухают», и производительность начинает падать. Обычно GTT предотвращают «разбухание» каталогов, но с этой реализацией и даже если у нас есть постоянная таблицы, все команды DML переадресуются в обычную временную таблицу, созданную при первом обращении. Более подробную информацию см. ниже в параграфе Как работает расширение.

Оператор DECLARE TEMPORARY TABLE не поддерживается QHB и этим расширением. Тем не менее этот оператор определяет временную таблицу для текущего подключения/сеанса, создает таблицы, которые не хранятся в системных каталогах и не являются долговременными. Их нельзя совместно использовать с другими сеансами. Этот оператор равнозначен стандартной команде QHB CREATE TEMPORARY TABLE, так что вам нужно только заменить ключевое слово DECLARE на CREATE.

С различными условиями сохраняется все поведение GTT в Oracle, за исключением того, что не поддерживается QHB:

ON COMMIT {DELETE | PRESERVE} ROWS
Задает действие, применяемое к глобальной временной таблице, когда выполняется операция фиксации (COMMIT).

DELETE ROWS: все строки таблицы будут удалены, если для нее не открыто ни одного удерживаемого курсора.

PRESERVE ROWS: после фиксации (COMMIT) строки таблицы будут сохранены.

LOGGED или NOT LOGGED [ ON ROLLBACK {DELETE | PRESERVE} ROWS ]
Определяет, протоколируются ли операции для этой таблицы. Значение по умолчанию — NOT LOGGED ON ROLLBACK DELETE ROWS.

NOT LOGGED: если указано, то совершаемые над таблицей операции добавления, изменения или удаления не протоколируются, но создание или удаление самой таблицы должно протоколироваться. Во время операции ROLLBACK или ROLLBACK TO SAVEPOINT:

Если таблица была создана в рамках транзакции, она удаляется.

Если таблица была удалена в рамках транзакции, она воссоздается, но без каких-либо данных.

ON ROLLBACK: задает действие, которое должно совершиться над созданной нежурналируемой временной таблицей при выполнении операции ROLLBACK или ROLLBACK TO SAVEPOINT. Значение по умолчанию — DELETE ROWS.

DELETE ROWS: если данные таблицы были изменены, все строки будут удалены.

PRESERVE ROWS: строки таблицы будут сохранены.

LOGGED: если указано, то совершаемые над таблицей операции добавления, изменения или удаления, а также создание или удаление, должны протоколироваться.

В QHB может поддерживаться только NOT LOGGED ON ROLLBACK DELETE ROWS. Создание или удаление глобальной временной таблицы протоколируется; подробную информацию см. ниже в параграфе Как работает расширение.

Установка

Модуль управления глобальными временными таблицами для QHB поставляется в виде пакета qhb-1.5.2-pgtt.

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

Если это расширение будет использоваться обычной ролью (не суперпользователем), необходимо добавить в каталог $libdir/plugins/ эту библиотеку.

export libdir=$(pg_config --pkglibdir)
sudo mkdir $libdir/plugins/
cd $libdir/plugins/
sudo ln -s ../pgtt.so

После этого ее можно будет применить с помощью команды LOAD '$libdir/plugins/ pgtt.so'. Чтобы можно было создавать и управлять GTT из-под обычной роли, потребуется дать пользователю право CREATE для схемы pgtt_schema.

Для выполнения теста выполните от имени суперпользователя следующую команду:

make installcheck

Для проверки использования расширения в выделенной схеме SESSION предоставляется дополнительный отдельный тест. Нужно удалить из файла pgtt.control следующие две строки:

schema = 'pgtt_schema'
relocatable = false
Then the tests can be executed using:

mkdir results
createdb gtt_relocation
psql -d gtt_relocation -f test/relocation.sql > results/relocation.out 2>&1
diff results/relocation.out test/expected/relocation.out
dropdb gtt_relocation

Конфигурация

pgtt.enabled

С помощью данного параметра можно включать и отключать расширение. По умолчанию параметр включен. Чтобы отключить расширение, используйте:

SET pgtt.enabled TO off;

Отключить или включить расширение можно в любой момент сеанса.

Использование расширения

Во всех базах данных, где необходимо использовать глобальные временные таблицы, потребуется создать расширение, используя:

CREATE EXTENSION pgtt;

Суперпользователь может загрузить расширение, используя:

LOAD 'pgtt';

Не суперпользователь должен загрузить библиотеку, используя каталог plugins/ следующим образом:

LOAD '$libdir/plugins/pgtt';

Обязательно следуйте приведенным выше инструкциям по установке, чтобы создать символическую ссылку из каталога plugins/ на файл библиотеки расширений.

Расширение pgtt использует для хранения связанных объектов выделенную схему; по умолчанию это pgtt_schema. Расширение следит за тем, чтобы эта схема всегда находилась в конце пути поиска (search_path).

gtt_testdb=# LOAD '$libdir/plugins/pgtt';
LOAD
gtt_testdb=# SHOW search_path;
    search_path
--------------------
 public,pgtt_schema
(1 row)

gtt_testdb=# SET search_path TO appschema,public;
SET
gtt_testdb=# SHOW search_path;
	  search_path
--------------------------------
 appschema, public, pgtt_schema
(1 row)

Схема pgtt автоматически добавляется в search_path, когда загружается расширение и в случае последующих изменений search_path.

Создание глобальной временной таблицы

Чтобы создать GTT-таблицу с именем test_gtt_table, выполните следующий оператор:

CREATE GLOBAL TEMPORARY TABLE test_gtt_table (
	id integer,
	lbl text
) ON COMMIT { PRESERVE | DELETE } ROWS;

Ключевое слово GLOBAL устарело, но его можно безопасно использовать, правда, что оно выдаст предупреждение:

WARNING:  GLOBAL is deprecated in temporary table creation
-- ПРЕДУПРЕЖДЕНИЕ: для создания временной таблицы GLOBAL является устаревшим

Для исключения этого предупреждающего сообщения, допускается использовать GLOBAL как комментарий:

CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table (
	LIKE other_table LIKE
	INCLUDING DEFAULTS
	INCLUDING CONSTRAINTS
	INCLUDING INDEXES
) ON COMMIT { PRESERVE | DELETE } ROWS;

расширение обнаружит ключевое слово GLOBAL.

Как видно из приведенного выше примера, поддерживается предложение LIKE, а также предложение AS как WITH DATA, так и WITH NO DATA (по умолчанию):

CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table
AS SELECT * FROM source_table WITH DATA;

В случае WITH DATA расширение заполнит GTT данными, возвращенными из оператора SELECT, только для текущего сеанса.

Предложение временной таблицы QHB ON COMMIT DROP данным расширением не поддерживается, GTT сохраняются в транзакциях. При использовании этого предложения возникнет ошибка.

Строки временной таблицы удаляются или сохраняются при фиксации транзакций в соответствии с предложением:

ON COMMIT { PRESERVE | DELETE } ROWS

Удаление глобальной временной таблицы

Чтобы удалить глобальную временную таблицу, выполняются те же действия, что и для обычной таблицы:

DROP TABLE test_gtt_table;

Глобальную временную таблицу можно удалить, даже если она используется другим сеансом.

Создание индексов в глобальной временной таблице

В глобальной временной таблице можно создавать индексы:

CREATE INDEX ON test_gtt_table (id);

так же, как и с любыми другими таблицами.

Ограничения глобальной временной таблицы

В глобальную временную таблицу можно добавить любое ограничение, кроме ограничения внешнего ключа (FOREIGN KEYS).

CREATE GLOBAL TEMPORARY TABLE t2 (
	c1 serial PRIMARY KEY,
	c2 VARCHAR (50) UNIQUE NOT NULL,
	c3 boolean DEFAULT false
)

Использование FOREIGN KEYS в глобальной временной таблице не допускается.

CREATE GLOBAL TEMPORARY TABLE t1 (c1 integer, FOREIGN KEY (c1) REFERENCES source (id));
ERROR:  attempt to create referential integrity constraint on global temporary table
-- ОШИБКА: попытка создания ограничения ссылочной целостности для глобальной временной
-- таблицы

ALTER TABLE t2 ADD FOREIGN KEY (c1) REFERENCES source (id);
ERROR:  attempt to create referential integrity constraint on global temporary table

Даже если QHB допускает во временной таблице внешние ключи, расширение pgtt пытается максимально имитировать поведение Oracle и других СУБД, таких как DB2, SQL Server или MySQL.

ORA-14455: attempt to create referential integrity constraint on temporary table.

Партиционирование

Партиционирование для глобальной временной таблицы не поддерживается, опять же не потому, что QHB не разрешает партиционирование временных таблиц, а потому, что другие СУБД, такие как Oracle, DB2 и MySQL, его не поддерживают. SQL Server поддерживает партиционирование глобальных временных таблиц.

Как работает расширение

Использование глобальных временных таблиц

Если параметр pgtt.enabled равен true (по умолчанию) и расширение было загружено (LOAD 'pgtt';), то первое обращение к таблицы с применением оператора SELECT, UPDATE или DELETE приведет к созданию временной таблицы с помощью определения «шаблонной» таблицы, созданной при вызове оператора CREATE GLOBAL TEMPORARY TABLE.

Сразу после создания временной таблицы при первом обращении исходный оператор SELECT, UPDATE или DELETE автоматически перенаправляется в новую обычную временную таблицу. При всех остальных обращениях будет задействована новая временная таблица, при этом первой в пути поиска всегда будет просматриваться схема pg_temp*, где создается эта таблица. Именно поэтому последующие обращения не затрагивают «шаблонную» таблицу.

Создание, переименование и удаление GTT является административной задачей и не должно осуществляться в сеансе приложения.

Обратите внимание, что переадресация останется активна, даже если дополнить таблицу именем табличного пространства. Например, просмотр внутренней нежурналируемой таблицы-шаблона:

bench=# LOAD 'pgtt';
LOAD
bench=# CREATE /*GLOBAL*/ TEMPORARY TABLE test_tt (id int, lbl text) ON COMMIT PRESERVE ROWS;
CREATE TABLE
bench=# INSERT INTO test_tt VALUES (1, 'one'), (2, 'two'), (3, 'three');
INSERT 0 3
bench=# SELECT * FROM pgtt_schema.test_tt;
 id |  lbl
----+-------
  1 | one
  2 | two
  3 | three
(3 rows)

фактически даст тот же результат, что и просмотр связанной временной таблицы, как в следующих примерах:

bench=# SELECT * FROM test_tt;
 id |  lbl
----+-------
  1 | one
  2 | two
  3 | three
(3 rows)

или

bench=# SELECT * FROM pg_temp.test_tt;
 id |  lbl
----+-------
  1 | one
  2 | two
  3 | three
(3 rows)

Если вы желаете действительно просмотреть таблицу-шаблон, чтобы убедиться, что она не содержит строк, следует выключить переадресацию расширения:

bench=# SET pgtt.enabled TO off;
SET
bench=# SELECT * FROM pgtt_schema.test_tt;
 id | lbl
----+-----
(0 rows)

bench=# SET pgtt.enabled TO on;
SET
bench=# SELECT * FROM pgtt_schema.test_tt;
 id |  lbl
----+-------
  1 | one
  2 | two
  3 | three
(3 rows)

Дополнительные примеры можно найти в тестовом файле.

Это также означает, что вы можете перенести данное расширение в выделенное пространство имен. Это может быть полезно, если в запросах вашего приложения для обращения к GTT имя таблицы дополняется схемой, и вы не можете этого изменить. Пример см. в t/sql/relocation.sql. По умолчанию это расширение нельзя перенести в другую схему, поэтому чтобы воспользоваться этим функционалом, понадобятся некоторые изменения конфигурации.

Если вы используете форму CREATE AS с предложением WITH DATA, как в этом примере:

CREATE /*GLOBAL*/ TEMPORARY TABLE test_gtt_table
AS SELECT * FROM source_table WITH DATA;

данное расширение сначала создаст нежурналируемую таблицу-шаблон, а затем немедленно создаст связанную временную таблицу, заполненную всеми данными, возвращенными оператором SELECT. Тогда при первом обращении не понадобится создавать таблицу — она уже существует и имеет данные.

Создание таблицы

Данное расширение перехватывает вызов оператора CREATE TEMPORARY TABLE ... и проверяет, имеется ли в нем ключевое слово GLOBAL или комментарий /*GLOBAL*/. При его обнаружении, вместо создания временной таблицы, расширение создает «шаблонную» нежурналируемую хранимую таблицу, следуя определению временной таблицы. После создания шаблона оно регистрирует эту таблицу в таблицу-«каталог» pg_global_temp_tables.

Оба объекта создаются в схеме расширения pgtt_schema.

Если pgtt.enabled равен false, ничего не происходит.

Ниже приводится описание таблицы-каталога:

          Table « pgtt_schema.pg_global_temp_tables »
  Colonne  |  Type   | Collationnement | NULL-able | Par défaut
-----------+---------+-----------------+-----------+------------
 relid     | integer |                 | not null  |
 nspname   | name    |                 | not null  |
 relname   | name    |                 | not null  |
 preserved | boolean |                 |           |
 code      | text    |                 |           |
Index :
    "pg_global_temp_tables_nspname_relname_key" UNIQUE CONSTRAINT, btree (nspname, relname)

relid: OID «шаблонной» нежурналируемой таблицы.

nspname: пространство имен pgtt_schema расширения по умолчанию.

relname: имя отношения GTT.

preserved: true или false для ON COMMIT { PRESERVE | DELETE }.

code: код, использованный во время создания глобальной временной таблицы.

Удаление таблицы

Данное расширение перехватывает вызов оператора DROP TABLE и просматривает таблицу pg_global_temp_tables, проверяя, объявлена ли целевая таблица. Найдя ее, оно удаляет «шаблонную» нежурналируемую таблицу и соответствующую запись из таблицы-каталога pgtt pg_global_temp_tables.

Если pgtt.enabled равен false, ничего не происходит.

Удаление используемой GTT, когда временная таблица уже была создана, вызовет ошибку. Это не разрешено.

Переименование таблицы

Данное расширение перехватывает вызов оператора ALTER TABLE ... RENAME и просматривает таблицу pg_global_temp_tables, проверяя, объявлена ли целевая таблица. Найдя ее, оно переименовывает «шаблонную» таблицу и меняет имя отношения в таблице pg_global_temp_tables. Если GTT уже использовалась в сеансе, соответствующая временная таблица уже существует, и в этом случае расширение откажется ее переименовывать. Для переименования она должна быть неактивна.

Если pgtt.enabled равен false, ничего не происходит.

Переименование используемой GTT, когда временная таблица уже была создана, вызовет ошибку. Это не разрешено.

qhb_dump / qhb_restore

Если используется расширение pgtt, то при создании дампа базы данных, содержимое таблицы-каталога pg_global_temp_tables тоже будет выгружено, как и все шаблонные нежурналируемые таблицы. При восстановлении из дампа база данных будет воссоздана в неизменном состоянии.

См. также

Документация по pgtt на Github

plpgsql_check

Описание

Это расширение представляет собой полный линтер для процедурного языка PL/pgSQL в QHB. Оно использует только внутренний синтаксический анализатор/оценщик QHB, поэтому позволяет видеть, какие ошибки могут возникнуть во время выполнения. Кроме того, оно анализирует SQL внутри подпрограмм и находит ошибки, которые обычно не обнаруживаются при выполнении команды CREATE PROCEDURE/FUNCTION. Этот модуль позволяет контролировать уровни многих предупреждений и подсказок. Наконец, он предоставляет возможность добавить маркеры типа PRAGAMA для включения/выключения многих аспектов, что позволяет скрыть сообщения, о которых уже известно, или напомнить о необходимости возврата к более глубокой очистке позже.

Установка

Модуль расширенной проверки PL/pgSQL для QHB поставляется в виде пакета qhb-1.5.2-plpgsql-check.

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

Функциональные возможности

Проверка полей ссылочных объектов базы данных и типов внутри встроенного SQL

Подтверждение того, что для параметров функции используются правильные типы

Выявление неиспользуемых переменных и аргументов функций, немодифицированных выходных аргументов (OUT)

Частичное обнаружение неисполняемого кода (кода после команды RETURN)

Обнаружение отсутствия команды RETURN в функции (часто после обработчиков исключений, сложная логика)

Выявление нежелательных скрытых приведений, которые могут вызвать проблемы с производительностью, как, например, неиспользуемые индексы

Способность собирать отношения и функции, используемые функцией

Способность проверять операторы EXECUTE на уязвимость SQL-инъекций.

Поддерживаются версии QHB 1.5.1 и выше.

Операторы SQL внутри функций на PL/pgSQL проверяются валидатором на предмет семантических ошибок. Эти ошибки можно обнаружить, вызвав функцию plpgsql_check_function:

qhb=# CREATE EXTENSION plpgsql_check;
LOAD
qhb=# CREATE TABLE t1(a int, b int);
CREATE TABLE

qhb=#
CREATE OR REPLACE FUNCTION public.f1()
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE r record;
BEGIN
  FOR r IN SELECT * FROM t1
  LOOP
    RAISE NOTICE '%', r.c; -- здесь ошибка - в таблице t1 отсутствует столбец "c"
  END LOOP;
END;
$function$;

CREATE FUNCTION

qhb=# SELECT f1(); -- выполнение не обнаруживает ошибку, поскольку таблица t1 пуста
  f1
 ────

 (1 row)

qhb=# \x
Expanded display is on.
qhb=# SELECT * FROM plpgsql_check_function_tb('f1()');
─[ RECORD 1 ]───────────────────────────
functionid │ f1
lineno     │ 6
statement  │ RAISE
sqlstate   │ 42703
message    │ record "r" has no field "c"
detail     │ [null]
hint       │ [null]
level      │ error
position   │ 0
query      │ [null]

qhb=# \sf+ f1
    CREATE OR REPLACE FUNCTION public.f1()
     RETURNS void
     LANGUAGE plpgsql
1       AS $function$
2       DECLARE r record;
3       BEGIN
4         FOR r IN SELECT * FROM t1
5         LOOP
6           RAISE NOTICE '%', r.c; --  здесь ошибка - в таблице t1 отсутствует столбец "c"
7         END LOOP;
8       END;
9       $function$

У функции plpgsql_check_function() имеется три возможных формата вывода: text, json или xml.

SELECT * FROM plpgsql_check_function('f1()', fatal_errors := false);
                         plpgsql_check_function
------------------------------------------------------------------------
 error:42703:4:SQL statement:column "c" of relation "t1" does not exist
 Query: UPDATE t1 SET c = 30
 --                   ^
 error:42P01:7:RAISE:missing FROM-clause entry for table "r"
 Query: SELECT r.c
 --            ^
 error:42601:7:RAISE:too few parameters specified for RAISE
(7 rows)

qhb=# SELECT * FROM plpgsql_check_function('fx()', format:='xml');
                 plpgsql_check_function
────────────────────────────────────────────────────────────────
 <Function oid="16400">                                        ↵
   <Issue>                                                     ↵
     <Level>error</level>                                      ↵
     <Sqlstate>42P01</Sqlstate>                                ↵
     <Message>relation "foo111" does not exist</Message>       ↵
     <Stmt lineno="3">RETURN</Stmt>                            ↵
     <Query position="23">SELECT (select a from foo111)</Query>↵
   </Issue>                                                    ↵
  </Function>
 (1 row)

Параметры

Установить уровень предупреждений можно через параметры функции.

Обязательный параметр

funcoid oid
Имя или сигнатура функции — для функций требуется спецификация функции. Любую функцию в QHB можно указать по OID, по имени или по сигнатуре. Когда известен OID или полная сигнатура функции, можно использовать параметр типа regprocedure, например 'fx()'::regprocedure или 16799::regprocedure. Возможная альтернатива — использовать только имя, когда имя функции уникально, например 'fx'. Когда имя не уникально или функция не существует, возникает ошибка.

Необязательные параметры

relid OID отношения, которому назначена триггерная функция. Этот параметр полезен, когда нужно проверить какую-либо триггерную функцию. Передается таблица, в которой работает триггер. Значение по умолчанию — 0.

fatal_errors (boolean)
Останавливает проверки при первой же ошибке (предотвращает массовые сообщения об ошибках). Значение по умолчанию — true.

other_warnings (boolean)
Показывает такие предупреждения, как разное количество атрибутов в назначении слева и справа, переменные перекрывают параметр функции, неиспользуемые переменные, нежелательное приведение и т. д. Значение по умолчанию — true.

extra_warnings (boolean)
Показывает такие предупреждения, как отсутствующие команды RETURN, скрываемые переменные, неисполняемый код, никогда не считываемый (неиспользуемый) параметр функции, немодифицированные переменные, модифицированные автоматические переменные и т. д. Значение по умолчанию — true.

performance_warnings (boolean)
Предупреждения, связанные с производительностью, такие как объявленный тип с модификатором типа, приведение, неявное приведение в предложении WHERE (может быть причиной, по которой индекс не используется) и т. д. Значение по умолчанию — false.

security_warnings (boolean)
Проверки, связанные с безопасностью, такие как обнаружение уязвимостей к SQL-инъекциям. Значение по умолчанию — false.

compatibility_warnings (boolean)
Проверки, связанные с совместимостью, такие как устаревшие явные параметры внутренних имен курсоров в refcursor или переменные курсора. Значение по умолчанию — false.

anyelementtype regtype
Фактический тип, который будет использоваться при тестировании типа anyelement. Значение по умолчанию — int.

anyenumtype regtype
Фактический тип, который будет использоваться при тестировании типа anyenum. Значение по умолчанию — '-'.

anyrangetype regtype
Фактический тип, который будет использоваться при тестировании типа anyrange. Значение по умолчанию — int4range.

anycompatibletype
Фактический тип, который будет использоваться при тестировании типа anycompatible. Значение по умолчанию — int.

anycompatiblerangetype
Фактический диапазонный тип, который будет использоваться при тестировании типа anycompatiblerange. Значение по умолчанию — int4range.

without_warnings
Отключить все предупреждения (игнорирует все параметры xxxx_warning, быстрое переопределение). Значение по умолчанию — false.

all_warnings Включить все предупреждения (игнорирует другие параметры xxx_warning, быстрое срабатывание). Значение по умолчанию — false.

newtable
oldtable
Имена новых (NEW) или старых (OLD) таблиц переходов соответственно. Эти параметры необходимы, когда таблицы переходов используются в триггерных функциях. Значение по умолчанию — NULL.

use_incomment_options
При значении true (по умолчанию) параметры в комментариях активны.

incomment_options_usage_warning
При значении true при использовании параметра в комментарии выдается предупреждение. Значение по умолчанию — false.

Триггеры

Чтобы проверить какой-либо триггер, нужно ввести отношение, которое будет использоваться с триггерной функцией:

CREATE TABLE bar(a int, b int);

qhb=# \sf+ foo_trg
    CREATE OR REPLACE FUNCTION public.foo_trg()
         RETURNS trigger
         LANGUAGE plpgsql
1       AS $function$
2       BEGIN
3         NEW.c := NEW.a + NEW.b;
4         RETURN NEW;
5       END;
6       $function$

Если не указать отношение, возникнет ошибка:

qhb=# SELECT * FROM plpgsql_check_function('foo_trg()');
ERROR:  missing trigger relation
HINT:  Trigger relation oid must be valid
-- ОШИБКА: отсутствует триггерное отношение
-- ПОДСКАЗКА: oid триггерного отношения должен быть допустимым

Правильная проверка триггера (с указанным отношением):

qhb=# SELECT * FROM plpgsql_check_function('foo_trg()', 'bar');
                 plpgsql_check_function
--------------------------------------------------------
 error:42703:3:assignment:record "new" has no field "c"
(1 row)

Для триггеров с транзитивными таблицами можно установить параметры oldtable и newtable:

CREATE OR REPLACE FUNCTION footab_trig_func()
returns trigger AS $$
DECLARE x int;
BEGIN
  if false THEN
    -- должно выполниться нормально;
    SELECT count(*) FROM newtab INTO x;

    -- должно завершиться неудачей;
    SELECT count(*) FROM newtab WHERE d = 10 INTO x;
  END if;
  return NULL;
END;
$$ language plpgsql;

SELECT * FROM plpgsql_check_function('footab_trig_func','footab', newtable := 'newtab');

Параметры в комментариях

Модуль plpgsql_check допускает сохраняемые настройки, записанные в комментариях. Эти параметры берутся перед проверкой из исходного кода функции. Синтаксис следующий:

@plpgsql_check_option: имя_параметра [=] значение [, имя_параметра [=] значение ...]

Настройки из параметров в комментарии имеют наивысший приоритет, но, в целом, это можно отключить, установив в параметре use_incomment_options значение false.

Пример:

CREATE OR REPLACE FUNCTION fx(anyelement)
returns text AS $$
BEGIN
  /*
   * переписать стандартный полиморфный тип в text
   * @plpgsql_check_options: anyelementtype = text
   */
  return $1;
END;
$$ language plpgsql;

Полная проверка кода

Функцию plpgsql_check_function можно использовать для массовой проверки функций/процедур и массовой проверки триггеров. Рекомендуется протестировать следующие запросы:

-- проверка всех нетриггерных функций PL/pgSQL
SELECT p.oid, p.proname, plpgsql_check_function(p.oid)
   FROM pg_catalog.pg_namespace n
   JOIN pg_catalog.pg_proc p ON pronamespace = n.oid
   JOIN pg_catalog.pg_language l ON p.prolang = l.oid
  WHERE l.lanname = 'plpgsql' AND p.prorettype <> 2279;

или

-- проверка всех триггерных функций PL/pgSQL
SELECT p.proname, tgrelid::regclass, cf.*
   FROM pg_proc p
        JOIN pg_trigger t ON t.tgfoid = p.oid
        JOIN pg_language l ON p.prolang = l.oid
        JOIN pg_namespace n ON p.pronamespace = n.oid,
        LATERAL plpgsql_check_function(p.oid, t.tgrelid) cf
  WHERE n.nspname = 'public' and l.lanname = 'plpgsql';

или

-- проверка всех функций PL/pgSQL (функций или триггерных функций с определенными триггерами)
SELECT
    (pcf).functionid::regprocedure, (pcf).lineno, (pcf).statement,
    (pcf).sqlstate, (pcf).message, (pcf).detail, (pcf).hint, (pcf).level,
    (pcf)."position", (pcf).query, (pcf).context
FROM
(
    SELECT
        plpgsql_check_function_tb(pg_proc.oid, COALESCE(pg_trigger.tgrelid, 0)) AS pcf
    FROM pg_proc
    LEFT JOIN pg_trigger
        ON (pg_trigger.tgfoid = pg_proc.oid)
    WHERE
        prolang = (SELECT lang.oid FROM pg_language lang WHERE lang.lanname = 'plpgsql') AND
        pronamespace <> (SELECT nsp.oid FROM pg_namespace nsp WHERE nsp.nspname = 'pg_catalog') AND
        -- игнорировать неиспользованные триггеры
        (pg_proc.prorettype <> (SELECT typ.oid FROM pg_type typ WHERE typ.typname = 'trigger') OR
         pg_trigger.tgfoid IS NOT NULL)
    OFFSET 0
) ss
ORDER BY (pcf).functionid::regprocedure::text, (pcf).lineno;

Ограничения

Модуль plpgsql_check должен обнаружить почти все ошибки в действительно статичном коде. Когда разработчики используют динамическую функциональность PL/pgSQL, например динамический SQL или тип данных record, возможны ложноположительные результаты. Они должны встречаться редко — в хорошо написанном коде — и тогда затронутую функцию следует переделать или выключить для нее plpgsql_check.

CREATE OR REPLACE FUNCTION f1()
RETURNS void AS $$
DECLARE r record;
BEGIN
  FOR r IN EXECUTE 'SELECT * FROM t1'
  LOOP
    RAISE NOTICE '%', r.c;
  END LOOP;
END;
$$ LANGUAGE plpgsql SET plpgsql.enable_check TO false;

Динамический SQL

Этот модуль не проверяет запросы, сформированные во время выполнения. Идентифицировать результаты динамических запросов невозможно — поэтому plpgsql_check не может установить правильный тип для переменных записи и не может проверить зависимые конструкции SQL и выражения.

Когда тип переменной записи неизвестен, можно назначить его явно с помощью директивы type:

DECLARE r record;
BEGIN
  EXECUTE format('SELECT * FROM %I', _tablename) INTO r;
  PERFORM plpgsql_check_pragma('type: r (id int, processed bool)');
  IF NOT r.processed THEN
    ...

ВНИМАНИЕ!
Проверка на предмет SQL-инъекций может выявить только некоторые уязвимости к ним. Это средство нельзя использовать для аудита безопасности! Некоторые проблемы не будут выявлены. Кроме того, эта проверка может выдать ложную тревогу — скорее всего, когда переменная цензурируется другой командой или когда это значение какого-либо составного типа.

Курсорные переменные

Модуль plpgsql_check нельзя использовать для выявления структуры ссылочных курсоров. В PL/pgSQL ссылка на курсор реализуется в виде имени глобального курсора. Во время проверки это имя неизвестно (ни при каких обстоятельствах), и глобальный курсор не существует. Это значительное затруднение для любого статического анализа. plpgsql_check не может установить правильный тип для переменных записи и не может проверить зависимые конструкции SQL и выражения. Решение такое же, как и для динамического SQL. Не выбирайте переменные записи в качестве целевых при использовании типа refcursor или выключайте plpgsql_check для таких функций.

CREATE OR REPLACE FUNCTION foo(refcur_var refcursor)
RETURNS void AS $$
DECLARE
  rec_var record;
BEGIN
  FETCH refcur_var INTO rec_var; -- это команда СТОП для plpgsql_check
  RAISE NOTICE '%', rec_var;     -- ошибка — запись rec_var еще не присвоена

В этом случае не следует использовать тип записи (вместо этого используйте известный составной тип):

CREATE OR REPLACE FUNCTION foo(refcur_var refcursor)
RETURNS void AS $$
DECLARE
  rec_var некоторый_составной_тип;
BEGIN
  FETCH refcur_var INTO rec_var;
  RAISE NOTICE '%', rec_var;

Временные таблицы

Модуль plpgsql_check не может верифицировать запросы к временным таблицам, созданным во время выполнения функции PL/pgSQL. В таком случае необходимо создать фальшивую временную таблицу или выключить plpgsql_check для этой функции.

На самом деле временные таблицы сохраняются в собственной (для каждого пользователя) схеме с более высоким приоритетом, чем постоянные таблицы. Поэтому можно сделать следующее (тщательно следуя инструкции):

CREATE OR REPLACE FUNCTION public.disable_dml()
RETURNS trigger
LANGUAGE plpgsql AS $function$
BEGIN
  RAISE EXCEPTION SQLSTATE '42P01'
     USING message = format('this instance of %I table doesn''t allow any DML operation', TG_TABLE_NAME),
           hint = format('you should use "CREATE TEMP TABLE %1$I(LIKE %1$I INCLUDING ALL);" statement',
                         TG_TABLE_NAME);
  RETURN NULL;
END;
$function$;

CREATE TABLE foo(a int, b int); -- никогда не содержит данных
CREATE TRIGGER foo_disable_dml
   BEFORE INSERT OR UPDATE OR DELETE ON foo
   EXECUTE PROCEDURE disable_dml();

qhb=# INSERT INTO  foo VALUES(10,20);
ERROR:  this instance of foo table does not allow any DML operation
HINT:  you should to run "CREATE TEMP TABLE foo(LIKE foo INCLUDING ALL);" statement
-- ОШИБКА: этот экземпляр таблицы foo не допускает никакие операции DML
-- ПОДСКАЗКА: следует выполнить оператор "CREATE TEMP TABLE foo(LIKE foo INCLUDING ALL);"
qhb=#

CREATE TABLE
qhb=# INSERT INTO  foo VALUES(10,20);
INSERT 0 1

Эта уловка частично эмулирует глобальные временные таблицы и допускает статическую валидацию. Другая возможность — использование шаблона обертки сторонних данных.

Можно использовать директиву table и создать эфемерную таблицу:

BEGIN
   CREATE TEMP TABLE xxx(a int);
   PERFORM plpgsql_check_pragma('table: xxx(a int)');
   INSERT INTO xxx VALUES(10);
   PERFORM plpgsql_check_pragma('table: [pg_temp].zzz(like schemaname.table1 including all)');
   ...

Список зависимостей

Функция plpgsql_show_dependency_tb покажет все функции, операторы и отношения, используемые внутри обрабатываемой функции:

qhb=# select * from plpgsql_show_dependency_tb('testfunc(int,float)');
┌──────────┬───────┬────────┬─────────┬────────────────────────────┐
│   type   │  oid  │ schema │  name   │           params           │
╞══════════╪═══════╪════════╪═════════╪════════════════════════════╡
│ FUNCTION │ 36008 │ public │ myfunc1 │ (integer,double precision) │
│ FUNCTION │ 35999 │ public │ myfunc2 │ (integer,double precision) │
│ OPERATOR │ 36007 │ public │ **      │ (integer,integer)          │
│ RELATION │ 36005 │ public │ myview  │                            │
│ RELATION │ 36002 │ public │ mytable │                            │
└──────────┴───────┴────────┴─────────┴────────────────────────────┘
(4 rows)

Необязательными аргументами plpgsql_show_dependency_tb являются relid, anyelementtype, enumtype, anyrangetype, anycompatibletype и anycompatiblerangetype.

Профилировщик

В модуле plpgsql_check содержится простой профилировщик функций и процедур PL/pgSQL. Он может работать как с доступом к разделяемой памяти, так и без него. Это зависит от конфигурации параметра shared_preload_libraries. Когда plpgsql_check инициализируется посредством shared_preload_libraries, он может выделить область в разделяемой памяти, и профили функций сохраняются там. Когда plpgsql_check не может выделить область в разделяемой памяти, профиль сохраняется в памяти сеанса.

Вследствие зависимостей библиотека plpgsql должна идти в shared_preload_libraries первой.

qhb=# show shared_preload_libraries ;
┌──────────────────────────┐
│ shared_preload_libraries │
╞══════════════════════════╡
│ plpgsql,plpgsql_check    │
└──────────────────────────┘
(1 row)

Профилировщик активен, когда включена GUC plpgsql_check.profiler. Профилировщику не требуется разделяемая память, но если ее недостаточно, то его возможности ограничиваются активным сеансом. Активировать профилировщик можно, вызвав функцию plpgsql_check_profiler(true), а выключить — вызвав ту же функцию, но с аргументом false (или с литералами on и off соответственно).

Когда модуль plpgsql_check инициализируется посредством параметра shared_preload_libraries, становится доступна еще одна GUC для конфигурирования объема разделяемой памяти, используемой профилировщиком: plpgsql_check.profiler_max_shared_chunks. Она определяет максимальное количество блоков операторов, которое можно хранить в разделяемой памяти. Для каждой функции (или процедуры) PL/pgSQL общее содержимое разделяется на блоки по 30 операторов. При необходимости для хранения полного содержимого одной функции может использоваться множество блоков. Размер одного блока составляет 1704 байта. Значение по умолчанию для этой GUC равно 15000; этого должно быть достаточно для больших проектов, содержащих сотни операторов на PL/pgSQL, и займет это примерно 24 МБ памяти. Если для проекта не требуется такое количество блоков, можно установить в этом параметре меньшее значение, чтобы снизить потребление памяти. Минимальное значение — 50 (что должно занять примерно 83 кБ памяти), а максимальное — 100000 (что должно занять примерно 163 МБ памяти). Изменение этого параметра требует перезапуска QHB.

Также профилировщик будет извлекать идентификатор запроса для каждой инструкции, содержащей выражение или оптимизируемый оператор. Обратите внимание, что для этого требуется установка pg_stat_statements (или иного схожего стороннего расширения). Для извлечения идентификатора запроса существует несколько ограничений:

если выражение на PL/pgSQL содержит нижележащие операторы, будет извлечен только идентификатор верхнего уровня

сам профилировщик не вычисляет идентификатор запроса, а полагается в этом на внешнее расширение, например, на pg_stat_statements. Это означает, что, в зависимости от поведения внешнего расширения, для некоторых операторов будет невозможно увидеть идентификатор расширения. Например, это происходит с операторами DDL, поскольку pg_stat_statements не демонстрирует идентификаторы для таких запросов.

идентификатор запроса извлекается только для инструкций, содержащих выражения. Это означает, что функция plpgsql_profiler_function_tb() может вывести в одной строке меньше идентификаторов запроса, чем инструкций.

ВНИМАНИЕ!
Изменение разделяемых профилей может снизить производительность на более загруженных серверах.

Профиль можно отобразить с помощью функции plpgsql_profiler_function_tb:

qhb=# select lineno, avg_time, source from plpgsql_profiler_function_tb('fx(int)');
┌────────┬──────────┬───────────────────────────────────────────────────────────────────┐
│ lineno │ avg_time │                              source                               │
╞════════╪══════════╪═══════════════════════════════════════════════════════════════════╡
│      1 │          │                                                                   │
│      2 │          │ declare result int = 0;                                           │
│      3 │    0.075 │ begin                                                             │
│      4 │    0.202 │   for i in 1..$1 loop                                             │
│      5 │    0.005 │     select result + i into result; select result + i into result; │
│      6 │          │   end loop;                                                       │
│      7 │        0 │   return result;                                                  │
│      8 │          │ end;                                                              │
└────────┴──────────┴───────────────────────────────────────────────────────────────────┘
(9 rows)

Профиль для каждого оператора (не для строки) можно отобразить с помощью функции plpgsql_profiler_function_statements_tb:

        CREATE OR REPLACE FUNCTION public.fx1(a integer)
         RETURNS integer
         LANGUAGE plpgsql
1       AS $function$
2       begin
3         if a > 10 then
4           raise notice 'ahoj';
5           return -1;
6         else
7           raise notice 'nazdar';
8           return 1;
9         end if;
10      end;
11      $function$

qhb=# select stmtid, parent_stmtid, parent_note, lineno, exec_stmts, stmtname
             from plpgsql_profiler_function_statements_tb('fx1');
┌────────┬───────────────┬─────────────┬────────┬────────────┬─────────────────┐
│ stmtid │ parent_stmtid │ parent_note │ lineno │ exec_stmts │    stmtname     │
╞════════╪═══════════════╪═════════════╪════════╪════════════╪═════════════════╡
│      0 │             ∅ │ ∅           │      2 │          0 │ statement block │
│      1 │             0 │ body        │      3 │          0 │ IF              │
│      2 │             1 │ then body   │      4 │          0 │ RAISE           │
│      3 │             1 │ then body   │      5 │          0 │ RETURN          │
│      4 │             1 │ else body   │      7 │          0 │ RAISE           │
│      5 │             1 │ else body   │      8 │          0 │ RETURN          │
└────────┴───────────────┴─────────────┴────────┴────────────┴─────────────────┘
(6 rows)

Все сохраненные профили можно отобразить, вызвав функцию plpgsql_profiler_functions_all:

qhb=# select * from plpgsql_profiler_functions_all();
┌───────────────────────┬────────────┬────────────┬──────────┬─────────────┬──────────┬──────────┐
│        funcoid        │ exec_count │ total_time │ avg_time │ stddev_time │ min_time │ max_time │
╞═══════════════════════╪════════════╪════════════╪══════════╪═════════════╪══════════╪══════════╡
│ fxx(double precision) │          1 │       0.01 │     0.01 │        0.00 │     0.01 │     0.01 │
└───────────────────────┴────────────┴────────────┴──────────┴─────────────┴──────────┴──────────┘
(1 row)

Имеются также две функции для удаления сохраненных профилей: plpgsql_profiler_reset_all() и plpgsql_profiler_reset(regprocedure).

Метрика покрытия

Модуль plpgsql_check предоставляет две функции:

plpgsql_coverage_statements(name)

plpgsql_coverage_branches(name)

Трассировщик

Модуль plpgsql_check предоставляет возможность трассировки — в этом режиме можно увидеть уведомления в начале или конце функций (краткий уровень детализации и уровень детализации по умолчанию) и в начале или конце операторов (подробный уровень детализации). Для подробного уровня и уровня по умолчанию отображается содержимое аргументов функций. Содержимое связанных переменных отображается на подробном уровне.

qhb=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE:  #0 ->> start of inline_code_block (Oid=0)
NOTICE:  #2   ->> start of function fx(integer,integer,date,text) (Oid=16405)
NOTICE:  #2        call by inline_code_block line 1 at PERFORM
NOTICE:  #2       "a" => '10', "b" => null, "c" => '2020-08-03', "d" => 'stěhule'
NOTICE:  #4     ->> start of function fx(integer) (Oid=16404)
NOTICE:  #4          call by fx(integer,integer,date,text) line 1 at PERFORM
NOTICE:  #4         "a" => '10'
NOTICE:  #4     <<- end of function fx (elapsed time=0.098 ms)
NOTICE:  #2   <<- end of function fx (elapsed time=0.399 ms)
NOTICE:  #0 <<- end of block (elapsed time=0.754 ms)

Номер после знака # является счетчиком блоков выполнения (этот номер относится к глубине стека контекста ошибки). Это позволяет сопрячь начало и конец функции.

Трассировка включается установкой в plpgsql_check.tracer значение on.

ВНИМАНИЕ!
Включение этого поведения значительно снижает производительность (в отличие от профилировщика).

Установить уровень вывода, используемого трассировщиком, можно с помощью plpgsql_check.tracer_errlevel (уровень по умолчанию — notice). Содержимое вывода ограничено длиной, заданной в переменной конфигурации plpgsql_check.tracer_variable_max_length. Активировать трассировщик можно, вызвав функцию plpgsql_check_tracer(true), а выключить — вызвав ту же функцию, но с аргументом false (или с литералами on и off соответственно).

Во-первых, использование трассировщика должно быть явно включено суперпользователем путем выполнения SET plpgsql_check.enable_tracer to on; или установки в plpgsql_check.enable_tracer значения on в qhb.conf. Это служит гарантией безопасности. Трассировщик выводит содержимое переменных PL/pgSQL, а затем некоторая информация, требующая повышенной безопасности, может быть продемонстрирована непривилегированному пользователю (когда он выполняет функцию со свойством SECURITY DEFINER (безопасность определившего)). Во-вторых, должно быть загружено расширение plpgsql_check. Это можно сделать, выполнив какую-либо функцию plpgsql_check, либо явно с помощью команды LOAD 'plpgsql_check';. Можно воспользоваться параметром конфигурации shared_preload_libraries, local_preload_libraries или session_preload_libraries.

В режиме с кратким уровнем детализации вывод сокращается:

qhb=# set plpgsql_check.tracer_verbosity TO terse;
SET
qhb=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE:  #0 start of inline code block (oid=0)
NOTICE:  #2 start of fx (oid=16405)
NOTICE:  #4 start of fx (oid=16404)
NOTICE:  #4 end of fx
NOTICE:  #2 end of fx
NOTICE:  #0 end of inline code block

В режиме с подробным уровнем детализации выводится подробная информация об операторах:

qhb=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE:  #0            ->> start of block inline_code_block (oid=0)
NOTICE:  #0.1       1  --> start of PERFORM
NOTICE:  #2              ->> start of function fx(integer,integer,date,text) (oid=16405)
NOTICE:  #2                   call by inline_code_block line 1 at PERFORM
NOTICE:  #2                  "a" => '10', "b" => null, "c" => '2020-08-04', "d" => 'stěhule'
NOTICE:  #2.1       1    --> start of PERFORM
NOTICE:  #2.1                "a" => '10'
NOTICE:  #4                ->> start of function fx(integer) (oid=16404)
NOTICE:  #4                     call by fx(integer,integer,date,text) line 1 at PERFORM
NOTICE:  #4                    "a" => '10'
NOTICE:  #4.1       6      --> start of assignment
NOTICE:  #4.1                  "a" => '10', "b" => '20'
NOTICE:  #4.1              <-- end of assignment (elapsed time=0.076 ms)
NOTICE:  #4.1                  "res" => '130'
NOTICE:  #4.2       7      --> start of RETURN
NOTICE:  #4.2                  "res" => '130'
NOTICE:  #4.2              <-- end of RETURN (elapsed time=0.054 ms)
NOTICE:  #4                <<- end of function fx (elapsed time=0.373 ms)
NOTICE:  #2.1            <-- end of PERFORM (elapsed time=0.589 ms)
NOTICE:  #2              <<- end of function fx (elapsed time=0.727 ms)
NOTICE:  #0.1          <-- end of PERFORM (elapsed time=1.147 ms)
NOTICE:  #0            <<- end of block (elapsed time=1.286 ms)

Особым функционалом трассировщика является трассировка оператора ASSERT при установке в plpgsql_check.trace_assert значения on. Если переменная plpgsql_check.trace_assert_verbosity равна DEFAULT, то когда выражение условия ASSERT равно false, отображаются все переменные функции или процедуры. Когда эта конфигурация равна VERBOSE, то отображаются все переменные из всех блоков PL/pgSQL. Это поведение не зависит от значения plpgsql.check_asserts. Его можно использовать, хотя во время выполнения PL/pgSQL утверждения отключаются.

qhb=# set plpgsql_check.tracer to off;
qhb=# set plpgsql_check.trace_assert_verbosity TO verbose;

qhb=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE:  #4 PLpgSQL assert expression (false) on line 12 of fx(integer) is false
NOTICE:   "a" => '10', "res" => null, "b" => '20'
NOTICE:  #2 PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM
NOTICE:   "a" => '10', "b" => null, "c" => '2020-08-05', "d" => 'stěhule'
NOTICE:  #0 PL/pgSQL function inline_code_block line 1 at PERFORM
ERROR:  assertion failed
CONTEXT:  PL/pgSQL function fx(integer) line 12 at ASSERT
SQL statement "SELECT fx(a)"
PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM
SQL statement "SELECT fx(10,null, 'now', e'stěhule')"
PL/pgSQL function inline_code_block line 1 at PERFORM

qhb=# set plpgsql.check_asserts to off;
SET
qhb=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE:  #4 PLpgSQL assert expression (false) on line 12 of fx(integer) is false
NOTICE:   "a" => '10', "res" => null, "b" => '20'
NOTICE:  #2 PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM
NOTICE:   "a" => '10', "b" => null, "c" => '2020-08-05', "d" => 'stěhule'
NOTICE:  #0 PL/pgSQL function inline_code_block line 1 at PERFORM
DO

Использование с plugin_debugger

Если вы используете plpgsql_check вместе с plugin_debugger (отладчик PL/pgSQL), то plpgsql_check следует инициализировать после plugin_debugger (поскольку plugin_debugger не поддерживает совместное использование отладочных API PL/pgSQL). Например (в qhb.conf):

shared_preload_libraries = 'plugin_debugger,plpgsql,plpgsql_check'

ВНИМАНИЕ!
Трассировщик выводит содержимое переменных или аргументов функций. У функций SECURITY DEFINER там могут содержаться конфиденциальные данные. По этой причине трассировщик по умолчанию выключен и должен включаться только с правами суперпользователя для plpgsql_check.enable_tracer.

Директивы

Поведение plpgsql_check в проверяемой функции можно сконфигурировать «директивной» функцией. Это аналог функциональности PRAGMA в языке PL/SQL или ADA. PL/pgSQL не поддерживает PRAGMA, но plpgsql_check выявляет функцию с именем plpgsql_check_pragma и забирает значения из параметров этой функции. Эти параметры plpgsql_check будут считаться верными до конца заданной группы операторов.

CREATE OR REPLACE FUNCTION test()
RETURNS void AS $$
BEGIN
  ...
  -- выключить проверку для следующих операторов
  PERFORM plpgsql_check_pragma('disable:check');
  ...
  -- снова включить проверку
  PERFORM plpgsql_check_pragma('enable:check');
  ...
END;
$$ LANGUAGE plpgsql;

Функция plpgsql_check_pragma представляет собой неизменяемую функцию, возвращающую единицу. Она определяется расширением plpgsql_check. Альтернативную функцию plpgsql_check_pragma можно объявить следующим образом:

CREATE OR REPLACE FUNCTION plpgsql_check_pragma(VARIADIC args[])
RETURNS int AS $$
SELECT 1
$$ LANGUAGE sql IMMUTABLE;

Использование директивной функции в разделе объявлений верхнего блока также устанавливает параметры на уровне функции.

CREATE OR REPLACE FUNCTION test()
RETURNS void AS $$
DECLARE
  aux int := plpgsql_check_pragma('disable:extra_warnings');
  ...

Более короткая запись для директивы тоже поддерживается:

CREATE OR REPLACE FUNCTION test()
RETURNS void AS $$
DECLARE r record;
BEGIN
  PERFORM 'PRAGMA:TYPE:r (a int, b int)';
  PERFORM 'PRAGMA:TABLE: x (like pg_class)';
  ...

Поддерживаемые директивы

echo:str — вывести строку (для тестирования). Внутри строки можно использовать следующие «переменные»: @@id, @@name, @@signature.

status:check,status:tracer, status:other_warnings, status:performance_warnings, status:extra_warnings, status:security_warnings. Эти директивы выводят данные текущего значения (например, что включен параметр other_warnings).

enable:check,enable:tracer, enable:other_warnings, enable:performance_warnings, enable:extra_warnings, enable:security_warnings.

disable:check,disable:tracer, disable:other_warnings, disable:performance_warnings, disable:extra_warnings, disable:security_warnings. Эти директивы можно использовать для выключения Подсказки в значении, возвращаемом функцией anyelement. Просто добавьте директиву перед оператором RETURN.

type:varname typename или type:varname (fieldname type, ...) — установить тип для переменной с типом record.

table: name (column_name type, ...) или table: name (like tablename) — создать эфемерную временную таблицу (если вы желаете указать схему, то допустима только схема pg_temp).

sequence: name — создать эфемерную временную последовательность

Директивы enable:tracer и disable:tracer активны для QHB версии 1.5.0 и выше.

Лицензия

Copyright (c) Павел Стехуле ( pavel.stehule@gmail.com )

Данная лицензия разрешает лицам, получившим копию данного программного обеспечения и сопутствующей документации (далее — Программное обеспечение), безвозмездно использовать Программное обеспечение без ограничений, включая неограниченное право на использование, копирование, изменение, слияние, публикацию, распространение, сублицензирование и/или продажу копий Программного обеспечения, а также лицам, которым предоставляется данное Программное обеспечение, при соблюдении следующих условий:

Указанное выше уведомление об авторском праве и данные условия должны быть включены во все копии или значимые части данного Программного обеспечения.

ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ИСКАМ ЗА УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, В ТОМ ЧИСЛЕ ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТЕ ИЛИ ИНОЙ СИТУАЦИИ, ВОЗНИКШИМ ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.

См. также

Документация по plpgsql_check на Github

sr_plan

Описание

Модуль sr_plan служит для управления хранением планов запросов в QHB. Он похож на систему Oracle Outline. Данный модуль можно использовать для блокировки плана выполнения. Это необходимо, если есть сомнения в планировщике или невозможно составить лучший план.

Установка

Модуль управления хранением планов запросов в QHB для QHB поставляется в виде пакета qhb-1.5.2-sr-plan.

Подключите репозиторий пакетов QHB и установите пакет расширения для выбранной платформы со страницы загрузки.

Применение

Установка расширения в базу данных:

CREATE EXTENSION sr_plan;

Если требуется сохранить план запроса, то необходимо установить следующую переменную:

SET sr_plan.write_mode = true;

Теперь планы для всех последующих запросов будут сохраняться в таблице sr_plans. Обратите внимание, что при этом будут сохранены все запросы, включая дубликаты.

Формирование примера запроса:

SELECT query_hash FROM sr_plans WHERE query_hash=10;

Отключение сохранения плана для запроса:

SET sr_plan.write_mode = false;

Включение плана:

UPDATE sr_plans SET enable=true;

После этого план запроса будет браться из таблицы sr_plans.

Кроме того, sr_plans позволяет сохранять параметризованный план запроса. При этом некоторые константы в запросе не существенны. Для параметров используется специальная функция _p (anyelement), например:

SELECT query_hash FROM sr_plans WHERE query_hash=1000+_p(10);

Если сохранить план для запроса, то можно разрешить его использование для последующих запросов:

SELECT query_hash FROM sr_plans WHERE query_hash=1000+_p(11);
SELECT query_hash FROM sr_plans WHERE query_hash=1000+_p(-5);

EXPLAIN для сохраненных планов

С помощью функции show_plan можно просмотреть сохраненные планы. Это требует знания хеша запроса, который можно получить из таблицы sr_plans.

Примеры:

Показать включенный план для хеша запроса:

SELECT show_plan(1);
                  show_plan
----------------------------------------------
 ("Seq Scan on public.explain_test")
 ("  Output: test_attr1, test_attr2")
 ("  Filter: (explain_test.test_attr1 = 10)")
(3 rows)

Получить второй сохраненный план, используя параметр index (игнорирует атрибут enable):

SELECT show_plan(1, index := 2);
                  show_plan
----------------------------------------------
 ("Seq Scan on public.explain_test")
 ("  Output: test_attr1, test_attr2")
 ("  Filter: (explain_test.test_attr1 = 10)")
(3 rows)

Использовать другой формат вывода (поддерживаются форматы json, text, xml, yaml):

SELECT show_plan(1, format := 'json');
                      show_plan
------------------------------------------------------
 ("[                                                 +
   {                                                 +
     ""Plan"": {                                     +
       ""Node Type"": ""Seq Scan"",                  +
       ""Parallel Aware"": false,                    +
       ""Relation Name"": ""explain_test"",          +
       ""Schema"": ""public"",                       +
       ""Alias"": ""explain_test"",                  +
       ""Output"": [""test_attr1"", ""test_attr2""], +
       ""Filter"": ""(explain_test.test_attr1 = 10)""+
     }                                               +
   }                                                 +
 ]")
(1 row)

Интеграция pg_stat_statements

Таблица sr_plans содержит столбцы query_id, которые можно использовать для соединений с таблицами и представлениями pg_stat_statements.

Примечание: в списке параметра shared_preload_libraries pg_stat_statements следует указывать после sr_plan.

См. также

Документация по sr_plan на Github

Поддерживаемые платформы

Платформа (то есть комбинация архитектуры процессора и операционной системы) считается поддерживаемой сообществом разработчиков QHB, если код адаптирован для работы на этой платформе и в настоящее время успешно собирается и проходит на ней регрессионные тесты.

В настоящее время тестирование совместимости в основном выполняется автоматически силами сообщества разработчиков QHB.

Если вы заинтересованы в использовании QHB на платформе, еще не представленной в данном разделе, но уверены, что код на ней работает или может работать, пожалуйста, сообщите нам по адресу qhb.support@quantom.info.

В текущей версии QHB 1.5.2 поддерживаются процессоры следующих архитектур:

x86_64

Сервер QHB собирается и будет работать в следующих операционных системах:

Linux
Возможна работа на всех последних дистрибутивах, но пока бинарные пакеты собираются только для операционных систем, перечисленных на странице загрузки.

Если вы столкнулись с проблемами установки на поддерживаемой платформе, пожалуйста, сообщите о них по адресу qhb.support@quantom.info.

Поддержка прикладных платформ

Прикладная платформа — это программное обеспечение, предназначенное для проектирования, разработки, выполнения и модернизации программных компонентов, автоматизирующих деятельность различных конкретных предприятий.

Поддержка решений 1С

2B: Поддержка решений 1С

Поддерживается платформа «1С:Предприятие», начиная с версии 8.3.18.

Дополнительные типы данных для совместимости с Microsoft SQL Server

В версии QHB 1.2.0 были добавлены типы данных mchar и mvarchar, являющиеся аналогами типов char и varchar, но имеющие ряд особенностей поведения, повторяющих поведение типов nchar и nvarchar в Microsoft SQL Server.

Отличия от стандартных типов char и varchar в QHB:

Сравнение без учета регистра, в том числе если один из аргументов mchar/mvarchar, а второй нет (для сравнения с учетом регистра вводятся специальные версии операторов сравнения: &<, &<=, &=, &>= и &>).

Тип mchar фиксированной длины дополняется пробелами справа, но при сравнениях эти пробелы игнорируются.

Данные типа mchar/mvarchar хранятся в кодировке utf-16 (для пользователя это не важно).

Для сравнения и приведения к верхнему/нижнему регистру используется ICU, поэтому результат не зависит от операционной системы.

Функция получения подстроки substr(str, pos [, length]), а не substring.

Отличия от поведения nchar и nvarchar в Microsoft SQL Server:

Типы mchar и mvarchar без указания длины трактуются как строки неопределенной неограниченной длины (похоже на nvarchar(max)); mchar неопределенной длины не дополняется пробелами справа (но явно заданные пробелы все-таки хранятся).

Функция вычисления длины строки length(string), а не len, как в Microsoft SQL Server.

Для типов данных mchar и mvarchar поддерживаются индексы B-деревья и хеш-индексы.

fulleq — дополнительный оператор равенства для совместимости с Microsoft SQL Server

Для совместимости с Microsoft SQL Server в версии QHB 1.2.0 был добавлен оператор fulleq (полное равенство) ==.

Оператор fulleq (==) отличается от обычного равенства (=) тем, что возвращает true при сравнении двух значений NULL (обычное равенство возвращает NULL согласно стандарту ANSI SQL). Такое поведение принято в СУБД семейства Microsoft SQL.

Оператор == определен для следующих типов данных:

bool

bytea

char

mchar

varchar

mvarchar

name

int2

int4

int8

int2vector

text

oid

xid

cid

oidvector

float4

float8

macaddr

inet

cidr

date

time

timestamp

timestamptz

interval

timetz

Быстрое опустошение временных таблиц

В QHB операция TRUNCATE транзакционная: ее можно откатить. Из-за этого она более затратна, чем в других СУБД.

В версии 1.2.0 была добавлена функция fasttruncate, которая очищает таблицу быстрее и предотвращает разрастание системного каталога pg_class. Пока ее можно применять только ко временным таблицам, благодаря чему очистка не вызывает конфликта с другими пользователями.

ВНИМАНИЕ!
Функция fasttruncate является внетранзакционной: при откате транзакции очистка таблицы не будет отменена!

Пример использования:

SELECT fasttruncate ('pg_temp.tmp3')

Немедленное обновление статистики отдельных таблиц

Для сильно меняющихся таблиц, например временных, стандартные средства сбора статистики могут быть неэффективны. В версии QHB 1.2.0 появилась возможность немедленного обновления статистики таблицы после ее изменения, т. н. немедленный анализ (online analyze).

Сбор статистики вызывается после фиксации транзакции, модифицировавшей таблицу, при выполнении определенных условий. Настройка этого поведения осуществляется с помощью соответствующих конфигурационных параметров в файле конфигурации qhb.conf (подробную информацию см. в подразделе Конфигурационные параметры немедленного обновления статистики).

Поддержка указаний для планировщика, позволяющих отключать или подключать определенные индексы при выполнении запроса

В версии QHB 1.2.0 были добавлены конфигурационные параметры для управления использованием индексов (подробную информацию см. в подразделе Конфигурационные параметры для управления использованием индексов). С их помощью можно просить планировщик не использовать определенные индексы. Если вы хотите, чтобы конкретный индекс использовался, этих параметров недостаточно, одновременно с ними нужно использовать параметр enable_seqscan и другие.

Эти новые параметры относятся к настройкам планирования. Как и для других настроек планирования, основным сценарием использования является изменение этих параметров на уровне транзакции, перед выполнением конкретного запроса. Технически возможно использование параметров на уровне базы данных или сервера, но если вам понадобится выключить использование индекса на уровне базы данных, имеет смысл рассмотреть его удаление.

Особенности использования QHB

По сравнению с привычными настройками, параметрами или функциями, используемыми в схожих СУБД, существуют изменения, которые были внесены командой разработчиков QHB.

В разделе Изменения в настройках по умолчанию описаны те настройки, которые изначально, при разметке нового кластера QHB, будут иметь значение, отличное от привычного.

В разделе Устаревшие функции и методы описаны функции и методы, от которых в QHB было решено отказаться по причине их устарелости.

Изменения в настройках по умолчанию

Параметр restart_after_crash получил значение по умолчанию off (выключен).

Устаревшие функции и методы

На данный момент устаревшие функции и методы отсутствуют.

Отличия СУБД «Квант-Гибрид» от PostgreSQL

Преимущества СУБД «Квант-Гибрид»

СУБД «Квант-Гибрид» (Quantum Hybrid Base, QHB) является глубоким форком open-source решения PostgreSQL и обеспечивает

совместимость по протоколам обращения;

совместимость (в одну сторону) по форматам данных;

совместимость (в одну сторону) по функциональности и встроенным расширениям;

совместимость со сторонними расширениям (при пересборке и/или исправлении зависимостей).

СУБД «Квант-Гибрид» — полностью отечественное решение

№ 6212 в Реестре российского ПО с 07.04.2020;

разработчик — полностью российская компания;

стабилизированное решение (выпущено несколько стабильных релизов, текущий релиз — 1.5.2);

профессиональный коллектив разработчиков с многолетним опытом;

профессиональная техническая поддержка;

возможность модификации «под заказ», в том числе:

в гораздо более сжатые сроки по сравнению с open-source решением и другими форками;

с очень глубокой интеграцией в ядро СУБД.

Основные направления модификации СУБД

1. Повышение стабильности, устойчивости, производительности

1.1. Балансировщик сетевой нагрузки и пулер соединений

Подробнее — QCP.

асинхронный режим работы;

тредовая модель;

режимы разделения на уровне сеанса и интеллектуальный режим (разделение сеанса между несколькими соединениями только для чтения);

автоматическое переключение на узлы резервного сервера.

1.2. Менеджер кэша дисковых блоков

Подробнее — TARQ.

устойчивость к вымыванию кэша;

минимизация времени «разогрева кэша»;

возможность построения In-Memory и Append-only таблиц.

1.3. Рефакторинг многих модулей ядра СУБД, устранение уязвимостей, переписывание на языке Rust.

2. Встроенная криптографическая защита данных

Подробнее — QSS.

на отечественных сертифицированных алгоритмах ГОСТ Р 34.13-2015, ГОСТ 34.13-2018, ГОСТ Р 34.12-2015, ГОСТ 34.12-2018, ГОСТ Р 34.11-2012, ГОСТ 34.11-2018 и стандартах Р 1323565.1.026–2019, Р 50.1.111-2016, Р 50.1.113-2016;

шифрование на уровне страниц данных, включая журналы WAL/репликации и двоичные резервные копии;

шифрование на уровне всех или отдельных таблиц;

без заметного/существенного снижения производительности.

3. Повышение сопровождаемости решения, снижение эксплуатационных рисков

3.1. Мониторинг активности базы данных

Подробнее — Мониторинг активности базы данных.

глубоко интегрированная в ядро система сбора метрик;

практически без влияния на производительность (длительность считывания одного показания ~30 нс);

в несколько раз больше, чем стандартных метрик PostgreSQL (включая показатели состояния сессии и шага исполнения запроса в режиме реального времени);

агрегация/экспорт/визуализация на том же или соседнем хосте (без нагрузки на основной сервер);

возможность включения/выключения сбора метрик по желанию пользователя.

3.2. Инструмент резервного копирования

Подробнее — qbackup.

двоичное полное и инкрементальное резервное копирование без остановки экземпляра;

параллельное многопоточное резервное копирование;

сжатие резервных копий;

удаленное резервное копирование;

ускоренное восстановление (быстрее, чем воспроизведение журналов WAL);

восстановление на момент времени;

ведение каталога резервных копий (в текстовом виде или JSON);

отслеживание измененных данных, или CDC (change data capture);

создание резервных копий по табличным пространствам;

валидация резервных копий;

возможность удаления файлов WAL при удалении резервной копии.

4. Развитие функциональности, отсутствующей в open-source решении:

4.1. Встроенная очередь сообщений

Подробнее — Встроенная очередь сообщений.

персистентные сообщения;

In-Memory сообщения.

4.2. Встроенный планировщик заданий

Подробнее — QSched.

создание и управление расписаниями/задачами;

ведение журналов планировщика;

включение/выключение динамического расчета времени следующего выполнения;

управление параллельными процессами планировщика.

4.3. Переменные уровня сеанса

Подробнее — qhb-variables.

скалярные переменные;

табличные переменные, интеграция с элементами таблиц.

4.4. Профилирование долгих запросов.

Подробнее — Профилирование долгих запросов.

4.5. Модуль прямой загрузки данных

Подробнее — QDL.

возможность ускорения загрузки данных примерно в 3 раза (по сравнению с командой COPY);

совместимость с CDC;

API для загрузки данных.

4.6. Внешнее хранение больших двоичных данных (blob/clob/bytea)

Подробнее — Rbytea.

4.7. Расширение для реализации битемпоральной модели данных

Подробнее — Qbim.

4.8. Методы статистического анализа на основе условных вероятностей Байеса

Подробнее — Qbayes.

4.9. Поддержка решений 1С в ядре СУБД; совместимость подтверждена фирмой «1С».

Подробнее — Поддержка решений 1С, Подтверждение фирмой «1С».

4.10. Расширенные XML функции (для поддержки миграции с Oracle)

Подробнее — Расширенные функции XML.

4.11. Инструменты QMiM поддержки миграции с СУБД Oracle и MSSQL (в пилотной эксплуатации, как отдельное решение)

Ссылки

Документация.

Страница загрузки.

Детальное сравнение.

Особенности и преимущества QHB

Основные возможности

Поддерживает современные стандарты баз данных (БД) - требования ACID:

Атомарность (Atomicity);

Согласованность (Consistency);

Изолированность (Isolation);

Устойчивость (Durability).

Обеспечивает уровни изоляции транзакций SERIALIZABLE, REPEATABLE READ, READ COMMITTED.

Поддерживает управление доступом с помощью многоверсионности (MVCC - MultiVersion Concurrency Control).
Используется для поддержания согласованности данных в конкурентных условиях. Система видит копию данных (версию базы данных) на момент начала транзакции несмотря на то, что состояние базы данных могло уже измениться, что защищает транзакцию от несогласованных изменений данных, которые могли быть вызваны другой конкурентной транзакцией и обеспечивает таким образом изоляцию транзакций. Блокировка, устанавливаемая ей для чтения, не конфликтует с блокировкой на запись, и поэтому чтение никогда не блокирует запись и наоборот.

Обеспечивает поддержку блокировок на уровне записей.

Обеспечивает журнал упреждающей записи (Write-Ahead Logging - WAL). Позволяет восстановить систему после возможных сбоев. Все изменения записываться на диск после того, как записи журнала, описывающие эти действия, будут гарантировано записаны на диск. Это позволяет не сбрасывать страницы данных на диск после фиксации каждой транзакции, так как всегда есть возможность восстановления базы данных используя журнал транзакций.

Обеспечивает ссылочную целостность.

Обеспечивает возможность расширения. Обеспечивает возможность добавления новых типов данных, функций, операторов, методов доступа, языков программирования без перекомпилирования ядра СУБД.

Обеспечивает возможность доступа к сторонним данным Обеспечивает возможность доступа к сторонним данным для работы с СУБД Microsoft SQL Server, MySQL, Oracle и PostgreSQL.

Ограничения QHB

В Таблице 1 описаны различные жесткие ограничения QHB. Однако практические ограничения, такие как предел производительности или доступного дискового пространства, могут проявиться раньше, чем будут достигнуты абсолютные жесткие ограничения.

Таблица 1. Ограничения QHB

Параметр | Верхний предел | Комментарий
размер базы данных | без ограничений |
количество баз данных | 4 294 950 911 |
отношения в базе данных | 1 431 650 303 |
размер отношения | 32 ТБ | со значением BLCKSZ по умолчанию, равным 8192 байта
строк в таблице | ограничено количеством кортежей, которое может уместиться на 4 294 967 295 страниц |
столбцов в таблице | 1600 | дополнительно ограничено размером кортежа, который может уместиться на одной странице; см примечание ниже
столбцов в результирующем наборе | 1664 |
размер поля | 1 ГБ |
длина идентификатора | 63 байта | можно увеличить, перекомпилировав QHB
индексов на таблицу | без ограничений | ограничен максимальным количеством отношений в базе данных
столбцов на индекс | 32 | можно увеличить, перекомпилировав QHB
ключи раздела | 32 | можно увеличить, перекомпилировав QHB

Максимальное количество столбцов таблицы дополнительно уменьшается, поскольку сохраняемый кортеж должен умещаться на одной странице кучи размером 8192 байта. Например, без учета заголовка кортеж, состоящий из 1600 столбцов int, будет занимать 6400 байт и может храниться на странице кучи, однако кортеж из 1600 столбцов bigint будет занимать 12 800 байт и, следовательно, на странице не поместится. Поля переменной длины таких типов, как text, varchar и char, могут хранить свои значения отдельно, в таблице TOAST, когда эти значения достаточно велики для этого. При этом внутри кортежа в куче таблицы должен остаться только 18-байтовый указатель. Для более коротких полей переменной длины используется 4-байтовый или 1-байтовый заголовок, а значение сохраняется внутри кортежа кучи.

Столбцы, которые были удалены из таблицы, также влияют на максимальное количество столбцов. Более того, хотя значения удаленных столбцов для вновь создаваемых кортежей внутренне помечаются как NULL в заведенной специально для них битовой карте кортежа, эта карта тоже занимает место.

Поддержка платформ

QHB поддерживает платформы:

Аппаратную платформу x86-64.

Поддержка операционных систем

QHB поддерживает следующие сертифицированные ФСТЭК операционные системы:

ОС Альт 8 СП (обозначение программного изделия — ЛКНВ.11100-01) (№ сертификата 3866);

ОС специального назначения «Astra Linux Special Edition» (в исполнении РУСБ.10015-01) x86_64, очередное обновление 1.7, (№ сертификата 2557);

ОС «РЕД ОС» (№ сертификата 4060).

Подробнее см. Поддерживаемые платформы и Бинарные дистрибутивы.

Стандартизация и унификация

Поддерживает следующие стандартные, унифицированные типы данных:

целочисленные типы: INT, SMALLINT, BIGINT;

числа с произвольной точностью;

типы с плавающей точкой: REAL, DOUBLE PRESISION, FLOAT;

последовательные типы;

денежные типы;

символьные типы данных: VARCHAR(n), CHAR(n), TEXT;

двоичные типы данных;

типы даты и времени: DATE, TIME, TIMESTAMP, TIMESTAMP WITH TIMEZONE, INTERVAL;

логический тип BOOLEAN;

типы перечислений;

геометрические типы;

типы, описывающие сетевые адреса;

битовые строки;

типы данных, предназначенные для текстового поиска;

UUID;

XML;

JSON;

массивы;

составные типы;

диапазонные типы;

типы доменов;

идентификаторы объектов;

псевдотипы.

Функциональные характеристики

Обладает следующими функциональными характеристиками:

приближённое соответствие стандарту SQL (SQL:2016, SQL:2011, SQL:2008, SQL:2006, SQL:2003, SQL:1999 и SQL-92);

поддержка представлений;

поддержка внешних ключей;

поддержка транзакций;

поддержка оконных функций;

поддержка наследований;

поддержка функций и операторов;

поддержка хранимых процедур;

поддержка различных типов индексов;

наличие встроенной системы полнотекстового поиска;

поддержка табличных пространств;

поддержка табличных триггеров БД и триггеров событий;

поддержка процедурных языков

поддержка кодировки UTF8;

поддержка NoSQL;

наличие программных интерфейсов для работы с C/C++, Java/JDBC, .NET, ODBC, Perl, Python, Ruby, Tcl;

наличие встроенных средств аутентификации пользователей, поддерживающих GSSAPI, SSPI, LDAP, RADIUS, PAM, BSD;

поддержка SSL;

возможность разграничения доступа к объектам БД;

возможность разграничения доступа к таблицам на уровне строк;

возможность безопасного хранения паролей;

возможность интеграции с подсистемой SE-Linux.

Доступность и надежность

Поддержка очереди сообщений на уровне ядра СУБД.

Обладает следующими функциональными возможностями по обеспечению сохранности информации при авариях:

наличие отказоустойчивого кластера;

наличие встроенных средств репликации данных: синхронная, асинхронная, каскадная;

возможность использования различных видов репликации данных: потоковая, логическая;

возможность построения отказоустойчивого кластера (ведущий-ведомый) с произвольным количеством реплик (ведомых серверов) в разных конфигурациях (теплый резерв, горячий резерв);

наличие встроенных средств «горячего» резервного копирования и восстановления данных.

Производительность и масштабируемость

Расширенная функциональность менеджера кэша дисковых блоков, устойчивого к вымыванию кэша.

Создание дампа и загрузка содержимого менеджера дисковых блоков для минимизации времени "разогрева кэша".

Для ускорения работы с таблицами имеется возможность создавать их особым параметром HOLDMEM, который указывает, где они будут размещаться.

Обладает следующими возможностями, обеспечивающими производительность и масштабируемость:

оптимизация для многопроцессорных/многоядерных серверов:

улучшенный механизм проверки блокировок, не оказывающий отрицательного влияния на производительность;

улучшенная производительность при использовании множества временных таблиц в отдельных обслуживающих процессах и при большом количестве одновременных подключений;

увеличенная скорость и эффективность планирования для различных типов запросов;

наличие стоимостного оптимизатора, учитывающего дисковые операции и процессорное время;

возможность асинхронного подтверждения транзакций;

возможность параллельного выполнения запросов;

возможность параллельного создания индексов и параллельного доступа к индексам;

возможность сканирования только индекса (покрывающие индексы);

возможность работы с большим количеством временных объектов в рамках одной сессии;

поддержка секционирования для больших таблиц;

режим работы APPEND_ONLY;

возможность размещать данные в общей памяти используя таблицы в оперативной памяти (In-Memory).

Администрирование и мониторинг

Обладает следующими средствами администрирования и мониторинга:

возможность планировать и контролировать задания, а также управлять их выполнением в базе данных (планировщик задач);

возможность использования функций для работы с переменными различных типов в рамках текущей сессии;

реализация тайм-аута для простаивающих сеансов на стороне сервера;

наличие унифицированной структуры пакетов Linux;

наличие выделенного соединения для администратора;

возможность для сбора статистики планирования и выполнения всех обрабатываемых сервером SQL-операторов.

Защита информации от несанкционированного доступа

СУБД может применяться для защищенной обработки информации ограниченного доступа, не содержащей сведения, составляющие государственную тайну в информационных системах совместно с другими средствами защиты информации от несанкционированного доступа к информации в информационных системах.

В СУБД применяются средства защиты информации, соответствующие требованиям по безопасности информации, установленным в документе «Требования по безопасности информации, устанавливающие уровни доверия к средствам технической защиты информации и средствам обеспечения безопасности информационных технологий» (ФСТЭК России, 2020) – не ниже 4 уровня доверия.

СУБД обеспечивает защиту информации, содержащейся в базах данных, находящихся под их управлением, путем реализации следующих процессов:

идентификация и аутентификация субъектов доступа и объектов доступа, предоставление доступа к базе данных в случае успешной аутентификации пользователя, блокирование доступа к базе данных в случае неуспешной аутентификации пользователя;

управление доступом субъектов доступа к объектам доступа, предоставление пользователям запрашиваемых типов доступа к объектам СУБД в соответствии с реализуемым методом управления доступом;

регистрация событий безопасности;

обеспечение целостности информационной системы и информации, блокировка работы (запуска) экземпляра БД в случае обнаружения фактов нарушения целостности;

блокировка доступа субъекта к остаточной информации при первоначальном назначении или при перераспределении внешней памяти (очистка внешней и оперативной памяти);

обеспечение доступности информации: резервное копирование и восстановление информации, содержащейся в базе данных.

СУБД предоставляет автоматизированные средства поддержки управления учетными записями пользователей для обеспечения выполнения следующих функций (мера защиты УПД.1, усиление 1 меры защиты УПД.1):

объединение учетных записей пользователей и ролей в группы на основе групповых ролей;

заведение, активация, блокирование и уничтожение учетных записей пользователей (ролей);

корректировка учетных записей пользователей;

предоставление пользователям прав доступа к объектам доступа СУБД.

СУБД предоставляет возможность членства одной роли в другой. Набор атрибутов, которыми обладает пользователь (роль), который входит в другие роли, представляет собой объединение атрибутов, предоставленных каждой роли, членом которой является пользователь (роль).

СУБД предоставляет пользователям (ролям) с атрибутом WITH ADMIN OPTION возможность включать в члены этой роли и исключать из нее другие роли.

Каждому объекту доступа в СУБД сопоставлен пользователь (роль), который является владельцем данного объекта. Право удаления объекта доступа предоставляется только пользователю (роли), обладающему атрибутом SUPERUSER, либо являющемуся владельцем данного объекта. Право удаления таблицы кроме владельца или пользователя c атрибутом SUPERUSER предоставлено владельцу схемы, в которую входит удаляемая таблица. Пользователь, являющийся владельцем таблицы, имеет право создавать индексы и системы правил для таблицы.

СУБД осуществляет идентификацию и аутентификацию пользователей СУБД. Пользователи СУБД однозначно идентифицируются и аутентифицируются для всех видов доступа. Аутентификация пользователя осуществляется с использованием паролей (мера защиты ИАФ.1).

СУБД осуществляет следующие функции управления идентификаторами пользователей СУБД (мера защиты ИАФ.3):

обеспечение создания уникального идентификатора пользователя;

присвоение идентификатора пользователю;

блокирование идентификатора;

СУБД осуществляет следующие функции управления аутентификационной информацией (пароль) пользователей СУБД (мера защиты ИАФ.4):

генерация пароля пользователя;

изменение пароля пользователя;

расширенные политики аутентификации.

СУБД предотвращает доступ субъекту к остаточной информации при первоначальном назначении или при перераспределении внешней памяти.

Специальные возможности

Содержит встроенные инструментарий для задач расчета вероятностных моделей с применением сетей Байеса. В системе применяются следующие алгоритмы:

наивный байесовский классификатор;

классификатор, основанный на функции взаимной информации;

методы Монте-Карло с цепями Маркова.

Содержит расширенные функции XML (для миграции с Oracle DB).

Обеспечивает поддержку битемпоральной модели данных.

Обеспечивает внешнее хранение больших двоичных данных (blob/clob/bytea)

Страница загрузки

Бинарные дистрибутивы для поддерживаемых платформ можно получить в репозитории QHB:

QHB для CentOS 7 и 8

QHB для РЕД ОС 7

QHB для Альт 8 СП

QHB для Альт Сервер 9

QHB для Fedora 36, 37, 38 и 39

QHB для Debian 10 и Astra Linux Special Edition 1.7

QHB для Ubuntu 18.04 и 22.04

QHB для openSUSE Leap 15.4

QHB в виде docker контейнера

Обновление уже установленных пакетов

QHB для CentOS 7 и 8

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Установите утилиту config-manager для управления конфигурацией репозиториев:

dnf install dnf-plugin-config-manager

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

rpm --import https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb

Установите дополнительный репозиторий epel-release:

yum -y install epel-release

и далее выполните для CentOS 7:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/centos/7/x86_64/qhb.repo

Для установки qhb-patroni и postgis требуется добавить репозиторий postgresql:

 yum -y -q install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

Для CentOS 8 выполните:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/centos/8/x86_64/qhb.repo

Примечание
Для данной ОС могут быть проблемы с установкой СУБД «Квант-Гибрид» и его расширений из-за отсутствия доступных репозиториев. Поддержка данной ОС может быть исключена в следующем релизе.

Для установки qhb-patroni и postgis требуется добавить репозиторий postgresql:

 yum -y -q install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm

Для CentOS 8 при необходимости установить расширение postgis требуются дополнительные репозитории:

dnf config-manager --add-repo https://repo.quantom.info/qhb/additional-package/centos/8/qhb-add.repo
dnf config-manager --set-enabled powertools

Установите бинарные пакеты командой:

dnf install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

dnf install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

QHB для РЕД ОС 7

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Установите утилиту config-manager для управления конфигурацией репозиториев:

dnf install dnf-plugin-config-manager

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

rpm --import https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb

и далее выполните для РЕД ОС 7:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/redos7/x86_64/qhb.repo

Установите бинарные пакеты командой:

dnf install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

dnf install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

QHB для Альт 8 СП

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Примечание
СУБД QHB поддерживает ОС Альт 8 СП только версии ядра 5.10.

Для установки потребуются дополнительные библиотеки libicu и libgostcrypto. Также, возможно, потребуется установка или обновление библиотеки glibc до версии 2.34 или выше; заранее проверьте версию вашей библиотеки.

Скачайте библиотеку (подходит версия 60.3-2) в любую локальную папку:

wget -c http://mirror.centos.org/centos/8/BaseOS/x86_64/os/Packages/libicu-60.3-2.el8_1.x86_64.rpm

Скачайте библиотеку libgostcrypto:

wget -c -r -l1 -A '*.rpm' -nd -np https://repo.quantom.info/qhb/std-1/centos/8/x86_64/libgostcrypto.0.6-0.6.1-1.x86_64.rpm

ВНИМАНИЕ!
СУБД QHB для ОС Альт 8 СП поставляется на дисках. Для получения обратитесь по адресу qhb.support@quantom.info.

Скопируйте с диска все бинарные пакеты в локальную папку и установите командой, например:

apt-get install *.rpm

Вы можете установить не все пакеты QHB, а только необходимые вам. В этом случае вы можете перечислить в команде нужные вам пакеты rpm через пробел. Выбирайте последние версии из rpm пакетов, которые скачались.

Библиотека libicu и пакет qhb-core — обязательны.

В результате установки пакета ядра QHB будет создан пользователь qhb, ядро СУБД QHB по умолчанию будет установлено в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

QHB для Альт Сервер 9

Примечание
Обратите внимание, что для СУБД «Квант-Гибрид» версии 1.5.1 на сервере под управлением OS Alt Server 9 должна быть установлена библиотека GLIBC версии не ниже 2.28.

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Для установки потребуется дополнительная библиотека libicu. Также, возможно, потребуется установка или обновление библиотеки glibc.

Скачайте библиотеку (подходит версия 60.3-2) в любую локальную папку:

wget -c http://mirror.centos.org/centos/8/BaseOS/x86_64/os/Packages/libicu-60.3-2.el8_1.x86_64.rpm

Скачайте в эту же папку пакеты программного продукта СУБД «Квант-Гибрид» из репозитория командой:

wget -c -r -l1 -A '*.rpm' -nd -np https://repo.quantom.info/qhb/std-1/centos/8/x86_64/

Обновите описания пакетов:

apt-get update

Находясь в этой же локальной папке, установите бинарные пакеты командой, например:

apt-get install *.rpm

Вы можете установить не все пакеты QHB, а только необходимые вам. В этом случае вы можете перечислить в команде нужные вам пакеты rpm через пробел. Выбирайте последние версии из rpm пакетов, которые скачались.

Библиотека libicu и пакет qhb-core — обязательны.

В результате установки пакета ядра QHB будет создан пользователь qhb, ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

QHB для Fedora 36, 37, 38 и 39

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Установить утилиту config-manager для управления конфигурацией репозиториев:

dnf install dnf-plugin-config-manager

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

rpm --import https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb

и далее, для Fedora 36:

Примечание
Для данной ОС могут быть проблемы с установкой СУБД «Квант-Гибрид» и его расширений из-за отсутствия доступных репозиториев. Поддержка данной ОС может быть исключена в следующем релизе.

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/fedora/36/x86_64/qhb.repo

для Fedora 37:

Примечание
Для данной ОС могут быть проблемы с установкой СУБД «Квант-Гибрид» и его расширений из-за отсутствия доступных репозиториев. Поддержка данной ОС может быть исключена в следующем релизе.

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/fedora/37/x86_64/qhb.repo

для Fedora 38:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/fedora/38/x86_64/qhb.repo

или для Fedora 39:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/fedora/39/x86_64/qhb.repo

Установите бинарные пакеты командой:

dnf install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

dnf install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

QHB для Debian 10 и Astra Linux Special Edition 1.7

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Для подключения к репозиторию выполните следующую команду:

apt install gnupg2 apt-transport-https wget

Для корректной работы репозитория СУБД «Квант-Гибрид» в файле конфигурации репозиториев /etc/apt/sources.list должна быть добавлена запись

для Debian 10

deb [arch=amd64] https://repo.quantom.info/qhb/std-1/debian/10 stretch main

для Astra Linux Special Edition «Смоленск» 1.7

deb [arch=amd64] https://repo.quantom.info/qhb/std-1/astra-smolensk/1.7/  stretch main

Примечание
Обратите внимание, что для исключения из OS Astra Linux отозванных сертификатов безопасности следует выполнить очистку согласно инструкции по прекращению использования нежелательного сертификата.

Для Astra требуется установка корневых сертификатов. Установите их с помощью команды:

apt-get install ca-certificates

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

wget -qO - https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb --no-check-certificate | apt-key add -

Далее выполните команды:

для Debian 10

echo 'deb [arch=amd64] https://repo.quantom.info/qhb/std-1/debian/10 stretch main' >> /etc/apt/sources.list

для Astra Linux Special Edition «Смоленск» 1.7

echo 'deb [arch=amd64] https://repo.quantom.info/qhb/std-1/astra-smolensk/1.7/ stretch main' >> /etc/apt/sources.list

и

apt update

Примечание
В случае появления ошибки команды apt update репозитория QHB для Астры 1.7 Ошб:6 https://repo.quantom.info/qhb/std-1/debian stretch InRelease Следующие подписи не могут быть проверены, так как недоступен открытый ключ: NO_PUBKEY 67A97AD5112F6828 надо выполнить команду импорта ключа репозитория QHB для Астры 1.7 apt-key adv --keyserver https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb --recv-keys 67A97AD5112F6828

Установите бинарные пакеты командой:

apt install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

apt install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

QHB для Ubuntu 18.04 и 22.04

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Для подключения к репозиторию выполните следующую команду:

apt install gnupg2 apt-transport-https wget

Для корректной работы репозитория СУБД «Квант-Гибрид» в файле конфигурации репозиториев /etc/apt/sources.list должна быть добавлена соответствующая запись.

для Ubuntu 18.04

echo 'deb https://repo.quantom.info/qhb/std-1/ubuntu/18 stretch main' >> /etc/apt/sources.list

для Ubuntu 22.04

echo 'deb https://repo.quantom.info/qhb/std-1/ubuntu/22 stretch main' >> /etc/apt/sources.list

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

wget -qO - https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb --no-check-certificate | apt-key add -

Далее выполните:

apt update

Установите бинарные пакеты командой:

apt install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

apt install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

QHB для openSUSE Leap 15.4

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командами:

rpm --import https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb

zypper ar 'http://download.opensuse.org/distribution/leap/15.4/repo/oss/' openSuseMain
zypper --gpg-auto-import-keys addrepo -f https://repo.quantom.info/qhb/std-1/opensuse/15.4/x86_64 QHB-Repo

Для установки qhb-patroni и postgis требуются дополнительные репозитории:

zypper ar https://download.postgresql.org/pub/repos/zypp/repo/pgdg-sles-15-pg11.repo
zypper ar https://ftp.lysator.liu.se/pub/opensuse/repositories/science/openSUSE_Leap_15.3/ science15.3-x86_64
zypper ar https://ftp.lysator.liu.se/pub/opensuse/repositories/science/15.4/ science15.4-x86_64
zypper --releasever 15.5 --no-gpg-checks ref

Установку пакета qhb-patroni (и только qhb-patroni) необходимо выполнить командой с флагом:

zypper --releasever 15.5 -n install qhb-patroni

и далее:

zypper refresh

Установите бинарные пакеты командой:

zypper install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

zypper install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

QHB в виде docker контейнера

Предлагается конфигурация: сервер СУБД «Квант-Гибрид» с расширениями и пул соединений QCP.

Получение Dockerfile и конфигураций

Создайте локальный каталог и разверните в нем скрипты docker из репозитория СУБД «Квант-Гибрид»:

wget https://repo.quantom.info/qhb/std-1/docker/qhb-docker-image.tar.gz -O - | tar -xz

Создание образов

ВНИМАНИЕ!
Потребуется docker-compose. Здесь и далее запускать docker-compose следует из-под обычного пользователя (не root). Возможно, для этого понадобится выполнить рекомендации по настройке docker.

Как правило, не требуется вручную создавать образ, т. к. он автоматически создается при первом запуске. Однако это можно сделать и явным образом:

docker-compose build

Примечание
Если возникают проблемы с построением образа, связанные с недоступностью репозитория centos, нужно проверить сетевые разрешения. Иногда, перед построением, просто помогает выполнение следующих команд:

sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --reload

Запуск контейнера

Для запуска контейнеров СУБД «Квант-Гибрид» и QCP с настройками по умолчанию выполните в текущем каталоге:

docker-compose up

Прервать выполнение можно с помощью Ctrl+C или же выполнив из другой консоли в текущем каталоге:

docker-compose down

Для запуска контейнеров в режиме «демона» выполните:

docker-compose up -d

При первом запуске база данных будет автоматически инициализирована.

Посмотреть записи журнала можно с помощью команды:

docker-compose logs

По умолчанию база данных сохраняется на диске в каталоге qhb/pgdata.

Остановка контейнеров

Если контейнеры были запущены посредством docker-compose up, то для остановки достаточно нажать Ctrl+C. Если же контейнеры были запущены в режиме «демона», т. е. посредством docker-compose up -d, то для их остановки выполните (из любой консоли) в текущем каталоге docker-compose down. Эту команду также можно выполнять и при остановленных контейнерах.

Настройка QCP

По умолчанию сервис QCP выключен. Для его запуска необходимо в файле docker-compose.yaml убрать (закомментировать) запись

entrypoint: ["echo", "Service qcp disabled"]

из раздела services: -> qcp:, а также настроить сервис, в частности, параметры подключения к серверу.

Настройка QCP осуществляется посредством редактирования файла ./qcp/config.yaml.

Обязательно замените в разделе servers:

%USER% — на имя пользователя, от имени которого запускается docker-compose

%DATABASE% — на имя базы данных; в простейшем случае база создается с именем, совпадающим с именем пользователя.

В дальнейшем, если вы создадите другую базу данных, возможно, этот параметр потребуется изменить. По умолчанию сервис QCP работает на порту 8080, при необходимости переопределите этот параметр. Обратите внимание, что настройки применяются только при запуске.

Отредактируйте файл docker-compose.yaml следующим образом:

В разделе services: -> qhb: -> volumes: замените

- ./qhb/pgdata:/qhb-data/

на

- /путь/до/pgdata:/qhb-data/

где /путь/до/pgdata — абсолютный путь до каталога базы данных с существующей базой данных.

В разделе services: -> qhb: -> environment: замените - USER на - USER=USERNAME.

Запустите контейнер с помощью команды

docker-compose -e USER="USERNAME" up

где USERNAME — имя пользователя, владеющего каталогом /путь/до/pgdata.

Пример отредактированного файла docker-compose.yaml:

version: "3.3"
services:
  qhb:
    build: qhb
    image: qhb
    network_mode: "host"
    environment:
      - USER=qhb
    volumes:
      - ./qhb/pgdata:/qhb-data/

  qcp:
    build: qcp
    image: qcp
    network_mode: "host"
    entrypoint: ["echo", "Service qcp disabled"]  # Закомментируйте эту строку, если будете использовать QCP
    volumes:
      - ./qcp/config.yaml:/config.yaml:ro

Обновление уже установленных пакетов

ВНИМАНИЕ!
Следует заметить, что при обновлении версии QHB необходимо позаботиться о миграции имеющихся баз данных, поскольку при смене версии может также меняться формат файлов базы данных. Обратитесь к разделу Установка с обновлением. О том, что в новой версии QHB происходит изменение формата файлов базы данных, можно узнать в разделе документации Замечания к релизу.

Если QHB уже установлена, ее обновления можно устанавливать с помощью пакетных менеджеров, имеющихся в ваших дистрибутивах операционных систем.

Примечание
В любом случае рекомендуется сделать бэкап исполняемых файлов СУБД перед началом процедуры обновления и сохранить резервную копию до момента ее окончания.

Как правило, для этого достаточно выполнить команду:

(на примере Fedora)

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

dnf update

В этом случае обновятся все пакеты.

Или с указанием конкретных пакетов:

dnf update qhb-core qhb-contrib qcp qdl qdlm qbackup metricsd qhb-serial qhb-license-bin

Иногда для обновления метаданных полезно обновить информацию о пакетах:

dnf clean all

Краткие инструкции

В данном разделе вашему вниманию предоставляются краткие инструкции по различным задачам при использовании QHB.

Они не описывают во всех подробностях различные тонкости и параметры, но представляют собой примерные, ознакомительные и наиболее употребительные сценарии.

Пошаговая инструкция по начальной загрузке, установке и запуску

Краткая инструкция по обновлению

Краткая инструкция по миграции кластера баз данных в QHB

Запуск и остановка сервера с помощью сервиса systemd

Пример настройки синхронной реплики

Пример настройки логирования

Установка защищенной базы данных

Работа с модулями

Инструкция по установке и использованию QCP

Работа сервера метрик в режиме разделенного коллектора и агрегатора

Работа с 1С

Инструкция по установке QHB под 1С

Конфигурирование QHB под 1С

Резервное копирование QHB под 1С

Пошаговая инструкция по начальной загрузке, установке и запуску

В этом разделе описывается сценарий установки СУБД «Квант-Гибрид» в первый раз или установки и обновления при начальной инициализации кластера баз данных, если есть возможность произвести резервное копирование и восстановление данных во вновь создаваемый кластер.

ВНИМАНИЕ!
В случаях, когда

необходимо обновление или миграция (бинарная) с других СУБД или с младших версий СУБД «Квант-Гибрид»;

каталог базы данных уже существует и не может быть создан заново;

необходимо сохранить данные уже используемой базы данных в бинарном виде

обратитесь к разделу документации Краткая инструкция по обновлению.
Также настоятельно рекомендуется использовать процедуры Резервного копирования и восстановления.

Загрузка и установка бинарных пакетов

Загрузка и установка бинарных пакетов производится из репозитория СУБД «Квант-Гибрид». Обратитесь к разделу документации Поддерживаемые платформы для уточнения, какие именно платформы поддерживаются СУБД «Квант-Гибрид».

Примечание
При установке команды должны выполняться от пользователя с правами суперпользователя. Если пользователь не обладает правами суперпользователя, выполните в терминале следующую команду (понадобится ввести пароль суперпользователя):

su -

или временно повысьте привилегии пользователя до суперпользователя командой

sudo -i

Для Centos 7 и 8

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Установите утилиту config-manager для управления конфигурацией репозиториев:

dnf install dnf-plugin-config-manager

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

rpm --import https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb

Установите дополнительный репозиторий epel-release:

yum -y install epel-release

и далее выполните для CentOS 7:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/centos/7/x86_64/qhb.repo

Для установки qhb-patroni и postgis требуется добавить репозиторий postgresql:

 yum -y -q install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

Для CentOS 8 выполните:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/centos/8/x86_64/qhb.repo

Примечание
Для данной ОС могут быть проблемы с установкой СУБД «Квант-Гибрид» и его расширений из-за отсутствия доступных репозиториев. Поддержка данной ОС может быть исключена в следующем релизе.

Для установки qhb-patroni и postgis требуется добавить репозиторий postgresql:

 yum -y -q install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm

Для CentOS 8 при необходимости установить расширение postgis требуются дополнительные репозитории:

dnf config-manager --add-repo https://repo.quantom.info/qhb/additional-package/centos/8/qhb-add.repo
dnf config-manager --set-enabled powertools

Установите бинарные пакеты командой:

dnf install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

dnf install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

См. также: Состав поставки, Страница загрузки

Для РЕД ОС 7

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Установите утилиту config-manager для управления конфигурацией репозиториев:

dnf install dnf-plugin-config-manager

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

rpm --import https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb

и далее выполните для РЕД ОС 7:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/redos7/x86_64/qhb.repo

Установите бинарные пакеты командой:

dnf install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

dnf install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

См. также: Состав поставки, Страница загрузки

Для Альт 8 СП

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Примечание
СУБД QHB поддерживает ОС Альт 8 СП только версии ядра 5.10.

Для установки потребуются дополнительные библиотеки libicu и libgostcrypto. Также, возможно, потребуется установка или обновление библиотеки glibc до версии 2.34 или выше; заранее проверьте версию вашей библиотеки.

Скачайте библиотеку (подходит версия 60.3-2) в любую локальную папку:

wget -c http://mirror.centos.org/centos/8/BaseOS/x86_64/os/Packages/libicu-60.3-2.el8_1.x86_64.rpm

Скачайте библиотеку libgostcrypto:

wget -c -r -l1 -A '*.rpm' -nd -np https://repo.quantom.info/qhb/std-1/centos/8/x86_64/libgostcrypto.0.6-0.6.1-1.x86_64.rpm

ВНИМАНИЕ!
СУБД QHB для ОС Альт 8 СП поставляется на дисках. Для получения обратитесь по адресу qhb.support@quantom.info.

Скопируйте с диска все бинарные пакеты в локальную папку и установите командой, например:

apt-get install *.rpm

Вы можете установить не все пакеты QHB, а только необходимые вам. В этом случае вы можете перечислить в команде нужные вам пакеты rpm через пробел. Выбирайте последние версии из rpm пакетов, которые скачались.

Библиотека libicu и пакет qhb-core — обязательны.

В результате установки пакета ядра QHB будет создан пользователь qhb, ядро СУБД QHB по умолчанию будет установлено в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

См. также: Состав поставки, Страница загрузки

Для Альт Сервер 9

Примечание
Обратите внимание, что для СУБД «Квант-Гибрид» версии 1.5.1 на сервере под управлением OS Alt Server 9 должна быть установлена библиотека GLIBC версии не ниже 2.28.

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Для установки потребуется дополнительная библиотека libicu. Также, возможно, потребуется установка или обновление библиотеки glibc.

Скачайте библиотеку (подходит версия 60.3-2) в любую локальную папку:

wget -c http://mirror.centos.org/centos/8/BaseOS/x86_64/os/Packages/libicu-60.3-2.el8_1.x86_64.rpm

Скачайте в эту же папку пакеты программного продукта СУБД «Квант-Гибрид» из репозитория командой:

wget -c -r -l1 -A '*.rpm' -nd -np https://repo.quantom.info/qhb/std-1/centos/8/x86_64/

Обновите описания пакетов:

apt-get update

Находясь в этой же локальной папке, установите бинарные пакеты командой, например:

apt-get install *.rpm

Вы можете установить не все пакеты QHB, а только необходимые вам. В этом случае вы можете перечислить в команде нужные вам пакеты rpm через пробел. Выбирайте последние версии из rpm пакетов, которые скачались.

Библиотека libicu и пакет qhb-core — обязательны.

В результате установки пакета ядра QHB будет создан пользователь qhb, ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

См. также: Состав поставки, Страница загрузки

Для Fedora

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Установить утилиту config-manager для управления конфигурацией репозиториев:

dnf install dnf-plugin-config-manager

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

rpm --import https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb

и далее, для Fedora 36:

Примечание
Для данной ОС могут быть проблемы с установкой СУБД «Квант-Гибрид» и его расширений из-за отсутствия доступных репозиториев. Поддержка данной ОС может быть исключена в следующем релизе.

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/fedora/36/x86_64/qhb.repo

для Fedora 37:

Примечание
Для данной ОС могут быть проблемы с установкой СУБД «Квант-Гибрид» и его расширений из-за отсутствия доступных репозиториев. Поддержка данной ОС может быть исключена в следующем релизе.

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/fedora/37/x86_64/qhb.repo

для Fedora 38:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/fedora/38/x86_64/qhb.repo

или для Fedora 39:

dnf config-manager --add-repo https://repo.quantom.info/qhb/std-1/fedora/39/x86_64/qhb.repo

Установите бинарные пакеты командой:

dnf install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

dnf install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

См. также: Состав поставки, Страница загрузки

Для Debian и Astra

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Для подключения к репозиторию выполните следующую команду:

apt install gnupg2 apt-transport-https wget

Для корректной работы репозитория СУБД «Квант-Гибрид» в файле конфигурации репозиториев /etc/apt/sources.list должна быть добавлена запись

для Debian 10

deb [arch=amd64] https://repo.quantom.info/qhb/std-1/debian/10 stretch main

для Astra Linux Special Edition «Смоленск» 1.7

deb [arch=amd64] https://repo.quantom.info/qhb/std-1/astra-smolensk/1.7/  stretch main

Примечание
Обратите внимание, что для исключения из OS Astra Linux отозванных сертификатов безопасности следует выполнить очистку согласно инструкции по прекращению использования нежелательного сертификата.

Для Astra требуется установка корневых сертификатов. Установите их с помощью команды:

apt-get install ca-certificates

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

wget -qO - https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb --no-check-certificate | apt-key add -

Далее выполните команды:

для Debian 10

echo 'deb [arch=amd64] https://repo.quantom.info/qhb/std-1/debian/10 stretch main' >> /etc/apt/sources.list

для Astra Linux Special Edition «Смоленск» 1.7

echo 'deb [arch=amd64] https://repo.quantom.info/qhb/std-1/astra-smolensk/1.7/ stretch main' >> /etc/apt/sources.list

и

apt update

Примечание
В случае появления ошибки команды apt update репозитория QHB для Астры 1.7 Ошб:6 https://repo.quantom.info/qhb/std-1/debian stretch InRelease Следующие подписи не могут быть проверены, так как недоступен открытый ключ: NO_PUBKEY 67A97AD5112F6828 надо выполнить команду импорта ключа репозитория QHB для Астры 1.7 apt-key adv --keyserver https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb --recv-keys 67A97AD5112F6828

Установите бинарные пакеты командой:

apt install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

apt install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

См. также: Состав поставки, Страница загрузки

Для Ubuntu

Примечание
Обратите внимание, что команды должны выполняться от пользователя с правами суперпользователя.

Для подключения к репозиторию выполните следующую команду:

apt install gnupg2 apt-transport-https wget

Для корректной работы репозитория СУБД «Квант-Гибрид» в файле конфигурации репозиториев /etc/apt/sources.list должна быть добавлена соответствующая запись.

для Ubuntu 18.04

echo 'deb https://repo.quantom.info/qhb/std-1/ubuntu/18 stretch main' >> /etc/apt/sources.list

для Ubuntu 22.04

echo 'deb https://repo.quantom.info/qhb/std-1/ubuntu/22 stretch main' >> /etc/apt/sources.list

Установите репозиторий программного продукта СУБД «Квант-Гибрид» командой:

wget -qO - https://repo.quantom.info/qhb/keys/RPM-GPG-KEY-qhb --no-check-certificate | apt-key add -

Далее выполните:

apt update

Установите бинарные пакеты командой:

apt install qhb-core [qhb-contrib] [qcp] [qdl] [qdlm] [qbackup] [metricsd] [qhb-serial] [qhb-license-bin] [...]

Например:

apt install qhb-core

В результате установки пакета ядра QHB будет создан пользователь qhb. Ядро QHB по умолчанию установится в каталог /usr/local/qhb. Многие утилиты QHB устанавливаются в каталог /usr/local/qhb/bin. Если этот каталог указать в переменной среды $PATH, это облегчит их запуск.

См. также: Состав поставки, Страница загрузки

В виде docker контейнера

Предлагается конфигурация: сервер СУБД «Квант-Гибрид» с расширениями и пул соединений QCP.

Получение Dockerfile и конфигураций

Создайте локальный каталог и разверните в нем скрипты docker из репозитория СУБД «Квант-Гибрид»:

wget https://repo.quantom.info/qhb/std-1/docker/qhb-docker-image.tar.gz -O - | tar -xz

Создание образов

ВНИМАНИЕ!
Потребуется docker-compose. Здесь и далее запускать docker-compose следует из-под обычного пользователя (не root). Возможно, для этого понадобится выполнить рекомендации по настройке docker.

Как правило, не требуется вручную создавать образ, т. к. он автоматически создается при первом запуске. Однако это можно сделать и явным образом:

docker-compose build

Примечание
Если возникают проблемы с построением образа, связанные с недоступностью репозитория centos, нужно проверить сетевые разрешения. Иногда, перед построением, просто помогает выполнение следующих команд:

sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --reload

Запуск контейнера

Для запуска контейнеров СУБД «Квант-Гибрид» и QCP с настройками по умолчанию выполните в текущем каталоге:

docker-compose up

Прервать выполнение можно с помощью Ctrl+C или же выполнив из другой консоли в текущем каталоге:

docker-compose down

Для запуска контейнеров в режиме «демона» выполните:

docker-compose up -d

При первом запуске база данных будет автоматически инициализирована.

Посмотреть записи журнала можно с помощью команды:

docker-compose logs

По умолчанию база данных сохраняется на диске в каталоге qhb/pgdata.

Остановка контейнеров

Если контейнеры были запущены посредством docker-compose up, то для остановки достаточно нажать Ctrl+C. Если же контейнеры были запущены в режиме «демона», т. е. посредством docker-compose up -d, то для их остановки выполните (из любой консоли) в текущем каталоге docker-compose down. Эту команду также можно выполнять и при остановленных контейнерах.

Настройка QCP

По умолчанию сервис QCP выключен. Для его запуска необходимо в файле docker-compose.yaml убрать (закомментировать) запись

entrypoint: ["echo", "Service qcp disabled"]

из раздела services: -> qcp:, а также настроить сервис, в частности, параметры подключения к серверу.

Настройка QCP осуществляется посредством редактирования файла ./qcp/config.yaml.

Обязательно замените в разделе servers:

%USER% — на имя пользователя, от имени которого запускается docker-compose

%DATABASE% — на имя базы данных; в простейшем случае база создается с именем, совпадающим с именем пользователя.

В дальнейшем, если вы создадите другую базу данных, возможно, этот параметр потребуется изменить. По умолчанию сервис QCP работает на порту 8080, при необходимости переопределите этот параметр. Обратите внимание, что настройки применяются только при запуске.

Отредактируйте файл docker-compose.yaml следующим образом:

В разделе services: -> qhb: -> volumes: замените

- ./qhb/pgdata:/qhb-data/

на

- /путь/до/pgdata:/qhb-data/

где /путь/до/pgdata — абсолютный путь до каталога базы данных с существующей базой данных.

В разделе services: -> qhb: -> environment: замените - USER на - USER=USERNAME.

Запустите контейнер с помощью команды

docker-compose -e USER="USERNAME" up

где USERNAME — имя пользователя, владеющего каталогом /путь/до/pgdata.

Пример отредактированного файла docker-compose.yaml:

version: "3.3"
services:
  qhb:
    build: qhb
    image: qhb
    network_mode: "host"
    environment:
      - USER=qhb
    volumes:
      - ./qhb/pgdata:/qhb-data/

  qcp:
    build: qcp
    image: qcp
    network_mode: "host"
    entrypoint: ["echo", "Service qcp disabled"]  # Закомментируйте эту строку, если будете использовать QCP
    volumes:
      - ./qcp/config.yaml:/config.yaml:ro

В данном сценарии уже подготовлены и при запуске будут проделаны шаги по инициализации кластера, запуску сервера и созданию базы данных.

Поэтому следующие шаги в данной инструкции можно пропустить. Может оказаться полезным раздел Доступ к базе данных.

Инициализация кластера баз данных

Создайте каталог для размещения базы данных командой

sudo mkdir <путь/имя_каталога>

Например:

sudo mkdir /var/lib/qhb/data

Разрешите доступ к нему пользователю qhb командой

sudo chown qhb <путь/имя_каталога>

В нашем случае:

sudo chown qhb /var/lib/qhb/data

Далее рекомендуется переключиться на пользователя qhb и выполнять команды от его имени. В ином случае необходимо использовать sudo -u qhb для всех последующих команд, если вы работаете от пользователя с правами sudo.

Инициализируйте кластер базы данных при помощи утилиты qhb_bootstrap или initdb.

<путь_к_бинарным_утилитам QHB>/qhb_bootstrap -D <путь/имя_каталога> -U qhb

В нашем случае:

/usr/local/qhb/bin/qhb_bootstrap -D /var/lib/qhb/data -U qhb

или, если установлена переменная среды $PATH:

qhb_bootstrap -D /var/lib/qhb/data -U qhb

В дальнейшем данный каталог будет использоваться при запуске и остановке сервера QHB. Его можно указать в переменной среды $PGDATA, что облегчит запуск следующих утилит — можно будет не указывать каталог в параметре -D.

См. также: Создание кластера баз данных, initdb, qhb_bootstrap

Запуск и остановка сервера

Рекомендуется переключиться на пользователя qhb и выполнять команды от его имени. В ином случае необходимо использовать sudo -u qhb для всех последующих команд, если вы работаете от пользователя с правами sudo.

Для запуска сервера используйте команду:

<путь_к_бинарным_утилитам QHB>/qhb_ctl -D <путь/имя_каталога> start

В нашем случае:

/usr/local/qhb/bin/qhb_ctl -D /var/lib/qhb/data start

Или, если установлены переменные среды $PGDATA и $PATH:

qhb_ctl start

Для остановки сервера используйте команду:

<путь_к_бинарным_утилитам QHB>/qhb_ctl -D <путь/имя_каталога> stop

В нашем случае:

/usr/local/qhb/bin/qhb_ctl -D /var/lib/qhb/data stop

Или, если установлена переменная среды $PGDATA:

qhb_ctl stop

См. также: Запуск сервера баз данных, Завершение работы сервера

Запуск и остановка сервера с помощью сервиса systemd

Примечание
Здесь описывается запуск сервиса QHB через подсистему инициализации и управления службами systemd. Убедитесь, что она установлена в вашем дистрибутиве Linux.

При установке из пакета rpm имеется возможность использовать сервис systemd для автоматического запуска сервера баз данных при старте системы. Для этого необходимо убедиться в наличии предварительно установленного скрипта запуска сервиса. Если необходимо поменять расположение каталога базы данных, задаваемое по умолчанию, выполните:

sudo systemctl edit qhb

Далее в открывшемся редакторе (файл изначально пуст) укажите нужный путь:

[Service]
Environment=PGDATA=/opt/qhb/data

Сохраните изменения и завершите работу редактора.

Перестройте сервис и, если необходимо, разрешите ему запускаться при перезагрузке операционной системы:

sudo systemctl daemon-reload
sudo systemctl enable qhb

В дальнейшем, после начальной настройки (см. Начало работы), запускать сервис нужно будет командой:

sudo systemctl start qhb

А останавливать командой:

sudo systemctl stop qhb

Создание базы данных

Первый способ проверить, есть ли у вас доступ к серверу баз данных, — попытаться создать базу данных. Работающий сервер QHB способен управлять многими базами данных. Как правило, для каждого проекта или пользователя используется отдельная база.

Возможно, ваш администратор уже создал для вас базу данных. В таком случае этот этап можно пропустить и перейти к следующему разделу.

Чтобы создать новую базу данных, в этом примере названную mydb, используется следующая команда:

$ createdb mydb -h localhost

Если в ответ на это не появляется никаких сообщений, значит, этот этап был успешным и можно пропустить оставшуюся часть этого раздела.

Если вы видите сообщение вида:

createdb: command not found
-- команда не найдена

значит, QHB не была установлена должным образом. Либо она вообще не была установлена, либо не была включена в путь поиска вашей оболочки. Тогда попробуйте вызвать команду по абсолютному пути:

$ /var/lib/qhb/data/bin/createdb mydb

Ваш путь может быть другим. Обратитесь к вашему администратору или ознакомьтесь с инструкциями по установке, чтобы исправить ситуацию.

Возможен и другой ответ:

createdb: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: No such file or directory
        Is the server running locally and accepting connections on that socket?
-- ошибка: не удалось подключиться к серверу через сокет "/tmp/.s.PGSQL.5432": Данный файл или каталог не существует
     -- Этот сервер действительно работает локально и принимает соединения через этот сокет?

Это означает, что сервер не был запущен или был запущен не там, где утилита createdb собиралась к нему подключиться. Опять же, обратитесь к инструкциям по установке или проконсультируйтесь с администратором.

Возможен и другой ответ:

createdb: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: FATAL:  role "joe" does not exist
-- ошибка: не удалось подключиться к серверу через сокет "/tmp/.s.PGSQL.5432": КРИТИЧНО:  роль "joe" не существует

где упоминается ваше имя пользователя. Это произойдет, если администратор не создал для вас учетную запись пользователя QHB. (Учетные записи пользователей QHB отличаются от учетных записей пользователей операционной системы). Если вы администратор, обратитесь к главе Роли в базе данных за помощью в создании учетных записей. Вам нужно будет стать пользователем операционной системы, под которым была установлена QHB (обычно qhb), чтобы создать первую учетную запись пользователя. Возможно также, что вам было присвоено имя пользователя QHB, отличающееся от имени пользователя вашей операционной системы; в этом случае следует использовать ключ -U или установить переменную среды PGUSER, чтобы указать свое имя пользователя в QHB.

Если у вас есть учетная запись пользователя, но у нее нет прав, необходимых для создания базы данных, вы увидите следующее:

createdb: database creation failed: ERROR:  permission denied to create database
-- ошибка: создать базу данных не удалось: ОШИБКА:  нет прав на создание базы данных

Не каждый пользователь имеет право создавать новые базы данных. Если QHB отказывается создавать для вас базы данных, ваш администратор должен предоставить вам разрешение на их создание. В таком случае обратитесь к нему. Если вы установили QHB самостоятельно, то для целей данного руководства следует войти в систему под учетной записью пользователя, под которой вы запустили сервер.¹

Кроме того, можно создавать базы данных с другими именами. QHB позволяет создавать любое количество баз данных. Имена баз данных должны начинаться с буквенного символа и иметь длину не более 63 байт. Удобный выбор — создать базу данных с вашим текущим именем пользователя. Многие программы предполагают такое имя по умолчанию, что может помочь вам сэкономить время на ввод текста. Чтобы создать эту базу данных, просто напишите:

$ createdb

Если вы больше не хотите использовать свою базу данных, ее можно удалить. Например, если вы являетесь владельцем (создателем) базы данных mydb, ее можно уничтожить с помощью следующей команды:

$ dropdb mydb

(Для этой команды имя базы данных не совпадает по умолчанию с именем учетной записи пользователя. Вы всегда должны указывать его.) Это действие физически удаляет все файлы, связанные с базой данных, и не может быть отменено, так что эту операцию следует выполнять крайне продуманно.

Больше информации по командам createdb и dropdb можно найти в соответствующих разделах.

¹

В качестве объяснения того, почему это работает: имена пользователей QHB отделены от учетных записей пользователей операционной системы. Подключаясь к базе данных, вы можете выбрать, под каким именем пользователя QHB подключаться; если вы этого не сделаете, по умолчанию будет использоваться имя вашей текущей учетной записи в операционной системе. Таким образом, в QHB всегда будет учетная запись пользователя, имя которого совпадает с именем пользователя операционной системы, запустившего сервер, причем этот пользователь всегда имеет разрешение на создание баз данных. Вместо входа в систему под этим пользователем можно везде указать параметр -U, чтобы передать имя пользователя QHB для подключения.

Доступ к базе данных

Создав базу данных, вы можете получить к ней доступ одним из следующих способов:

С помощью интерактивного терминала QHB, называемого psql, который позволяет в интерактивном режиме вводить, редактировать и выполнять команды SQL.

С использованием существующего графического инструмента внешнего интерфейса, например pgAdmin, DBeaver или офисного пакета с поддержкой ODBC или JDBC. Эти возможности не рассматриваются в этом руководстве.

Написав собственное приложение с использованием одной из нескольких доступных языковых привязок.

Испытать примеры из этого руководства можно, запустив psql. Это можно сделать для базы данных mydb, набрав команду:

$ psql -h localhost -d mydb

Если вы не укажете имя базы данных, по умолчанию будет использоваться имя вашей учетной записи. Мы уже сталкивались с этой схемой в предыдущем разделе при работе с createdb.

Внутри psql вы увидите следующее сообщение:

psql - Interactive terminal qhb (1.5.2)
Enter "\help" in order to get help

mydb(username)=#

Это будет означать, что вы являетесь суперпользователем базы данных, что наиболее вероятно, если вы сами установили экземпляр QHB. Суперпользователь имеет неограниченный доступ к базе данных; впрочем, для целей данного руководства это не имеет значения.

Если вы столкнулись с проблемами при запуске psql, вернитесь к предыдущему разделу. Диагностики createdb и psql схожи, и если первая сработала, то вторая тоже должна сработать.

Последняя строка, выведенная psql, является приглашением и показывает, что psql ожидает ввод, то есть вы можете вводить в рабочее пространство запросы SQL. Попробуйте эти команды:

mydb(username)=# select version();
version
----------
QHB 1.5.2
(1 row)

mydb(username)=# SELECT current_date;
    date
------------
 2023-10-07

mydb(username)=# SELECT 2 + 2;
 ?column?
----------
        4
(1 row)

Программа psql имеет ряд внутренних команд, которые не являются командами SQL. Они начинаются с символа обратного слэша «\». Например, можно получить справку по синтаксису различных команд SQL в QHB, написав:

mydb(username)=# \h

Чтобы выйти из psql, напишите:

mydb(username)=# \q

и psql завершит свою работу и вернет вас в командную оболочку. (Чтобы узнать больше о внутренних командах, введите \? в приглашении psql.) Все возможности psql освещаются в соответствующем разделе. В этом руководстве мы не будем использовать эти функциональные возможности явно, но вы можете самостоятельно применять их по мере необходимости.

Краткая инструкция по обновлению

В этом разделе описывается базовый сценарий обновления QHB с помощью утилиты qhb_upgrade. Более подробную информацию см. на ее справочной странице.

В случае если ваш сервер уже остановлен, первый шаг можно пропустить.

Для остановки сервера баз данных введите:

qhb_ctl -D $PGDATA stop

или, если он запускался через сервис:

sudo systemctl stop qhb

Примечание
Файлы конфигурации и данных, используемые СУБД, обычно хранятся в одном каталоге, называемом PGDATA (аналогично имени переменной среды). В наших примерах мы будем использовать /opt/qhb/data.old как путь к данным старого каталога кластера базы данных и /opt/qhb/data как путь к данным нового каталога кластера базы данных. Каталог /usr/local/qhb.old/bin — это место хранения исполняемых файлов старой версии QHB, а каталог /usr/local/qhb/bin — это место хранения исполняемых файлов новой версии QHB.

Переместите старый кластер:

sudo mv /opt/qhb/data /opt/qhb/data.old

Переместите текущий установочный каталог QHB, чтобы он не мешал новой установке QHB:

sudo mv /usr/local/qhb /usr/local/qhb.old

Установите новые пакеты QHB командой от пользователя с правами суперпользователя:

yum [ install | upgrade ] <путь до каталога c пакетами/названия пакетов>

Примечание
В зависимости от ОС команда для установки может меняться. Если вы устанавливаете пакеты из репозитория QHB, можно воспользоваться пошаговой инструкцией по начальной загрузке, установке и запуску.

Вновь создайте папку для базы данных:

sudo mkdir -p /opt/qhb/data

Разрешите пользователю qhb доступ к ней:

sudo chown -R qhb /opt/qhb/data

Далее рекомендуется переключиться на пользователя qhb и выполнять команды от его имени. В ином случае необходимо использовать sudo -u qhb для всех последующих команд, если вы работаете от пользователя с правами sudo.

Инициализируйте кластер базы данных командой:

qhb_bootstrap -D /opt/qhb/data

Выполните обновление QHB через утилиту qhb_upgrade:

qhb_upgrade \
  --old-bindir=/usr/local/qhb.old/bin/ \
  --new-bindir=/usr/local/qhb/bin/ \
  --old-datadir=/opt/qhb/data.old \
  --new-datadir=/opt/qhb/data --verbose

Примечание
В случае ошибки обновления необходимо убедиться, что пользователь qhb имеет права доступа ко всем указанным каталогам.
Также пользователь qhb должен обладать правами на запись в той директории, из которой происходит запуск команды qhb_upgrade.

Ожидаемый результат:

Upgrade Complete

Для запуска сервера баз данных введите:

qhb_ctl -D /opt/qhb/data start

или

sudo systemctl start qhb

Миграция кластера баз данных в QHB

В настоящее время поддерживается миграция в QHB, только начиная с версии PostgreSQL 12. Если используется более ранняя версия PostgreSQL, необходимо сначала выполнить обновление до версии 12. Также поддерживается миграция со старых версий QHB. Данная инструкция охватывает только основные моменты миграции, на которые нужно обратить внимание.

В данном примере рассматривается вариант миграции с PostgreSQL версии 12 на QHB текущей версии. Более подробно процедура миграции описывается на справочной странице утилиты qhb_upgrade.

Подготовка к миграции

Пример исходных данных

Данные PostgreSQL
Каталог с запускаемыми файлами: /usr/pgsql-12/bin
Каталог с данными: /var/lib/pgsql/12/data/
Суперпользователь: postgres
Порт: 5432
Служба: postgresql-12

Данные QHB
Каталог с запускаемыми файлами: /usr/local/qhb/bin
Каталог с данными: /opt/qhb/data
Суперпользователь: qhb
Порт: 5432
Служба: qhb

Если необходимо, чтобы сервисы postgresql-12 и qhb работали одновременно (например, в целях тестирования работоспособности QHB), нужно изменить номер порта для QHB, т. е. изменить значение параметра port в файле параметров qhb.conf, например, на 5433. Для обновления через qhb_upgrade это не является обязательным, так как сервисы запускаются по очереди.

ВНИМАНИЕ!
Если при установке QHB на машину, где уже расположена PostgreSQL, система выдает оповещение о необходимости откатить версии некоторых пакетов, которые были установлены PostgreSQL (например, llvm или llvm-libs), то рекомендуется отменить установку и затем установить пакет llvm-compat-libs-12.0.1 отдельно. Только после успешной установки пакета можно повторить установку QHB (при этом установка больше не будет требовать откатить версию, так как будет установлено сразу 2 версии llvm).

Языковые настройки

Заранее уточните значения параметров с языковыми настройками нового кластера баз данных QHB (locale, lc-collate и lc-ctype — локаль, порядок сортировки строк и классификация символов соответственно). Как правило, эти параметры имеют одинаковое значение и должны соответствовать настройкам исходного кластера баз данных PostgreSQL. Выяснить значения этих настроек в PostgreSQL можно через команду \l в psql, выведя список баз данных с их параметрами. В данном примере значения параметров одинаковы и равны en_US.UTF-8.

Переименование в кластере баз PostgreSQL суперпользователя postgres в qhb

Утилита qhb_upgrade должна работать от имени одного и того же пользователя как с PostgreSQL, так и с QHB. Рекомендуется использовать в качестве такого пользователя qhb. Так как суперпользователь qhb по умолчанию отсутствует в базе PostgreSQL, следует переименовать пользователя postgres в qhb. Для этого нужно зайти в PostgreSQL от имени другого суперпользователя. Если такого пользователя не существует, можно его создать, а после переименования пользователя postgres — удалить.

Зайдем от имени суперпользователя postgres:

/usr/pgsql-12/bin/psql -d postgres -U postgres

Создадим нового суперпользователя temp_su, зайдем от его имени, переименуем пользователя postgres в qhb, подключимся под qhb и удалим уже ненужного пользователя temp_su:

CREATE ROLE temp_su LOGIN SUPERUSER;
\c postgres temp_su
ALTER USER postgres RENAME TO qhb;
\c postgres qhb
DROP ROLE temp_su;

Создание тестовой базы данных и таблицы в ней

Для того чтобы наглядно удостовериться в успешном переносе данных, особенно при тестовом прогоне, можно создать в PostgreSQL отдельную базу данных и таблицу в ней, заполнив ее хотя бы одной строкой.

CREATE DATABASE test;
\c test
CREATE TABLE test (id int, txt text);
INSERT INTO test VALUES(1, "test1");

Остановка работы сервисов и смена владельца каталога QHB

Остановка сервисов postgres-12 и qhb (если сервис qhb был запущен):

sudo systemctl stop postgresql-12
sudo systemctl stop qhb

Для того чтобы утилита qhb_upgrade нормально отработала от имени системного пользователя qhb, поменяем владельца для каталога данных PostgreSQL на qhb. Для каталогов верхнего уровня необходимо выдать на время права, позволяющие другим пользователям заходить в них и читать данные.

sudo chmod o+rx /var/lib/pgsql
sudo chmod o+rx /var/lib/pgsql/12
sudo chown -R qhb:qhb /var/lib/pgsql/12/data

Инициализация кластера баз данных QHB

Далее рекомендуется переключиться на системного пользователя qhb и выполнять последующие команды от его имени:

sudo su - qhb

Очистка каталога данных QHB

Здесь предполагается, что каталог /opt/qhb/data является корневым каталогом данных QHB, выбранном при установке и настройке базы данных. Если каталог не является к этому моменту пустым, необходимо его полностью очистить. В противном случае миграция данных не запустится.

rm -rf /opt/qhb/data/*

Инициализация нового кластера

По умолчанию в кластере баз данных создается суперпользователь с именем пользователя операционной системы, запускавшего установку, т. е. qhb.

/usr/local/qhb/bin/qhb_bootstrap \
--locale=en_US.UTF-8 \
--lc-collate=en_US.UTF-8 \
--lc-ctype=en_US.UTF-8 \
--encoding=UTF8 \
--data-dir /opt/qhb/data

Выполнение проверки возможности миграции базы с PostgreSQL на QHB

Запускать утилиту qhb_upgrade лучше из отдельного временного каталога, например /tmp/qhb_upgrade. В каталоге будут сохраняться журналы записи работы утилиты, в частности, файл qhb_upgrade_internal.log, в который записываются результаты проверки возможности миграции или сообщения об ошибках при выполнении миграции. Желательно сделать этот каталог недоступным для других пользователей.

mkdir /tmp/qhb_upgrade
chmod 700 /tmp/qhb_upgrade
cd /tmp/qhb_upgrade

ВНИМАНИЕ!
Версия qhb_upgrade при миграции из PostgreSQL в QHB должна быть не меньше 1.3.1. Проверка выполняется через выполнение /usr/local/qhb/bin/qhb_upgrade --version.

Следующая команда выполняет проверку возможности миграции, для этого используется параметр --check.

/usr/local/qhb/bin/qhb_upgrade \
--new-bindir=/usr/local/qhb/bin \
--new-datadir=/opt/qhb/data \
--old-bindir=/usr/pgsql-12/bin \
--old-datadir=/var/lib/pgsql/12/data \
-U qhb -v --check

Результаты проверки выведутся на экран, а также в файл qhb_upgrade_internal.log. В случае успешного завершения проверки на экран предпоследней строкой выведется сообщение «Clusters are compatible» (кластеры совместимы).

Выполнение миграции

Для выполнения миграции в предыдущей команде необходимо убрать параметр --check.

/usr/local/qhb/bin/qhb_upgrade \
--new-bindir=/usr/local/qhb/bin \
--new-datadir=/opt/qhb/data \
--old-bindir=/usr/pgsql-12/bin \
--old-datadir=/var/lib/pgsql/12/data \
-U qhb -v

В случае успешной миграции на экран предпоследней строкой выведется «Upgrade Complete» (обновление завершено). Также результаты работы утилиты будут зафиксированы в файле журнала qhb_upgrade_internal.log.

Возврат исходных прав для каталога

В общем случае в каталоге /var/lib/pgsql могут быть расположены несколько кластеров баз данных. Вернем исходные права доступа для верхних каталогов и вернем прежнего владельца для каталога с данными PostgreSQL /var/lib/pgsql/12/data. Это может понадобиться в случае отмены миграции и возврата к использованию баз PostgreSQL.

sudo chmod o-rx /var/lib/pgsql
sudo chmod o-rx /var/lib/pgsql/12
sudo chown -R postgres:postgres /var/lib/pgsql/12/data

Проверка переноса данных

Предварительно должны быть отредактированы все настройки в соответствующих файлах настроек (qhb.conf, qhb_hba.conf и qhb_ident.conf).

Запуск QHB и проверка перенесенных данных:

sudo systemctl start qhb
/usr/local/qhb/bin/psql -U qhb -d test -c "select * from test"

Запуск и остановка сервера с помощью сервиса systemd

Примечание
Здесь описывается запуск сервиса QHB через подсистему инициализации и управления службами systemd. Убедитесь, что она установлена в вашем дистрибутиве Linux.

При установке из пакета rpm имеется возможность использовать сервис systemd для автоматического запуска сервера баз данных при старте системы. Для этого необходимо убедиться в наличии предварительно установленного скрипта запуска сервиса. Если необходимо поменять расположение каталога базы данных, задаваемое по умолчанию, выполните:

sudo systemctl edit qhb

Далее в открывшемся редакторе (файл изначально пуст) укажите нужный путь:

[Service]
Environment=PGDATA=/opt/qhb/data

Сохраните изменения и завершите работу редактора.

Перестройте сервис и, если необходимо, разрешите ему запускаться при перезагрузке операционной системы:

sudo systemctl daemon-reload
sudo systemctl enable qhb

В дальнейшем, после начальной настройки (см. Начало работы), запускать сервис нужно будет командой:

sudo systemctl start qhb

А останавливать командой:

sudo systemctl stop qhb

Пример настройки синхронной реплики

ВНИМАНИЕ!
В данном разделе приводится конечный пример настройки кластера баз данных с синхронной репликацией. Обратите внимание, что здесь приводятся некоторые параметры (например, IP-адреса хостов или каталоги баз данных), которые могут не соответствовать вашим.

Начальные условия

Операционная система на серверах — CentOS 7.

172.31.101.70 — IP сервера, назначенного основным (мастер).

172.31.101.71 — IP резервного сервера (реплика).

СУБД установлена из RPM пакетов (см. Страница загрузки. QHB для Centos 7 и 8).

Каталог размещения БД — PG_DATA = /u01/qhb/db.

/usr/local/qhb/ — путь установки QHB по умолчанию.

Сервисы systemd сконфигурированы с учетом вышеперечисленного.

Настройка основного сервера

Создаем пользователя QHB для репликации:

create user repluser replication password 'repluser';

Устанавливаем необходимые параметры в файле конфигурации vi /u01/qhb/db/qhb.conf:

listen_addresses = '*'
wal_level = hot_standby
max_wal_senders = 2
max_replication_slots = 2
hot_standby = on
hot_standby_feedback = on
logging_collector = on
log_directory = './log'
synchronous_commit = on
synchronous_standby_names = '*'

Устанавливаем параметры доступа к базе данных с машины резервного сервера в файле конфигурации

vi /u01/qhb/db/qhb_hba.conf

host replication repluser 172.31.101.70/32 md5
host replication repluser 172.31.101.71/32 md5

ВНИМАНИЕ!
Существующую запись host replication изменять не следует.

Производим рестарт сервиса СУБД

systemctl restart qhb && systemctl status qhb

Настройка резервного сервера

Останавливаем сервис СУБД на сервере реплики

systemctl stop qhb && systemctl status qhb

Удаляем директорию с данными базы данных

rm -rf /u01/qhb/db/* # Полное удаление данных

ВНИМАНИЕ!
Удаляя директорию, вы должны полностью осознавать необратимость данного действия и тот факт, что все данные, файлы конфигурации и записи журналов будут утеряны без возможности восстановления.

В случае если ситуация позволяет, выполняем восстановление через резервную копию.

Если объем данных в базе большой, то рекомендуется воспользоваться копией данных, при ее наличии.

Восстановление через утилиту бэкапирования запускается от системного пользователя QHB:

sudo -u qhb \#
/usr/local/qhb/bin/qhb_basebackup \#Утилита бэкапа
-h 172.31.101.70 \ # IP адрес мастер сервера
-p 5432 \ # Порт по умолчанию; рекомендуется сменить, если конфигурация была изменена
-U repluser \ # Пользователь, созданный ранее на основном сервере, с правами репликации
-D /u01/qhb/db/ \ # Директория размещения СУБД
-Fp -Xs -P -R # Набор ключей, необходимых для репликации

Для лучшего понимания следует ознакомиться с функционированием ключей -Fp, -Xs, -P и -R данной утилиты, описанных в параграфе Параметры qhb_basebackup.

Если копия делается при помощи qhb_basebackup, параметры подключения для реплики будут указаны в файле qhb.auto.conf.
Следует удостовериться в корректности заданной строки подключения.

Если копия базы данных была развернута через файл (или иной корректный способ, не указанный в данной инструкции), то до старта необходимо скорректировать несколько параметров:

запуск сервера СУБД в резервном режиме. Для этого требуется создать файл standby.signal в директории кластера базы данных (рядом с qhb.conf).

sudo -u qhb touch /u01/qhb/db/standby.signal
ls -la standby.signal
#-rw------- 1 qhb qhb     0 Nov 25 15:34 standby.signal

в файле конфигурации qhb.conf необходимо указать строку подключения к основному серверу:

primary_conninfo = 'user=repluser password=repluser host=172.31.101.70 port=5432 sslmode=disable sslcompression=0 gssencmode=disable krbsrvname=postgres target_session_attrs=any'

Примечание
Строка подключения длинная, и некоторые консольные редакторы могут применить параметр word-wrap, нарушая визуальную целостность строки.

В обоих случаях параметры СУБД в файле конфигурации будут скопированы с мастера, и следует изменить некоторые значения на соответствующие реплике.

Параметры на реплике указываются следующие:

vi /u01/qhb/db/qhb.conf

listen_addresses = '*'
logging_collector = on
log_directory = 'log' # Удостоверьтесь в существовании директории и наличии необходимых прав.

Теперь и только теперь можно запустить СУБД на втором сервере, реплике.

systemctl start qhb && systemctl status qhb

Проверка

Проверяем на мастере:

/usr/local/qhb/bin/psql -x -c "select * from pg_stat_replication"

Должен высветиться один слот:

pid              | 15566
usesysid         | 16385
usename          | repluser
application_name | walreceiver
client_addr      | 172.31.101.71
client_hostname  |
client_port      | 40516
backend_start    | 2020-11-25 09:45:22.249802-03
backend_xmin     |
state            | streaming
sent_lsn         | 0/30001F8
write_lsn        | 0/30001F8
flush_lsn        | 0/30001F8
replay_lsn       | 0/30001F8
write_lag        |
flush_lag        |
replay_lag       |
sync_priority    | 1
sync_state       | sync
                   ^^^^^^^
reply_time       | 2020-11-25 09:47:22.710523-03

Причем параметр sync_state должен иметь значение sync (репликация синхронная).

Создадим таблицу на мастере:

CREATE TABLE rtest (vname VARCHAR(40));

Заполним случайными значениями на миллион строк:

insert into rtest select substr(md5(random()::text), 0, 40) from generate_series(1,1000000);

На резервном сервере проверяем чтение из таблицы:

qhb=# select count(*) from rtest;
  count
---------
 1000000
(1 row)

Дополнительные настройки

В некоторых вариантах рекомендуется прописать явное указание слота репликации.

На реплике в файл qhb.auto.conf добавить параметр primary_slot_name = 'standby_slot2\_qhb'.

На мастере выполнить команду SQL:

SELECT pg_create_physical_replication_slot('standby_slot2_qhb');

Запрос по активным слотам репликации должен показывать на мастере нашу реплику

SELECT * FROM pg_replication_slots WHERE active=true;

См. также

Внешнее руководство с инструкциями и пояснениями, qhb_basebackup

Пример настройки протоколирования

Настройки qhb.conf:

Включить протоколирование: logging_collector = on;

Включить протоколирование в файлы журналов: log_directory = '/u01/db/log' каталог журналов должен быть определен по месту. Может быть абсолютный путь, но можно указать и вариант относительного пути ./log/;

Установить уровень протоколирования: log_min_messages = info или другой подходящий;

Параметр на сам факт протоколирования в систему: log_connections = true;

Включить протоколирование ошибочных запросов: log_min_error_statement=info;

Включить протоколирование имени хоста подключения: log_hostname = true — желательно протоколировать его в дополнение к IP-адресу по причинам безопасности или дальнейшего устранения неполадок;

Включить протоколирование действий по репликации: log_replication_commands = true. Обязательно для установки кластеров высокой доступности;

Включить протоколирование операций на СУБД (для безопасности): log_statement = 'none' # none, ddl, mod, all;

Сформировать шаблон строки журнала для записи приведенных выше параметров:

log_line_prefix = '%m [%p] '           # special values:
                                        #   %a = application name
                                        #   %u = user name
                                        #   %d = database name
                                        #   %r = remote host and port
                                        #   %h = remote host
                                        #   %p = process ID
                                        #   %t = timestamp without milliseconds
                                        #   %m = timestamp with milliseconds
                                        #   %n = timestamp with milliseconds (as a Unix epoch)
                                        #   %i = command tag
                                        #   %e = SQL state
                                        #   %c = session ID
                                        #   %l = session line number
                                        #   %s = session start timestamp
                                        #   %v = virtual transaction ID
                                        #   %x = transaction ID (0 if none)
                                        #   %q = stop here in non-session
                                        #        processes
                                        #   %% = '%'
                                        # e.g. '<%u%%%d> '

Собранный пример строки с форматом протоколирования: %m [%p]: [%l] [txid=%x] user=%u, db=%d, app%a, client=%r

Ссылки на дополнительные ресурсы

Общее руководство от EDB для ознакомления

Презентация на тему протоколирования в Postgres

Пояснения по протоколированию модифицирующих операций

Установка защищенной базы данных QHB

При установке СУБД в ИТ-инфраструктуре в тестовых целях можно ограничиться настройками по умолчанию. Однако если планируется использовать СУБД продолжительное время и/или в продуктивном контуре, следует выполнить несколько рекомендаций по установке базовой защиты экземпляра базы данных. Все необходимые действия можно разделить на несколько этапов:

Настройка защищенного (SSL) соединения;

Осмысленное разграничение прав ролей, групп и пользователей в базе данных;

Избавление от всех паролей «по умолчанию»;

Защита контура иными средствами если необходимо.

Данное руководство содержит:

базовую защиту самой системы QHB;

инструкцию по настройке подключения через SSL;

инструкцию по выпуску сертификатов для подключения через SSL;

примеры и рекомендации разграничения прав ролей, групп и пользователей.

Настройка защищенного соединения

QHB позволяет настроить шифрование данных при обмене между узлами с помощью SSL.

Установка необходимых библиотек

Для начала необходимо убедиться, что требуемые пакеты/библиотеки установлены в системе. Проверить наличие openssl можно командой:

openssl version

При отсутствии необходимого пакета следует установить его. Для примера, на CentOS 7 нужно выполнить команду:

yum install openssl

Пример команды для установки на Ubuntu:

apt-get install openssl

Пакет openssl должен быть установлен как на сервере СУБД, так и на компьютере, с которого производится клиентское подключение.

Шифрование соединения с помощью SSL

Для шифрования соединения между клиентом и сервером должны быть созданы соответствующие сертификаты сервера и клиента(ов). В данном контексте под ключом понимается закрытый ключ, а под сертификатом — открытый ключ.

ВНИМАНИЕ!
Важно понимать, что закрытый ключ необходимо держать в тайне и не распространять.

Создание корневого ключа и корневого сертификата

# Данная команда создает файл корневого ключа длиной 1024 бит
openssl genrsa -out rootCA.key 1024
# Данная команда создает сертификат 'rootCA.crt' со сроком жизни 365 дней
openssl req -x509 -new -key rootCA.key -days 365 -out rootCA.crt -subj '/C=XX/L=Default City/O=Default Company Ltd/CN=root'

Первая команда, genrsa, генерирует закрытый ключ. Аргумент -out rootCA.key 1024 — это файл, который будет сгенерирован после выполнения команды. Число в конце означает размер последовательности ключа в битах, можно использовать число 2048 или 4096.

Вторая команда, req, генерирует сертификат. В ней нужно передать следующие аргументы:

-x509 — указывает на необходимость создания корневого или тестового сертификата.

-new — указывает на необходимость создания нового сертификата.

-key rootCA.key — указание на файл закрытого ключа.

-days 365 — срок жизни сертификата; если не указывать данный параметр, будет указан срок 30 дней.

-out rootCA.crt — файл открытого ключа, который будет создан по завершении команды.

-subj — информация для создаваемого сертификата. Содержит следующие поля:

C=XX — код страны.

L=Default City — город.

O=Default Company Ltd — название компании.

Можно вписывать любые данные, но для последующего удобства желательно вписать настоящие.

Создание сертификата сервера подписанного rootCA

# Данная команда создает файл ключа сервера длинной 1024 бит
openssl genrsa -out server.key 1024
# Создается неподписанный сертификат сервера (запрос на сертификат)
openssl req -new -key server.key -out server.csr -subj '/C=XX/L=Default City/O=Default Company Ltd/CN=servername'
# Сертификат сервера подписывается корневым сертификатом
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365

Используемые аргументы первых двух команд аналогичны прошлому блоку, но в аргумент -subj команды req добавляется поле CN=servername, представляющее собой название объекта. Можно вписать любое, но для последующего удобства желательно использовать название сервера.

Третья команда, x509, в данном контексте используется для подписи сертификата. В ней нужно передать следующие аргументы:

-req — означает, что на вход подается неподписанный сертификат.

-in server.csr — файл, который необходимо подписать.

-CA rootCA.crt — файл, которым будет производится подпись.

-CAkey rootCA.key — файл закрытого ключа, сертификатом которого будет производится подпись.

-CAcreateserial — создание файла серийного номера центра сертификации.

-out server.crt — файл открытого ключа, который будет создан по завершении команды.

-days 365 — срок жизни сертификата; если не указывать данный параметр, будет указан срок 30 дней.

Создание сертификата клиента подписанного rootCA

openssl genrsa -out qhb.key 1024
openssl req -new -key qhb.key -out qhb.csr -subj '/C=XX/L=Default City/O=Default Company Ltd/CN=servername'
openssl x509 -req -in qhb.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out qhb.crt -days 365

Используемые аргументы команды аналогичны прошлому блоку. Данный сертификат потребуется при настройке соединения клиента с базой данных.

Копирование сертификата сервера, приватного ключа сервера и rootCA

Скопируйте сертификат сервера, приватный ключ сервера и rootCA в директорию кластера QHB и после копирования назначьте им права доступа только на чтение:

cd $PGDATA
chmod 0400 server.crt server.key rootCA.crt

Включение SSL в QHB

В интерактивном терминале psql выполните команды:

qhb=# ALTER SYSTEM SET ssl TO 'ON';
qhb=# ALTER SYSTEM SET ssl_ca_file TO 'rootCA.crt';
qhb=# ALTER SYSTEM SET ssl_cert_file TO 'server.crt';
qhb=# ALTER SYSTEM SET ssl_key_file TO 'server.key';

Также эти параметры можно изменять в файле qhb.conf. Подробную информацию см. в подразделе SSL.

Использование специфичного шифра

Примечание
Данный этап необязателен. Выполните его, если есть необходимость использовать специфичный шифр

В интерактивном терминале psql выполните команду:

qhb=# ALTER SYSTEM SET ssl_ciphers TO 'желаемый_шифр';

В параметре желаемый_шифр необходимо написать набор параметров, разделенных двоеточием (:). По умолчанию данная строка содержит набор параметров HIGH:MEDIUM:+3DES:!aNULL. Значение по умолчанию является самым оптимальным, если у вас нет особых требований к безопасности.

Пояснения по стандартным параметрам:

HIGH — наборы шифров, которые используют шифры из группы HIGH (например, AES, Camellia, 3DES).

MEDIUM — наборы шифров, использующие шифры из группы MEDIUM (например, RC4, SEED).

+3DES — порядок шифров для группы HIGH по умолчанию в OpenSSL определен некорректно. В нем 3DES оказывается выше AES128, что неправильно, так как он считается менее безопасным, чем AES128, и работает гораздо медленнее. Соответственно, данная запись изменяет последовательность использования шифров, чтобы шифр Triple DES использовался после наборов шифров High и Medium.

!aNULL — Отключает наборы анонимных шифров, не требующие проверки подлинности. Такие наборы уязвимы для атак с посредником, поэтому использовать их не следует.

Также эти параметры можно изменять в файле qhb.conf. Подробную информацию см. в подразделе SSL.

Перезагрузка сервера QHB

psql -c "select pg_reload_conf();"

или:

qhb_ctl -D DATADIR restart -mf

Редактирование файла qhb_hba.conf

Для включения ssl необходимо заменить host на hostssl в файле $PGDATA/qhb_hba.conf:

Со стороны сервера:

# IPv4 local connections:
# host all all 127.0.0.1/32 trust
hostssl all all 127.0.0.1/32 trust

Аналогичные действия необходимо произвести с клиентской стороны:

# IPv4 local connections:
# host all all 127.0.0.1/32 trust
hostssl all all 127.0.0.1/32 trust
hostssl all all app_server_1/32 md5
hostssl all all app_server_2/32 md5

Подробное описание файла qhb_hba.conf представлено в описании каталога pg_hba_file_rules.

Применение обновленной конфигурации

После редактирования файла qhb_hba.conf на шаге Редактирование файла qhb_hba.conf выполните:

psql -c "select pg_reload_conf();"

Тестирование соединения

Если все настроено правильно, то соединение должно выглядеть так:

psql -h localhost

psql (10.4)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
#

Если оно выглядит так:

psql (10.4)
Type "help" for help.
#

т. е. отсутствует строка с указанием протокола SSL: SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off), то настройка не была произведена окончательно.

Включение аутентификации клиента по сертификату

Копирование сертификата на клиентскую часть

На данном шаге необходимо скопировать сертификат клиента, который был создан на шаге Создание сертификата клиента. Для примера, следующая строка кода показывает копирование на удаленную машину с адресом 192.168.0.13:

scp qhb.crt qhb.key rootCA.crt qhb@192.168.0.13:/usr/local/qhb

Далее этим файлам необходимо задать права только на чтение на удаленной машине:

cd /usr/local/qhb
chmod 0400 qhb.crt qhb.key rootCA.crt

Настройка qhb_hba.conf

Для использования аутентификации клиента по сертификату необходимо на сервере добавить строку с подключением клиента с удаленной машины qhb_hba.conf. Для примера используется IP машины из предыдущего шага:

hostssl all all 192.168.0.13/32 cert clientcert=1

Отличия от шага Редактирование файла qhb_hba.conf прошлой части в том, что на данном шаге сертификат необходим для подтверждения, что соединение происходит между двумя доверенными сторонами. Для того чтобы изменения вступили в силу, необходимо перезагрузить QHB.

qhb_ctl -D DATADIR reload

Проверка удаленного соединения

Как и в шаге Тестирование соединения прошлой части, после ввода команды:

psql "port=5432 host=192.168.0.12 user=qhb sslcert=/usr/local/qhb/qhb.crt sslkey=/usr/local/qhb/qhb.key sslrootcert=/usr/local/qhb/rootCA.crt sslmode=require"

текст в консоли должен выглядеть так:

psql (10.4)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-DES-CBC3-SHA, bits: 168, compression: off)
Type "help" for help.
#

Если упоминания SSL нет или соединение не происходит, то, скорее всего, не все шаги выполнены правильно.

Создание ролей групп и пользователей

В QHB, как и в любой другой СУБД, необходимо обеспечивать разграничение прав. Для понимания данного вопроса ниже описаны способы создания пользователей, групп и ролей.

Создание роли или пользователя

Пользователь QHB — это роль с правом CONNECT. Для создания нового пользователя QHB хорошо подходят команды CREATE USER и CREATE ROLE. Однако у пользователя должна быть роль LOGIN. Роль входа назначается пользователю при использовании любого из следующих трех подходов на интерактивном терминале psql:

qhb=# CREATE USER percuser WITH ENCRYPTED PASSWORD 'secret';

qhb=# CREATE ROLE percuser WITH ENCRYPTED PASSWORD 'secret';

qhb=# CREATE ROLE percuser;
qhb=# ALTER ROLE percuser WITH LOGIN ENCRYPTED PASSWORD 'secret';

После ввода любого из вышеуказанных примеров QHB автоматически добавляет код на указанный ниже

qhb=# CREATE ROLE percuser
WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS
ENCRYPTED PASSWORD 'secret';

В данном примере видно, что нет ролей, которые давали бы возможность изменять базу. После создания пользователя или роли вы можете использовать следующий запрос, чтобы узнать, может ли пользователь войти в систему (имеет ли право на подключение):

qhb=# select rolcanlogin from pg_roles where rolname = 'percuser';
rolcanlogin
-------------
t
(1 row)

Мы видим, что запрос вернул true, значит, данная роль/пользователь может подключаться к базе данных. Для примера, запрос ниже показывает роль, которая не может подключиться:

qhb=# CREATE ROLE percrole;
CREATE ROLE

qhb=# select rolcanlogin from pg_roles where rolname = 'percrole';
rolcanlogin
-------------
f
(1 row)

Различия создания роли и создания пользователя

При использовании CREATE USER происходит автоматическое добавление роли возможности логина.

qhb=# CREATE USER percuser WITH ENCRYPTED PASSWORD 'secret';
CREATE ROLE

qhb=# select rolcanlogin from pg_roles where rolname = 'percuser';
rolcanlogin
-------------
t
(1 row)

А при использовании CREATE ROLE ее необходимо добавлять вручную.

qhb=# CREATE ROLE percuser WITH ENCRYPTED PASSWORD 'secret';
CREATE ROLE

qhb=# select rolcanlogin from pg_roles where rolname = 'percuser';
rolcanlogin
-------------
f
(1 row)

qhb=# ALTER ROLE percuser WITH LOGIN;
ALTER ROLE

qhb=# select rolcanlogin from pg_roles where rolname = 'percuser';
rolcanlogin
-------------
t
(1 row)

Создание группы и групповой роли

В QHB роль может наследовать другую роль. Это означает, что одна роль может получить доступ, используя права другой роли. Этого можно добиться с помощью ключевых слов GRANT или INHERIT.

Всегда рекомендуется предоставлять права доступа пользователям с помощью групповых ролей.

Рассмотрим пример:

1 база db1;

2 схемы sh1 и sh2.

Допустим, нам нужно создать 10 отдельных пользователей и 10 пользователей приложений, соблюдая следующие требования:

Пять пользователей приложения должны иметь доступ только для чтения к таблицам схемы sh1 и доступ для чтения и записи к таблицам схемы sh2.

Остальные пять пользователей приложения должны иметь доступ для чтения и записи к таблицам схемы sh1 и доступ только для чтения к таблицам схемы sh2.

Все 10 индивидуальных учетных записей пользователей могут иметь доступ только для чтения к таблицам обеих схем.

В этой ситуации можно создать четыре групповых роли, например:

sh1_readonly:

qhb=# CREATE ROLE sh1_readonly;
qhb=# GRANT USAGE, SELECT ON ALL TABLES IN SCHEMA sh1 TO sh1_readonly;

sh1_readwrite:

qhb=# CREATE ROLE sh1_readwrite;
qhb=# GRANT USAGE, SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA sh1 TO sh1_readwrite;

sh2_readonly:

qhb=# CREATE ROLE sh2_readonly;
qhb=# GRANT USAGE, SELECT ON ALL TABLES IN SCHEMA sh2 TO sh2_readonly;

sh2_readwrite:

qhb=# CREATE ROLE sh2_readwrite;
qhb=# GRANT USAGE, SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA sh2 TO sh2_readwrite;

Теперь можно предоставить эти групповые роли пользователям в соответствии с тремя вышеуказанными требованиями.

qhb=# GRANT sh1_readonly,sh2_readonly TO user1, user2, user3, ..., user10;
qhb=# GRANT sh1_readonly,sh2_readwrite TO appuser1, appuser2, ..., appuser5;
qhb=# GRANT sh1_readwrite,sh2_readonly TO appuser6, appuser7, ..., appuser10;

Назначая групповые роли пользователям, имеется возможность разделить, какие права разрешены какому пользователю.

Распределение прав

QHB поддерживает множество прав.

Например:

SELECT

INSERT

UPDATE

DELETE

TRUNCATE

REFERENCES

TRIGGER

CREATE

USAGE

EXECUTE

Эти права могут быть предоставлены с помощью команды GRANT.

Работа с модулями

Инструкция по установке и использованию QCP

Работа сервера метрик в режиме разделенного коллектора и агрегатора

Инструкция по установке и использованию QCP

В данной инструкции представлены простые шаги по установке пулера соединений QCP, а также базовая настройка сервиса systemd. Более подробную информацию см. в главе Асинхронный пул соединений QCP.

Чтобы начать использовать QCP, необходимо установить поставляемый пакет в систему. Если вы устанавливаете пакеты из репозитория QHB, можно воспользоваться пошаговой инструкцией по начальной загрузке, установке и запуску. После подключения к репозиторию установить пакет, например, для Centos 7, можно следующим образом:

sudo yum install qcp

После установки будет создан файл с примером конфигурации в /etc/qcp/config-example.yaml. Его необходимо скопировать в /etc/qcp/config.yaml и отредактировать под свои нужды, выставив необходимые параметры подключения к СУБД.

Управление сервисом осуществляется через поставляемый юнит systemd.

Перед первым запуском, после установки QCP, необходимо выполнить перезагрузку демона systemd, чтобы изменения вступили в силу:

systemctl daemon-reload

Запуск QCP:

sudo systemctl start qcp

Остановка QCP:

sudo systemctl stop qcp

Для того чтобы QCP автоматически запускался при старте системы, выполните следующую команду:

sudo systemctl enable qcp

При изменении конфигурации необходимо выполнить следующую команду, чтобы QCP перечитал файл конфигурации:

sudo systemctl reload qcp

Работа сервера метрик в режиме разделенного коллектора и агрегатора

Сервер метрик состоит из двух компонентов:

Коллектор. Эта часть отвечает за непосредственный обмен метриками между приложениями (например, СУБД «Квант-Гибрид») и сервером метрик. Отправка метрик осуществляется через разделяемую память, которой управляет сервер метрик. Данный компонент не занимается обработкой метрик, лишь их сбором из множества источников. Далее метрики передаются во второй компонент — агрегатор.

Агрегатор. Как следует из названия, этот компонент агрегирует, то есть накапливает метрики и делает статистические вычисления: усреднение, вычисление перцентилей, максимальных значений и т. д. Также этот компонент занимается отправкой агрегированных метрик дальше, например, в Graphite, или записывает их в CSV-файл.

По умолчанию сервер метрик запускается в режиме, который объединяет в себе и коллектор, и агрегатор. Такая архитектура позволяет минимизировать затраты на передачу данных; они осуществляются через оперативную память.

Однако можно запускать сервер метрик в режиме, когда активен только один из компонентов, причем они могут быть разнесены на разные физические устройства. В таком случае собранные метрики будут отправляться в агрегатор с использованием протокола UDP. Обратите внимание, что UDP не гарантирует доставку пакетов, обратитесь к системному администратору, чтобы оценить риски. Этот вопрос выходит за границы данного руководства.

ВНИМАНИЕ!
Сервер метрик в режиме коллектора все еще должен работать на том же хосте, что и исследуемое приложение, так как обмен метриками осуществляется через разделяемую память. Техническая возможность вынести коллектор на отдельный хост отсутствует.

Возможные причины для переноса агрегатора сервера метрик на отдельный хост

Разделение коллектора и агрегатора не является необходимым, но в ряде случаев их разделение может быть полезно, а именно:

Конечный получатель агрегированных метрик (например, Graphite) может быть недоступен с хоста, где запускается исследуемое приложение.

Исследуемое приложение разнесено на разные хосты, и есть желание агрегировать метрики по всем хостам «насквозь», не учитывая этого различия.

Для снижения нагрузки на хост, где запущен сервер метрик. Однако следует отметить, что агрегация метрик не отнимает много ресурсов, поэтому такая оптимизация сомнительна.

Данный перечень не является исчерпывающим, но призван описать возможные применения.

Настройка агрегатора сервера метрик на отдельном хосте

Запуск сервера метрик в режиме коллектора осуществляется одним из следующий способов, от более предпочтительных к менее:

Удалите или закомментируйте целиком раздел aggregation: в конфигурационном файле /etc/metricsd/config.yaml.

/usr/bin/metricsd --only-collector --config /etc/metricsd/config.yaml, где /etc/metricsd/config.yaml — путь к конфигурационному файлу.

С помощью дополнительного файла /etc/systemd/system/metricsd.service.d/override.conf (вместо override.conf можно выбрать любое другое имя, заканчивающееся на .conf) со следующим содержимым:

[Service]
ExecStart=
ExecStart=/usr/bin/metricsd --only-collector --config /etc/metricsd/config.yaml

Посредством изменения файла metricsd.service и добавления флага --only-collector к вызову metricsd в поле ExecStart=. Данный способ не рекомендуется к применению. Используйте его, только если точно знаете, что делаете, на свой страх и риск.

Коллектор в данном случае является просто каналом для передачи метрик на другой хост к агрегатору. Агрегатор настраивается на подключение к соответствующему серверу (Prometheus, Graphite и др.).

Сервер метрик, работающий в режиме агрегатора, устанавливается на отдельном сервере, см. раздел Установка.

Запуск сервера метрик в режиме агрегатора осуществляется одним из следующий способов, от более предпочтительных к менее:

Удалите или закомментируйте целиком раздел collection: в конфигурационном файле /etc/metricsd/config.yaml.

/usr/bin/metricsd --only-aggregator --config /etc/metricsd/config.yaml, где /etc/metricsd/config.yaml — путь к конфигурационному файлу.

С помощью дополнительного файла /etc/systemd/system/metricsd.service.d/override.conf (вместо override.conf можно выбрать любое другое имя, заканчивающееся на .conf) со следующим содержимым:

[Service]
ExecStart=
ExecStart=/usr/bin/metricsd --only-aggregator --config /etc/metricsd/config.yaml

Посредством изменения файла metricsd.service и добавления флага --only-aggregator к вызову metricsd в поле ExecStart=. Данный способ не рекомендуется к применению. Используйте его, только если точно знаете, что делаете, на свой страх и риск.

ВНИМАНИЕ!
Убедитесь в том, что:

в разделе «aggregation» конфигурации сервера метрик, работающего в режиме агрегатора, указан такой адрес listen_address, получение данных на который будет доступно с сервера метрик, работающего в режиме коллектора;

в разделе «collection» конфигурации сервера метрик, работающего в режиме коллектора, указан верный адрес агрегатора aggregator_address, соответствующий адресу из предыдущего пункта;

во избежание фрагментирования пакетов UDP, в разделе «collection» конфигурации сервера метрик, работающего в режиме коллектора, указан подходящий размер допустимого пакета udp_payload. Обратитесь к вашему системному/сетевому администратору для уточнения подходящего размера;

ваш сетевой администратор не запретил передачу пакетов UDP по сети.

Детали конфигурации сети выходят за рамки данного руководства. При возникновении вопроса обратитесь к вашему системному и/или сетевому администратору.

Пример файла конфигурации коллектора:

# Уровень детализации на сервере.
verbosity: "warn"

# Конфигурация сбора метрик.
#
# Если вы не хотите запускать сбор метрик, этот раздел можно пропустить.
collection:
  # Адрес и порт (UDP) агрегатора метрик.
  # Необязательны, если агрегация метрик выполняется на том же экземпляре сервера;
  # в противном случае необходимы.
  aggregator_address: "127.0.0.1:5400"

  # Unix-адрес квитирующего сервера коллектора.
  # Символ `@` в начале означает абстрактный сокет (т. е. в файловой системе
  # никакого сокета не создается).
  bind_addr: "@metrics-collector"

  # Путь, по которому создаются запросы spsc.
  queue_path: "/metrics-collector-queues"

  # Вместимость приемника запросов.
  # Необязательно. Значение по умолчанию – 1024.
  queue_capacity: 1024

  # Количество обработанных элементов, после которого обновляются разделяемые
  # счетчики.
  # Необязательно. Значение по умолчанию – до 10.
  batch_size: 10

  # Количество обрабатываемых потоков.
  # Необязательно. Значение по умолчанию – до 2.
  threads: 2

  # Объем полезных данных UDP.
  # Необязательно. Значение по умолчанию – 1024.
  udp_payload: 1024

  # Максимальный интервал между передачами.
  # Необязательно. Значение по умолчанию – "1s" (1 с).
  send_interval: "1s"

  # Количество потоков рабочих процессов.
  # Необязательно. Значение по умолчанию – 2.
  workers: 2

Пример файла конфигурации агрегатора:

# Уровень детализации на сервере.
verbosity: "info"

# Конфигурация агрегации метрик.
#
# Если вы не хотите запускать агрегацию метрик, этот раздел можно пропустить.
aggregation:
  # Адрес прослушивания агрегатора
  # Необязателен, если сбор метрик выполняется на том же экземпляре сервера;
  # в противном случае необходим.
  listen_address: "0.0.0.0:5400"

  # Интервал между каждой агрегацией
  send_interval: 10s

  # Список используемых перцентилей для величин синхронизации. Значения по
  # умолчанию – [50, 90, 95, 99, 999].
  # Значения больше 1000 не поддерживаются.
  percentiles_list: [50, 90, 95, 99, 999]

  # Срок существования «индикаторных» (gauge) значений. Необязательно;
  # по умолчанию 5 минут.
  gauge_lifetime: 5min

  # Конфигурация серверов. Должен быть сконфигурирован как минимум один сервер.
  backends:
    # Конфигурация сервера graphite
    - graphite:
      # Адрес конечной точки TCP Graphite для текстового протокола. Порт по
      # умолчанию 2003.
      # Доступен только протокол TCP, поэтому если Graphite не ожидает передачи
      # данных на этом порту, вы получите ошибку!
      address: "Address:2003"
      # Префикс, стоящий в начале имени всех метрик. Необязательно; по умолчанию
      # это пустая строка.
      prefix: "Address"
      # Максимальное время ожидания подключения. Необязательно; по умолчанию 30 секунд.
      connection_timeout: "30 sec"
      # Максимальное время ожидания передачи данных. Необязательно; по умолчанию 5 секунд.
      send_timeout: "5 sec"

Работа с 1С

Инструкция по установке QHB под 1С

Конфигурирование QHB под 1С

Резервное копирование QHB под 1С

Инструкция по установке QHB под 1С

Первым шагом является установка QHB. После ее установки необходимо инициализировать кластер баз данных с нужными параметрами:

sudo -u qhb /usr/local/qhb/bin/qhb_bootstrap --locale=ru_RU.UTF-8 --lc-collate=ru_RU.UTF-8 --lc-ctype=ru_RU.UTF-8 --encoding=UTF8 -D /u01/1CBase/ -U qhb

Далее необходимо создать базу данных, после чего подключить ее к 1С. Сделать это можно двумя путями: через консоль администрирования серверов 1С или через приложение 1С. И в том, и в другом случае необходимо указывать информацию в полях для добавления информационной базы в соответствии с вашей конфигурацией и инфраструктурой. В поле «Тип СУБД» нужно выбрать PostgreSQL.

В качестве примера указания параметров информационной базы можно разобрать добавление информационной базы через приложение 1C.

Наименование столбца | Необходимые данные
Кластер серверов 1С:Предприятия | Наименование кластера серверов
Имя информационной базы в кластере | Наименование созданной базы
Защищенное соединение | Выбор параметра зависит от вашей конфигурации; по умолчанию «Выключено»
Тип СУБД | PostgreSQL
Сервер баз данных | IPv6 адрес
Имя базы данных | Наименование базы данных
Пользователь базы данных | Имя пользователя с правами для создания базы на сервере баз данных
Пароль пользователя | Пароль пользователя с правами для создания базы на сервере баз данных
Смещение дат | Выбор параметра зависит от вашей конфигурации; по умолчанию 0

При указании флага Создать базу данных в случае ее отсутствия будет создана новая база данных, без необходимости предварительного создания экземпляра базы данных.

ВНИМАНИЕ!
Для корректной работы необходимо на каждом экземпляре базы данных выполнить следующую команду:

UPDATE pg_catalog.pg_operator SET oprcanhash = false WHERE oid in (7200, 7201);

Конфигурирование QHB под 1С

Пример настроек в конфигурации QHB:

ВНИМАНИЕ!
Стоит ответственно подойти к конфигурации базы данных для различных пакетов 1С. Конфигурация ниже является исключительно примером и может отличаться от той конфигурации, что необходима вам. В случае, если вы используете уже готовую конфигурацию от другой системы, невозможно дать гарантии, что эта конфигурация заработает.

#------------------------------------------------------------------------------
# ПАРАМЕТРЫ ПОДДЕРЖКИ 1C
#------------------------------------------------------------------------------
# Специально скорректированы для 1C
# Разработано для сервера
# 8 ядер
# RAM 64GB
#------------------------------------------------------------------------------
# Оценочное количество пользователей
max_connections = 525

# Память
shared_buffers = 16GB
temp_buffers = 128MB

# Можно менять в зависимости от рабочей нагрузки
work_mem = 256MB
maintenance_work_mem = 256MB
effective_cache_size = 48GB

# WAL и сопутствующие параметры
wal_level = minimal
commit_delay = 1000
max_wal_size = 4GB
min_wal_size = 2GB
checkpoint_timeout = 15min

# Параметры автоочистки
autovacuum_max_workers = 4
vacuum_cost_limit = 400
autovacuum_naptime = 20s
# Для некоторых тяжелых таблиц можно настроить индивидуально
autovacuum_vacuum_scale_factor = 0.01
autovacuum_analyze_scale_factor = 0.025

# Раздел настройки транзакций, запросов и планов
max_locks_per_transaction = 256
seq_page_cost = 1.0
random_page_cost = 2.5
from_collapse_limit = 8
join_collapse_limit = 8

huge_pages = off
row_security = off

# Параметры, связанные со взаимодействием ОС
max_files_per_process = 10000
max_parallel_maintenance_workers = 6
max_parallel_workers_per_gather = 0
synchronous_commit = off

# Особые параметры
escape_string_warning = off
standard_conforming_strings = off
online_analyze.enable = on
plantuner.fix_empty_table = on
online_analyze.verbose = off
online_analyze.min_interval = 10000
online_analyze.capacity_threshold = 99999
online_analyze.table_type = 'temporary'

ВНИМАНИЕ!
Для системы 1С необходимо установить максимальный размер файлов, разрешенный для хранения в базе данных, равным 495 МБ.

Резервное копирование QHB под 1С

Для резервного копирования QHB используется инструмент резервного копирования qbackup. Обратите внимание, что 1С позволяет загружать в базу данных файлы большого размера, и в таком случае резервное копирование данных утилитами qhb_dump или qhb_dumpall не может быть выполнено. В таких ситуациях следует применять альтернативные варианты резервного копирования. При миграции на QHB следует предварительно выгрузить файлы больше 500 МБ средствами 1С.

ВНИМАНИЕ!
Необходимо периодически не только делать копии данных, но и архивировать файлы журналов. Также нужно убедиться в том, что эти файлы сохраняются в отдельный архив. Обратитесь к статьям по практике создания резервных копий.

Сокращения

Это список сокращений, обычно используемых в документации QHB и в обсуждениях, касающихся QHB.

Сокращение | Полное название
ANSI | American National Standards Institute, Американский национальный институт стандартов
API | Application Programming Interface, Интерфейс прикладного программирования
ASCII | American Standard Code for Information Interchange, Американский стандартный код для обмена информацией
BKI | Backend Interface, Внутренний интерфейс
CA | Certificate Authority, Центр сертификации
CIDR | Classless Inter-Domain Routing, Бесклассовая адресация
CPAN | Comprehensive Perl Archive Network, Обширный сетевой архив ресурсов для языка PERL
CRL | Certificate Revocation List, Список отозванных сертификатов
CSV | Comma Separated Values, Значения, разделенные запятыми
CTE | Common Table Expression, Общее табличное выражение
CVE | Common Vulnerabilities and Exposures, Общий перечень уязвимостей и рисков
DBA | Database Administrator, Администратор базы данных
DBI | Database Interface (Perl), Интерфейс баз данных (Perl)
СУБД | Система управления базами данных (англ. Database Management System, сокр. DBMS)
DDL | Data Definition Language, Язык описания данных, включающий такие команды SQL, как CREATE TABLE, ALTER USER
DML | Data Manipulation Language, Язык управления данными, включающий такие команды SQL, как INSERT, UPDATE, DELETE
DST | Daylight Saving Time, Летнее время
ESQL | Embedded SQL, Встроенный SQL
FAQ | Frequently Asked Questions, Часто задаваемые вопросы
FSM | Free Space Map, Карта свободного пространства
GEQO | Genetic Query Optimizer, Генетический оптимизатор запросов
GIN | Generalized Inverted Index, Обобщенный инвертированный индекс
GiST | Generalized Search Tree, Обобщенное дерево поиска
Git | Git
GMT | Greenwich Mean Time, Среднее время по Гринвичу
GSSAPI | Generic Security Services Application Programming Interface, Общий программный интерфейс сервисов безопасности
GUC | Grand Unified Configuration, Главная унифицированная конфигурация, подсистема QHB, которая управляет конфигурацией сервера
HBA | Host-Based Authentication, Аутентификация по имени узла
HOT | Кортежи только в куче (Heap Only Tuple)
IEC, МЭК | International Electrotechnical Commission, Международная электротехническая комиссия
IEEE | Institute of Electrical and Electronics Engineers, Институт инженеров по электротехнике и радиоэлектронике
IPC | Inter-Process Communication, Межпроцессное взаимодействие
ISO, ИСО | International Organization for Standardization, Международная организация по стандартизации
ISSN | International Standard Serial Number, Международный стандартный сериальный номер
JDBC | Java Database Connectivity, Соединение с базами данных на Java
JIT | Just-in-Time compilation, JIT-компиляция
JSON | JavaScript Object Notation, Нотация объектов JavaScript
LDAP | Lightweight Directory Access Protocol, Облегченный протокол доступа к каталогам
LSN | Log Sequence Number, Регистрационный номер транзакции в журнале, см. pg_lsn и Внутреннее устройство WAL
MITM | Man-in-the-middle attack, Атака посредника
MSVC | Microsoft Visual C
MVCC | Multi-Version Concurrency Control, Многоверсионное управление параллельным доступом
NLS | National Language Support, Поддержка национальных языков
ODBC | Open Database Connectivity, Открытый интерфейс взаимодействия с базами данных
OID | Object Identifier, Идентификатор объекта
OLAP | Online Analytical Processing, Оперативная аналитическая обработка данных
OLTP | Online Transaction Processing, Оперативная обработка транзакций
ORDBMS | Object-Relational Database Management System, Объектно-реляционная система управления базами данных
РАМ | Pluggable Authentication Modules, Подключаемые модули аутентификации
PGXS | Инфраструктура сборки расширений СУБД «Квант-Гибрид»
PID | Process Identifier, Идентификатор процесса
PITR | Point-In-Time Recovery, Восстановление на момент времени (Непрерывное архивирование)
PL | Procedural Languages, Процедурные языки (на стороне сервера)
POSIX | Portable Operating System Interface, Переносимый интерфейс операционных систем
QHB | QuantumHybridBase, СУБД «Квант-Гибрид»
RDBMS | Relational Database Management System, Реляционная система управления базами данных
RFC | Request For Comments, Рабочее предложение
SGML | Standard Generalized Markup Language, Стандартный обобщенный язык разметки
SNI | Server Name Indication, Server Name Indication (RFC 6066)
SPI | Server Programming Interface, Интерфейс программирования сервера
SP-GiST | Space-Partitioned Generalized Search Tree, Обобщенное дерево поиска с разбиением пространства
SQL | Structured Query Language, Язык структурированных запросов
SRF | Set-Returning Function, Функция, возвращающая множество
SSH | Secure Shell, Безопасная оболочка
SSL | Secure Sockets Layer, Уровень защищенных сокетов
SSPI | Security Support Provider Interface, Интерфейс поставщика поддержки безопасности
SYSV | Unix System V
TCP/IP | Transmission Control Protocol (TCP) / Internet Protocol (IP), Протокол управления передачей / Межсетевой протокол
TID | Tuple Identifier, Идентификатор кортежа
TLS | Transport Layer Security, Протокол защиты транспортного уровня
TOAST | The Oversized-Attribute Storage Technique, Методика хранения сверхбольших атрибутов
TPC | Transaction Processing Performance Council, Совет по оценке производительности обработки транзакций
URL | Uniform Resource Locator, Унифицированный указатель ресурса
UTC | Coordinated Universal Time, Всемирное координированное время
UTF | Unicode Transformation Format, Формат преобразования Юникода
UTF8 | Unicode Transformation Format, 8-bit, Формат преобразования Юникода, 8-бит
UUID | Universally Unique Identifier, Универсально уникальный идентификатор
WAL | Write-Ahead Log, Журнал упреждающей записи
XID | Transaction Identifier, Идентификатор транзакции
XML | Extensible Markup Language, Расширяемый язык разметки

Глоссарий

Ниже представлен перечень терминов с описанием их значений в контексте QHB и реляционных баз данных в целом.

ACID

Atomicity (Атомарность), Consistency (Согласованность), Isolation (Изолированность) и Durability (Прочность). Этот набор свойств транзакций в базе данных призван гарантировать корректность операций при параллельном выполнении и даже в случае ошибок, при отключении питания и т. п.

DELETE

Команда SQL, удаляющая строки из данной таблицы или отношения.

Подробную информацию см. на справочной странице команды DELETE.

GRANT

Команда SQL, дающая пользователю или роли доступ к определенным объектам в базе данных.

Подробную информацию см. на справочной странице команды GRANT.

INSERT

Команда SQL, добавляющая в таблицу новые данные.

Подробную информацию см. на справочной странице команды INSERT.

NULL

Понятие несуществования, которое является основным положением теории реляционных баз данных. Оно обозначает отсутствие какого-либо определенного значения.

Qhbmaster (процесс)

Самый первый процесс экземпляра СУБД. Он запускает другие вспомогательные процессы и управляет ими, а также создает обслуживающие процессы по требованию.

Подробную информацию см. в разделе Запуск сервера баз данных.

REVOKE

Команда, лишающая указанный список ролей доступа к определенному набору объектов базы данных.

Подробную информацию см. на справочной странице команды REVOKE.

ROLLBACK

Команда, отменяющая все операции, выполненные с начала транзакции.

Подробную информацию см. на справочной странице команды ROLLBACK.

SELECT

Команда SQL, запрашивающая данные из базы. Обычно команды SELECT не должны никаким образом изменять базу данных, однако нельзя исключить возможность, что побочным действием функций, вызванных в каком-либо запросе, станет изменение данных.

Подробную информацию см. на справочной странице команды SELECT.

TOAST

Механизм, который обеспечивает хранение больших атрибутов строк таблицы, разделяя их и размещая в дополнительной таблице, называемой таблицей TOAST. У каждого отношения с большими атрибутами имеется собственная таблица TOAST.

Подробную информацию см. в разделе TOAST.

UPDATE

Команда SQL, изменяющая уже существующие строки определенной таблицы. Эта команда не может добавлять или удалять строки.

Подробную информацию см. на справочной странице команды UPDATE.

WAL

См. Журнал упреждающей записи.

Автовакуум (процесс)

Ряд фоновых процессов, регулярно выполняющих операции очистки и анализа. Вспомогательный процесс, который координирует работу и присутствует всегда (если только автоочистка не отключена), называется процессом запуска автовакуума, а процессы, выполняющие задачи, называются рабочими процессами автоовакуума.

Подробную информацию см. в подразделе Процесс «Автовакуум».

Агрегатная функция (подпрограмма)

Функция, которая объединяет (агрегирует) множество входных значений, например подсчитывая их количество, складывая их или вычисляя среднее, в результате чего на выходе получается одно значение.

Подробную информацию см. в разделе Агрегатные функции.

См. также Оконная функция (подпрограмма).

Анализ (операция)

Процесс сбора статистики по данным в таблицах и других отношениях, помогающий планировщику запросов принимать решения, касающиеся способов выполнения запросов.

(Не путайте этот термин с параметром ANALYZE команды EXPLAIN.)

Подробную информацию см. на справочной странице команды ANALYZE.

Аналитическая функция

См. Оконная функция (подпрограмма).

Архивирование WAL (процесс)

Вспомогательный процесс, который в активном состоянии сохраняет копии файлов WAL в целях создания резервных дубликатов данных или для поддержания актуального состояния реплик.

Подробную информацию см. в разделе Непрерывное архивирование и восстановление на момент времени (PITR).

Атомарность

Свойство транзакции, которое заключается в том, что все ее операции выполняются вместе либо не выполняются вовсе. Кроме того, в случае сбоя системы в процессе выполнения транзакции никаких частичных изменений после восстановления не появится. Это одно из свойств ACID.

Атомарный

Применительно к данным означает, что элемент данных нельзя разделить на меньшие части.

Применительно к транзакциям в базах данных см. атомарность.

Атрибут

Элемент с определенным именем и типом данных, содержащийся в кортеже.

База данных

Именованный набор локальных объектов SQL.

Подробную информацию см. в разделе Обзор главы «Управление базами данных».

Базовая резервная копия

Двоичная копия всех файлов кластера баз данных, которая создается утилитой qhb_basebackup. В сочетании с файлами WAL может использоваться в качестве начальной точки для восстановления, трансляции журналов или потоковой репликации.

Блокировка

Механизм, позволяющий некоторому процессу ограничивать или запрещать параллельный доступ к ресурсу.

Ветвь

Каждый из отдельных наборов файлов-сегментов, в которых хранится отношение. В основной ветви находятся собственно данные отношения. Помимо нее существуют две дополнительные ветви для метаданных: карта свободного пространства и карта видимости. У нежурналируемых отношений также имеется ветвь инициализации.

Внешний ключ

Вид ограничения, определяемый для одного или нескольких столбцов в таблице и требующий, чтобы значения в этих столбцах указывали на ноль или одну строку в другой (или, в редких случаях, той же) таблице.

Временная таблица

Таблица, существующая либо в рамках сеанса, либо в рамках транзакции, в соответствии с выбранным при ее создании режимом. Такие таблицы не являются журналируемыми, и их содержимое не видно в других сеансах. Временные таблицы часто применяются для хранения промежуточных данных в ходе многошаговой операции.

Подробную информацию см. на справочной странице команды CREATE TABLE.

Вспомогательный процесс

Процесс внутри экземпляра СУБД, который отвечает за некоторую конкретную фоновую задачу для этого экземпляра. К вспомогательным процессам относятся процесс запуска автовакуума (но не рабочие процессы автоовакуума), фоновый процесс записи, процесс контрольных точек, процесс протоколирования, процесс запуска, процесс архивирования WAL, приемник WAL (но не передатчики WAL), а также процесс записи WAL.

Главный сервер

См. Основной сервер.

Домен

Пользовательский тип данных, основанный на другом нижележащем типе данных. Он действует так же, как нижележащий тип, за исключением возможного ограничения набора допустимых значений.

Подробную информацию см. в разделе Типы доменов.

Журнал упреждающей записи

Журнал, в котором отслеживаются изменения в кластере баз данных, производимые при выполнении операций пользователями и самой системой. Он содержит множество отдельных записей WAL, вносимых последовательно в файлы WAL.

Журналируемость

Таблица считается журналируемой, если вносимые в нее изменения отсылаются в WAL. По умолчанию все обычные таблицы являются журналируемыми. Таблицу можно сделать нежурналируемой либо во время создания, либо с помощью команды ALTER TABLE.

Записывание WAL (процесс)

Процесс, осуществляющий перенос записей WAL из разделяемой памяти в файлы WAL.

Подробную информацию см. в разделе Журнал упреждающей записи.

Запись

См. Кортеж.

Запись WAL

Низкоуровневое описание отдельного изменения данных. Оно содержит достаточно информации для повторного выполнения (воспроизведения) этого изменения в случае, если при сбое системы оно не зафиксировалось. В записях WAL используется невыводимый двоичный формат.

Подробную информацию см. в разделе Внутреннее устройство WAL.

Запись журнала

Устаревшее название записи WAL.

Запрос

Указание, передаваемое клиентом обслуживающему процессу, в результате выполнения которого обычно либо выдаются результаты, либо меняются данные в базе.

Идентификатор транзакции

Числовой уникальный идентификатор, последовательно назначаемый каждой транзакции при первой операции, меняющей данные в базе. Часто он сокращенно обозначается xid. Когда эти идентификаторы хранятся на диске, каждый занимает всего 32 бита, поэтому создать их можно только примерно для 4 миллиардов пишущих транзакций. Чтобы система могла работать дольше, дополнительно используются эпохи, размер которых также составляет 32 бита. Когда счетчик транзакций достигает максимального значения xid, он сбрасывается до 3 (меньшие значения зарезервированы) и значение эпохи увеличивается на 1. В некоторых контекстах значения эпохи и xid рассматриваются вместе как одна 64-битная величина.

Подробную информацию см. в разделе Типы идентификаторов объектов.

Изолированность

Свойство, которое заключается в том, что результаты транзакции не видны для параллельных транзакций, пока она не будет зафиксирована. Это одно из свойств ACID.

Подробную информацию см. в разделе Изоляция транзакций.

Индекс (отношение)

Отношение, содержащее производную информацию от исходных данных таблицы или материализованного представления. Его внутренняя структура обеспечивает быструю выборку и извлечение исходных данных.

Подробную информацию см. на справочной странице команды CREATE INDEX.

Карта видимости (ветвь)

Хранимая структура, содержащая метаданные о каждой странице данных в основной ветви таблицы. Для записи о каждой странице в ней отводится два бита: первый (all-visible, полностью видимая) показывает, что все кортежи в странице видны всем транзакциям, а второй (all-frozen, полностью замороженная) — что все кортежи в странице помечены как замороженные.

Карта свободного пространства (ветвь)

Хранимая структура, содержащая метаданные о каждой странице данных в основной ветви таблицы. В ней указывается, какой объем доступен для новых кортежей в каждой странице, при этом сама она специально оптимизирована для эффективного поиска свободного места для нового кортежа заданного размера.

Подробную информацию см. в разделе Карта свободного пространства.

Каталог

В стандарте SQL этот термин обозначает то, что в терминологии QHB называется базой данных.

(Не путайте это понятие с понятием системного каталога).

Подробную информацию см. в разделе Обзор главы «Управление базами данных».

Каталог данных

Главный каталог в файловой системе сервера, содержащий все файлы данных и подкаталоги, связанные с кластером баз данных (за исключением табличных пространств и, возможно, WAL). Обычно на каталог данных ссылается переменная среды PGDATA.

Каталог данных в совокупности со всеми дополнительными табличными пространствами образует хранилище данных кластера.

Подробную информацию см. в разделе Структура файлов базы данных.

Класс (устаревшее)

См. Отношение.

Кластер баз данных

Совокупность баз данных и глобальных объектов SQL, а также их общих статических и динамических метаданных. Иногда также называется просто кластером.

В QHB термин кластер иногда обозначает также экземпляр СУБД. (Не путайте этот термин с командой SQL CLUSTER.)

Клиент (процесс)

Любой процесс, возможно, удаленный, который запускает сеанс путем подключения к экземпляру СУБД для взаимодействия с базой данных.

Ключ

Средство идентификации строки в таблице или другом отношении по значениям, содержащимся в одном или нескольких атрибутах этого отношения.

Конкурентный доступ

Концепция, определяющая возможность одновременного выполнения нескольких независимых операций в одной базе данных. В QHB конкурентный доступ реализуется механизмом многоверсионного управления конкурентным доступом.

Контрольная точка

Точка в последовательности записей WAL, в которой гарантируется, что в файлы данных кучи и индекса попала вся информация из разделяемой памяти, измененная до этой контрольной точки; для обозначения этой точки в WAL записывается и сбрасывается запись контрольной точки.

Контрольной точкой также называется процесс выполнения всех действий, необходимых для достижения контрольной точки в приведенном выше определении. Этот процесс запускается при выполнении предопределенных условий, например, по истечении заданного времени или после помещения в журнал некоторого объема записей; также ее может вызвать пользователь командой CHECKPOINT.

Подробную информацию см. в разделе Конфигурация WAL.

Кортеж

Упорядоченный набор атрибутов. Порядок атрибутов может определяться таблицей (или другим отношением), в которой содержится кортеж. В этом случае кортеж часто называют строкой. Он также может определяться структурой результирующего множества; такие кортежи иногда называют записями.

Куча

Содержит значения атрибутов строк (то есть непосредственно данные) отношения. Куча размещается в одном или нескольких файловых сегментах в основной ветви отношения.

Материализованное представление (отношение)

Отношение, которое определяется оператором SELECT (подобно обычному представлению), но при этом содержит данные как обычная таблица. Его содержимое нельзя изменить операторами INSERT, UPDATE и DELETE.

Подробную информацию см. на справочной странице команды CREATE MATERIALIZED VIEW.

Материализованность

Свойство данных, означающее, что они были вычислены заранее и сохранены для последующего использования, а не вычисляются каждый раз на ходу.

Этот термин используется применительно к материализованным представлениям и означает, что данные, полученные из запроса представления, сохраняются на диске отдельно от источника этих данных.

Этот термин также используется применительно к некоторым многоэтапным запросам и означает, что данные, полученные на некотором этапе, сохраняются в памяти (а могут быть также вытеснены на диск), благодаря чему они могут быть многократно прочитаны на следующем этапе.

Многоверсионное управление конкурентным доступом (MVCC)

Механизм, позволяющий нескольким транзакциям читать и записывать одни и те же строки и при этом не ждать друг друга. В QHB для реализации MVCC создаются копии (версии) кортежей, когда их данные меняются; после того, как все транзакции, которым были видны старые версии, завершаются, эти версии удаляются.

Мусорный объем

Объем в страницах данных, который не занимают текущие версии строк таблицы, в том числе неиспользуемый (свободный) объем и объем, занимаемый старыми версиями строк.

Нежурналируемость

Свойство некоторых отношений: изменения в них не отражаются в журнале WAL. Для таких отношений отсутствует возможность репликации и восстановления после сбоя.

Нежурналируемые таблицы в основном применяются для хранения промежуточных рабочих данных, которыми совместно пользуются различные процессы.

Временные таблицы всегда являются нежурналируемыми.

Обертка сторонних данных

Способ представления данных, находящихся не в локальной базе данных, таким образом, что они отображаются как содержащиеся в локальных таблицах. Обертка сторонних данных позволяет определить сторонний сервер и сторонние таблицы.

Подробную информацию см. на справочной странице команды CREATE FOREIGN DATA WRAPPER.

Область данных

См. Каталог данных.

Обслуживающий процесс (backend)

Процесс экземпляра СУБД, который реализует сеанс клиента и обрабатывает его запросы.

(Не путайте это понятие со схожими понятиями фоновый рабочий процесс и фоновый процесс записи.)

Объект SQL

Любой объект, который может быть создан командой CREATE. Большинство объектов относятся к одной базе данных, поэтому они обычно называются локальными объектами.

Большинство локальных объектов принадлежат определенным схемам в соответствующей базе данных, как например отношения (любых видов), подпрограммы (любых видов), типы данных и т. д. Имена таких объектов одного вида в отдельной схеме должны быть уникальными.

Кроме того, существуют локальные объекты, не принадлежащие схемам, например расширения, приведения типов данных и обертки сторонних данных. Имена таких объектов одного вида должны быть уникальными в базе данных.

Другие виды объектов, например роли, табличные пространства,источники репликации, подписки логической репликации, а также сами базы данных не являются локальными объектами SQL, так как они существуют вне какой-либо отдельной базы данных; они называются глобальными объектами. Имена таких объектов должны быть уникальными во всем кластере баз данных.

Подробную информацию см. в разделе Обзор главы Управление базами данных.

Ограничение

Условие, определяющее допустимость значений данных в таблице или атрибутов в домене.

Подробную информацию см. в разделе Ограничения.

Ограничение уникальности

Вид ограничения, определенного для отношения, который ограничивает значения, допустимые в одном или нескольких столбцах таким образом, чтобы каждое значение или набор значений могли присутствовать в отношении только один раз — то есть ни в какой другой строке отношения не может быть значений, равных данным.

Так как значения NULL не считаются равными друг другу, то наличие в отношении нескольких строк со значениями NULL не нарушает ограничение уникальности.

Оконная функция (подпрограмма)

Вид функций, используемых в запросах, который применяется к партиции результирующего множества запроса; результат такой функции зависит от значений в строках одной партиции или рамки.

В качестве оконных могут использоваться и агрегатные функции, но оконные функции способны, например, определять ранг для каждой строки в партиции. Оконные функции также называют аналитическими функциями.

Подробную информацию см. в разделе Оконные функции.

Оптимизатор

См. Планировщик запросов.

Основной сервер

Когда две и более базы данных связываются вместе посредством репликации, сервер, считающийся достоверным источником информации, называется основным или главным.

Отношение

Общий термин, который охватывает все объекты в базе данных, имеющие имя и упорядоченный список атрибутов. Отношениями являются таблицы, последовательности, представления, сторонние таблицы, материализованные представления, составные типы и индексы.

В более общем смысле отношением является набор кортежей; например, результат выполнения запроса тоже отношение.

В QHB название класс является устаревшим синонимом отношения.

Очистка (VACUUM)

Процесс удаления устаревших версий кортежей из таблиц или материализованных представлений и другая тесно связанная с ним обработка данных, потребность в которой продиктована реализацией MVCC в QHB. Очистку можно запустить с помощью команды VACUUM, но она также может осуществляться автоматически процессами автовакуума.

Подробную информацию см. в разделе Регулярная очистка.

Параллельный запрос

Возможность распределять выполнение частей запроса между параллельными процессами, используя возможности серверов с несколькими ЦП.

Партиционированная таблица (отношение)

Отношение, которое по смыслу не отличается от таблицы, но его содержимое хранится распределенно, в нескольких партициях.

Партиция

Одно из нескольких отдельных (не пересекающихся) подмножеств большего множества.

Применительно к партиционированной таблице: одна из таблиц, содержащих часть данных партиционированной таблицы, которая называется родительской. Партиция сама по себе является таблицей, поэтому запросы могут обращаться к ней напрямую; при этом партиция иногда тоже может быть партиционированной таблицей, позволяя создавать иерархии.

Применительно к оконным функциям в запросе партиция соответствует заданному пользователем критерию, определяющему, какие из смежных строк результирующего множества запроса может обрабатывать такая функция.

Первичный ключ

Особый вид ограничения уникальности, определяемого для таблицы или другого отношения, который дополнительно гарантирует, что все атрибуты в первичном ключе не имеют значений NULL. Как можно понять из названия, в таблице может быть только один первичный ключ, хотя в ней может быть несколько ограничений уникальности, также не допускающих значения NULL в атрибутах.

Передатчик WAL (процесс)

Специальный обслуживающий процесс, который передает WAL по сети. Принимать записи может приемник WAL на реплике, qhb_receivewal или любая другая клиентская программа, понимающая протокол репликации.

Переработка

См. Файл WAL.

Планировщик запросов

Компонент QHB, предназначенный для определения (планирования) наиболее эффективного способа выполнения запросов. Также называется оптимизатором запросов, оптимизатором или просто планировщиком.

Подключение

Установленная линия передачи данных между клиентским процессом и обслуживающим процессом, обычно сетевая, в рамках которой реализуется сеанс. Иногда этот термин употребляется как синоним сеанса.

Подробную информацию см. в разделе Подключения и аутентификация.

Подпрограмма

Определенный набор инструкций, сохраненный в СУБД, который может выполняться по запросу. Подпрограммы могут быть написаны на самых разных языках программирования. Понятие подпрограмм включает функции (в том числе функции, возвращающие множества, и триггерные функции), агрегатные функции и процедуры.

QHB уже содержит большое количество подпрограмм, но при этом поддерживает и добавление пользовательских подпрограмм.

Пользователь

Роль с правом LOGIN.

Последовательность (отношение)

Вид отношения, позволяющий генерировать значения. Как правило, генерируются последовательные неповторяющиеся числа. Обычно последовательности используются для генерирования суррогатных значений первичного ключа.

Представление

Отношение, которое определяется оператором SELECT, но само по себе не хранится. При каждом обращении к представлению его определение подставляется в запрос, как если бы пользователь просто ввел его в качестве подзапроса вместо имени представления.

Подробную информацию см. на справочной странице команды CREATE VIEW.

Приведение

Преобразование элемента данных из его текущего типа в другой тип данных.

Подробную информацию см. на справочной странице команды CREATE CAST.

Приемник WAL (процесс)

Вспомогательный процесс, который выполняется на реплике и получает WAL от основного сервера для воспроизведения процессом запуска.

Подробную информацию см. в разделе Доставка журналов на резервные серверы.

Проверочное ограничение

Вид ограничения, определяемый для отношения и ограничивающий множество значений, допустимых в одном или нескольких атрибутах. Проверочное ограничение может обращаться к любому атрибуту в той же строке отношения, но не может обращаться к другим строкам этого же отношения или к другим отношениям.

Подробную информацию см. в разделе Ограничения.

Протоколирование (процесс)

Вспомогательный процесс, который в активном состоянии записывает сообщения о событиях в базе данных в свой текущий файл журнала. Данный файл сменяется новым, когда выполняется некоторый критерий (ограничение по времени или объему). Также этот процесс называется syslogger.

Подробную информацию см. в разделе Регистрация ошибок и протоколирование.

Процедура (подпрограмма)

Вид подпрограммы, характеризующийся отсутствием возвращаемых значений. Кроме того, процедуры могут содержать операторы управления транзакциями, например COMMIT и ROLLBACK. Вызываются процедуры командой CALL.

Подробную информацию см. на справочной странице команды CREATE PROCEDURE.

Процесс запуска

Вспомогательный процесс, который воспроизводит WAL во время восстановления после сбоя и в физической реплике.

(Это имя имеет исторические причины: процесс запуска был назван так до того, как была реализована репликация; имя отражает задачу процесса, поскольку он связан с запуском сервера после сбоя.)

Процесс контрольных точек (checkpointer)

Вспомогательный процесс, обеспечивающий выполнение процедуры контрольной точки.

Прочность

Гарантия того, что после фиксирования транзакции произведенные ей изменения не будут потеряны даже в случае системной ошибки или краха сервера. Это одно из свойств ACID.

Разделяемая память

Область ОЗУ, совместно используемая процессами, относящимися к одному экземпляру СУБД. В нее отображаются фрагменты файлов базы данных, а также она служит временным хранилищем для записей WAL и содержит дополнительную общую информацию. Обратите внимание, что разделяемая память относится ко всему экземпляру, а не к отдельной базе данных в нем.

Самая крупная часть разделяемой памяти носит название разделяемые буферы, и в них в виде страниц отображаются фрагменты файлов данных. Страница, измененная в буфере, но еще не сохраненная в файловой системе, называется «грязной».

Подробную информацию см. в подразделе Память.

Расширение

Дополнительный программный модуль, который может быть установлен в экземпляре СУБД для расширения его функциональности.

Подробную информацию см. в разделе Упаковка связанных объектов в расширение.

Резервный сервер

См. Реплика (сервер).

Результирующее множество

Отношение, передаваемое из обслуживающего процесса клиенту после выполнения команды SQL, обычно SELECT, но если указать предложение RETURNING, это также могут быть команды INSERT, UPDATE или DELETE.

То, что результирующее множество является отношением, означает, что один запрос может фигурировать в определении другого запроса, превращаясь в подзапрос.

Реплика (сервер)

База данных, связанная с основной базой и содержащая копию некоторых или всех данных последней. Прежде всего такие базы создаются для расширения возможностей доступа к этим данным, а также для обеспечения доступности данных в случае потери основного сервера.

Репликация

Процесс переноса информации с одного сервера на другой называется репликацией. Она может реализовываться как физическая репликация, когда с одного сервера на другой точно копируются все изменения на уровне файлов, или как логическая репликация, когда изменения в определенном подмножестве данных передаются с помощью представления на более высоком уровне.

Роль

Набор прав доступа к объектам экземпляра СУБД. Принадлежность к роли тоже может считаться правом, которым можно наделять другие роли. Это часто используется для удобства распределения прав или когда нужно дать одинаковые права множеству пользователей.

Подробную информацию см. на справочной странице команды CREATE ROLE.

Сеанс

Состояние, в котором клиент может взаимодействовать с обслуживающим процессом, используя установленное подключение.

Сегмент

См. Файловый сегмент.

Сегмент WAL

См. Файл WAL.

Сервер

Компьютер, на котором работают экземпляры QHB. Термин сервер может обозначать как настоящее аппаратное оборудование, так и контейнер или виртуальную машину.

Иногда сервером также называют экземпляр СУБД или сетевой узел.

Сервер баз данных

См. Экземпляр СУБД.

Сетевой узел (хост)

Компьютер, взаимодействующий с другими компьютерами по сети. Иногда так называют сервер. Также это название применимо к компьютеру, на котором работают клиентские процессы.

Система накопительной статистики

Система, которая в активном состоянии накапливает статистическую информацию о действиях экземпляра сервера.

Подробную информацию см. в разделе Система накопительной статистики.

Системный каталог

Набор таблиц, которые описывают структуру всех объектов SQL экземпляра СУБД. Системный каталог располагается в схеме pg_catalog. Эти таблицы содержат данные во внутреннем представлении и, как правило, не считаются полезными для изучения пользователями. В более понятном виде часть этой информации доступна в ряде удобных для пользователя представлений, также находящихся в схеме pg_catalog. Кроме того, существует схема information_schema (см. главу Информационная схема), содержащая дополнительные таблицы и представления, из которых можно получить отчасти ту же, но также и некоторую другую информацию, предоставляемую в соответствии с требованиями стандарта SQL.

Подробную информацию см. в разделе Схемы.

Согласованность

Свойство базы, суть которого состоит в том, что ее данные всегда удовлетворяют ограничениям целостности. Транзакциям позволяется временно нарушать такие ограничения до их фиксирования, но если к моменту завершения транзакции эти нарушения не устраняются, транзакция автоматически откатывается. Это одно из свойств ACID.

Соединение (join)

Операция (и ключевое слово SQL), выполняемая в запросах для объединения данных из нескольких отношений.

Сопоставление пользователей

Трансляция учетных записей локальной базы данных в учетные записи удаленной системы данных, определяемая оберткой сторонних данных.

Подробную информацию см. на справочной странице команды CREATE USER MAPPING.

Ссылочная целостность

Средство ограничения данных в каком-либо отношении по внешнему ключу таким образом, чтобы им обязательно соответствовали данные в другом отношении.

Стандарт SQL

Совокупность документов, определяющих язык SQL.

Столбец

Атрибут, относящийся к таблице или представлению.

Сторонний сервер

Именованная совокупность сторонних таблиц, для обращения к которым используется одна обертка сторонних данных, и которые также имеют общие параметры конфигурации.

Подробную информацию см. на справочной странице команды CREATE SERVER.

Сторонняя таблица (отношение)

Отношение, строки и столбцы которого представляются сходными с таковыми в обычной таблице, но запросы к нему проходят через обертку сторонних данных, которая в ответ возвращает результирующие множества, структурированные согласно определению сторонних таблиц.

Подробную информацию см. на справочной странице команды CREATE FOREIGN TABLE.

Страница данных

Основная структура, в которой хранятся данные отношений. Все страницы имеют одинаковый размер. Страницы данных обычно хранятся на диске, каждая в определенном файле, и могут быть прочитаны в разделяемые буферы, где могут быть изменены, став в результате грязными. При записи на диск они вновь становятся чистыми. Новые страницы, изначально существующие только в памяти, также считаются грязными, пока не будут записаны на диск.

Строка

См. Кортеж.

Схема

Схема — это пространство имен объектов SQL, принадлежащих одной базе данных. Каждый отдельный объект SQL должен располагаться ровно в одной схеме.

Все системные объекты SQL располагаются в схеме pg_catalog.

В более общем смысле термин схема означает совокупность всех описаний данных (определений таблиц, ограничений, комментариев и т. д.) в определенной базе или в ее подмножестве.

Подробную информацию см. в разделе Схемы.

Таблица

Совокупность кортежей, имеющих общую структуру данных (одинаковое количество атрибутов в том же порядке, имеющих одинаковые имена и типы). Таблица является наиболее распространенным видом отношения в QHB.

Подробную информацию см. на справочной странице команды CREATE TABLE.

Табличное пространство

Именованное расположение в файловой системе сервера. Все объекты SQL, для которых требуется хранилище за рамками их определений в системном каталоге, должны располагаться в некотором табличном пространстве. Изначально в кластере баз данных есть только одно табличное пространство, которое называется pg_default и по умолчанию содержит все объекты SQL.

Подробную информацию см. в разделе Табличные пространства.

Точка сохранения

Специальная отметка в последовательности операций транзакции. Изменения данных, произведенные после этого момента времени, можно отменить, вернув данные в состояние на момент сохранения.

Подробную информацию см. на справочной странице команды SAVEPOINT.

Транзакции/сек (TPS)

Среднее количество транзакций, выполняющихся за секунду, подсчитанное по всем активным сеансам за время наблюдения. Эта величина используется для оценки производительности экземпляра СУБД.

Транзакция

Совокупность команд, которые должны выполниться как одна атомарная команда: они завершаются успешно либо отменяются все вместе, а результаты их выполнения не видны в других сеансах до завершения транзакции и, возможно, даже позже (в зависимости от уровня изоляции).

Подробную информацию см. в разделе Изоляция транзакций.

Триггер

Функция, которую можно определить для выполнения какой-либо определенной операции (INSERT, UPDATE, DELETE, TRUNCATE) с некоторым отношением. Триггер выполняется в рамках той же транзакции, что и вызвавший его оператор, и если в функции триггера произойдет ошибка, этот оператор тоже не будет выполнен успешно.

Подробную информацию см. на справочной странице команды CREATE TRIGGER.

Файл WAL

Также называется сегментом WAL или файлом сегмента WAL. Представляет собой один из последовательно нумеруемых файлов, служащих хранилищем WAL. Все эти файлы имеют одинаковый предопределенный размер и записываются по порядку. В файле WAL перемежаются изменения, производимые в нескольких параллельных сеансах. В случае сбоя системы эти файлы также по порядку считываются и все записанные в них изменения воспроизводятся, в результате чего восстанавливается состояние системы до сбоя.

Каждый файл WAL может быть освобожден после того, как во время контрольной точки все изменения из него будут переписаны в соответствующие файлы данных. Освобожденный файл может быть удален или переименован (это называется переработкой) так, чтобы его можно было использовать в будущем.

Подробную информацию см. в разделе Внутреннее устройство WAL.

Файл журнала

Файлы журналов содержат предназначенные для человека текстовые сообщения о событиях. Например, в журнале могут фиксироваться ошибки входа, длительные запросы и т. д.

Подробную информацию см. в разделе Обслуживание файлов журнала.

Файловый сегмент

Физический файл, содержащий данные для определенного отношения. Размер сегментов ограничивается параметром конфигурации (обычно это 1 гигабайт), поэтому отношения большего объема разбиваются на несколько сегментов.

Подробную информацию см. в разделе Структура файлов базы данных.

(Не путайте это понятие с подобным понятием сегмент WAL.)

Фиксирование

Акт успешного завершения транзакции в базе данных, обеспечивающий прочность и видимость ее результатов для других транзакций.

Подробную информацию см. на справочной странице команды COMMIT.

Фоновая запись (процесс)

Вспомогательный процесс, записывающий «грязные» страницы данных из разделяемой памяти в файловую систему. Он периодически запускается, но лишь на короткое время, чтобы вызываемые им затратные операции ввода/вывода распределялись во времени с целью исключения значительных пиков нагрузки, блокирующих другие процессы.

Подробную информацию см. в подразделе Фоновая запись.

Фоновый рабочий процесс

Процесс внутри экземпляра СУБД, выполняющий системный или пользовательский код. Обеспечивает инфраструктуру для различной функциональности QHB, в частности, для логической репликации и параллельных запросов. Кроме того, отдельные рабочие процессы могут создаваться расширениями.

Подробную информацию см. в главе Фоновые рабочие процессы.

Функция (подпрограмма)

Вид подпрограммы, который получает ноль или более аргументов, выдает ноль или более выходных значений и может выполняться только в рамках одной транзакции. Функции могут вызываться в ходе выполнения запросов, например SELECT. Некоторые функции могут возвращать множества; они называются функциями, возвращающими множества.

Функции также вызываются при срабатывании триггеров.

Подробную информацию см. на справочной странице команды CREATE FUNCTION.

Экземпляр СУБД

Группа обслуживающих и вспомогательных процессов, использующих общую область разделяемой памяти. Экземпляром СУБД управляет один процесс qhbmaster, а этот экземпляр управляет ровно одним кластером баз данных со всеми его базами. На одном сервере могут работать несколько экземпляров СУБД, если их TCP-порты не конфликтуют.

Конкретный экземпляр реализует все функциональные возможности СУБД: читает и записывает файлы, работает с разделяемой памятью, обеспечивает свойства ACID, принимает подключения клиентских процессов, проверяет права доступа, выполняет восстановление после сбоя, осуществляет репликацию и т. д.

Элемент данных

Внутреннее представление одного значения некоторого типа данных SQL.

Эпоха

См. Идентификатор транзакции.

Поддержка цветового оформления

Большинство программ в пакете QHB могут выводить в консоль цветной текст. В этом приложении описывается, как это настраивается.

Когда используется цветовое оформление

Для использования цветного текста следует установить переменную среды PG_COLOR в одно из следующих значений:

always — цвет используется всегда.

auto — цвет используется, если стандартный поток вывода сообщений об ошибках связан с устройством терминала.

В ином случае цвет не используется.

Конфигурация цветового оформления

Фактически используемые цвета конфигурируются с помощью переменной среды PG_COLORS (обратите внимание на множественное число — colors). Значение переменной представляет собой список пар ключ=значение, разделенных двоеточиями. Ключи указывают, для чего будет применяться цвет, а значения задают цвет в формате SGR (Select Graphic Rendition), который интерпретируется терминалом.

В настоящее время применяются следующие ключи:

error
выделяет текст «ошибки» в соответствующих сообщениях

warning
выделяет текст «предупреждения» в соответствующих сообщениях

locus выделяет в сообщениях информацию о расположении (например, имя программы и имя файла)

Значение по умолчанию: error=01;31:warning=01;35:locus=01 (01;31 = красный жирным шрифтом, 01;35 = пурпурный жирным шрифтом, 01 = жирный шрифт, цвет по умолчанию).

Подсказка
Этот формат указания цветов также применяется и в других программных продуктах, например в GCC, GNU coreutils и GNU grep.

Лицензирование QHB версии 1.5.2

ВНИМАНИЕ!
При отсутствии лицензии производительность СУБД ограничена.

Бинарные утилиты

Утилита qhb-serial

Утилита идентификации аппаратуры хоста qhb-serial поставляется в составе одноименного пакета qhb-serial.

При установке она размещается в общедоступном каталоге, обычно в /usr/bin/.

Утилита используется для запроса идентификаторов аппаратуры хоста или виртуальной машины, куда устанавливается QHB, для передачи их вендору или дилеру QHB. По этой информации генерируется лицензионный файл, привязанный именно к этому хосту или виртуальной машине.

Запуск утилиты:

qhb-serial

Параметры:

запуск без параметров
Вывести serial number текущего хоста, если он не является виртуальной машиной.

-u
--uuid
Вывести вместо serial number текущего хоста system-uuid виртуальной машины.

-s
--cpus
Вывести количество ядер хоста или виртуальной машины, которое будет учитываться при проверке лицензии.

-r
--report
Сформировать полный вывод параметров хоста или виртуальной машины для передачи вендору и формирования файла лицензии.

--help
Показать справку об аргументах командной строки qhb-serial и завершиться.

Пример запуска утилиты:

qhb-serial --report

Утилита qhb-license

Утилита просмотра файла лицензии qhb-license поставляется в составе пакета qhb-license-bin.

При установке она размещается в общедоступном каталоге, обычно в /usr/bin/.

Утилита используется для просмотра параметров лицензионного файла, полученного от вендора или дилера QHB.

Пример запуска утилиты:

qhb-license --license-path /var/lib/qhb/data/license

Пример вывода утилиты:

Идентификатор лицензии: fa4764bf-5fd8-4579-b8cb-d90b7259f62d
Идентификатор аппаратного обеспечения (HID): 37245DA9E484489EDD71F263E0CDF9F8
Идентификатор системы (UUID): B5AD08BC368E984DA7AD7A407D7F705D
Дата начала действия лицензии: 2023-03-15
Дата начала срока тех. поддержки: 2023-01-10
Дата окончания срока тех. поддержки: 2023-11-11
Имя пользователя: USERNAME
Код пользователя: USERCODE
Имя продукта: СУБД Квант-Гибрид
Код продукта: QHB-std1
Код сделки: BARGAINCODE
Номер релиза: 1.5
Редакция: FULL
Тип лицензии: TEST
Список лицензионных метрик
        CONNECTIONS: 2
        PROC: 8
Тип продукта: DBMS
Дата окончания действия лицензии: 2023-11-11

Для получения краткой справки запустите утилиту с флагом --help.

qhb-license --help

Получение файла лицензии

Клиент получает лицензионный файл, который необходим для использования QHB. Вместе с файлом лицензии клиент получает номер лицензии, по которому его можно идентифицировать. Данный номер также необходим для обращения в техническую поддержку.

Применение лицензионных параметров

При запуске QHB ожидает наличия лицензионного файла license в каталоге, куда он установлен, например /usr/local/qhb/license.

Для проверки лицензии требуется произвести идентификацию аппаратуры хоста пользователя, которая выполняется пакетом qhb-serial. Данная утилита также необходима для запуска QHB и утилит, использование которых предусматривает наличие лицензионного файла.

Примечание
Утилита qhb-serial может не требоваться для запуска таких утилит, как qcp и qdl, если в лицензионном файле идентификация аппаратуры хоста не заполняется.

По проверке соответствия параметров лицензирования, указанных в лицензионном файле текущего дистрибутива QHB, выводятся следующие сообщения:

первоначально при выводе версии печатается краткая информация о сроке действия лицензии;

далее в обычный журнал сообщений выводятся найденные ошибки загрузки и валидации лицензии.

Запрос лицензионных параметров во время работы

Для удобства администратора QHB предоставляется SQL-функция, которая позволяет получить детали текущей используемой лицензии.

Допустимые значения | Описание
lid | Идентификатор лицензии
hid | Идентификатор аппаратного обеспечения
system_uuid | Универсальный уникальный идентификатор системы
license_start | Дата начала действия лицензии
support_start | Дата начала действия техподдержки
support_stop | Дата окончания действия техподдержки
valid_thru | Дата окончания действия лицензии
user_name | Имя пользователя
user_code | Код пользователя
release | Текущий релиз
product_name | Название продукта
product_code | Код продукта
bargain_code | Код сделки
license_type | Тип лицензии
edition | Редакция
product_type | Тип продукта

Например:

# select qhb_license_info('product_code');
-- QHB

# select qhb_license_info('release');
-- 1.3

Дополнительно предоставляется SQL-функция, которая позволяет получить сразу все детали текущей используемой лицензии в виде таблицы.

# select attname, attvalue from qhb_license();

При невозможности обработать указанный параметр функция qhb_license_info возвращает пустую строку в виде значения типа text.

Возвращаемые данные соответствуют значениям, указанным в самой лицензии (лицензионный файл), и могут отличаться от значений, соответствующих сборке QHB.

Действия клиента в случае ошибки загрузки лицензии

При обычной проверке лицензии (при запуске QHB) могут возникать два типа ошибок:

ошибки загрузки лицензии;

ошибки валидации лицензии относительно текущей сборки/релиза QHB.

В случае ошибки загрузки лицензии следует удостовериться, что:

лицензия расположена по ожидаемому продуктом пути: в каталоге установки, например /usr/local/qhb/license;

файл лицензии не был изменен после получения от коммерческого отдела (файл подписан криптографически, и любые правки делают его недействительным);

у пользователя QHB есть права на чтение лицензионного файла.

В случае соблюдения этих условий и повторного возникновения ошибки следует обратиться в техническую поддержку.

Действия клиента в случае ошибки валидации лицензии

В случае ошибки валидации лицензии детальная информация об ошибке доступна в начале файла журнала QHB, сформированного при запуске.

Сообщение включает в себя результат валидации (каждый некорректный параметр выводится на новой строке) и краткое указание для дальнейших действий, локализованное для текущих настроек ОС (LC_ALL=ru/en).

Лицензирование QDL и QCP

При запуске данные утилиты ожидают наличие лицензионного файла qdl-license или qcp-license (аналогично QHB) в директории местонахождения исполняемого файла.

Сообщения об ошибках загрузки и валидации лицензии выводятся в лог соответствующих утилит (в зависимости от настроек это могут быть стандартные потоки ввода/вывода либо файл журнала).

Возможность запросить лицензионные параметры для утилит во время их работы в данном релизе не предусмотрена.

В случае ошибки валидации лицензии детальная информация об ошибке доступна в виде результата валидации (каждый некорректный параметр выводится на новой строке) и краткого указания для дальнейших действий (вывод зависит от текущих настроек локали).

Контроль целостности QHB версии 1.5.2

Описание подсистемы контроля целостности

Механизм контроля целостности реализован с помощью фонового процесса integrity_checker.

Фоновый процесс осуществляет проверку исполняемых модулей и утилит на целостность бинарного файла. Также фоновый процесс осуществляет проверку целостности программного кода хранимых процедур самой СУБД.

Проверка состоит в удостоверении корректности контрольных сумм, подсчитанных при сборке установочного пакета СУБД и после авторизованных изменений.

Объекты контроля целостности QHB

Объектами являются поставляемые бинарные утилиты и программный код хранимых процедур, как встроенных так и добавленных пользователем в процессе работы СУБД.

Для целостности бинарных утилит проверка происходит в соответствии с файлами, расположенными в /var/lib/qhb/data/integrity и имеющими расширение .sha256.

Мероприятия по обнаружению нарушений целостности

В случае обнаружения несовпадения контрольных сумм бинарных утилит и их комплектации в поставке СУБД, происходит блокировка пользователей СУБД, имеющих возможность логина, за исключением супер-пользователей и Администраторов СУБД (см. раздел Роли ИБ). Блокировку обеспечивает фоновый процесс СУБД logon_jobs. Блокировка действительная при логине по методам md5 и password.

В случае обнаружения несовпадения контрольных сумм блоков БД, хранящих программный код хранимых процедур, происходит аналогичная блокировка пользователей.

Также происходит блокировка возможности загружать сторонние библиотеки (включая расширения QHB) в адресное пространство запущенной СУБД.

Внимание! В случае обнаружения несоответсвия контрольных сумм бинарных утилит и/или программного кода хранимых процедур на реплике в режиме асинхронной репликации, происходит аварийное завершение реплики.

Внимание! После восстановления целостности каталога следует перезапустить СУБД, чтобы разблокировать пользователей и адресное пространство для изменений.

Включение подсистемы контроля целостности

Для включения следует указать опции logon_jobs и integrity_checks в конфигурационном файле:

logon_jobs=on
integrity_checks=on

По умолчанию данные параметры отключены.

Также следует выбрать периодичность проверок в единицах временной размерности:

integrity_period=20s

Профили безопасности

Профили безопасности позволяют производить групповую настройку политик безопасности СУБД в отношении пользователей.

Описание таблиц

Связь пользователя с определенным профилем безопасности хранится в таблице pg_catalog.qhb_auth_profile_authid, где столбец role_id указывает на Oid пользователя СУБД, расположенного в каталоге pg_catalog.pg_authid, а столбец profile_id указывает на сам профиль, расположенный в таблице pg_catalog.qhb_auth_profile.

По умолчанию, доступен один начальный профиль безопасности (единственная запись в qhb_auth_profile) с идентификатором Oid равным 9352. Удаление этого профиля может повредить стабильности работы системы, так как он используется для всех пользователей, кому еще не был назначен администратором СУБД выделенный профиль.

Информация о заблокированных пользователях хранится в таблице pg_catalog.qhb_user_lockout, причем блокировки применяются только при включенном параметре logon_jobs = on конфигурации СУБД в qhb.conf. По умолчанию этот параметр выключен, то есть logon_jobs = off.

Перед обновлением кластера утилитами qhb_upgrade следует выключить параметр logon_jobs выставив его в off и перезапустить СУБД. Иначе стабильность процесса обновления кластера не гарантируется.

Атрибутивный состав таблиц

для таблицы qhb_user_lockout доступны следующие столбцы:

Oid oid - идентификатор записи, первичный ключ;

Oid role_id - идентификатор пользователя из таблицы pg_catalog.pg_authid;

int4 invalid_attempts - счетчик ошибочных попыток ввода пароля, необходим для своевременной блокировки;

bool locked - признак блокировки пользователя role_id;

timestamp locked_since - дата и время начала блокировки;

timestamp last_activity - дата и время последнего захода в СУБД.

для таблицы qhb_auth_profile доступны следующие столбцы:

Oid oid - идентификатор записи, первичный ключ;

int4 max_failed_attempts - разрешенное данным профилем число ошибочных попыток ввода пароля;

bool auto_unlock - признак автоматической разблокировки пользователей данного профиля безопасности;

int4 lockout_duration_sec - длительность блокировки в секундах, при включенной автоматической разблокировке;

int4 inactivity_lockout_sec - опция блокировки по таймауту неактивности пользователей данного профиля, значение в секундах;

bool password_check - признак проверки задаваемых паролей на соответствие политикам безопасности профиля;

int4 password_len - опция задания минимальной длины пароля, значение в количестве символов;

int4 alphabet_size - опция задания минимальной мощности алфавита пароля.

для таблицы qhb_auth_profile_authid доступны следующие столбцы:

Oid role_id - идентификатор пользователя из таблицы pg_catalog.pg_authid, первичный ключ;

Oid profile_id идентификатор профиля из таблицы pg_catalog.qhb_auth_profile.

Процедуры SQL для управления профилями

Разблокировать всех пользователей для данного профиля

qhb_auth_profile_unlock_by_profile ( id_profile )

Где id_profile (тип - Oid) - идентификатор профиля.

Пример:

SELECT qhb_auth_profile_unlock_by_profile(9352);

Заблокировать всех пользователей для данного профиля

qhb_auth_profile_lockout_by_profile ( id_profile )

Где id_profile (тип - Oid) - идентификатор профиля.

Пример:

SELECT qhb_auth_profile_lockout_by_profile(9352);

Установить минимальную мощность алфавита пароля для данного профиля

qhb_auth_profile_set_password_alphabet_size ( id_profile,  total_char)

Где id_profile (тип - Oid) - идентификатор профиля. total_char (тип - INTEGER) - мощность алфавита (общее количество символов) или NULL для отключения проверки.

Пример:

SELECT qhb_auth_profile_set_password_alphabet_size(9352, 60);

Установить минимальную длину пароля для данного профиля

qhb_auth_profile_set_password_len ( id_profile,  pass_length)

Где id_profile (тип - Oid) - идентификатор профиля. pass_length (тип - INTEGER) - минимальная длина пароля или NULL для отключения проверки.

Пример:

SELECT qhb_auth_profile_set_password_len(9352, 6);

Отключить проверку сложности пароля в данном профиле

qhb_auth_profile_disable_password_check ( id_profile )

Где id_profile (тип - Oid) - идентификатор профиля.

Пример:

SELECT qhb_auth_profile_disable_password_check(9352);

Включить проверку сложности паролей в данном профиле

qhb_auth_profile_enable_password_check ( id_profile )

Где id_profile (тип - Oid) - идентификатор профиля.

Пример:

SELECT qhb_auth_profile_enable_password_check(9352);

Заблокировать данного пользователя

qhb_auth_profile_lockout_user ( id_user )

Где id_user (тип - Oid) - идентификатор пользователя.

Пример:

SELECT qhb_auth_profile_lockout_user(10);

Разблокировать данного пользователя

qhb_auth_profile_unlock_user ( id_user )

Где id_user (тип - Oid) - идентификатор пользователя.

Пример:

SELECT qhb_auth_profile_unlock_user(10);

Включить либо выключить автоматическую разблокировку пользователей данного профиля

qhb_auth_profile_set_auto_unlock ( id_profile,  status)

Где id_profile (тип - Oid) - идентификатор профиля. status (тип - BOOLEAN) - значение параметра.

Пример:

SELECT qhb_auth_profile_set_auto_unlock(9352, true);

Установить допустимое число ошибок при вводе пароля для пользователей данного профиля

qhb_auth_profile_set_max_failed_attempts ( id_profile,  num_errors)

Где id_profile (тип - Oid) - идентификатор профиля. num_errors (тип - INTEGER) - допустимое число ошибок при вводе пароля.

Пример:

SELECT qhb_auth_profile_set_max_failed_attempts(9352, 10);

Убрать проверку на максимальное число ошибок при вводе пароля для пользователей данного профиля

qhb_auth_profile_set_unlimited_attempts ( id_profile )

Где id_profile (тип - Oid) - идентификатор профиля.

Пример:

SELECT qhb_auth_profile_set_unlimited_attempts(9352);

Назначить данному пользователю профиль по умолчанию

qhb_auth_profile_unset ( id_user )

Где id_user (тип - Oid) - идентификатор пользователя.

Пример:

SELECT qhb_auth_profile_unset(10);

Назначить данному пользователю данный профиль безопасности

qhb_auth_profile_set ( id_user, id_profile )

Где id_user (тип - Oid) - идентификатор пользователя. id_profile (тип - Oid) - идентификатор профиля.

Пример:

SELECT qhb_auth_profile_set(10, 9352);

Удалить профиль безопасности

qhb_auth_profile_delete ( id_profile )

Где id_profile (тип - Oid) - идентификатор профиля.

Пример:

SELECT qhb_auth_profile_delete(1);

Создать новый профиль безопасности по данному шаблону

qhb_auth_profile_create ( id_profile )

Где id_profile (тип - Oid) - идентификатор профиля-образца.

Пример:

SELECT qhb_auth_profile_create(9352);

ВНИМАНИЕ!
В случае ручного управления таблицами каталога, при выполнении запросов SQL напрямую к таблицам pg_authid, qhb_auth_profile, qhb_user_lockout, qhb_auth_profile_authid, возможно нарушение стабильности работы подсистемы профилей безопасности и СУБД в целом. Эти таблицы являются таблицами разделяемого каталога, и поэтому не поддерживают триггеры для отслеживания изменений в них. Следовательно, логика была реализована в SQL процедурах, указанных в таблице выше.

Проверка паролей на соответствие политике профиля

Проверка паролей на сложность, требуемую политикой профиля, происходить только при установке пароля при включенном значении password_check в соответствующем профиле. При использовании администратором СУБД процедур для управления политиками сложности пароля, следует также задать новые пароли для всех пользователей, относящихся к профилю и разблокировать их для успешного входа. При ослаблении параметра сложности пароля блокировки не происходит и пользователь может зайти со своим прежним паролем.

Интеграция с ролями ИБ

Для пользователей, назначенных Администраторами СУБД (qhb_dbms_admin) доступно создание, изменение, удаление и назначение профилей безопасности. Также они имеют возможность подвергнуть других пользователей блокировке и разблокированию (за исключением других суперпользователей и Администраторов СУБД).

Для пользователей, назначенных Администраторами конкретных БД (qhb_db_admin) не предоставляется дополнительных полномочий, таких как создание, изменение, удаление и назначение профилей безопасности.

Замечания к релизу QHB версии 1.5.2

Добавлено

QSched: Автоматическая очистка журнала запуска по параметрам.

Qbim: Методы описания объектов, параметры установки расширения.

QCP: Добавлена возможность применения параметров без перезапуска QCP.

Сервер метрик: Добавлена группа мониторинга работы отдельных операторов SQL queryid.

Сервер метрик: Добавлена группа метрик mem_stat.

Сервер метрик: Добавлена SQL-функция metrics_config_show().

Профили безопасности: Добавлена возможность групповой настройки политик безопасности СУБД в отношении пользователей.

Контроль целостности: Добавлена возможность контроля целостности бинарных файлов qhb-core и contrib, а также процедур (программного кода).

Роли ИБ: Добавлены новые сущности: роли Администратора СУБД и Администраторов БД.

Поддержка дистрибутивов Fedora 36, 37, 38 и 39.

Улучшено

Обновлено ядро QHB, теперь оно основано на PostgreSQL 15.5.

Сервер метрик: Расширен список метрик группы db_stat.

backtrace Доработан фильтр фатальных ошибок. Для следующих видов ошибок исключена генерация файлов обратной трассировки:

неверный ввод пароля;

отсутствие пользователя;

отсутствие базы данных.

QCP: Добавлено описание подключения к СУБД.

Qbayes: расширение переведено в промышленную эксплуатацию.

qhb_audit:

переработан набор атрибутов событий аудита;

добавлены события ИБ:

создание учетных записей пользователей системы управления базами данных;

изменение атрибутов учетных записей пользователей системы управления базами данных;

запуск и остановка системы управления базами данных с указанием причины остановки;

изменение конфигурации системы управления базами данных;

восстановление базы данных;

изменение правил разграничения доступа в системе управления базами данных;

факты нарушения целостности объектов контроля;

создание и изменение процедур (программного кода), хранимых в базах данных, и представлений;

внедрена система ротации текстового лога событий аудита.

Добавлен параметр расширенной маскировки версии QHB.

Исправлено

Прочее

Добавлены ограничения в производительности СУБД при отсутствии лицензии.

Исправлены, устранены или проанализированы на отсутствие опасные состояния и уязвимости:

BDU:2024-03569;

BDU:2024-01121;

BDU:2023-07905;

BDU:2023-07904;

BDU:2023-07840;

BDU:2023-04944;

BDU:2023-04768;

BDU:2023-04767;

BDU:2023-03247;

BDU:2023-03024;

BDU:2023-02003;

CVE-2024-4317;

CVE-2024-0985;

CVE-2023-2455;

CVE-2023-2454;

CVE-2022-41862;

CVE-2023-39418;

CVE-2023-39417;

CVE-2023-5868;

CVE-2023-5869;

CVE-2023-5870;

CVE-2021-45707.

Прекращена поддержка дистрибутивов Fedora 33, 34 и 35.

Прекращена поддержка дистрибутива Astra Linux SE 1.6.

Прекращена поддержка дистрибутива Debian 9.

Замечания к релизу QHB версии 1.5.1

Добавлено

Модуль регистрации событий безопасности qhb_audit.

Расширенные функции для работы с XML.

В утилиту pgbench добавлен флаг для инициализации зашифрованных таблиц.

Сервер метрик: Поддержка Prometheus.

Сервер метрик: Поддержка Vector.

Поддержка дистрибутива Debian 10.

Поддержка дистрибутива RedOS 7.

Поддержка дистрибутива Astra Linux SE 1.7.

Поддержка дистрибутивов Ubuntu 18.04, 22.04.

Поддержка дистрибутива openSUSE Leap 15.4.

QDLM: Модуль загрузки данных метрик в базу данных.

Qbackup: Удаленное восстановление.

Qbackup: Кумулятивный бэкап.

Qbackup: Сжатый инкрементальный бэкап.

Qbackup: Сжатый удаленный инкрементальный бэкап.

Qbackup: Резервное копирование с ведомой ноды.

Поддержка сторонних расширений:

mysql_fdw (Обертка внешних данных MySQL для QHB);

Orafce (Функции совместимости и пакеты Oracle);

pg_hint_plan (Модуль настройки планов выполнения с помощью «подсказок»);

pg_repack (Модуль реорганизации таблиц с минимальными блокировками);

pg_store_plans (Модуль для отслеживания статистики плана выполнения операторов SQL);

pgtt (Модуль управления глобальными временными таблицами);

plpgsql_check (Модуль расширенной проверки plpgsql);

sr_plan (Модуль управления хранением планов запросов в QHB).

Улучшено

Перенесены некоторые оптимизации сортировки из PostgreSQL 15.

Перенесены некоторые оптимизации по работе с файлами из PostgreSQL 15.

QDL: Множественные улучшения, существенная переработка.

Сервер метрик: Оптимизация, существенное снижение потребления ресурсов (CPU).

Сервер метрик: Возможность перезапускать сервер метрик без перезапуска QHB.

Сервер метрик: Добавлена группа «Встроенные метрики».

добавлена возможность использования жестких ссылок при обновлении QHB через qhb_upgrade.

добавлен параметр конфигурации для включения/отключения файлов трассировки.

добавлен параметр конфигурации для запуска планировщика при старте СУБД.

добавлен параметр конфигурации для запуска брокера очереди сообщений при старте СУБД.

Исправлено

QDL: Расчет контрольных сумм в QDL.

Qbackup: Множественные исправления в резервном копировании.

Qbackup: Стабилизация работы CDC.

Прочее

Исправлены, устранены или проанализированы на отсутствие опасные состояния и уязвимости:
BDU:2019-01231, BDU:2019-04174, BDU:2021-05535, BDU:2021-05857, BDU:2021-05996, BDU:2022-00821, BDU:2022-02629, BDU:2022-02928, BDU:2022-03872, BDU:2022-04971, BDU:2022-05174, BDU:2022-07190, CVE-2021-23214, CVE-2021-23222, CVE-2021-43767, CVE-2022-1552, CVE-2022-25852, CVE-2022-2625.

Замечания к релизу QHB версии 1.5.0

Добавлено

Расширение QHB Part_Import, частичный импорт файлов данных из «внешней» БД.

Переменные уровня сеанса — qhb_variables.

Qbackup: Возможность валидировать резервные копии (команда validate).

Qbackup: Возможность удаления WAL-файлов при удалении резервной копии.

Поддержка отслеживания статуса выполнения запросов с помощью qhb_running_queries() и qhb_dump_qep(<query_id>).

Экспериментальная поддержка работы планировщика заданий QSched.

Поддержка дистрибутива Fedora 35.

Поддержка дистрибутива Альт 8 СП.

QDL: Новые типы данных.

QDL: API для загрузки данных без использования CSV.

Улучшено

Обновлено ядро QHB, теперь оно основано на PG 14.

Qbackup: Производительность сжатия резервных копий.

Qbayes: добавлены параметры, ограничивающие «коммутативный взрыв».

Qbayes: добавлен тип discrete_integer (перечислимый тип данных) в описание модели.

Устранена уязвимость в процессе сброса WAL - урегулирована параметром wal_after_error_sleep_delay.

QDL: Убрано ограничение в 8Кб на размер данных.

Исправлено

Добавлены mock-файлы описания расширений для 2B для исправления сценариев платформы «1С:Предприятие».

Удалено

Пользовательская интерактивная утилита для работы из командной строки qsql удалена. Рекомендовано использовать утилиту psql.

Замечания к релизу QHB версии 1.4.0

Добавлено

Поддержка дистрибутива Fedora 34.

Расширение QHB Qbayes, методы статистического анализа на основе условных вероятностей Байеса.

Модуль QHB Disk Cache Dump, предоставляющий механизм сохранения и восстановления содержимого дискового кеша.

Qbackup: Поддержка удаленного резервного копирования с использованием протокола репликации.

Qbackup: Подкоманда replica для создания реплики сервера.

Qbackup: Опции работы с паролями (диалоговый режим).

Qbackup: Обновлена структура каталога.

Qbackup: Поддержка табличных пространств.

Qbackup: Поддержка CDC (Change Data Capture, захват изменения данных) для инкрементальных резервных копий.

Qbackup: Возможность быстрой контрольной точки перед резервным копированием.

Qbackup: Опция, позволяющая базировать новые инкрементальные резервные копии на последних удачно завершившихся копиях.

Qbackup: Возможность указать комментарий для резервной копии при ее создании.

Qbackup: Расширены возможности команды remove.

QDL: Механизм совместимости с CDC.

QDL: Поддержка получения данных из stdin.

QCP: Поддержка списка stand_by серверов для автоматического переключения на резервный сервер в случае потери связи с текущим сервером БД.

Улучшено

Qbackup: В 2 раза ускорено восстановление для инкрементальных резервных копий.

QDL: Значительно улучшена производительность многопоточного режима.

QDL: Улучшены сообщения об ошибках разбора CSV.

QSS: Повышение безопасности. Функционирование в отдельном процессе.

QSS: Унификация администрирования пользователей и управление ключами.

Исправлено

Qbackup: Исправлено отображение информации в некоторых ошибках.

Qbackup: Возможность удаления информации о резервных копиях без директории каталога.

QDL: Из файла конфигурации удалено поле encoding (было помечено как устаревшее в релизе 1.2.0).

Замечания к релизу QHB версии 1.3.1

Добавлено

Поддержка дистрибутива ROSA Enterprise Linux Server (RELS) 7.

Исправлено

Исправлены ошибки расширения Qbim, возникающие в некоторых ситуациях при изменении версий объекта.

qhb_upgrade: убрана ошибочная проверка на количество пользователей в обновляемом кластере.

QCP: Добавлено отображение параметра application_name входящих клиентов в режиме Debug.

Исправления для повышения стабильности 2B.

Другие незначительные исправления.

Замечания к релизу QHB версии 1.3.0

Добавлено

Поддержка таблиц с параметром HOLDMEM = ONLY.

Множество новых метрик в metricsd.

Утилита magma_key_gen для генерации пользовательских ключей QSS на крипто-токенах, поддерживающих аппаратное шифрование по ГОСТ 34.12-2018 и ГОСТ 34.13-2018.

QDL: Поддержка типа UUID.

QDL: Широкая поддержка специальных значений для типов date и timestamp: Epoch, Infinity, -Infinity.

Qbackup: Механизмы поддержки и актуализации предыдущих версий каталога.

Qbackup: В каталог добавлена информация о версии базы данных и каталога версий.

QCP: Отмена текущего запроса на сервере при отключении клиента.

QCP: «Проброс» запроса на отмену текущего запроса от клиента к конечному серверу

QCP: Автоматическая отмена всех текущих запросов при остановке QCP.

QCP: Отправка heartbeat-сообщений неактивным подключениям к БД.

QCP: Клиенты могут подключаться к QCP через сокеты домена Unix.

Улучшено

Оптимизирована работа с памятью в Tarq.

Локализация в qhb_upgrade.

MChar в 2B научился работать с like escape.

QDL: Зафиксирован формат записи для типов float, double precision, numeric.

QDL: Увеличена производительность генератора значений NULL.

QDL: Увеличена производительность анализатора значений с плавающей запятой и numeric.

QDL: Уменьшен размер исполняемого файла за счет удаления легаси-кода.

QDL: Упрощен генератор скрипта для create_table, уменьшена вероятность ошибок времени исполнения.

QCP: Более гибкая настройка логирования.

QCP: Более подробные сообщения об ошибках, возникающих в процессе работы.

Исправлено

Утечки в вакууме при использовании Tarq.

Параметры настройки rbytea.

Ошибка неправильного параметра auth в qhb_bootstrap под названием scram_sha_256.

В 2B удалена рекурсия из MChar like.

shared_buffers_partitions: правильная обработка параметра.

Запуск qhb_upgrade от пользователя root (запрещен).

Ошибка при инициализации кластера с помощью qhb_bootstrap при указании флага --waldir.

Qbackup: Ошибка повторяющегося идентификатора копии при быстром последовательном создании нескольких резервных копий.

Qbackup: Ошибка несовпадения директории каталога копий с метаинформацией каталога при ручном перемещении файла каталога.

Qbackup: Пути в метаинформации каталога теперь всегда абсолютные.

Qbackup: Ошибки во время резервного копирования теперь всегда отражаются в статусе копии.

QCP: qcp, и qcp-ctrl при ошибках теперь возвращают ненулевой код завершения

Замечания к релизу QHB версии 1.2.0

Добавлено

QHB 2B: Расширения QHB для поддержки 1С. Поддерживается платформа «1С:Предприятие», начиная с версии 8.3.18.

Утилита qhb_upgrade, обеспечивающая миграцию базы данных из предыдущих версий QHB и PostgreSQL версии 12.

Поддержка дистрибутивов Fedora 33.

Улучшено

Проверена работоспособность имеющихся пакетов в дистрибутиве Альт Сервер 9.

Замечания к релизу QHB версии 1.1.0

QHB Plan Cache

Экспериментальная функция выключена по умолчанию, добавлена для проверки стабильности и работоспособности.

Новая функция

QHB Bootstrap

Система инициализации базы, функциональный аналог initdb, полностью переделан для учета особенностей QHB.

Новая функция

QSS

Модуль безопасного хранения (шифрования с поддержкой криптоалгоритма «Кузнечик»).

QSS пополнился функциями qhb_get_qss_tables и qhb_get_qss_indexes.

Улучшился вывод в случае ошибки.

Добавился новый режим работы с параллельным набором ключей, qss_recrypt с командой сбора зашифрованных таблиц и их индексов.

Добавлена поддержка base64-encoding ключей, «Кузнечика» в CTR режиме.

Добавлена поддержка rbytea.

Добавлена утилита QSS Pinpad.

Добавлена поддержка RuToken.

QSQL

Пользовательская интерактивная утилита для работы из командной строки.

Поддержка мета-команд \password и \include.

Append Only Storage

Данное хранилище позволяет только добавлять записи, но делает это с максимальной скоростью, поскольку не выполняется полноценного MVCC анализа.

Предназначено для таблиц, с которыми не производятся модификации, например, журналов, данных с датчиков и т. п.

Новая функция

QBackup

Добавлена новая подсистема резервного копирования и восстановления с поддержкой каталога, архивации резервных копий и сохранения «разности» вместо полной резервной копии.

QDL

Добавлено

Параллельный режим работы QDL.

Пользовательская документация.

Демонстрационный файл конфигурации в пакете RPM.

Изменено

Для того чтобы создать базу, больше не нужен результат работы с СУБД.

Стабилизирована поддержка основных типов.

Переделана работа со страницами и кортежами для улучшения стабильности.

Стандартизировано API записи во всех компонентах QDL.

Исправлено

Ряд системных функций, работа которых приводила к записи некорректных размеров кортежей в метаинформацию страницы.

Утилиты

qhb_archivecleanup: обновлены зависимости и добавлена русская локализация.

qhb_checksum: добавлен режим параллельной работы и нормальный вывод прогресс-бара с флагом --verbose.

qhb_checksum: теперь корректно работает с файлами больших размеров.

Реализована утилита createdb.

Реализована утилита dropdb.

Метрики

Подсистема сбора и предоставления для внешнего отображения системных метрик. Предоставление пока только в grafana.

Метрики добавлены в буфер-менеджер TARQ.

grafana больше не инициализирует время выполнения под каждый вызов with_grafana.

Перещелкивание «часов» буфера назад при переводе системных часов.

Прочие улучшения

Оптимизирован буфер-менеджер TARQ.

Из релизной сборки удалены debug-символы.

Исправлены параметры сборки, приводившие к снижению производительности.

Реализация чекпоинтера на Rust.

Убраны многие раздражающие ограничения и устранены критические ошибки.