Перейти к основному содержимому

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

примечание

Эта страница переведена при помощи нейросети GigaChat.

Полезное расширение для PostgreSQL обычно включает несколько объектов SQL; например, новый тип данных потребует новые функции, новые операторы и, вероятно, новые классы операторов индексации. Собрать все эти объекты в единый пакет полезно для упрощения управления базой данных. PostgreSQL называет такой пакет расширением. Чтобы определить расширение, нужен хотя бы скриптовый файл, содержащий команды SQL для создания объектов расширения, и файл управления, определяющий несколько базовых свойств самого расширения. Если расширение включает код на языке C, то обычно также присутствует общий библиотечный файл, в который был собран код на языке C. После подготовки файлов простая команда CREATE EXTENSION загружает объекты в базу данных.

Основное преимущество использования расширения вместо простого запуска SQL-скрипта для загрузки множества свободных объектов в базу данных заключается в том, что PostgreSQL поймет, что объекты расширения связаны друг с другом. Удалить все объекты можно одной командой DROP EXTENSION (нет необходимости поддерживать отдельный скрипт удаления). Более полезно то, что pg_dump знает, что ему не нужно сбрасывать отдельные объекты-члены расширения — он просто включит команду CREATE EXTENSION в дампы. Это значительно упрощает переход к новой версии расширения, которая может содержать больше или другие объекты, чем прежняя версия. Однако важно иметь файлы управления расширением, сценарий и остальные файлы при загрузке такой выгрузки в новую базу данных.

PostgreSQL не позволит удалить отдельный объект, содержащийся в расширении, за исключением удаления всего расширения. Изменять определение объекта-члена расширения тоже возможно (например, с помощью CREATE OR REPLACE FUNCTION для функции), но учтите, что измененное определение не попадет в pg_dump. Подобные изменения имеют смысл только при условии одновременного внесения аналогичных изменений в файл сценария расширения. (Существуют исключения для таблиц, содержащих данные конфигурации; подробности смотрите в разделе «Таблицы конфигурации расширений».) В производственном окружении лучше подготовить скрипт обновления расширения для внесения изменений в объекты-члены расширения.

Файл расширения может устанавливать привилегии на объекты, входящие в его состав, используя операторы GRANT и REVOKE. Полученный итоговый набор привилегий для каждого объекта (если они установлены) будет сохранен в системном каталоге pg_init_privs. При использовании pg_dump команда CREATE EXTENSION включается в дамп, вслед за ней идут операторы GRANT и REVOKE, необходимые для установки привилегий на объектах так, как они были на момент создания дампа.

PostgreSQL в настоящий момент не поддерживает выдачу операторами расширения команд CREATE POLICY или SECURITY LABEL. Предполагают настройку указанных элементов после создания расширения. Политики уровня защиты записей (RLS) и метки безопасности объектов расширения будут включены в дампы, создаваемые с помощью pg_dump.

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

Формат управляющего файла совпадает с форматом файла postgresql.conf, то есть представляет собой список назначений вида parameter_name = value, каждое на отдельной строке. Пустые строки и комментарии вводятся символами #. Любое значение, не являющееся одним словом или числом, необходимо заключить в кавычки.

В управляющем файле могут задаваться следующие параметры:

directory (string)

Каталог, содержащий файл(ы) сценария SQL расширения. Если указанный путь не абсолютный, имя считается относительно каталога установки SHAREDIR. По умолчанию этот параметр эквивалентен указанию directory = 'extension'.

default_version (string)

Версия по умолчанию для расширения (установленная, если версия не указана в команде CREATE EXTENSION). Пропуск этого параметра приведет к неудачному выполнению команды CREATE EXTENSION, если параметр VERSION не указан, поэтому обычно рекомендуется его задание.

comment (string)

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

encoding (string)

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

module_pathname (string)

Параметром задается значение, заменяющее каждый экземпляр MODULE_PATHNAME в файле(ах) сценария. Если параметр не установлен, замена не осуществляется. Часто принято задавать его как $libdir/shared_library_name и использовать MODULE_PATHNAME в командах CREATE FUNCTION для функций на языке C, чтобы исключить необходимость жестко прописывать имя общей библиотеки в файлах сценариев.

requires (string)

Список названий расширений, от которых зависит данное расширение, например requires = 'foo, bar'. Расширения из списка должны быть предварительно установлены.

superuser (boolean)

Если параметр установлен в true (это стандартное значение), только суперпользователи смогут создать расширение или обновить его до новой версии (смотрите также параметр trusted ниже). Если значение установлено в false, нужны только привилегии, требуемые для выполнения команд в сценарии установки или обновления. Обычно параметр устанавливают в true, если одна или несколько команд сценария требуют привилегий суперпользователя. (Такие команды все равно не выполнятся, но удобнее выдавать сообщение об ошибке заранее.)

