Создание генераторов и триггеров в InterBase
В данной статье я опишу реализацию небольшой утилиты для InterBase, которая создает генераторы, автоматически вызываемые из триггеров. На создание такой утилиты меня подвигло то, что я не нашел никакого средства для этого, а создавать генераторы руками мне неудобно, да и слишком много времени это отнимает.
Общая информация
Вообще, считается хорошим стилем создание первичного ключа в виде уникального числа. Мало того, это наиболее удобный способ для связи таблиц. Такой первичный ключ часто называют "айдишкой" (от слова "ID", идентификатор) таблицы.
Значения такого поля могут задаваться и вручную, но тогда придется вручную же контролировать их уникальность, что практически невозможно при многопользовательском режиме использования БД. Для этого в InterBase был включен механизм "генераторов", аналогичный механизму "последовательностей" в Oracle. Единственная цель генератора - дать уникальное (в его контексте) числовое значение при вызове.
Сами по себе генераторы практически не имеют смысла, так как они никак не связаны с таблицами БД. Для запроса значения генератора применяются триггеры, автоматически вызываемые СУБД при возникновении определенных событий. В основном, триггер, использующий генератор, вызывается по событию "BeforeInsert", то есть перед непосредственной вставкой данных.
Текст создания классического генератора и триггера для него, в общем случае, выглядит следующим образом:
CREATE GENERATOR GEN_%TBL_NAME%; CREATE TRIGGER NEW_%TBL_NAME% FOR %TBL_NAME% ACTIVE BEFORE INSERT POSITION 0 AS begin if (NEW.%ID_FIELD% is NULL) then NEW.%ID_FIELD% = gen_id(GEN_%TBL_NAME%,1); endЗдесь %TBL_NAME% - имя таблицы, для которой создается триггер и генератор, а %ID_FIELD% - имя поля, являющегося первичным ключом.
Реализация
Итак, для реализации утилиты нам необходимо для начала найти список таблиц базы данных, а также их первичных ключей. Для этого мы сделаем запросы к словарю данных базы (словарь данных еще называется метаданными), который содержится в системных таблицах, имена которых начинаются с последовательности "rdb$".
Список таблиц можно получить следующим образом:
select rdb$relation_name from rdb$relations where (rdb$system_flag=0) and (rdb$view_source is null) order by rdb$relation_name asc;Список первичных ключей получаем так:
select i.rdb$field_name from rdb$relation_constraints r, rdb$index_segments i where r.rdb$relation_name=:TBL_NAME and r.rdb$constraint_type='PRIMARY KEY' and r.rdb$index_name=i.rdb$index_name order by i.rdb$field_positionТеперь все необходимое для дальнейшей работы получено и мы можем создавать генераторы с триггерами.
Предположив, что вы уже создали модуль данных и окно подключения к базе данных, перейду сразу к содержательной части. Предлагаю вам создать вот такое окно:
В поле списка опций (большое белое поле) у нас будут имена таблиц, а надписи на кнопках говорят сами за себя. Как вы видите, ничего сложного. Код обработки нажатия на кнопку "Create generators..." таков:
procedure TForm2.btnCreateClick(Sender: TObject); var i: integer; begin with dm do for i := 0 to pred(lbTables.Count) do begin if lbTables.Checked[i] then if not HasTrigger(lbTables.Items[i]) then begin if not tm.InTransaction then tm.StartTransaction; try CreateTriggeredGenerator(lbTables.Items[i]); except tm.Rollback; showmessage('Unable to create objects for the table '+lbTables.Items[i]+'.'); end; if tm.InTransaction then begin tm.Commit; showmessage('Objects for the table '+lbTables.Items[i]+' is successfully created.'); end; end; end; end;Здесь для каждой выбранной в списке таблицы проверяется, существует ли уже триггер с генератором для этой таблицы. Если нет, то они создаются.
Проверка существования триггера выглядит вот таким образом:
function TForm2.HasTrigger(const TableName: string): boolean; var s: string; i: integer; begin Result := false; if (Gens.Count < 1) then exit; with dm do begin qHasTrigger.ParamByName('TBL_NAME').Value := TableName; qHasTrigger.Open; while not qHasTrigger.Eof do begin s := qHasTrigger.FieldByName('rdb$trigger_source').AsString; if (length(s) > 0) then for i := 0 to pred(Gens.Count) do if pos(UpperCase(Gens[i]),UpperCase(s)) > 0 then Result := true; qHasTrigger.Next; end; qHasTrigger.Close; end; end;Вот и всё, Удачи!