Предлагаемое решение основано на следующих двух фактах:
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