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и т. д.).