L.1. Работа с пакетами #
В Postgres Pro реализованы пакеты, подобные пакетам в Oracle. Они могут быть полезны при портировании с PL/SQL на PL/pgSQL. В этом разделе описаны различия между пакетами в Postgres Pro и Oracle.
В Oracle пакет — это объект, представляющий собой схему, которая группирует логически связанные типы, глобальные переменные и подпрограммы (процедуры и функции). Пакет состоит из спецификации и тела (хотя, в общем случае тело пакета может отсутствовать). В спецификации пакета объявляются элементы, на которые можно ссылаться снаружи пакета и использовать в приложениях: типы, переменные, константы, исключения, курсоры и подпрограммы. Тело пакета включает в себя реализацию подпрограмм пакета, объявление внутренних переменных, а также секцию инициализации. Переменные, типы и подпрограммы, объявленные в теле пакета, недоступны снаружи пакета.
Ниже приведён пример спецификации пакета counter
на PL/SQL в Oracle, содержащего глобальную переменную n
и функцию inc
:
CREATE PACKAGE counter IS n int; FUNCTION inc RETURN int; END;
В теле пакета counter
в Oracle объявлены функция inc
и глобальная переменная k
(доступна только в теле пакета).
CREATE PACKAGE BODY counter IS k int := 3; -- переменная доступна только в теле пакета FUNCTION inc RETURN int IS BEGIN n := n + 1; RETURN n; END; -- Инициализация пакета BEGIN n := 1; FOR i IN 1..10 LOOP n := n + n; END LOOP; END;
Глобальные переменные пакета существуют в течение сеанса. Обратите внимание, что в теле пакета была определена секция инициализации — блок кода, который выполняется один раз за сеанс при первом обращении к любому элементу пакета. В Oracle к элементам пакета можно обращаться, используя запись с точкой: имя_пакета
.элемент_пакета
.
SET SERVEROUTPUT ON BEGIN dbms_output.put_line(counter.n); dbms_output.put_line(counter.inc()); END; / 1024 1025
В Postgres Pro пакет по сути представляет собой схему, которая содержит функцию инициализации и может содержать только функции, процедуры и составные типы. Функция инициализации — это функция на PL/pgSQL с именем __init__
, которая не имеет аргументов и возвращает значение типа void
. Функция инициализации должна быть определена до любой другой функции пакета. По умолчанию все переменные, объявленные в функции инициализации пакета, функциях пакета и процедурах пакета, являются публичными, поэтому к ним можно обращаться снаружи пакета, используя запись с точкой в других функциях, процедурах и анонимных блоках, импортирующих пакет. Модификатор #private
определяет функции и процедуры как внутренние, а модификатор #export
определяет, какие переменные пакета являются публичными. Например, на переменную bar
, объявленную в функции __init__
пакета foo
, можно ссылаться как foo.bar
.
Переменные пакета доступны только из кода на языке PL/pgSQL. Для получения значений глобальных переменных пакета из SQL-запроса (SELECT
) или DML-оператора (INSERT
, UPDATE
, DELETE
), необходимо создать функцию-геттер, возвращающую значение глобальной переменной. Для определения переменных пакета, объявленных как константы, используется стандартный для PL/pgSQL синтаксис. Функция инициализации пакета вызывается автоматически при первом обращении к любому из следующих элементов в текущем сеансе:
- Любая функция данного пакета с модификатором
#package
- Блок кода (анонимный или функция), импортирующий данный пакет
Функцию инициализации можно вызвать вручную, чтобы сбросить значения переменных пакета. Для этого также можно использовать встроенную функцию plpgsql_reset_packages()
, но она сбрасывает значения глобальных переменных всех пакетов в текущем сеансе (аналог DBMS_SESSION.RESET_PACKAGES
в Oracle).
Описанный выше пример пакета counter
в Oracle после портирования в Postgres Pro примет следующий вид:
CREATE PACKAGE counter CREATE FUNCTION __init__() RETURNS void AS $$ -- инициализация пакета #export n DECLARE n int := 1; -- публичная переменная n, доступная и внутри, и снаружи пакета k int := 3; -- внутренняя переменная k, доступная только внутри пакета BEGIN FOR i IN 1..10 LOOP n := n + n; END LOOP; END; $$ CREATE FUNCTION inc() RETURNS int AS $$ BEGIN n := n + 1; RETURN n; END; $$ ;
Использовать этот пакет в Postgres Pro можно так:
DO $$ #import counter BEGIN RAISE NOTICE '%', counter.n; RAISE NOTICE '%', counter.inc(); END; $$; NOTICE: 1024 NOTICE: 1025
Стоит отметить следующее.
Пакет не может содержаться в схеме, так как сам является схемой.
Имя пакета должно отличаться от имени любого уже существующего пакета или схемы в текущей базе данных.
Пакет может содержать только составные типы, глобальные переменные, процедуры и функции (например, создание таблиц, представлений, последовательностей в пакетах не поддерживается).
Все переменные пакета являются публичными, если используется модификатор
#export on
или если модификатор#export
отсутствует. Все функции и процедуры пакета являются публичными, если не используется модификатор#private
. Публичные переменные пакета доступны из любого блока кода, импортирующего пакет.Спецификация пакета и тело пакета не определяются отдельно.
Как функции пакета, так и импортирующие его блоки кода должны быть написаны на языке PL/pgSQL.
Обращение извне ко всем элементам пакета должно происходить через квалифицированные имена, то есть с указанием имени пакета. Внутри пакета функции могут обращаться к элементам напрямую.
Глобальные переменные пакета могут инициализироваться как при объявлении:
DECLARE x int := 42;
так и позже в теле функции инициализации
__init__
. Для внешнего кода, использующего пакет, эти варианты эквивалентны.Типы должны быть объявлены в начале пакета, до определения первой подпрограммы.
Функция
__init__
должна быть первой подпрограммой, определённой в пакете.
Для поддержки пакетов Postgres Pro включает следующие усовершенствования:
SQL-команды
CREATE [OR REPLACE] PACKAGE
иDROP PACKAGE
L.1.1. Модификаторы функций и переменных #
Модификаторы функций и переменных начинаются с символа #
и располагаются между именем и оператором DECLARE
(пример приведён ниже).
L.1.1.1. Модификатор #package
#
Модификатор #package
указывает, что данная функция должна трактоваться как пакетная, что означает следующее:
Вызов функции приводит к автоматической инициализации содержащего её пакета, если пакет не был инициализирован ранее в текущем сеансе.
Функция может обращаться к переменным своего пакета напрямую.
Функция __init__
не должна содержать модификатор #package
. Создание функции с модификатором #package
возможно только в схеме, уже содержащей функцию __init__
(то есть в пакете). Если функция создаётся при выполнении команды CREATE OR REPLACE PACKAGE
, модификатор #package
можно не указывать, так как он автоматически добавляется этой командой.
L.1.1.2. Модификатор #import
#
Модификатор #import
указывает, что данная функция будет работать с переменными некоторого внешнего пакета (или нескольких пакетов). Это называется импортированием пакета и означает следующее:
Обращение к данной функции будет приводить к автоматической инициализации импортируемого пакета, если он не был инициализирован ранее в текущем сеансе.
Функция может обращаться к переменным импортированного пакета по его имени. Для импорта пакета используйте следующий синтаксис:
#import
список_пакетов
Здесь
список_пакетов
— список имён импортируемых пакетов. Пакеты также можно импортировать отдельно, например:#import foo, bar
равнозначно следующему:
#import foo #import bar
Пакеты, импортированные в функции __init__
, автоматически импортируются для всех остальных функций данного пакета.
В следующем примере использования модификатора #import
процедура showValues
вызывает функцию p
из пакета htp
и функцию set_action
из пакета dbms_application_info
.
CREATE OR REPLACE PROCEDURE showValues(p_Str varchar) AS $$ #import htp, dbms_application_info BEGIN CALL dbms_application_info.set_action('Show hello'); CALL htp.p('<p>' || p_Str || '</p>'); END; $$LANGUAGE plpgsql;
L.1.1.3. Модификатор #private
#
Модификатор #private
определяет, что функция является внутренней, то есть она доступна только внутри пакета и на неё нельзя ссылаться снаружи. Внутренние функции необходимы для обслуживающих процессов пакета. Они доступны только в указывающем их пакете.
L.1.1.4. Модификатор #export
#
Модификатор #export
определяет, что переменная пакета является публичной, то есть на неё можно ссылаться снаружи пакета. Модификатор #export
используется при инициализации пакета:
#export имя_переменной_1, имя_переменной_2, ... имя_переменной_N
Все переменные пакета являются публичными, если используется модификатор
#export on
или если модификатор#export
отсутствует.Если пакет содержит модификатор
#export
с указанными переменными, эти переменные являются публичными, а все остальные переменные пакета — внутренними.Модификатор
#export
можно использовать в функции__init__
несколько раз, тогда все указанные с ним переменные считаются публичными.Модификатор
#export off
означает, что никакие переменные не должны быть публичными, и указание экспортируемых переменных в этом случае вызовет ошибку.Модификатор
#export on
делает все переменные публичными, и последующее использование модификатора#export
вызовет ошибку.Модификатор
#export
должен содержать хотя бы одну переменную.Внутренние переменные можно использовать только внутри определяющего их пакета. Такие переменные можно вызывать из функций и процедур пакета с модификатором
#package
. Если они объявляются отдельно, необходимо указывать имя пакета.Публичные переменные можно использовать как внутри, так и снаружи пакета, если функции, процедуры и анонимные блоки содержат модификатор
#import
с именем пакета.
L.1.2. Встроенные функции #
В PL/pgSQL имеется встроенная функция plpgsql_reset_packages()
, которая возвращает все пакеты в данном сеансе в исходное состояние.
SELECT plpgsql_reset_packages();
L.1.3. Команды SQL #
Работать с пакетами можно, используя обычные команды для схем (например, CREATE SCHEMA
и DROP SCHEMA
) с модификаторами функций, но удобнее использовать специальные команды CREATE OR REPLACE PACKAGE
и DROP PACKAGE
.
L.1.3.1. CREATE OR REPLACE PACKAGE
#
CREATE OR REPLACE PACKAGE
либо создаст новый пакет, либо заменит существующее определение.
CREATE [OR REPLACE] PACKAGEимя_пакета
тело_пакета
;
Здесь имя_пакета
— это имя схемы, а тело_пакета
— набор команд создания пакета с рядом особенностей, о которых следует помнить:
Команда
CREATE SCHEMA
выполняется автоматически.Модификатор
#package
можно не использовать — все функции получают его автоматически.При создании функций не указывается язык.
Имена создаваемых функций и типов указываются без имени пакета.
Отдельные подкоманды верхнего уровня в теле пакета не завершаются точкой с запятой.
Имя пакета должно отличаться от имени любого уже существующего пакета или схемы в текущей базе данных.
В приведённом ниже примере показано, как можно создать пакет посредством команды CREATE SCHEMA
.
CREATE SCHEMA foo; CREATE TYPE foo.footype AS (a int, b int); CREATE FUNCTION foo.__init__() RETURNS void AS $$ DECLARE x int := 1; BEGIN RAISE NOTICE 'foo initialized'; END; $$ LANGUAGE plpgsql; CREATE FUNCTION foo.get() RETURNS int AS $$ #package BEGIN RETURN x; END; $$ LANGUAGE plpgsql; CREATE FUNCTION foo.inc() returns void AS $$ #package BEGIN x := x + 1; END; $$ LANGUAGE plpgsql;
Ниже показано, как можно добиться того же результата командой CREATE PACKAGE
:
CREATE PACKAGE foo CREATE TYPE footype AS (a int, b int) CREATE FUNCTION __init__() RETURNS void AS $$ DECLARE x int := 1; BEGIN RAISE NOTICE 'foo initialized'; END; $$ CREATE FUNCTION get() RETURNS int AS $$ BEGIN RETURN x; END; $$ CREATE FUNCTION inc() RETURNS void AS $$ BEGIN x := x + 1; ENG; $$ ;
При замене существующего пакета необходимо учитывать следующие особенности:
Команду
CREATE OR REPLACE PACKAGE
можно использовать только для замены схем, являющихся пакетами (то есть содержащих только функции, составные типы и функцию инициализации).Элементы старого пакета, не определённые в новом пакете, будут удалены, если у них нет зависимых объектов; в противном случае команда
CREATE OR REPLACE PACKAGE
завершится ошибкой.Если при замене пакета не меняется сигнатура функции, тело функции заменяется, а зависимые объекты сохраняются. Если сигнатура функции меняется, фактически создаётся новая, отдельная функция. Последнее возможно только при отсутствии зависимых объектов, иначе
CREATE OR REPLACE PACKAGE
завершится ошибкой.Те же ограничения касаются замены типов.
Если команда завершилась ошибкой из-за наличия зависимых объектов, то вместо списка объектов в сообщении об ошибке будет указан содержащий их пакет.
CREATE OR REPLACE PACKAGE
— это полностью транзакционная команда, поэтому любые ошибки при определении элементов пакета приведут к полному откату всех изменений.
L.1.3.2. DROP PACKAGE
#
DROP PACKAGE
удаляет пакеты (схемы) из базы данных вместе с зависимыми объектами.
DROP PACKAGE [IF EXISTS] список_имён_пакетов
[CASCADE];
Здесь список_имён_пакетов
— список пакетов, которые нужно удалить. Команда является полностью транзакционной, поэтому либо удаляются все пакеты, либо, если команда завершилась ошибкой, не удаляется ни один. Эта команда аналогична DROP SCHEMA
с несколькими особенностями, о которых следует помнить:
Команду
DROP PACKAGE
можно использовать только для удаления схем, являющихся пакетами (то есть содержащих только функции, составные типы и функцию инициализации).Если команда завершилась ошибкой из-за наличия зависимых объектов, то вместо списка объектов в сообщении об ошибке будет указан содержащий их пакет.
При использовании ключевого слова
CASCADE
команда найдёт зависимые внешние объекты и, если они являются членами других пакетов, удалит также и эти пакеты.
Все зависимости, упомянутые в данном разделе, являются зависимостями в стандартном для Postgres Pro смысле (см. Раздел 5.15). Например, использование в определении одной функции сигнатуры другой создаёт такую зависимость, а просто вызов одной функции из другой — нет. Обращение функции к переменным пакета вне пакета также не создаёт зависимостей (даже в случае функции-геттера).
L.1.4. Ограничения #
В Oracle пакет представляет собой неделимую структуру, которую можно изменить только целиком, используя команду
CREATE OR REPLACE
. В Postgres Pro Enterprise пакет представляет собой схему, поэтому его элементы можно изменять по отдельности командамиCREATE OR REPLACE PROCEDURE
,CREATE OR REPLACE FUNCTION
илиALTER TYPE
. Таким образом, можно легко заменить функцию или тип, которые не имеют зависимостей, а не заменять весь пакет. Если заменяется пакет, необходимо вручную заменить все зависимые пакеты и функции.Поскольку в Postgres Pro пакет является схемой, пользователь должен обладать правами для данной схемы:
GRANT USAGE ON SCHEMA foo, bar TO hr_user;
Если пакет должен выполняться с правами создателя, пользователь должен обладать всеми правами для данной схемы:
GRANT ALL ON SCHEMA hr_main TO hr_user;
Если пакет должен запускаться с правами вызывающего, пользователь должен иметь конкретные права на таблицы и представления, которые используются в пакете, например:
GRANT SELECT demo.employee_tab TO hr_user;
В отличие от Oracle, где сохраняется вся информация о зависимостях пакетов и вообще любых объектов PL/SQL (триггеров, функций, процедур и т. д.), Postgres Pro не хранит зависимости на уровне тела функции и объявления переменных, поэтому эти зависимости невозможно отследить при изменении пакета.
В Oracle при нарушении зависимостей пакет получает статус
INVALID
. В этом случае пакет всё ещё существует в базе данных, но не работает. В отличие от этого, в Postgres Pro любой объект, созданный или изменённый в базе данных, должен быть рабочим. Таким образом, если в результате какой-либо операции в Postgres Pro пакет становится нерабочим (например, удаляется таблица, используемая в объявлении типа пакета), она не может быть выполнена — сначала этот пакет необходимо удалить, так как он не может оставаться нерабочим в базе данных.При замене пакетов убедитесь, что никакие другие сеансы не используют их (другими словами, перед заменой пакетов остановите использующее их приложение). После этого перезапустите приложение, и обновлённые версии пакетов будут доступны во всех сеансах. Это ограничение вызвано отсутствием библиотечного кеша в Postgres Pro (в отличие от Oracle), поскольку каждый сервер кеширует код PL/pgSQL. Если бы пакет менялся в одном сеансе, а другой сеанс всё ещё работал бы со старой версией пакета, это могло бы привести к несогласованности, особенно если бы изменился список глобальных переменных пакета. На данный момент не реализованы глобальная блокировка для изменения пакета (аналог соответствующих механизмов блокировки в Oracle) и отслеживание изменений пакетов (аналог исключения «ORA-04068: existing state of packages has been discarded» (существующее состояние пакетов было сброшено) в Oracle).
В отличие от Oracle, пакеты не разделяются на спецификацию и тело.
Предложения, изменяющие объявление процедуры (такие как
RESULT_CACHE
,DETERMINISTIC
, в Oracle) не поддерживаются. Директивы компилятора PL/SQL также не поддерживаются (например,INLINE pragma
,UDF pragma
и т. д.).