Утилита Pangolin Inplace Upgrade. Утилита для обновления системных каталогов Pangolin без изменения структуры данных
Утилита Pangolin Inplace Upgrade отдельный компонент, упакованный в стандартный rpm/deb-пакет. Такой подход обеспечивает удобную эксплуатацию утилиты, а также ее сопровождение.
Расположение файлов утилиты
Файлы утилиты размещаются в директории /opt/pangolin-utilities/bin. Права на каталоги и файлы при установке rpm/deb-пакета устанавливаются следующим образом:
- каталоги
/opt/pangolin-utilities/*: владелец и группа — postgres, права — 700. - файлы
/opt/pangolin-utilities/*: владелец и группа — postgres, права — 400. /opt/pangolin-utilities/bin/pg_inplace_upgrade/inplace_upgrade.sh: владелец и группа — postgres, права — 500./opt/pangolin-utilities/bin/pg_inplace_upgrade/update_catalog_version: владелец и группа — postgres, права — 500./opt/pangolin-utilities/bin/pg_inplace_upgrade/verify_pg_catalog.py: владелец и группа — postgres, права — 500.
Если во время установки пакетаpangolin-inplace-upgrade учетная запись пользователя postgres отсутствует, она будет создана автоматически.
В процессе сборки rpm/deb-пакета включается автоматический сбор всех runtime-зависимостей на этапе компиляции. Это позволяет гарантировать работоспособность утилиты на целевых системах без необходимости дополнительной установки пакетов. Списки необходимых runtime-зависимостей приведены в разделе«Список пакетных зависимостей, необходимых для сопровождения Pangolin».
Описание решения
Для выполнения задачи обновления каталога используется утилита pangolin-inplace-upgrade, в основе скрипт inplace_upgrade.sh, который применяет:
- заранее подготовленные разработчиками SQL-скрипты обновления системного каталога (правила написания приведены в разделе «Правила написания SQL-скриптов для обновления системных данных каталога»);
- утилиту
update_catalog_versionдля изменения версии каталога вglobal/pg_control.
Скрипт обновления, и все что он использует, устанавливается в директорию /opt/pangolin-utilities/bin/pg_inplace_upgrade из поставляемого дистрибутива.
Скрипт inplace_upgrade.sh, прилагаемые SQL-скрипты обновления и утилита update_catalog_version являются необходимыми для корректной работы. Изменение/удаление любого из них приведет к неправильной работе утилиты.
Для работы утилиты необходимы пакеты компонентов той версии, на которую будет совершаться обновление.
При обновлении каталога меняется содержимое системных таблиц pg_catalog (таких, как pg_class, pg_type и т.д.). Сами системные таблицы и индексы в данном типе обновления не затрагиваются. Объекты базы данных (функции, представления, типы) описаны в системном каталоге и хранятся в виде записей его таблиц. Добавлять или изменять можно только объекты по OID до 16383 включительно. Все объекты OID которых выше 16383 не должны меняться с помощью SQL-скриптов обновления, так как они относятся к пользовательским объектам, изменение которых не предусмотрено.
inplace_upgrade.sh использует функцию ядра block_user_data_modification для установки ограничений на следующий ряд SQL-операций на время ее работы:
DROP TYPE/FUNCTION/VIEW;ALTER TYPE/FUNCTION/VIEW ... RENAME;CREATE OR REPLACE FUNCTION/VIEW, кромеCREATE FUNCTION/VIEW.
Вышеперечисленные ограничения можно при необходимости снять, тем самым разрешив любые изменения системного и пользовательских каталогов, но это увеличит вероятность порчи пользовательских данных и системного каталога в случае наличия ошибок в SQL скриптах. Снятие ограничений производится флагом --drop-on.
По сравнению с pg_upgrade инструмент (inplace_upgrade.sh) работает гораздо быстрее, так как не выполняет полную выгрузку и загрузку данных системного каталога.
Скрипт inplace_upgrade.sh
Обновление начинается с актуализации номера версии системного каталога в global/pg_control и переименования каталогов с пользовательскими табличными пространствами PG_<MAJOR_POSTGRESQL>_<CATALOG_VERSION_NO>. Далее скрипт обновляет системный каталог для всех баз данных СУБД в два этапа:
-
На первом этапе скрипт запускается последовательно во всех базах данных с последующим откатом транзакции (ROLLBACK):
-
Если тестовая загрузка проходит успешно, начинается второй этап загрузки скриптов, также последовательно во всех базах данных, но уже с фиксацией изменений (COMMIT).
-
Если тестовая загрузка завершается с ошибкой, второй этап не запускается, и скрипт выполняет откат версии каталога и возвращает названия каталогов с табличными пространствами.
- Если второй этап загрузки завершается с ошибкой, скрипт также выполняет откат версии каталога и возвращает названия каталогов с табличными пространствами.
-
При вызове скрипта inplace_upgrade.sh используются ключи:
-
Обязательные ключи:
-
-s | --utildir– директория со скриптомinplace_upgrade.sh, утилитойupdate_catalog_versionи папкойsql_upgrade_6xx, содержащей SQL-скрипты для обновленияpg_catalog. SQL-скрипты пишутся разработчиками в соответствии изменениями вpg_catalog, которые вносят их доработки. В случае стандартной установки данный параметр имеет значение/opt/pangolin-utilities/bin/pg_inplace_upgrade; -
-d | --pgdatadir– путь к директории с данными (обычно имеет такое же значение, как и$PGDATA); -
-l | --logdir– директория для сохранения логов:- логи PostgreSQL, сгенерированные в процессе обновления, записываются в файл
postgres_update.log; - полный лог работы скрипта обновления
inplace_upgrade.shзаписывается в файлinplace_upgrade.log; - короткий отчет обновления
report.log;
- логи PostgreSQL, сгенерированные в процессе обновления, записываются в файл
-
-h | --host– хост СУБД для подключенияpg_dumpиpsql; -
-p | --port– порт СУБД для подключенияpg_dump,pg_ctlиpsql; -
-u | --user– пользователь СУБД с правами суперпользователя для подключенияpg_dumpиpsql; -
-n | --old-version– текущая версия продукта (например 6.1.8 для СУБД Pangolin 6.1.8). Формат строки проверяется; -
-N | --new-version– версия продукта на которую происходит обновление (например 6.4.0 для СУБД Pangolin 6.4.0). Формат строки проверяется; -
-b | --dbname– имя базы данных для подключенияpg_dumpиpsql(обычно имеет значениеpostgres); -
-m | --dumpdir– директория для сохранения дампов (sql-дампы до, после и после отката обновления); -
-B | --backupdir– директория для сохранения:- резервной копии файлов системного каталога;
- файлов с перечнем таблиц и индексов системного каталога и информацией о соответствующих им файлах;
- файл
user_tbls.txtс директориями пользовательских табличных пространств; - файл
version.txtс версией PostgreSQL до обновления;
-
-t | --pg_ctldir– директория с исполняемыми файлами утилитpg_ctlиpostgresновой версии; -
-T | --pg_utildir– директория с исполняемыми файлами утилитpsqlиpg_dumpновой версии.
-
Необязательные ключи:
-
-P | --password– пароль для подключения к СУБД. Если не задан, то пароль берется из переменной окруженияPASSWORD; -
-r | --replica– ключ указывается при запуске утилиты обновления на реплике; -
-D | --drop_on– флаг, разрешающий операции, запрещенные функциейblock_user_data_modification; -
-k | --test-skip– флаг, устанавливается для отключения запуска тестов системного каталога; -
-a | --add-tables– флаг для выполнения резервного копирования таблицpg_largeobject,pg_largeobject_metadataпри необходимости; -
-V | --version– версия утилиты; -
-C | --compress– степень сжатия данных резервной копии и дампов системного каталога алгоритмом gzip. Может иметь значение от 1 до 9, которое обозначает степень сжатия. Если ключ-Cотсутствует, то сжатие не выполняется. Ключ необходимо указывать в режиме info. -
-M | --dump-null– флаг, указывающий формировать дампы, после обновления системного каталога, без сохранения их на диске. Ключ необходимо указывать в режиме info. -
-F | --extra-free-size– флаг для задания дополнительного запаса свободного места на диске (в МБ). Значение по-умолчанию1024МБ.Резерв необходим, так как в процессе обновления создаются временные служебные файлы, а размер дампов системного каталога может измениться. В режиме info указанный объем будет добавлен к расчетному размеру:
- бэкапа;
- суммарного размера дампов системного каталога всех баз данных до обновления системного каталога;
- суммарного размера дампов системного каталога всех баз данных после его обновления.
-
-? | --help– справка.
Скрипт inplace_upgrade.sh создает информационные и служебные файлы, которые не учитываются при оценке необходимого свободного пространства для бэкапа и дампов в режиме info. Накладные расходы зависят от количества баз данных и размера их системных каталогов (в среднем не превышает 100 КБ на одну базу данных, но в редких случаях может быть больше, например если системный каталог сильно превышает 1ГБ). Значения по умолчанию в 1024 МБ должно быть достаточно для того что бы хватило дискового пространства для служебных файлов в большинстве случаев, но флаг -F | --extra-free-size позволяет в случае необходимости скорректировать оценку дополнительного свободного пространства.
Команды:
info– команда проверки актуальности обновления и формирования информационных файловuser_tbls.txt,version.txtдля последующего запуска обновления;update– команда запуска обновления;reset– команда отката обновления. Откат возможен до первого запуска базы с доступом пользователей.
Требования для запуска
Пользователь должен иметь права на запуск скрипта обновления inplace_upgrade.sh. Также этот пользователь должен иметь права суперпользователя на доступ к СУБД.
Проверьте, что установленная локаль на узлах соответствует: LANG: en_US.UTF-8, LC_ALL: en_US.UTF-8.
Режимы работы скрипта
Существуют три режима работы скрипта inplace_upgrade.sh:
info– проверка возможности обновления;update– обновление;reset– откат обновления;
inplace_upgrade.sh в режиме info
В режиме info скрипт осуществляет проверку возможности обновления (наличие утилиты update_catalog_version, папок с SQL-скриптами и всех необходимых прав доступа к ним) и необходимость обновления для новой версии СУБД. Производится проверка необходимого свободного пространства для дальнейшего формирования бэкапа таблиц системного каталога и дампов системного каталога, обновляемых баз данных. Также скрипт создает файлы:
user_tbls.txt– содержит список директорий пользовательских табличных пространств для последующего их обновления скриптом в режимеupdate;version.txt– используется для сохранения значения мажорной версии PostgreSQL (MAJOR_POSTGRESQL), которая является элементом пути в пользовательском табличном пространствеPG_<MAJOR_POSTGRESQL>_<CATALOG_VERSION_NO>;size_backups.txt– содержит информацию о занимаемом месте бэкапом;size_dumps.txt– содержит информацию о занимаемом месте дампами.
Для кластерной конфигурации необходимо запускать скрипт в режиме info и на мастере и на реплике.
Запуск скрипта осуществляется при включенной СУБД.
Пример запуска скрипта:
./inplace_upgrade.sh -d <pgdata> -l <директория логов> -s <директория с утилитой> -B <директория резервной копии> -p <порт> -h <хост> -u <суперпользователь> -b <база данных для подключения по умолчанию> -n <текущая версия продукта> -N <новая версия продукта> info --t <директория утилит postgres and pg_ctl> -T <директория утилит psql and pg_dump>
Ограничения
Упаковка файлов утилиты в rpm/deb-пакет производится без предварительного этапа компиляции.
Необходим доступ к папке sql_upgrade_6xx, ее внутренним папкам и доступ на чтение ко всем файлам внутри папок для администратора СУБД, запускающего утилиту. СУБД должна быть запущена со старыми бинарными файлами.
Коды возврата
Предусмотренные коды:
0– вызов утилиты требуется;1– провал запуска утилиты, утилита не может быть запущена корректно;2– вызов утилиты в режимеupdateне требуется.
inplace_upgrade.sh в режиме update
В режиме update скрипт осуществляет обновление таблиц схемы pg_catalog, номера версии системного каталога в pg_control и табличных пространств пользователей.
Пример запуска скрипта:
./inplace_upgrade.sh -d <pgdata> -l <директория логов> -p <порт> -h <хост> -u <суперпользователь> -b <база данных для подключения по умолчанию> -s <директория с утилитой> -m <директория для sql-дампов> -B <директория бэкапа> -n <текущая версия продукта> -N <новая версия продукта> update -t <директория утилит postgres and pg_ctl> -T <директория утилит psql and pg_dump>
Процесс восстановления СУБД после неудачного окончания работы скрипта в режиме update описан в разделе «Восстановление после неудачного обновления исполняемых файлов».
Ограничения
Перед запуском обновления СУБД должна быть остановлена. Доступ для обычных пользователей должен быть ограничен, чтобы не было проблем при снятии дампов системных каталогов (не менялись системные таблицы pg_class, pg_type, pg_proc и другие) и последующем их анализе, если потребуется при ручном восстановлении СУБД. Бинарные файлы СУБД должны быть новой версии и должны содержать необходимые системные объекты (если требуется). В противном случае может возникнуть проблема как в примере ниже.
Пример отсутствия функции в ядре (при ее добавлении может быть ошибка в транзакции):
"ERROR: Fail script: ERROR: there is no built-in function named "<name_function>"", "CONTEXT: SQL statement "CREATE FUNCTION pg_catalog.name_function()"
Коды возврата
Предусмотренные коды:
0– успех;1– провал запуска утилиты, требуется ручное восстановление СУБД;4– требуется восстановление файлов из резервной копии системного каталога, также необходим откат исполняемых файлов СУБД к исходной версии;5– изменения не применились, необходим откат исполняемых файлов СУБД к исходной версии.
inplace_upgrade.sh в режиме reset
В режиме reset скрипт осуществляет восстановление pg_catalog и номера каталога СУБД.
Пример запуска скрипта:
./inplace_upgrade.sh -d <pgdata> -l <директория логов> -p <порт> -h <хост> -u <суперпользователь> -b <база данных для подключения по умолчанию> -s <директория с утилитой> -m <директория для sql-дампов> -B <директория бэкапа> reset -t <директория утилит postgres and pg_ctl> -T <директория утилит psql and pg_dump>
Процесс восстановления СУБД после неудачного окончания работы скрипта в режиме reset описан в разделе «Ручное восстановление системного каталога СУБД».
Ограничения
Должен быть доступ к папке, указанной в --backupdir и доступ на чтение к файлам, внутри этой папки.
Перед запуском восстановления СУБД должна быть остановлена. Доступ для обычных пользователей должен быть ограничен.
Коды возврата
0– успех;1– провал запуска утилиты, требуется ручное восстановление СУБД.
Утилита update_catalog_version
Пользователь, запускающий обновление, должен иметь права на исполнение утилиты update_catalog_version.
Утилита update_catalog_version производит сравнение версии текущего каталога в файле pg_control (его расположение по-умолчанию: $PGDATA/global/pg_control) и версии, на которую планируется обновление. Также утилита проверяет наличие файла резервной копии pg_control.bak в директории, указанной через опцию --backup. Если такой файл уже существует (что может свидетельствовать о повторном запуске), утилита завершает работу, выводит сообщение об ошибке и возвращает код ошибки 1.
В случае совпадения текущей версии каталога с целевой версией обновление не производится, и утилита завершает работу с кодом 2. Если новая версия больше текущей, выполняется резервное копирование pg_control в файл pg_control.bak (расположенный в директории, указанной в --backup), после чего обновляется версия каталога в файле pg_control. При успешном обновлении утилита завершает работу и возвращает код 0.
Если во время обновления версии возникает ошибка, восстанавливается предыдущая версия каталога из резервной копии, и утилита завершает работу с кодом 1. В случае невозможности восстановления из резервной копии возвращается код 3. Если новая версия каталога меньше текущей, обновление не выполняется, выводится сообщение об ошибке, и возвращается код 1.
Для принудительного понижения версии каталога используется опция --force (-f). В этом случае не выполняются проверки на увеличение версии каталога, не проверяется наличие резервной копии, а файл резервной копии перезаписывается. В случае отказа от обновления версии каталога, бинарные файлы СУБД не смогут быть запущены.
Ключи утилиты update_catalog_version:
-D,--pgdatadir– директория с СУБД (PGDATA);-b,--backupdir– директория для сохранения резервной копии файлаpg_control;-C,--catalog-version-new– новая версия системного каталога (на которую происходит обновление) (CATALOG_VERSION_NO);-c,--catalog-version-old– старая версия системного каталога (с которой происходит обновление) (CATALOG_VERSION_NO);-f,--force– флаг принудительной смены версии;-y,--dry-run– флаг проверки соответствия версии текущего каталога обновлению, без его изменения;-V,--version– флаг печати версии утилиты;-?,--help– флаг печати справки об утилите.
Режимы работы утилиты update_catalog_version
Работа утилиты без --force
Утилита запускается с ключами:
update_catalog_version -c <catalog_version_number_old> -C <catalog_version_number_new> -b <dir_backup> -D <PGDATA>
Алгоритм:
- Проверяется, заданы ли все основные параметры (
-D,-c,-C,-b). Если хотя бы один из них не задан, выводится сообщение вstdoutи возвращается код1. - Проверяется, не запущен ли PostgreSQL. Если он запущен, выводится сообщение об ошибке и возвращается код
1. - Проверяется, не понижается ли версия каталога. Если версия понижается, выводится сообщение об ошибке и возвращается код
1. - Проверяется наличие файла резервной копии
pg_control.bak. Если файл существует, выводится сообщение об ошибке и возвращается код1. Это необходимо для предотвращения повторного ошибочного обновления. - Проверяется соответствие версии текущего каталога версии, переданной с ключом
-c. Если версии не совпадают, выводится сообщение об ошибке и возвращается код1. - Проверяется соответствие версии текущего каталога версии, переданной с ключом
-C. Если версии совпадают, выводится сообщение соответствия версий и возвращается код2. - Формируется файл резервной копии
pg_control.bak. Если файл не был сформирован, выводится сообщение об ошибке и возвращается код1. - Обновляется версия системного каталога в файле
pg_control. Если произойдет ошибка, выполняется восстановление файлаpg_controlиз резервной копии, выводится сообщение об ошибке и возвращается код1. Если восстановление файлаpg_controlне удается, выводится сообщение об ошибке и возвращается код3.
В этом режиме обеспечивается максимальная защита: понижение версии запрещено, повторные запуски блокируются, все ошибки восстанавливают исходное состояние.
Работа утилиты с --force
update_catalog_version -c <catalog_version_number_old> -C <catalog_version_number_new> -b <dir_backup> -D <PGDATA> -f
Алгоритм:
- Проверяется, заданы ли все основные параметры (
-D,-c,-C,-b). Если хотя бы один из них не задан, выводится сообщение вstdoutи возвращается код1. - Проверяется, не запущен ли PostgreSQL. Если он запущен, выводится сообщение об ошибке и возвращается код
1. - Проверяется соответствие версии текущего каталога версии, переданной с ключом
-c. Если версии не совпадают, выводится сообщение об ошибке и возвращается код1. - Проверяется соответствие версии текущего каталога версии, переданной с ключом
-C. Если версии совпадают, выводится сообщение соответствия версий и возвращается код2. - Формируется файл резервной копии
pg_control.bak. Если файл не был сформирован, выводится сообщение об ошибке и возвращается код возврата1. - Обновляется версия системного каталога в файле
pg_control. Если произойдет ошибка, выполняется восстановление файлаpg_controlиз резервной копии, выводится сообщение об ошибке и возвращается код1. Если восстановление файлаpg_controlне удается, выводится сообщение об ошибке и возвращается код3.
Работа утилиты с --dry-run
update_catalog_version -c <catalog_version_number_old> -C <catalog_version_number_new> -b <dir_backup> -D <PGDATA> -y
Алгоритм:
- Проверяется, заданы ли все основные параметры (
-D,-c,-C,-b). Если хотя бы один из них не задан, выводится сообщение вstdoutи возвращается код1. - Проверяется, не понижается ли версия каталога. Если версия понижается, выводится сообщение об ошибке и возвращается код
1. - Проверяется наличие файла резервной копии
pg_control.bak. Если файл существует, выводится сообщение об ошибке и возвращается код1. Это необходимо для предотвращения повторного ошибочного обновления. - Проверяется соответствие версии текущего каталога версии, переданной с ключом
-c. Если версии не совпадают, выводится сообщение об ошибке и возвращается код1. - Проверяется соответствие версии текущего каталога версии, переданной с ключом
-C. Если версии совпадают, выводится сообщение соответствия версий и возвращается код2. Если версии не совпадают, выводится сообщение о возможности обновления и возвращается код0.
Ограничения
Перед запуском update_catalog_version для изменения версии каталога СУБД должна быть остановлена. Доступ для обычных пользователей должен быть ограничен.
Коды возврата утилиты update_catalog_version
Предусмотренные коды:
0– успешное завершение обновления версии каталога (в режиме проверки--dry-runданный код означает, что обновление возможно);1– ошибка обновления версии каталога, файлpg_controlне изменился;2– обновление каталога не требуется (в режиме проверки--dry-runданный код означает, что обновление не требуется);3– ошибка обновления версии каталога, файлpg_controlбыл изменен, требуется ручное восстановление из файла резервной копииpg_control.bak.
Правила написания SQL-скриптов для обновления системных данных каталога
Разработчикам необходимо придерживаться правил написания SQL-скриптов обновления:
- SQL-скрипты должны иметь права на чтение пользователем, запускающим обновление.
- На данный момент возможно добавление в системный каталог объектов: functions, view, type. Добавление других объектов возможно, но на текущий момент это не проверялось.
- Системные объекты (например, функции), с
OID<10000добавляются по заданному системному OID. Если задаваемый OID уже занят другим системным объектом, то возникнет конфликт, и транзакция откатится с указанием конфликта OID. - Для некоторых системных объектов добавление по заданному OID невозможно, так как они инициализируются в СУБД при вызове
initdb, а при обновлении данной утилиты должны добавляться по свободным системных OID в диапазоне12000<=OID<16384, чтобы не вызвать конфликтов с существующими системными объектами СУБД.
Написание скриптов должно проводиться строго в соответствии с описанными далее шаблонам, отклонение от шаблонов может привести к порче объектов системного каталога.
При создании новых объектов системного каталога необходимо точно знать какие таблицы системного каталога задействуются при создании этого объекта и учесть это в скрипте обновления.
Скрипты должны предоставлять диагностическую информацию об изменяемых данных на случай возникновения проблем обновления и необходимости их ручного восстановления.
При создании новой функции обновляется таблица pg_proc системного каталога.
Шаблоны
SQL-скрипты для обновления пишутся разработчиками по шаблонам, описанным в данном разделе.
При обновлении системного каталога утилита возвращает стандартные привилегии на следующие функции:
select oid, pronamespace::regnamespace, proname, proacl from pg_proc where oid in (5556, 8668, 9816, 9817, 9875,8669);
oid | pronamespace | proname | proacl
------+--------------+-------------------------------------+--------
5556 | pg_catalog | stirhandler |
8668 | pg_catalog | get_last_wal_key_rotation_time |
8669 | pg_catalog | get_last_master_key_rotation_time |
9816 | pg_catalog | pg_available_extensions_ext |
9817 | pg_catalog | pg_available_extension_versions_ext |
9875 | pg_catalog | pangolin_check_enterprise_license |
Это связано с тем, что они могли получить отличные от стандартных привилегии в случае наличия в БД дефолтных привилегий (DEFAULT PRIVILEGES).
Для данных функций необходимо еше раз выдать пользовательские привилегии, если они необходимы.
Для функций
-
Для функций добавляемых в исходные файлы:
postgresql/src/include/catalog/pg_proc.dat;postgresql/src/include/catalog/pg_proc.pangolin.dat;postgresql/src/include/catalog/pg_proc.xid64.dat.
OID задается в диапазоне от 0 до 9999 включительно. Условия проверки функции перед ее созданием пишутся разработчиками в соответствии с их задачами. Удаление функции не рекомендуется, так как OID системных функций находится в диапазоне от 1 до 9999, а в данном диапазоне удаление функции производится без учета зависимостей, относящихся к этой функции.
-
Для функций добавляемых в исходный файл
postgresql/src/backend/catalog/system_functions.sqlOID задается в диапазоне от 12000 до 16383 включительно.При создании нового представления обновляются таблицы
pg_class,pg_type,pg_rewriteсистемного каталога, которые необходимо указать в соответствии с описанным далее шаблоном. Также меняется ряд таблиц системного каталога, которые не нужно явно указывать в SQL-скрипте обновления, но необходимо учесть в тесте системного каталога базы данных после обновления.
Шаблон:
-- Создать функцию с OID < 10000,
-- либо 12000 <= OID <= 16383
-- ---------------------------------------------------------
-- select oid, proname from pg_proc where oid = 9308;
-- oid | proname | proacl
-- ------+-----------------------+-----------------------
-- 9308 | pg_integrity_add_file | {postgres=X/postgres}
-- ---------------------------------------------------------
-- 1. Проверка наличия функции
IF NOT (SELECT (COUNT(oid)>0)::boolean FROM pg_catalog.pg_proc WHERE
(pronamespace::regnamespace::name,oid::regprocedure::name)
=
('pg_catalog'::name,'pg_integrity_add_file(text)'::name) -- 'схема.имя_функции(входные_аргументы)'
-- входные аргументы как в запросе CREATE FUNCTIONS, а также в столбце proargtypes
) THEN
-- Проверка, что oid не занят другой функцией
IF (SELECT (COUNT(oid)>0)::boolean FROM pg_catalog.pg_proc WHERE oid=9308) THEN -- oid функции
RAISE EXCEPTION 'The pg_proc.oid=% is occupied by different function: % !','9308','9308'::regprocedure::name; -- oid функции, oid функции
END IF;
-- 2. Создание новой системной функции.
-- Выбор способа создания функции в зависимости от значения oid
-- Вызов функции binary_upgrade_set_next_pg_proc_oid для установки определенного oid в диапазоне от 0 до 10000.
PERFORM pg_catalog.binary_upgrade_set_next_pg_proc_oid('9308'::pg_catalog.oid); -- oid функции
-- Вызов функции binary_upgrade_set_next_free_pg_proc_oid для установки определенного oid в диапазоне от 12000 до 16383
-- PERFORM pg_catalog.binary_upgrade_set_next_free_pg_proc_oid();
CREATE FUNCTION pg_catalog.pg_integrity_add_file(file_check text) -- CREATE FUNCTION
RETURNS boolean
LANGUAGE internal
STABLE PARALLEL SAFE STRICT
AS $function$IntegrityAddFiles$function$;
-- Проверка наличия созданной функции в каталоге после ее создания
IF NOT (SELECT (COUNT(oid)>0)::boolean FROM pg_catalog.pg_proc WHERE
(oid, pronamespace::regnamespace::name, oid::regprocedure::name)
=
(9308::oid, 'pg_catalog'::name, 'pg_integrity_add_file(text)'::name) -- oid,схема,имя_функции(входные_аргументы)
) THEN
RAISE EXCEPTION 'The function "pg_catalog.pg_integrity_add_file (oid=9308)" is missing after creation.'; -- имя_функции
END IF;
-- 3. Установка привилегий на функцию (proacl в таблице pg_catalog.pg_proc) и других параметров.
-- Требуется (!) явная установка стандартного значения proacl, так как настройки default privileges бд могут привести к отличному значению при создании функции.
-- Формат привилигии (acl): 'роль=привилегии/кем_предоставлены'; для роли public: '=привилегии/кем_предоставлены'
-- Также выполним преобразование acl к типу aclitem для дополнительной проверки ('{pangolin=X/public}'::aclitem[];)
-- Значение 'proacl=NULL' означает привилегии по-умолчанию - все привилегии для владельца (proowner) а также привилегии для PUBLIC (в случае функции).
-- Привилегии по-умолчанию: select acldefault('f',proowner::regrole) from pg_proc where oid = 9308; --> {=X/postgres,postgres=X/postgres}
-- Преобразование 'pg_catalog.pg_integrity_add_file(text)'::regprocedure необходимо, чтобы обновить именно ту функцию, которая была создана выше в схеме pg_catalog и не затронуть другие.
-- В скобках после имени функции указываются входные аргументы, перечисленные в столбце proargtypes таблицы pg_proc. Они соответсвуют параметрам запроса CREATE FUNCTION).
UPDATE pg_catalog.pg_proc AS pp SET
proacl = '{postgres=X/postgres}'::aclitem[] -- стандартные привилегии
-- proallargtypes = '{25}', -- 'select 25::regtype;'-->'text'
-- proargmodes = '{i}',
-- proargnames = '{file_check}',
WHERE pp.oid='pg_catalog.pg_integrity_add_file(text)'::regprocedure::oid; -- схема,имя_функции(входные_аргументы)
-- 4. Проверка состояния "после изменений" из затронутых таблиц системного каталога
-- (эта информация может понадобиться позже для диагностики или восстановления)
SELECT rtrim(ltrim(replace(pg_proc::text, ',', '|'), '('), ')') INTO msg FROM pg_catalog.pg_proc
WHERE oid='pg_catalog.pg_integrity_add_file(text)'::regprocedure::oid; -- схема,имя_функции(входные_аргументы)
RAISE NOTICE 'CREATE FUNCTION pg_catalog.pg_integrity_add_file: %', quote_ident(msg);
-- (также можно получить ddl функции)
-- SELECT regexp_replace(pg_get_functiondef(oid),'[\n\r]+',' ','g') INTO msg FROM pg_catalog.pg_proc
-- WHERE oid='pg_catalog.pg_integrity_add_file(text)'::regprocedure; -- схема,имя_функции(входные_аргументы)
-- RAISE NOTICE 'CREATE FUNCTION pg_catalog.pg_integrity_add_file: %', quote_ident(msg);
ELSE
RAISE EXCEPTION 'The function "pg_catalog.pg_integrity_add_file" already exists in schema pg_catalog with oid: %',
'pg_catalog.pg_integrity_add_file(text)'::regprocedure::oid; -- схема,имя_функции(входные_аргументы)
END IF;
Для представления по системному 12000 <= OID < 16384
Для функций добавляемых в исходный файл postgresql/src/backend/catalog/system_views.sql OID задается в диапазоне от 12000 до 16383 включительно.
Условия проверки представления перед его созданием пишутся разработчиками в соответствии с их задачами. Удаление представления или изменение не рекомендуется, так как будут утеряны предоставленные на них права у пользователей (если они присутствовали), а также многие другие зависимости.
Шаблон:
-- Создать представление со значениями oid из диапазона: 12000 <= OID < 16383
-- ----------------------------------------------------------------------------------------
-- SELECT oid, relname, relacl FROM pg_class WHERE relname = 'pg_available_extensions_ext';
-- oid | relname | relacl
-- -------+-----------------------------+--------------------------------------------------
-- 12398 | pg_available_extensions_ext | {postgres=arwdDxt/postgres,=r/postgres}
-- ----------------------------------------------------------------------------------------
-- 1. Проверка наличия представления
IF NOT (SELECT (COUNT(oid)>0)::boolean FROM pg_catalog.pg_class WHERE
(relnamespace::regnamespace::name,oid::regclass::name)
=
('pg_catalog'::name,'pg_available_extensions_ext'::name) -- схема, имя_представления
) THEN
-- 2. Создание нового представления
-- Вызываются функции binary_upgrade для записи объектов представления по OID в диапазоне с 12000 до 16833
PERFORM pg_catalog.binary_upgrade_set_next_free_array_pg_type_oid(); -- свободный OID для typarray в таблице pg_type
PERFORM pg_catalog.binary_upgrade_set_next_free_pg_type_oid(); -- свободный OID для pg_type
PERFORM pg_catalog.binary_upgrade_set_next_free_heap_pg_class_oid(); -- свободный OID для pg_class
PERFORM pg_catalog.binary_upgrade_set_next_free_pg_rewrite_oid(); -- свободный OID для pg_rewrite
-- Создание представления
CREATE VIEW pg_catalog.pg_available_extensions_ext AS -- CREATE VIEW
SELECT E.name, E.default_version, X.extversion AS installed_version, E.comment, E.path, E.blocked
FROM pg_available_extensions_ext() AS E
LEFT JOIN pg_extension AS X ON E.name = X.extname;
-- Проверка наличия созданного представления в каталоге после его создания
IF NOT (SELECT (COUNT(oid)>0)::boolean FROM pg_catalog.pg_class WHERE
(relnamespace::regnamespace::name,oid::regclass::name)
=
('pg_catalog'::name,'pg_available_extensions_ext'::name) -- oid,схема,имя_функции(входные_аргументы)
) THEN
RAISE EXCEPTION 'The view "pg_catalog.pg_available_extensions_ext" is missing after creation.'; -- имя_представления
END IF;
-- 3. Установка привилегий на представление
-- Требуется явная установка стандартного значения proacl, так как настройки default privileges БД могут привести к отличному значению.
-- Преобразование 'pg_catalog.pg_available_extensions_ext'::regclass::oid необходимо, чтобы обновить именно то представление, которое было создана выше в схеме pg_catalog и не затронуть другие.
UPDATE pg_catalog.pg_class AS pc SET
relacl = '{postgres=arwdDxt/postgres,=r/postgres}'::aclitem[] -- стандартные привилегии
WHERE pc.oid='pg_catalog.pg_available_extensions_ext'::regclass::oid; -- схема,имя_представления
INSERT INTO pg_catalog.pg_init_privs (objoid, classoid, objsubid, privtype, initprivs)
VALUES (
'pg_catalog.pg_available_extensions_ext'::regclass::oid, -- схема,имя_представления
1259,
0,
'i',
'{postgres=arwdDxt/postgres,=r/postgres}'::aclitem[] -- стандартные первоначальные привилегии
);
-- 4. Проверка состояния "после изменений" из затронутых таблиц системного каталога
-- (эта информация может понадобиться позже для диагностики или восстановления)
-- Замечание. Описание представления содержится в нескольких таблицах каталога - pg_class, pg_type, pg_rewrite, pg_depend и (возможно) других.
-- Сложно описать связи между ними заранее, поэтому их анализ оставляем на усмотрение разработчиков.
-- Здесь предполагаем, связи будут установлены корректно автоматически.
-- В идеале следует представить все затронутые таблицы системного каталога после создания представления.
SELECT rtrim(ltrim(replace(pg_class::text, ',', '|'), '('), ')') INTO msg FROM pg_catalog.pg_class WHERE
oid='pg_catalog.pg_available_extensions_ext'::regclass::oid; -- схема, имя_представления
RAISE NOTICE 'CREATE VIEW pg_catalog.pg_available_extensions_ext: %', quote_ident(msg);
ELSE
RAISE EXCEPTION 'The view "pg_available_extensions_ext" already exists in schema pg_catalog with oid: %',
'pg_catalog.pg_available_extensions_ext'::regclass::oid; -- схема, имя_представления
END IF;
Для типов по системному 12000 <= OID < 116384
При создании нового типа по OID от 12000 до 16393 обновляются таблицы pg_class, pg_type и pg_rewrite системного каталога, которые необходимо указать в соответствии с описанным далее шаблоном. Также меняется ряд таблиц системного каталога, которые не нужно явно указывать в SQL-скрипте обновления, но необходимо учесть в тесте системного каталога базы данных после обновления.
Важная информация:
Добавление типов не проверялось на объектах системного каталога, существующего в Pangolin версии 6.4.0. Добавление системных типов по OID от 0 до 9999 включительно производится в файлы:
postgresql/src/include/catalog/pg_type.datpostgresql/src/include/catalog/pg_type.xid64.dat
Механизм добавления схож с добавлением новых функций по 0 до 9999 включительно в таблицу pg_type.
Далее рассмотрен пример добавления системного типа по OID от 12000 до 16383 включительно.
Условия проверки типа перед его созданием пишутся разработчиками в соответствии с их задачами. Удаление типа или его изменение не рекомендуется, так как будут утеряны зависимости, связанные с этим типом.
Шаблон:
-- Проверка наличия типа
IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type WHERE
typname = 'my_type') THEN
-- Вызываются функции binary_upgrade для записи объектов представления по системным OID с 12000 до 16833
PERFORM pg_catalog.binary_upgrade_set_next_free_array_pg_type_oid(); -- Устанавливается свободный OID для array_pg_type, необходимого для my_view, в таблице pg_catalog.pg_type
PERFORM pg_catalog.binary_upgrade_set_next_free_pg_type_oid(); -- Устанавливается свободный OID для pg_type, необходимого для my_view, в таблице pg_catalog.pg_type
PERFORM pg_catalog.binary_upgrade_set_next_free_heap_pg_class_oid(); -- Устанавливается свободный OID для pg_class, необходимого для my_view, в таблице pg_catalog.pg_class
PERFORM pg_catalog.binary_upgrade_set_next_free_pg_rewrite_oid(); -- Устанавливается свободный OID для pg_rewrite, необходимого для my_view, в таблице pg_catalog.pg_rewrite
-- Создание самого типа
CREATE TYPE my_type AS (f1 int, f2 text);
-- распечатка состояния "после изменений" из затронутых таблиц системного каталога
-- (эта информация может понадобиться позже для диагностики или восстановления)
-- Сложно описать связи между ними заранее, поэтому их анализ оставляем на усмотрение разработчиков.
-- В данном предполагаем, связи будут установлены корректно автоматически.
-- В дальнейшей работе можно провести такой анализ.
-- В идеале следует показывать все затронутые таблицы системного каталога после создания представления.
SELECT rtrim(ltrim(replace(pg_catalog.pg_type::text, ',', '|'), '('), ')') INTO msg FROM pg_catalog.pg_type WHERE typname = 'my_type';
RAISE NOTICE 'CREATE VIEW pg_catalog.my_type: %', quote_ident(msg);
ELSE
-- Если тип уже присутствует в системном каталоге, то выдавать ошибку, так как неправильно установлены параметры обновления для текущей и новой версий СУБД
RAISE EXCEPTION 'The type "my_type" already exists, there may be a product version error.';
END IF;
-- Проверка что тип добавился
IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type WHERE typname = 'my_type') THEN
RAISE EXCEPTION 'The type "my_type" does not exist.';
END IF;
Изменение или удаление уже существующих объектов является очень опасной операцией с системным каталогом, так как может привезти к трудновосстановимой или безвозвратной потере данных, таких как назначение прав, потеря зависимостей и т.д. Любые подобные операции должны дополнительно тестироваться и проходить дополнительное ревью специалистами L3.
Для изменения текущих объектов в системном каталоге используйте функцию UPDATE:
-- Изменить функцию
IF EXISTS (SELECT 1 FROM pg_catalog.pg_proc WHERE
proname = 'my_function') THEN
-- распечатка состояния "до изменений" из затронутых таблиц системного каталога
-- (эта информация может понадобиться позже для диагностики или восстановления)
SELECT rtrim(ltrim(replace(pg_catalog.pg_proc::text, ',', '|'), '('), ')') INTO msg FROM pg_catalog.pg_proc WHERE proname = 'my_function';
RAISE NOTICE 'CREATE VIEW pg_catalog.my_function: %', quote_ident(msg);
-- Обновление параметров в таблице pg_catalog.pg_proc
-- Необходимо проанализировать изменяемые значения на предмет порчи текущих характеристик объекта, например ACL(привилегии)
UPDATE pg_catalog.pg_proc AS pp SET
proallargtypes = '{25,16,23}', --{text, bool, int4}
proargmodes = '{i,i,o}',
proacl = '{postgres=X/postgres}'
WHERE pp.oid = (select oid from pg_catalog.pg_proc where proname = 'my_function');
-- распечатка состояния "после изменений" из затронутых таблиц системного каталога
-- (эта информация может понадобиться позже для диагностики или восстановления)
SELECT rtrim(ltrim(replace(pg_catalog.pg_proc::text, ',', '|'), '('), ')') INTO msg FROM pg_catalog.pg_proc WHERE proname = 'my_function';
RAISE NOTICE 'CREATE VIEW pg_catalog.my_function: %', quote_ident(msg);
ELSE
-- Если функция отсутствует в системном каталоге, то выдавать ошибку, так как неправильно установлены параметры обновления для текущей и новой версий СУБД
RAISE EXCEPTION 'The function "my_function" does not exists, there may be a product version error.';
END IF;
Для удаления объектов системного каталога или их пересоздания запустите утилиту с флагом --drop-on, тогда функции DROP, ALTER, REPLAСE и DELETE не вызовут ошибку в скриптах обновления и изменения применятся:
-- Удалить функцию
IF EXISTS (SELECT 1 FROM pg_catalog.pg_proc WHERE
proname = 'my_function') THEN
-- распечатка состояния "до изменений" из затронутых таблиц системного каталога
-- (эта информация может понадобиться позже для диагностики или восстановления)
SELECT rtrim(ltrim(replace(pg_catalog.pg_proc::text, ',', '|'), '('), ')') INTO msg FROM pg_catalog.pg_proc WHERE proname = 'my_function';
RAISE NOTICE 'CREATE VIEW pg_catalog.my_function: %', quote_ident(msg);
-- Удаление функции
DROP FUNCTION my_function;
END IF;
-- Проверка отсутствия функции
IF EXISTS (SELECT 1 FROM pg_catalog.pg_proc WHERE
proname = 'my_function') THEN
RAISE EXCEPTION 'The function "my_function" is exists.';
END IF;