ПРИМЕЧАНИЕ. Каждый раз, когда вы попытаетесь обратиться к этим кодам вне блока обработчика, вы получите ноль.
. ! .
Следующая структура блока кода завершается группой обработчиков исключений. Первые два обрабатывают ошибки SQLCODE в виде пользовательских исключений. Эти пользовательские исключения могут быть обработаны во внешнем блоке или их назначением может быть аварийное завершение процедуры и возврат клиенту полезного сообщения.
Если не появилось ни одного из предусмотренных исключений, а было некоторое другое, то его перехватит оператор WHEN ANY. Его обработчик вызывает хранимую процедуру для вывода записи протокола, передавая код SQLCODE вместе с другими входными аргументами, полученными из контекста блока:
BEGIN
. . .
WHEN SQLCODE -802 DO
EXCEPTION E_EXCEPTION_1;
WHEN SQLCODE -803 DO
EXCEPTION E_EXCEPTION_2;
WHEN ANY DO
EXECUTE PROCEDURE P_ANY_EXCEPTION (SQLCODE, другие входные данные ...);
END
Предположим, вам нужно перехватить и внести во внешнюю таблицу протокола непредвиденную ошибку, перед тем как позволить исключению выполнить свою работу и завершить процедуру или триггер. Начиная с версии 1.5, можно повторно вызвать исключение - вы можете выполнить некоторую обработку исключения и завершить обработчик оператором EXCEPTION для возбуждения исключения и передачи управления на конечный END. Выполнение останавливается, и управление передается клиенту с кодом или именем исключения и подходящим сообщением в массиве состояния ошибки.
В вашем обработчике вы выбираете SQLCODE или GDSCODE и некоторые другие контекстные переменные, записываете запись в протокол, а затем заново вызываете исключение:
BEGIN
. . .
WHEN ANY DO
BEGIN
EXECUTE PROCEDURE P_ANY_EXCEPTION (SQLCODE, другие входные данные ...);
EXCEPTION; END
END ^
Исключения в триггерах
Пользовательские исключения в триггерах способны поддерживать бизнес-правила. В примере базы данных EMPLOYEE есть правило, по которому покупатели, которым отказано в доверии, отмечаются в столбце ON_HOLD, который ограничен значениями NULL или "*". Когда добавляется запись в SALES или изменяется существующая запись с неотправленным товаром для такого покупателя, то такой заказ отклоняется, если флаг ON_HOLD не имеет значения NULL. Другое правило гласит, что заказ, который уже был отправлен, не может изменяться.
При добавлении или изменении записей заказов мы можем написать триггеры BEFORE, которые при нарушении этих правил выдают исключения и блокируют операцию.
Для любой версии Firebird мы можем написать два триггера для осуществления этих правил: BEFORE INSERT и BEFORE UPDATE.
Создадим исключения для двух условий:
CREATE EXECPTION E_CANT_ACCEPT
'Operation refused. REASON: Customer is on hold.' ^
/* Операция отклоняется. Причина: деятельность покупателя приостановлена */
CREATE EXCEPTION E_CANT_EXTEND
'Operation refused. REASON: Order already shipped.' ^
/* Операция отклоняется. Причина: заказ уже отправлен */
COMMIT ^
Триггеры для версии 1.0.x:
CREATE TRIGGER BI_SALES0 FOR SALES
ACTIVE BEFORE INSERT POSITION 0 AS
BEGIN
IF (EXISTS (SELECT 1 FROM CUSTOMER
WHERE CUST_NO = NEW.CUST_NO
AND ON_HOLD IS NOT NULL)) THEN
EXCEPTION E_CANT_ACCEPT;
END ^
/* */
CREATE TRIGGER BU_SALES0 FOR SALES
ACTIVE BEFORE UPDATE POSITION 0 AS
BEGIN
IF (OLD.ORDER_STATOS = 'shipped') THEN
EXCEPTION E_CANT_EXTEND; ELSE
IF (EXISTS (SELECT 1 FROM CUSTOMER
WHERE CUST_NO = NEW.CUST_NO
AND ON_HOLD IS NOT NULL) ) THEN
EXCEPTION E_CANT_ACCEPT;
END ^
Сообщения исключений во время выполнения
Некоторые улучшения версии 1.5 по обработке сообщений исключений обеспечивают больше возможностей написания обработчиков исключений. Статическое сообщение исключения, определенное в CREATE EXCEPTION, во время выполнения может быть заменено на другой текст, обеспечивающий лучший контекст для пользователя, более точно идентифицирующий проблемные данные.
В следующем примере мы используем возможности версии 1.5 по реализации тех же самых правил, что и в двух триггерах предыдущего примера. На этот раз мы поместим эти правила в один триггер и используем сообщения времени выполнения.
Вот исключение:
CREATE EXCEPTION E_REFUSE_ORDER 'Operation refused. ' ^
/* Операция отвергнута */
А вот триггер:
CREATE TRIGGER BA_SALES0 FOR SALES
ACTIVE BEFORE INSERT OR UPDATE POSITION 0 AS
DECLARE VARIABLE ORDER_STATE SMALLINT = 0;
BEGIN
IF (UPDATING AND OLD.ORDER_STATUS = 'shipped') THEN
ORDER_STATE = 1;
IF (
(EXISTS (SELECT ON_HOLD FROM CUSTOMER
WHERE CUST_NO = NEW.CUST_NO
AND ON_HOLD IS NOT NULL)
AND (INSERTING OR ORDER_STATE = 0)) THEN
ORDER_STATE = 2;
IF (ORDER_STATE = 1) THEN
EXCEPTION E_REFUSE_ORDER 'Order ' || NEW.PO_NUMBER || ' already shipped.';
/* EXCEPTION E_REFUSE_ORDER 'Заказ ' || NEW.PO_NUMBER || ' уже отправлен' */
ELSE
IF (ORDER_STATE = 2) THEN
EXCEPTION E_REFUSE_ORDER
'Order '|| NEW.PO_NUMBER ||'. Customer ' || NEW.CUST_NO || ' is on hold.';
/* 'Заказ '|| NEW.PO_NUMBER ||'. Покупатель ' || NEW.CUST_NO || ' заблокирован'; */
END ^