Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/FireBird/Yaffil - А Ковязин
Шрифт:
Интервал:
Закладка:
Если сделать подтверждение транзакции, в которой выполнился первый триггер, который выключил (или включил) второй триггер, а затем запустить снова транзакцию, то мы увидим изменения в состоянии второго триггера. Но какой смысл это делать, ведь суть идеи состояла в том, чтобы включать триггеры на лету, не теряя значения в буфере контекстных переменных NEW или OLD.
В общем, это был пример того, что не следует делать в триггерах. Другим примером того, чего не следует делать в триггерах, является изменение данных в той же таблице, к которой привязан триггер, не через контекстные переменные, а с помощью обычных SQL-команд INSERT/UPDATE/DELETE. Например некий триггер на вставку вызывает хранимую процедуру, внутри которой происходит вставка записи в ту же таблицу. Вставка опять вызовет срабатывание нашего триггера, и возникнет зацикливание. Следует очень внимательно относиться к использованию триггеров, так как зацикливание в ряде случаев может привести к аварийному завершению сервера InterBase.
Ошибки и исключения в триггерах
Если база достаточно сложная (лучше сказать, достаточно реальная), то вам никак не избежать появления ошибок. Более того, ошибки типа "конфликт с другими пользователями" являются повседневным и нормальным явлением в многопользовательской среде. Как InterBase обрабатывает ошибки в триггерах? Ведь ситуация может быть достаточно нетривиальная - например, вставка записи в главную таблицу запускает хранимую процедуру, которая вставляет записи в подчиненные таблицы, причем при вставке в подчиненные таблицы срабатывают триггеры на вставку, которые получают новые значения генераторов и подставляют их в нужные поля. Можно представить не один подобный уровень вложенности. Что произойдет, когда где-то в "дальних" ветках этого дерева событий возникнет ошибка?
При возникновении ошибок на любом этапе - в триггере, в вызываемых им ХП или в неявно активизируемых других триггерах - InterBase сообщит об ошибке и откатит изменения в таблицах, проведенные в рамках инициировавшего эту цепочку оператора. Оператор - это предложение INSERT/UPDATE/DELETE или SELECT, а также EXECUTE PROCEDURE.
Таких операторов может быть в транзакции несколько. Отменяется все действия только в рамках оператора, вызвавшего ошибку. Клиентское приложение может отследить возникновение ошибки и подтвердить транзакцию. Другими словами, ошибка в триггере не обязательно требует отката транзакции. Клиентское приложение может обработать ошибку, полученную при выполнении оператора и, например, выполнить вместо этих изменений какие-то другие, если такова логика предметной области, или изменить логику выполнения дальнейших изменений в этой транзакции и подтвердить реально выполненные в транзакции изменения
Теперь, когда мы знаем, что делает InterBase при возникновении ошибки в триггере, неплохо бы понять, что можем сделать мы, чтобы обработать ошибочную ситуацию. Если мы будем верить в то, что все наши триггеры и ХП не имеют ошибок и конфликтов между действиями пользователей быть не может, то можем вообще не обрабатывать ошибки на уровне базы данных. Если же ошибка возникнет, InterBase пошлет нашему клиентскому приложению сообщение об ошибке, которое мы вольны обработать или нет, - в любом случае InterBase уже выполнил свою миссию - откатил ошибочное действие в триггере. Однако есть и другой путь.
Мы можем воспользоваться обработкой ошибочных ситуаций непосредственно в теле триггера (или хранимой процедуры) с помощью конструкции WHEN...DO. Использование этой конструкции аналогично применению ее в хранимых процедурах, и подробнее об использовании WHEN...DO см. главу "Расширенные возможности языка хранимых процедур InterBase" (ч. 1).
Точно так же как и в хранимых процедурах, в триггерах можно возбуждать собственные исключения. Так как триггер фактически представляет собой разновидность исполнимой хранимой процедуры, то возбуждение в нем исключения прервет работу триггера и приведет к отмене всех действий, совершенных в триггере, - явных и неявных.
События InterBase
Одной из мощных возможностей InterBase, часто используемых в триггерах, являются события (events). События представляют собой строковые сообщения, которые могут быть посланы из триггера или хранимой процедуры. Получат эти события те клиенты InterBase, которые зарегистрированы как заинтересованные в данных событиях. Таким образом, можно оповещать клиента о каких-то изменения внутри базы данных.
События не являются постоянным объектом базы данных - они нигде в базе данных не хранятся, не создаются и не модифицируются, а порождаются "на лету". Чтобы послать какое-то событие, необходимо воспользоваться следующей конструкцией:
POST_EVENT 'текст_сообщения';
Надо сказать, что 'текст_сообщения' может браться из переменной и, таким образом, можно порождать события динамически, например так:
...
If (<какое-то булево выражение>) then
BEGIN
Event_text ='IT IS TRUE!';
END
ELSE
BEGIN
Event_text ='IT IS FALSE!';
END
FALSE_EVENT :Event_text;
Однако если ни одно клиентское приложение, соединенное с базой данных, в которой порождаются какие-то события, не является зарегистрированным на получение этих событий, то все они "уйдут в эфир" и фактически пропадут.
Для регистрации (подписки) на получение нужных событий используют специальные функции InterBase API, которые реализованы, например, в библиотеке FIBPlus - в компоненте SuperlBAlerter.
Как только приложение регистрируется для получения какого-либо события, запись об этом заносится в таблицу блокировок InterBase, которая является единой для всех пользователей сервера InterBase, и сервер начинает просматривать все порождаемые события на предмет появления зарегистрированных данным клиентом. Если такое событие появляется, то клиентское приложение получает соответствующий сигнал, на который может отреагировать каким-либо образом.
События в триггерах являются удобным механизмом для организации протокола изменений в определенных таблицах.
Заключение
Триггеры являются мощным средством для реализации бизнес-логики на стороне сервера Размещение операций обработки данных в триггерах позволяет упростить и централизовать бизнес-логику приложений, но одновременно несет в себе определенные трудности, связанные с отладкой приложений СУБД на уже работающих базах.
В любом случае при разработке достаточно сложных приложений для СУБД InterBase использование триггеров является одной из возможностей сделать работу создателя СУБД проще и приятнее.
User Defined Functions
Зачастую от программистов, использующих другие серверы баз данных, можно услышать мнение, что SQL InterBase не отличается большим разнообразием встроенных функций. Формально такая точка зрения имеет основания, однако разработчики InterBase сознательно пошли на это ограничение. Как уже неоднократно было сказано, InterBase отличается скромными системными требованиями и занимает мало места на жестком диске. Небольшой совокупный размер файлов продиктован, в частности, тем, что InterBase не перегружен разнообразными дополнительными и, в общем-то, редко используемыми функциями Зато InterBase включает возможность расширить стандартный набор функций любыми дополнениями, которые нужны в конкретной базе данных. Таким образом, разработчик может реализовать для своих приложений даже такие функции, которые никогда не входят в поставку серверов баз данных.
Механизм подключения функций
Специально для расширения функциональности SQL InterBase предлагает механизм функций, определяемых пользователем (user defined functions). Вы можете создать динамическую библиотеку (Dynamic Link Library) при помощи любой системы разработки, которая позволяет создавать выполнимые файлы данного формата. В частности, можно использовать Borland Delphi, Borland C++ Builder, Microsoft Visual C++ и т. д. Далее, необходимо поместить полученную DLL в каталог, из которого InterBase сможет вызывать библиотеку, и декларировать нужные функции из DLL в своей базе данных при помощи команды DECLARE EXTERNAL FUNCTION. После этого вы сможете вызывать указанные функции, как если бы они были встроенными функциями InterBase.
InterBase до версии 6.0 требовал, чтобы DLL находилась в любом из каталогов, указанных в системной переменной PATH InterBase 6 и выше (включая клоны Firebird и Yaffil) требует, чтобы DLL была помещена в специальный каталог UDF, находящийся в общем каталоге установки InterBase
Создание собственных функций
Мы не будем подробно останавливаться на всех особенностях создания пользовательских функций, поскольку данный механизм достаточно прост, однако продемонстрируем написание и подключение одной функции на примере. В качестве средства разработки мы будем использовать Borland Delphi. Для примера также будет применяться стандартная база данных EMPLOYEE.GDB.
В нашем примере мы создадим функцию, которая будет преобразовывать строку к верхнему регистру. Подобная функция может оказаться полезной, если вы не задали опцию COLLATE для ваших строковых полей. Кроме того, работа со строковыми параметрами, как правило, вызывает наибольшее количество вопросов при создании пользовательских функций. Разумеется, мы исходим из предположения, что вы знакомы с принципом создания DLL при помощи Delphi.