trusted (boolean)

Если параметр установлен в true (этого значения по умолчанию нет), ряд пользователей, не являющихся суперпользователями, получат право устанавливать расширение, у которого параметр superuser установлен в true. Установка доступна любому пользователю, имеющему привилегию CREATE в текущей базе данных. Когда пользователь, отправивший команду CREATE EXTENSION, не является суперпользователем, но получил право установить расширение благодаря данному параметру, сценарий установки или обновления выполняется от имени загрузочного суперпользователя, а не от имени вызвавшего пользователя. Параметр не действует, если superuser равен false. Обычно параметр не следует устанавливать в состояние «истина», если расширение способно обеспечить доступ к возможностям, доступным только суперпользователям, таким как доступ к файловой системе. Кроме того, обозначение расширения как доверенного предполагает значительные дополнительные усилия для безопасного написания установочных и обновляющих сценариев расширения; подробнее смотрите раздел «Безопасность расширений».

relocatable (boolean)

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

schema (string)

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

Помимо основного файла управления extension.control, расширение может содержать второстепенные файлы управления формата extension--version.control. Если присутствуют, они должны располагаться в каталоге файлов сценариев. Формат второстепенных файлов аналогичен основному файлу управления. Параметры, установленные во второстепенном файле управления, заменяют соответствующие параметры основного файла при установке или обновлении до соответствующей версии расширения. Исключением являются параметры directory и default_version, которые не устанавливаются во второстепенных файлах управления.

Файлы SQL-сценариев расширения могут содержать произвольные команды SQL, исключая команды управления транзакциями (BEGIN, COMMIT и др.) и команды, недопустимые в блоках транзакций (например, VACUUM), так как файлы сценариев выполняются неявно внутри транзакции.

Файлы SQL-сценариев также могут содержать строки, начинающиеся с \echo, которые игнорируются механизмом расширений и воспринимаются как комментарии. Эта особенность обычно применяется для вывода ошибок, если файл сценария загружается вручную через psql вместо использования CREATE EXTENSION. Без этого пользователи могли бы случайно загрузить содержимое расширения как обычные объекты, а не как единое целое, что потребовало бы дополнительного вмешательства для исправления ситуации.

Если сценарий расширения содержит строку @extowner@, она заменяется именем пользователя, инициирующего команду CREATE EXTENSION или ALTER EXTENSION, заключенным в кавычки. Чаще всего такая функциональность применяется расширениями, отмеченными как доверенные, чтобы передать владение некоторыми объектами пользователю-исполнителю, а не суперпользователю. (Тем не менее, нужно соблюдать осторожность. Например, передача владения функцией на языке C непривилегированному пользователю открывает потенциальную угрозу эскалации привилегий.)

Хотя файлы сценариев могут содержать любые символы, поддерживаемые выбранной кодировкой, файлы управления должны содержать только стандартный ASCII, поскольку PostgreSQL не располагает способом выяснить кодировку содержимого файла управления. На практике проблема возникает лишь в тех случаях, когда требуется включить нелатинские символы в комментарий к расширению. Рекомендуется избегать использования параметра comment в файле управления и применять команду COMMENT ON EXTENSION внутри файла сценария для добавления комментариев.

Перемещаемость расширения

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

  • Полностью перемещаемое расширение позволяет свободно перемещать его объекты в другую схему в любое время, даже после загрузки в базу данных. Перемещение осуществляется командой ALTER EXTENSION SET SCHEMA, которая автоматически переносит все объекты в новую схему. Для полной поддержки перемещения расширение не должно содержать никаких внутренних ссылок на имена схем, используемых его объектами. Кроме того, все объекты расширения должны находиться в одной и той же схеме изначально (исключение составляют объекты, не относящиеся к каким-либо схемам, например, процедурные языки). Полностью перемещаемый статус отмечается установкой relocatable = true в файле управления.
  • Переходящее расширение, позволяющее выбрать схему при первой установке, но не позднее. Такой вариант характерен, если сценарий расширения должен явно обращаться к целевой схеме, например, при конфигурировании функций SQL. Установите параметр schema в файле управления и используйте ссылку на целевую схему в файле сценария. Во время выполнения файла сценария каждая ссылка на данную строку будет заменена действительным именем целевой схемы. Пользователь устанавливает целевую схему через параметр команды.
  • Если расширение не поддерживает перемещение вовсе, укажите это в файле управления, а также укажите имя предопределенной целевой схемы. Это предотвращает смену схемы с помощью параметра команды, если только новая схема не совпадает с указанной в файле управления. Такая ситуация обычно необходима, если расширение внутренне ссылается на конкретные имена схем, которые невозможно заменить простой заменой. Возможность подстановки имен предусмотрена и здесь, но ее применение ограничено ввиду наличия жестко заданного имени схемы в файле управления.

