Связывание имени исключения с кодом ошибки в PL/SQL Oracle
В Oracle, имена определены лишь для самых распространенных исключений. Тысячи других ошибок в СУБД имеют лишь номера и снабжены пояснительными сообщениями. Вдобавок инициировать исключение с номером ошибки (в диапазоне от –20 999 до –20 000) может и разработчик приложения, воспользовавшись для этой цели процедурой RAISE_APPLICATION_ERROR.
Наличие в программном коде исключений без имен вполне допустимо, но такой код малопонятен и его трудно сопровождать. Допустим, вы написали программу, при выполнении которой Oracle выдает ошибку, связанную с данными, например ORA-01843: not a valid month. Для перехвата этой ошибки в программу включается обработчик следующего вида:
EXCEPTION WHEN OTHERS THEN IF SQLCODE = -1843 THEN
Но код получается совершенно непонятным. Чтобы сделать смысл этого кода более очевидным, следует воспользоваться директивой EXCEPTION_INIT.
Встроенная функция SQLCODE возвращает номер последней сгенерированной ошибки.
Директива EXCEPTION_INIT
Директива компилятора EXCEPTION_INIT (команда, выполняемая во время компиляции) связывает идентификатор, объявленный с ключевым словом EXCEPTION, с внутренним кодом ошибки. Установив такую связь, можно инициировать исключение по имени и указать это имя в условии WHEN обработчика ошибок.
С директивой EXCEPTION_INIT условие WHEN, использованное в предыдущем примере, приводится к следующему виду:
PROCEDURE my_procedure IS invalid_month EXCEPTION; PRAGMA EXCEPTION_INIT (invalid_month, −1843); BEGIN ... EXCEPTION WHEN invalid_month THEN
Жесткое кодирование номера ошибки становится излишним; имя ошибки говорит само за себя.
Директива EXCEPTION_INIT должна располагаться в разделе объявлений блока. Указанное в ней исключение должно быть объявлено либо в том же блоке, либо во внешнем, либо в спецификации пакета.
Синтаксис директивы в анонимном блоке:
DECLARE имя_исключения EXCEPTION; PRAGMA EXCEPTION_INIT (имя_исключения, целое_число);
Здесь имя_исключения — имя исключения, объявляемого программистом, а целое_число — номер ошибки Oracle, которую следует связать с данным исключением. Номером ошибки может служить любое число со следующими ограничениями:
- Номер ошибки не может быть равен –1403 (один из двух кодов ошибок NO_DATA_FOUND). Если вы по какой-либо причине захотите связать свое именованное исключение с этой ошибкой, передайте директиве EXCEPTION_INIT значение 100.
- Номер ошибки не может быть равен 0 или любому положительному числу, кроме 100.
- Номер ошибки не может быть отрицательным числом, меньшим –1 000 000.
Рассмотрим пример возможного объявления исключения. В приведенном ниже программном коде объявляется и связывается исключение со следующим номером:
ORA-2292 integrity constraint (OWNER.CONSTRAINT) violated - child record found.
Ошибка происходит при попытке удаления родительской записи, у которой в таблице имеются дочерние записи (то есть записи с внешним ключом, ссылающимся на родительскую запись):
PROCEDURE delete_company (company_id_in IN NUMBER) IS /* Объявление исключения. */ still_have_employees EXCEPTION; /* Имя исключения связывается с номером ошибки. */ PRAGMA EXCEPTION_INIT (still_have_employees, 2292); BEGIN /* Попытка удаления информации о компании. */ DELETE FROM company WHERE company_id = company_id_in; EXCEPTION /* При обнаружении дочерних записей инициируется это исключение! */ WHEN still_have_employees THEN DBMS_OUTPUT.PUT_LINE ('Пожалуйста, сначала удалите данные о служащих компании.'); END;
Рекомендации по использованию EXCEPTION_INIT
Директиву EXCEPTION_INIT целесообразно использовать в двух ситуациях:
- при необходимости присвоить имя безымянному системному исключению, задействованному в программе (следовательно, если в Oracle не определено имя для некоторой ошибки, это еще не означает, что с ней можно работать только по номеру);
- когда нужно присвоить имя специфическому для приложения исключению, инициируемому процедурой RAISE_APPLICATION_ERROR. Это позволяет обрабатывать данное исключение по имени, а не по номеру.
В обоих случаях все директивы EXCEPTION_INIT желательно объединить в пакет, чтобы определения исключений не были разбросаны по всему коду приложения. Допустим, вы интенсивно используете динамический SQL, и при выполнении запросов часто возникает ошибка «invalid column name» (неверное имя столбца). Запоминать код ошибки не хочется, но и определять директивы имя для исключения в 20 разных программах тоже неразумно. Поэтому имеет смысл определить собственные «системные исключения» в отдельном пакете для работы с динамическим SQL:
CREATE OR REPLACE PACKAGE dynsql IS invalid_table_name EXCEPTION; PRAGMA EXCEPTION_INIT (invalid_table_name, -903); invalid_identifier EXCEPTION; PRAGMA EXCEPTION_INIT (invalid_identifier, -904);
Теперь перехват этих ошибок в программе может производиться следующим образом:
WHEN dynsql.invalid identifier THEN ...
Аналогичный подход рекомендуется использовать при работе с кодами ошибок –20NNN, передаваемыми процедуре RAISE_APPLICATION_ERROR. Создайте пакет, в котором этим кодам будут присваиваться имена. Он может выглядеть примерно так:
PACKAGE errnums IS en_too_young CONSTANT NUMBER := -20001; exc_too_young EXCEPTION; PRAGMA EXCEPTION_INIT (exc_too_young, -20001); en_sal_too_low CONSTANT NUMBER := -20002; exc_sal_too_low EXCEPTION; PRAGMA EXCEPTION_INIT (exc_sal_too_low , -20002); END errnums;
При наличии такого пакета можно использовать код следующего вида, не указывая номер ошибки в коде:
PROCEDURE validate_emp (birthdate_in IN DATE) IS min_years CONSTANT PLS_INTEGER := 18; BEGIN IF ADD_MONTHS (SYSDATE, min_years * 12 * -1) < birthdate_in THEN RAISE_APPLICATION_ERROR (errnums.en_too_young, 'Возраст работника должен быть не менее ' || min_years || ' лет.'); END IF; END;
Метки: EXCEPTION, Oracle, Исключения.