Эта глава описывает, как группировать взаимосвязанные конструкты PL/SQL в пакет. Пакетированные конструкты могут включать коллекцию процедур или совокупность определений типов и объявлений переменных. Например, пакет Управление кадрами может включать процедуры приема на работу и увольнения. Однажды написанный, ваш общецелевой пакет компилируется и сохраняется в базе данных ORACLE, где, как библиотека, его содержимое может совместно использоваться многими приложениями.
Замечание: Пакеты могут сохраняться в базе данных ORACLE лишь при наличии Процедурного расширения базы данных.
ПАКЕТ - это объект базы данных, который группирует логически связанные типы, программные объекты и подпрограммы PL/SQL. Пакеты обычно состоят из двух частей, спецификации и тела, хотя иногда в теле нет необходимости. СПЕЦИФИКАЦИЯ пакета - это интерфейс с вашими приложениями; она объявляет типы, переменные, константы, исключения, курсоры и подпрограммы, доступные для использования в пакете. ТЕЛО пакета полностью определяет курсоры и подпрограммы, тем самым реализуя спецификацию пакета.
В отличие от подпрограмм, пакеты нельзя вызывать, передавать им параметры или вкладывать их друг в друга. В остальном формат пакета аналогичен формату подпрограммы:
PACKAGE имя IS -- спецификация (видимая часть)
-- объявления общих типов и объектов
-- спецификации подпрограмм
END [имя];
PACKAGE BODY имя IS -- тело (скрытая часть)
-- объявления личных типов и объектов
-- тела подпрограмм
[BEGIN
-- предложения инициализации]
END [имя];
Спецификация содержит ОБЩИЕ объявления, которые видимы вашему
приложению. Тело содержит детали реализации и ЛИЧНЫЕ
объявления, которые скрыты от вашего приложения.
Представляйте себе спецификацию как функциональный интерфейс, а тело - как "черный ящик" (см. рис.7-1).
Рис.7-1
Интерфейс пакета
Приложение Пакет База данных
╡°°°°°°°°°°°°≈ ╡°°°°°°°°°°°°°°°≈
▀ ▀ ▀ ▀
▀ ▀ ╡°°°°°°°°°°°°°°≈----\ ▀ ▀
▀ ▀°°°°°°▀ Спецификация ▀ \ ▀ ▀
▀ ▀ │°°°°°°°°°°°°°°▄ \ ╡°°°°°°≈ ▀
▀ ▀ ▀┴┴┴┴┴┴┴┴┴┴┴┴┴┴▀ ▀ │°°°°°°▄ ▀
▀ ▀ ▀┴┴┴ Тело ┴┴┴┴┴▀ / ▀┴┴┴┴┴┴▀ ▀
≤°°°°°°°°°°°°╠ ▀┴┴┴┴┴┴┴┴┴┴┴┴┴┴▀ /▀ ≤°°°°°°╠ ▀
≤°°°°°°°°°°°°°°╠------/ ▀ ▀
≤°°°°°°°°°°°°°°°╠
Вы можете отлаживать, развивать или заменять тело пакета, не
изменяя интерфейса к этому телу (т.е. спецификации пакета).
Пакеты создаются интерактивно в SQL*Plus или SQL*DBA с помощью команд CREATE PACKAGE и CREATE PACKAGE BODY. В следующем примере пакетируются тип записи, курсор и две процедуры управления кадрами:
CREATE PACKAGE emp_actions AS -- спецификация
TYPE EmpRecTyp IS RECORD (emp_id INTEGER, salary REAL);
CURSOR desc_salary (emp_id NUMBER) RETURN EmpRecTyp;
PROCEDURE hire_employee
(ename CHAR,
job CHAR,
mgr NUMBER,
sal NUMBER,
comm NUMBER,
deptno NUMBER);
PROCEDURE fire_employee (emp_id NUMBER);
END emp_actions;
CREATE PACKAGE BODY emp_actions AS -- тело
CURSOR desc_salary (emp_id NUMBER) RETURN EmpRecTyp IS
SELECT empno, sal FROM emp ORDER BY sal DESC;
PROCEDURE hire_employee
(ename CHAR,
job CHAR,
mgr NUMBER,
sal NUMBER,
comm NUMBER,
deptno NUMBER) IS
BEGIN
INSERT INTO emp VALUES (empno_seq.NEXTVAL, ename, job,
mgr, SYSDATE, sal, comm, deptno);
END hire_employee;
PROCEDURE fire_employee (emp_id NUMBER) IS
BEGIN
DELETE FROM emp WHERE empno = emp_id;
END fire_employee;
END emp_actions;
Заметьте, что процедура hire_employee использует
последовательность базы данных empno_seq и функцию SYSDATE,
чтобы вставлять соответственно номер нового сотрудника и дату
приема.
Видимыми и доступными для приложений являются лишь объявления в спецификации пакета. Детали реализации в теле пакета скрыты и недоступны. Поэтому вы можете исправлять тело (реализацию), не перекомпилируя вызывающих программ.
Модульность
Пакеты позволяют вам инкапсулировать логически связанные типы, объекты и подпрограммы в поименованный модуль PL/SQL. Каждый пакет легко понять, а интерфейсы между пакетами просты, ясны и хорошо определены. Это облегчает разработку приложений.
Облегчение проектирования
Все, что вам надо изначально знать при проектировании приложения - это информация интерфейса для спецификации пакета. Вы можете кодировать и компилировать спецификацию без тела. После того, как спецификация откомпилирована, хранимые подпрограммы, обращающиеся к пакету, также могут быть откомпилированы. Вы не обязаны полностью определять тела пакетов до тех пор, пока не будете готовы к реализации деталей приложения.
Скрытие информации
С помощью пакетов вы можете указывать, какие типы, объекты и подпрограммы являются общими (видимыми и доступными) или личными (скрытыми и недоступными). Например, если пакет содержит четыре подпрограммы, то три из них могут быть общими, а одна личной. Пакет скрывает определение личной подпрограммы, так что лишь этот пакет, а не ваши приложения, будет затронут, если это определение изменится. Это упрощает сопровождение и развитие. Кроме того, скрывая детали реализации от пользователей, вы защищаете целостность вашей базы данных.
Расширенная функциональность
Пакетированные общие переменные и курсоры продолжают существовать в течение всей сессии. Поэтому они могут совместно использоваться всеми процедурами, выполняющимися в данном окружении. Кроме того, через них можно передавать данные между транзакциями без необходимости записывать такие данные в базу данных.
Улучшенная производительность
Когда вы вызываете пакетированную подпрограмму первый раз, в память загружается весь пакет. Поэтому последующие вызовы других подпрограмм этого пакета не требуют операций ввода-вывода.
Помимо этого, пакеты останавливают каскадные зависимости, и тем самым избегают излишних перекомпиляций. Например, когда вы изменяете определение независимой функции, ORACLE должен перекомпилировать все хранимые подпрограммы, которые вызывают эту функцию. Однако, когда вы изменяете определение пакетированной функции, перекомпиляция вызывающих подпрограмм не требуется, потому что они не зависят от тела пакета.
Рис.7-2
Сфера пакетов
╡°
▀ ╡°
▀ ▀ ╡°
▀ ▀ ▀ процедура
▀ спецификация объекта °▄ тело пакета °▄ функция
▀ ▀ ▀ процедура
▀ ▀ ≤°
▀ ≤°
▀
схема °▄
▀
▀ ╡°
▀ ▀ ╡°
▀ ▀ ▀ функция
▀ спецификация объекта °▄ тело пакета °▄ функция
▀ ▀ ▀ процедура
▀ ▀ ≤°
▀ ≤°
▀
▀ другие объекты
▀
≤°
Спецификация перечисляет ресурсы пакета, доступные приложениям.
Она содержит всю информацию, необходимую вашему приложению для
использования этих ресурсов. Например, следующее объявление
показывает, что функция с именем fac принимает один аргумент
типа INTEGER и возвращает значение типа INTEGER:
FUNCTION fac (n INTEGER) RETURN INTEGER; -- возвращает n!
Это вся информация, необходимая вам для вызова данной функции.
Вам нет необходимости рассматривать фактическую реализацию
функции fac (например, итеративна она или рекурсивна).
Только подпрограммы и курсоры имеют реализацию, или ОПРЕДЕЛЕНИЕ. Поэтому, если спецификация пакета объявляет лишь типы, константы, переменные и исключения, тело пакета не нужно. Приведем пример такого пакета:
-- пакет, состоящий только из спецификации
PACKAGE trans_data IS
TYPE TimeTyp IS RECORD
(minute SMALLINT,
hour SMALLINT);
TYPE TransTyp IS RECORD
(category VARCHAR2,
account INTEGER,
amount REAL,
time TimeTyp);
minimum_balance CONSTANT REAL := 10.00;
number_processed INTEGER;
insufficient_funds EXCEPTION;
END trans_data;
Пакет trans_data не нуждается в теле, потому что типы,
константы, переменные и исключения не требуют реализации. Такие
пакеты позволяют вам определять глобальные переменные - для
использования подпрограммами и триггерами - которые существуют
на протяжении всей сессии.
Обращение к содержимому пакета
Для обращения к типам, объектам и подпрограммам, объявленным в спецификации пакета, используются квалифицированные ссылки:
имя_пакета.имя_типа
имя_пакета.имя_объекта
имя_пакета.имя_подпрограммы
Вы можете обращаться к содержимому пакета из триггеров базы
данных, хранимых подпрограмм, встроенных блоков PL/SQL, а также
анонимных блоков PL/SQL, посылаемых в ORACLE интерактивно через
SQL*Plus или SQL*DBA. В следующем примере вы обращаетесь к
пакетированной переменной miminum_balance, которая объявлена в
пакете trans_data:
DECLARE
new_balance REAL;
...
BEGIN
...
IF new_balance < trans_data.minimum_balance THEN
...
END IF;
...
Тело пакета может также содержать личные объявления, которые определяют типы и объекты, необходимые для внутренней работы пакета. Сфера таких объявлений локальна в теле пакета. Поэтому объявленные здесь типы и объекты недоступны нигде, кроме тела пакета. В отличие от спецификации пакета, декларативная часть тела пакета может содержать тела подпрограмм.
За декларативной частью тела пакета может следовать необязательная часть инициализации, которая обычно содержит предложения, инициализирующие некоторые из переменных, ранее объявленных в пакете.
Часть инициализации пакета не играет большой роли, потому что, в отличие от подпрограмм, пакет нельзя вызывать или передавать ему параметры. Следовательно, часть инициализации пакета отрабатывает лишь один раз, при первом обращении к пакету.
PACKAGE emp_actions IS
/* Объявить внешне видимые типы, курсор, исключение. */
TYPE EmpRecTyp IS RECORD (emp_id INTEGER, salary REAL);
TYPE DeptRecTyp IS RECORD (dept_id INTEGER, location CHAR);
CURSOR desc_salary (emp_id NUMBER) RETURN EmpRecTyp;
salary_missing EXCEPTION;
/* Объявить внешне вызываемые подпрограммы. */
FUNCTION hire_employee
(ename CHAR,
job CHAR,
mgr INTEGER,
sal NUMBER,
comm NUMBER,
deptno INTEGER) RETURN INTEGER;
PROCEDURE fire_employee (emp_id INTEGER);
PROCEDURE raise_salary (emp_id INTEGER, increase NUMBER);
FUNCTION nth_highest_salary (n INTEGER) RERURN EmpRecTyp;
END emp_actions;
PACKAGE BODY emp_actions IS
number_hired INTEGER; -- видна только в этом пакете
/* Определить курсор, объявленный в пакете. */
CURSOR desc_salary (emp_id NUMBER) RETURN EmpRecTyp IS
SELECT empno, sal FROM emp ORDER BY sal DESC;
/* Определить подпрограммы, объявленные в пакете. */
FUNCTION hire_employee
(ename CHAR,
job CHAR,
mgr INTEGER,
sal NUMBER,
comm NUMBER,
deptno INTEGER) RETURN INTEGER IS
new_empno INTEGER;
BEGIN
SELECT empno_seq.NEXTVAL INTO new_empno FROM DUAL;
INSERT INTO emp VALUES (new_empno, ename, job,
mgr, SYSDATE, sal, comm, deptno);
number_hired := number_hired + 1;
RETURN(new_empno);
END hire_employee;
PROCEDURE fire_employee (emp_id INTEGER) IS
BEGIN
DELETE FROM emp WHERE empno = emp_id;
END fire_employee;
PROCEDURE raise_salary (emp_id INTEGER, increase NUMBER) IS
current_salary NUMBER;
BEGIN
SELECT sal INTO current_salary FROM emp
WHERE empno = emp_id;
IF current_salary IS NULL THEN
RAISE salary_missing;
ELSE
UPDATE emp SET sal = sal + increase
WHERE empno = emp_id;
END IF;
END raise_salary;
FUNCTION nth_highest_salary (n INTEGER) RETURN EmpRecTyp IS
emp_rec EmpRecTyp;
BEGIN
OPEN desc_salary;
FOR i IN 1..n LOOP
FETCH desc_salary INTO emp_rec;
EXIT WHEN desc_salary%NOTFOUND;
END LOOP;
CLOSE desc_salary;
RETURN (emp_rec);
END nth_highest_salary;
/* Определить локальные функции, доступные лишь в пакете. */
FUNCTION rank (emp_id INTEGER, job_title CHAR)
RETURN INTEGER IS
/* Возвращает ранг (высший = 1) сотрудника при данной *
* должности на основе рейтинга производительности. */
head_count INTEGER;
score NUMBER;
BEGIN
SELECT COUNT(*) INTO head_count FROM emp
WHERE job = job_title;
SELECT rating INTO score FROM reviews
WHERE empno = emp_id;
score := score / 100; -- максимальный рейтинг = 100
RETURN (head_count + 1) - ROUND(head_count * score);
END rank;
BEGIN -- здесь начинается часть инициализации пакета
INSERT INTO emp_audit VALUES (SYSDATE, USER, 'EMP_ACTIONS');
number_hired := 0;
END emp_actions;
Не забывайте, что часть инициализации пакета выполняется лишь
один раз, при первом обращении к пакету. Поэтому, в последнем
примере, в аудиторскую таблицу emp_audit будет вставлена всего
одна строка. Аналогично, переменная number_hired
инициализируется лишь однажды. При каждом вызове процедуры
hire_employee переменная number_hired обновляется. Однако
счетчик, поддерживаемый этой переменной, ведется для сессии.
Это значит, что он отражает число новых сотрудников,
обработанных одним пользователем, но НЕ общее число вновь
принятых сотрудников.
В следующем примере пакетировано несколько типичных банковских транзакций. Предполагается, что дебитующие и кредитующие транзакции вводятся в нерабочее время через автоматические кассовые машины, а затем применяются к банковским счетам на следующее утро.
PACKAGE bank_transactions IS
/* Объявить внешне видимую константу. */
minimum_balance CONSTANT NUMBER := 100.00;
/* Объявить внешне вызываемые процедуры. */
PROCEDURE apply_transactions;
PROCEDURE enter_transaction
(acct NUMBER,
kind CHAR,
amount NUMBER);
END bank_transactions;
PACKAGE BODY bank_transactions IS
/* Объявить глобальную перем. для состояния транзакции. */
new_status VARCHAR2(70) := 'Unknown';
/* Использовать упреждающие объявления, потому что
apply_transactions вызывает credit_account и
debit_account, которые еще не объявлены к моменту
вызова. */
PROCEDURE credit_account (acct NUMBER, credit REAL);
PROCEDURE debit_account (acct NUMBER, debit REAL);
/* Определить процедуры, специфицированные в пакете. */
PROCEDURE apply_transactions IS
/* Применить транзакции, ожидающие в таблице транзакций, *
* к банковским счетам. Использовать курсор. */
CURSOR trans_cursor IS
SELECT acct_id, kind, amount FROM transactions
WHERE status = 'Pending'
ORDER BY time_tag
FOR UPDATE OF status; -- для блокировки строк
BEGIN
FOR trans IN trans_cursor LOOP
IF trans.kind = 'D' THEN
debit_account(trans.acct_id, trans.amount);
ELSIF trans.kind = 'C' THEN
crebit_account(trans.acct_id, trans.amount);
ELSE
new_status := 'Rejected';
END IF;
UPDATE transactions SET status = new_status
WHERE CURRENT OF trans_cursor;
END LOOP;
END apply_transactions;
PROCEDURE enter_transaction
/* Добавить транзакцию в таблицу транзакций. */
(acct NUMBER,
kind CHAR,
amount NUMBER) IS
BEGIN
INSERT INTO transactions
VALUES (acct, kind, amount, 'Pending', SYSDATE);
END enter_transaction;
/* Определить локальные процедуры, доступные только *
* внутри пакета. */
PROCEDURE do_journal_entry
/* Записать транзакцию в журнал. */
(acct NUMBER,
kind CHAR,
new_bal NUMBER) IS
BEGIN
INSERT INTO journal
VALUES (acct, kind, new_bal, sysdate);
IF kind = 'D' THEN
new_status := 'Debit applied';
ELSE
new_status := 'Credit applied';
END IF;
END do_journal_entry;
PROCEDURE credit_account (acct NUMBER, credit REAL) IS
/* Кредитовать счет, если номер счета действителен. */
old_balance NUMBER;
new_balance NUMBER;
BEGIN
SELECT balance INTO old_balance FROM accounts
WHERE acct_id = acct
FOR UPDATE OF balance; -- чтобы заблокировать стр.
new_balance := old_balance + credit;
UPDATE accounts SET balance = new_balance
WHERE acct_id = acct;
do_journal_entry(acct, 'C', new_balance);
EXCEPTION
WHEN NO_DATA_FOUND THEN
new_status := 'Bad account number';
WHEN OTHERS THEN
new_status := SUBSTR(SQLERRM,1,70);
END credit_account;
PROCEDURE debit_account (acct NUMBER, debit REAL) IS
/* Дебитовать счет, если номер счета действителен, *
* и на счете достаточная сумма. */
old_balance NUMBER;
new_balance NUMBER;
insufficient_funds EXCEPTION;
BEGIN
SELECT balance INTO old_balance FROM accounts
WHERE acct_id = acct
FOR UPDATE OF balance; -- чтобы заблокировать стр.
new_balance := old_balance - debit;
IF new_balance >= minimum_balance THEN
UPDATE accounts SET balance = new_balance
WHERE acct_id = acct;
do_journal_entry(acct, 'D', new_balance);
ELSE
RAISE insufficient_funds;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
new_status := 'Bad account number';
WHEN OTHERS THEN
new_status := SUBSTR(SQLERRM,1,70);
END debit_account;
END bank_transactions; -- часть инициализации не нужна
В этом пакете часть инициализации не используется.
Однако элементы, объявленные в спецификации пакета emp_actions, такие как исключение salary_missing, являются видимыми вне пакета. Следовательно, код PL/SQL вне пакета может обращаться к переменной salary_missing. Такие элементы называются ОБЩИМИ.
Если вам надо поддерживать какие-нибудь элементы на протяжении всей сессии или между транзакциями, помещайте их в декларативную часть тела пакета. Например, значение переменной number_hired остается неизменным между вызовами hire_employee. Не забывайте, однако, что это значение number_hired индивидуально для вашей сессии.
Если, помимо этого, вы должны сделать какие-нибудь элементы общими, помещайте их в спецификацию пакета. Например, константа minimum_balance, объявленная в спецификации пакета bank_transactions, доступн для общего использования.
PACKAGE journal_entries IS
PROCEDURE journalize (amount NUMBER, trans_date CHAR);
PROCEDURE journalize (amount NUMBER, trans_date NUMBER);
END journal_entries;
PACKAGE BODY journal_entries IS
PROCEDURE journalize (amount NUMBER, trans_date CHAR) IS
BEGIN
INSERT INTO journal
VALUES (amount, TO_DATE(trans_date, 'DD-MON-YYYY'));
END journalize;
PROCEDURE journalize (amount NUMBER, trans_date NUMBER) IS
BEGIN
INSERT INTO journal
VALUES (amount, TO_DATE(trans_date, 'J'));
END journalize;
END journal_entries;
Первая процедура принимает дату trans_date как строку символов,
тогда как вторая - как число (юлианский день). Тем не менее,
обе процедуры обрабатывают эти данные как следует.
emp_actions.hire_employee(name, title, ...);
Это указывает компилятору PL/SQL, что hire_employee находится в
пакете emp_actions.
Вы можете вызывать пакетированные подпрограммы из триггера базы данных, другой хранимой подпрограммы, приложения прекомпилятора ORACLE, приложения OCI, или из инструмента ORACLE, такого как SQL*Plus. Ниже приведены некоторые примеры.
Из другой хранимой подпрограммы
Хранимая подпрограмма может вызывать пакетированную подпрограмму. Например, в независимой подпрограмме может появиться следующий вызов пакетированной процедуры hire_employee:
emp_actions.hire_employee(name, title, ...);
Из прикладной программы
Приложение прекомпилятора или приложение OCI может вызывать хранимые подпрограммы из анонимных блоков PL/SQL. В следующем примере вы вызываете пакетированную процедуру hire_employee из программы прекомпилятора ORACLE:
EXEC SQL EXECUTE
BEGIN
emp_actions.hire_employee(name, title, ...);
END;
END-EXEC;
Фактические параметры name и title - это хост-переменные.
Из инструмента ORACLE
Вы можете вызывать пакетированные подпрограммы интерактивно из инструментов ORACLE, таких как SQL*Plus, SQL*Forms или SQL*DBA. Например, из SQL*Plus вы могли бы вызвать пакетированную процедуру hire_employee следующим образом:
SQL> EXECUTE emp_actions.hire_employee('TATE', 'CLERK', ...);
Предостережение
Перед выполнением пакетированной подпрограммы ORACLE устанавливает неявную точку сохранения. Если подпрограмма сбивается в результате необработанного исключения, ORACLE осуществляет откат к этой точке сохранения. Тем самым отменяется вся работа, проделанная подпрограммой.
Ограничение
Тело пакетированной подпрограммы может содержать любое предложение SQL или PL/SQL. Однако подпрограммы, участвующие в распределенной транзакции, триггерах базы данных и приложениях SQL*Forms, не могут вызывать пакетированных подпрограмм, содержащих предложения COMMIT, ROLLBACK или SAVEPOINT.
Удаленный доступ
Для вызова пакетированных подпрограмм, хранимых в удаленной базе данных ORACLE, используйте следующий синтаксис:
имя_пакета.имя_подпрограммы@связьБД(параметр1, параметр2, ...);
В следующем примере вызывается пакетированная процедура
hire_employee, определенная в пакете emp_actions в базе данных
newyork:
emp_actions.hire_employee@newyork(name, title, ...);
Состояния пакетов и зависимости
Спецификация пакета всегда находится в одном из двух состояний: действительна или недействительна. Спецификация пакета ДЕЙСТВИТЕЛЬНА, если ни ее исходный код, ни любой из объектов, к которым она обращается, не был ни удален (DROP), ни заменен (REPLACE), ни изменен (ALTER) с момента последней компиляции этой спецификации. С другой стороны, спецификация пакета НЕДЕЙСТВИТЕЛЬНА, если ее исходный код или любой из объектов, к которым она обращается, был либо удален (DROP), либо заменен (REPLACE), либо изменен (ALTER) с момента последней компиляции этой спецификации. Когда ORACLE помечает спецификацию пакета как недействительную, он также помечает как недействительные все объекты, обращающиеся к этому пакету.
К телу пакета применяются те же правила, с той разницей, что ORACLE может перекомпилировать тело пакета, НЕ помечая его спецификацию как недействительную. Эта возможность позволяет ограничить каскад перекомпиляций, вызываемых зависимостями.
Характеристики сессии
Переменные, константы и курсоры, объявленные в пакете, имеют следующие уникальные характеристики:
Зависимости
Когда спецификация пакета перекомпилируется, ORACLE помечает зависимые объекты как недействительные. К таким объектам относятся независимые или пакетированные подпрограммы, которые вызывают или обращаются к объектам, объявленным в перекомпилируемой спецификации пакета. Когда вы вызовете или обратитесь к зависимому, но еще не перекомпилированному, объекту, ORACLE автоматически перекомпилирует его во время выполнения.
Когда перекомпилируется тело пакета, ORACLE определяет, действительны ли объекты, от которых зависит тело пакета. К таким объектам относятся независимые подпрограммы и спецификации пакетов, к которым существуют обращения из процедур или курсоров, определенных в перекомпилируемом теле пакета. Если среди таких объектов есть недействительные, ORACLE перекомпилирует их, прежде чем перекомпилировать тело пакета. Если все перекомпиляции успешны, тело пакета становится действительным. В противном случае ORACLE возвращает ошибку выполнения, а тело пакета остается недействительным. Ошибки компиляции сохраняются в словаре данных вместе с пакетом.
ORACLE хранит спецификацию и тело пакета раздельно в словаре данных. Другие объекты, которые вызывают глобальные объекты данного пакета или обращаются к ним, зависят только от спецификации пакета. Поэтому вы можете переопределять объекты в теле пакета (что приведет к перекомпиляции тела), не нарушая действительности зависимых объектов.
Для дополнительной информации о зависимостях пакетов обратитесь к документу ORACLE7 Server Application Developer's Guide.
FUNCTION ABS (n NUMBER) RETURN NUMBER;
Содержимое пакета STANDARD видно приложениям непосредственно,
т.е. не требует квалифицированных ссылок. Так, вы можете
вызывать функцию ABS из триггера базы данных, их хранимой
подпрограммы, из приложения прекомпилятора ORACLE, из приложения
OCI, а также из разнообразных инструментов ORACLE, включая
SQL*Forms, SQL*Menu, SQL*Plus и SQL*ReportWriter.
Если вы переобъявите ABS в программе PL/SQL, то ваше локальное объявление перекроет глобальное объявление. Однако вы по-прежнему можете вызывать эту встроенную функцию, используя квалифицированную ссылку:
... STANDARD.ABS(x) ...
Большинство встроенных функций имеют перекрывающиеся имена.
Так, в пакете STANDARD содержатся следующие объявления:
FUNCTION TO_CHAR (right DATE) RETURN VARCHAR2;
FUNCTION TO_CHAR (left NUMBER) RETURN VARCHAR2;
FUNCTION TO_CHAR (left DATE, right VARCHAR2) RETURN VARCHAR2;
FUNCTION TO_CHAR (left NUMBER, right VARCHAR2) RETURN VARCHAR2;
PL/SQL разрешает обращения к TO_CHAR сопоставлением количества и
типоа данных фактических и формальных параметров.
Окружение ORACLE
Важным клиентом PL/SQL является сервер ORACLE. Пакет с именем DBMS_STANDARD предоставляет средства языка, которые помогают вашему приложению взаимодействовать с ORACLE. Например, этот пакет предоставляет процедуру raise_application_error, которая позволяет вам выдавать пользовательские сообщения об ошибках. Таким образом, вы можете возвращать ошибки приложению и избегать возврата необработанных исключений. Синтаксис вызова этой процедуры имеет вид
raise_application_error(номер_ошибки, сообщение_об_ошибке);
где номер_ошибки - отрицательное число в интервале
-20000..-20999, а сообщение_об_ошибке - строка символов длиной
до 512 символов. Пакет DBMS_STANDARD является расширением
пакета STANDARD, так что вам не требуется использовать для него
квалифицированные ссылки.
Приложение может вызывать процедуру raise_application_error только из выполняющейся хранимой подпрограммы. При своем вызове процедура raise_application_error завершает подпрограмму, выполняет откат всех сделанных ей изменений и возвращает приложению определенные пользователем номер ошибки и сообщение, которые могут перехватываться пользователем как любая ошибка ORACLE. Например:
PROCEDURE raise_salary (emp_id NUMBER, increase REAL) IS
current_salary NUMBER;
BEGIN
SELECT sal INTO current_salary FROM emp
WHERE empno = emp_id;
IF current_salary IS NULL THEN
raise_application_error(-20101, 'Salary is missing');
ELSE
UPDATE emp SET sal = sal + increase
WHERE empno = emp_id;
END IF;
END raise_salary;
Вызывающее приложение получает исключение PL/SQL, которое оно
может обработать с помощью функций SQLCODE и SQLERRM в
обработчике исключений OTHERS. Более того, приложение может
использовать прагму EXCEPTION_INIT, чтобы сопоставить
специфические номера ошибок, возвращаемые процедурой
raise_application_error, своим собственным исключениям,
например:
EXEC SQL EXECUTE
DECLARE
...
null_salary EXCEPTION;
PRAGMA EXCEPTION_INIT(null_salary, -20101);
BEGIN
...
raise_salary(:emp_number, :amount);
EXCEPTION
WHEN null_salary THEN
INSERT INTO emp_audit VALUES (:emp_number, ...);
...
END;
END-EXEC;
Этот способ позволяет вызывающему приложению обрабатывать
условия ошибок в специфических обработчиках исключений.
Как правило, процедура raise_application_error используется в триггерах базы данных. За примером обратитесь к разделу "Триггеры базы данных" в главе 4.
Спецификации пакетов отражают проект вашего приложения. Поэтому определяйте их раньше, чем тела пакетов. Помещайте в спецификации лишь те типы, объекты и подпрограммы, которые должны быть видны пользователям данного пакета. Это поможет другим разработчикам избежать ошибочного применения пакета из-за ориентирования их на несущественные детали реализации.
Чтобы уменьшить число требуемых перекомпиляций при изменениях в коде, помещайте в спецификацию пакета как можно меньше элементов. Изменения в теле пакета не требуют от ORACLE перекомпиляции зависимых процедур. Однако при изменении спецификации пакета ORACLE вынужден перекомпилировать каждую хранимую подпрограмму, обращающуюся к данному пакету.