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

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

примечание

Эта страница переведена при помощи нейросети 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 при создании зависимых расширений.

Если расширение ссылается на объекты, принадлежащие другому расширению, рекомендуется квалифицировать эти ссылки по схеме. Чтобы сделать это, напишите @extschema:name@ в файле сценария расширения, где name --- это название другого расширения (которое должно быть указано в списке requires данного расширения). Эта строка будет заменена именем (взятым в двойные кавычки, если необходимо) целевой схемы другого расширения. Хотя эта нотация избавляет от необходимости делать жесткие предположения о названиях схем в сценарии расширения, ее использование может встроить имя схемы другого расширения в установленные объекты текущего расширения. (Обычно это происходит, когда @extschema:name@ используется внутри строкового литерала, такого как тело функции или настройка search_path. В остальных случаях ссылка на объект сводится к идентификатору объекта (OID) во время разбора и не требует последующих поисков.) Если имя схемы другого расширения таким образом встраивается, нужно предотвратить перемещение другого расширения после установки вашего, добавив имя другого расширения в список no_relocate данного расширения.

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

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

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

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

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

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