Динамическое формирование сообщения EXCEPTION в процедурах и триггерах

Максим Доценко (Maxim Docenko) maxdoc@mail.ru
Начиная с Firebird 1.5 можно заменить текст exception прямо при его вызове. См. exception.

Предлагаемое решение основано на следующих двух фактах:
  1. Все операции (insert, delete, update, execute procedure) являются атомарными. Если из-за выполнения одного из вышеперечисленных операторов 100 раз запустятся триггеры, а на 101 раз какой либо из триггеров вызовет EXCEPTION, то все изменения внесенные как самим оператором, так и сработавшими триггерами, автоматически откатятся. Т. е. все вернется к тому состоянию, которое было до вызова этого оператора.
  2. EXCEPTION хранятся в системной таблице rdb$exceptions, где поле rdb$exception_name (char(31)) содержит соответственно наименование, а rdb$message (varchar(78)) текст ошибки выдаваемой клиенту. Как видим, есть одно ограничение – сообщение не может содержать больше чем 78 символов.
 
Примечание. Кроме этой проблемы основная проблема при работе с русскоязычными exception состоит в том, что поле rdb$message имеет кодировку none. Как результат, записать русский текст нормально можно, только если соединение имеет кодировку none. В случае кодировки 1251 также возможны проблемы с чтением такого exception.
Берем два этих общеизвестных факта, складываем их и получаем:

Если в триггере или хранимой процедуре изменить сообщение раннее созданного объекта EXCEPTION (например, ERR) следующей командой:
UPDATE rdb$exceptions
SET rdb$message= 'Рта-Ля-Ля'
WHERE rdb$exception_name='ERR';
а затем сразу вызвать его:
EXCEPTION ERR;
то клиент получит исключение с текстом 'Рта-Ля-Ля', а сервер вернет текст сообщения ERR в состояние, предшествующее нашим изменениям. Вот и все!!!

Правда вполне может возникнуть ситуация когда ошибка произойдет одновременно у нескольких клиентов. В этой ситуации кто-то из клиентов получит исключение deadlock. Но ничем – кроме того, что они получат невразумительное сообщение об ошибке это не грозит – все равно все изменения откатятся, да и вероятность данного события в большинстве разработок невелика...

Для удобства все эти действия можно оформить следующим образом:
/* Exceptions definitions */
CREATE EXCEPTION ERR '???';
/* SP Definitions */
CREATE PROCEDURE ERROR(E VARCHAR(2000))
AS
declare variable EE varchar(78);
/*максимально возможный размер для текста ошибки*/
BEGIN
   EE='';
   EE=:E;
   /*если входной параметр больше чем 78 то тут возникает ошибка
   которая успешно обрабатывается ниже*/
   UPDATE rdb$exceptions
   SET rdb$message=:E
   WHERE rdb$exception_name='ERR';
   EXCEPTION ERR;
   /*сюда попадаем только если входной параметр Е больше 78 символов*/
   /*EE - содержит 78 символов в обрезанных от E*/
   when any do
      begin
         UPDATE rdb$exceptions
         SET rdb$message=:EE
         WHERE rdb$exception_name='ERR';
         EXCEPTION ERR;
      end
END

Как видим в процедуру ERROR можно передавать сообщение, которое состоит больше чем из 78 символов. Оно обрежется до необходимых 78 (используется метод описанный http://www.volny.cz/iprenosil/interbase/ip_ib_code.htm).

Теперь приведу пару примеров, как это использовать.

Пример использования в триггере:
if (new.id=new.parent) then
   execute procedure error('Ссылка на самого себя не допустима !!!');
/*формирование текста на лету */
select id,name from goods where id=new.id into :id,:name;
if (id is not null) then
   begin
      e='Номер '||id||' уже используется товаром :"'||name||'"';
      execute procedure error(e);
   end

Подпишитесь на новости Firebird в России

Подписаться