Во всех случаях файл сценария выполняется с предварительным назначением search_path, направляющим на целевую схему; то есть, CREATE EXTENSION производит эффект, аналогичный следующему действию:

SET LOCAL search_path TO @extschema@, pg_temp;

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

Целевая схема выбирается следующим образом: сначала проверяется параметр schema в файле управления, затем опция SCHEMA в команде CREATE EXTENSION, если указана, иначе принимается текущая схема создания объектов по умолчанию (первая в списке search_path вызываемого клиента). Если параметр schema задан в файле управления, целевая схема создастся автоматически, если она еще не существует. В остальных случаях схема должна быть предварительно создана.

Если какие-либо зависимые расширения перечислены в секции requires файла управления, их схемы также добавляются в начальное значение search_path, следуя за целевой схемой нового расширения. Это позволяет видеть объекты зависимых расширений в файле сценария нового расширения.

Для целей безопасности схема pg_temp автоматически присоединяется к концу search_path во всех вариантах.

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

Таблицы конфигурации расширений

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

Решить проблему можно, отметив соответствующую таблицу или последовательность как конфигурационный объект, заставляя pg_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 пустой, вся информация таблицы выгружается в pg_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 был истинным только для строк, созданных самим расширением.

Для последовательностей второй аргумент функции pg_extension_config_dump не оказывает влияния.

Условие фильтрации для таблицы конфигурации можно менять повторным вызовом функции pg_extension_config_dump. Единственным способом отменить отметку таблицы как конфигурационной является отключение ее связи с расширением с помощью команды ALTER EXTENSION ... DROP TABLE.

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

Последовательности, связанные со столбцами serial или bigserial, должны быть явно помечены для сохранения их состояния. Родительская связь недостаточна для выполнения этой задачи.

Обновления расширений

Одно из достоинств механизма расширений заключается в предоставлении удобных способов управления обновлениями SQL-команд, определяющими объекты расширения. Этого достигают путем привязывания имени версии или номера к каждой выпускаемой версии сценария установки расширения. Дополнительно, если требуется поддержка динамического обновления баз данных от одной версии к другой, предоставляются скрипты обновления, которые выполняют необходимые изменения для перехода от одной версии к другой. Имя файлов обновления следует шаблону extension--old_version--target_version.sql (например, foo--1.0--1.1.sql содержит команды для перевода версии 1.0 расширения foo в версию 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.

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

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

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

SELECT * FROM pg_extension_update_paths('extension_name');

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

Установка расширений с использованием сценариев обновления

Если расширение существовало какое-то время, скорее всего, оно прошло через несколько версий, для каждой из которых потребуется создать сценарии обновления. Например, если расширение foo выпустилось в версиях 1.0, 1.1 и 1.2, необходимы сценарии обновления foo--1.0--1.1.sql и foo--1.1--1.2.sql. До PostgreSQL 10 приходилось дополнительно создавать файлы сценариев foo--1.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, предоставляемые расширениями, подвергаются риску атак, связанных с поиском объектов, поскольку их разбор происходит во время выполнения, а не создания.

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

Если невозможно настроить search_path так, чтобы он содержал только безопасные схемы, следует считать, что любое неквалифицированное имя может разрешиться до объекта, созданного злонамеренным пользователем. Избегайте конструкций, зависящих от неявного использования search_path, например, выражения IN и CASE expression WHEN всегда ищут нужный оператор через путь поиска. Лучше использовать конструкции OPERATOR(*schema*.=) ANY и CASE WHEN expression.

Общее расширение обычно не может рассчитывать на то, что оно установлено в защищенную схему, а значит, даже ссылки на собственные объекты, квалифицируемые схемой, не гарантируют полную безопасность. Например, если расширение определяет функцию 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 и явном включении ссылок на схему установки расширения там, где это необходимо. (Такая практика полезна и при создании представлений.) Примеры можно увидеть в модулях contrib в исходном коде PostgreSQL.

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

Пример расширения

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

Файл сценария pair--1.0.sql выглядит так:

-- пожаловаться, если сценарий запущен в psql, а не через CREATE EXTENSION
\echo Используйте "CREATE EXTENSION pair" для загрузки этого файла. \quit

CREATE TYPE pair AS ( k text, v text );

CREATE 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 FUNCTION lower(pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW(lower($1.k), lower($1.v))::@extschema@.pair;'
SET search_path = pg_temp;

CREATE 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@
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, чтобы загрузить объекты в необходимую базу данных.