FirebirdSQL logo
 Объявления UDRСообщения 

Экземпляр триггера

Внешний триггер должен реализовать интерфейс IExternalTrigger. Дляупрощения просто наследуем класс IExternalTriggerImpl.

Метод dispose вызывается при уничтожении экземпляра триггера, в нём мыдолжны освободить ранее выделенные ресурсы. В данном случае простовызываем деструктор.

Метод getCharSet используется для того, чтобы сообщить контексту внешнего триггерунабор символов, который мы хотим использовать при работе с соединением из текущего контекста.По умолчанию соединение из текущего контекста работает в кодировке текущего подключения, что не всегда удобно.

Метод execute вызывается при выполнении триггера на одно из событий длякоторого создан триггер. В этот метод передаётся указатель на статусвектор, указатель на контекст внешнего триггера, действие (событие)которое вызвало срабатывание триггера и указатели на сообщения длястарых и новых значений полей. Возможные действия (события) триггераперечислены константами в интерфейсе IExternalTrigger. Такие константыначинаются с префикса ACTION_. Знания о текущем действие необходимо,поскольку в Firebird существуют триггеры созданные для несколькихсобытий сразу. Сообщения необходимы только для триггеров на действиятаблицы, для DDL триггеров, а также для триггеров на события подключения иотключения от базы данных и триггеров на события старта, завершения иотката транзакции указатели на сообщения будут инициализированызначением nil. В отличие от процедур и функций сообщения триггеровстроятся для полей таблицы на события которой создан триггер.Статические структуры для таких сообщений строятся по тем же принципам,что и структуры сообщений для входных и выходных параметров процедуры,только вместо переменных берутся поля таблицы.

Note
Замечание

Обратите внимание, что если вы используете отображение сообщений наструктуры, то ваши триггеры могут сломаться после изменения составаполей таблицы и их типов. Чтобы этого не произошло, используйте работу ссообщением через смещения получаемые из IMessageMetadata. Это не такактуально для процедур и функций, поскольку входные и выходные параметрыменяются не так уж часто. Или хотя бы вы делаете это явно, что можетнатолкнуть вас на мысль, что необходимо переделать и внешнююпроцедуру/функцию.

В нашем простейшем триггере мы определяем тип события, и в теле триггеравыполняем следующий PSQL аналог

...
  if (:new.B IS NULL) THEN
    :new.B = :new.A + 1;
...

Фабрика функций

Теперь необходимо написать фабрику и саму функцию. Они будут расположеныв модуле SumArgsFunc. Примеры для написания процедур и триггеров будутпредставлены позже.

unit SumArgsFunc;

{$IFDEF FPC}
{$MODE DELPHI}{$H+}
{$ENDIF}

interface

uses
  Firebird;

{ *********************************************************
    create function sum_args (
      n1 integer,
      n2 integer,
      n3 integer
    ) returns integer
    external name 'myudr!sum_args'
    engine udr;
 ********************************************************* }

type
  // структура на которое будет отображено входное сообщение
  TSumArgsInMsg = record
    n1: Integer;
    n1Null: WordBool;
    n2: Integer;
    n2Null: WordBool;
    n3: Integer;
    n3Null: WordBool;
  end;
  PSumArgsInMsg = ^TSumArgsInMsg;

  // структура на которое будет отображено выходное сообщение
  TSumArgsOutMsg = record
    result: Integer;
    resultNull: WordBool;
  end;
  PSumArgsOutMsg = ^TSumArgsOutMsg;

  // Фабрика для создания экземпляра внешней функции TSumArgsFunction
  TSumArgsFunctionFactory = class(IUdrFunctionFactoryImpl)
    // Вызывается при уничтожении фабрики
    procedure dispose(); override;

    { Выполняется каждый раз при загрузке внешней функции в кеш метаданных.
      Используется для изменения формата входного и выходного сообщения.

      @param(AStatus Статус вектор)
      @param(AContext Контекст выполнения внешней функции)
      @param(AMetadata Метаданные внешней функции)
      @param(AInBuilder Построитель сообщения для входных метаданных)
      @param(AOutBuilder Построитель сообщения для выходных метаданных)
    }
    procedure setup(AStatus: IStatus; AContext: IExternalContext;
      AMetadata: IRoutineMetadata; AInBuilder: IMetadataBuilder;
      AOutBuilder: IMetadataBuilder); override;

    { Создание нового экземпляра внешней функции TSumArgsFunction

      @param(AStatus Статус вектор)
      @param(AContext Контекст выполнения внешней функции)
      @param(AMetadata Метаданные внешней функции)
      @returns(Экземпляр внешней функции)
    }
    function newItem(AStatus: IStatus; AContext: IExternalContext;
      AMetadata: IRoutineMetadata): IExternalFunction; override;
  end;

  // Внешняя функция TSumArgsFunction.
  TSumArgsFunction = class(IExternalFunctionImpl)
    // Вызывается при уничтожении экземпляра функции
    procedure dispose(); override;

    { Этот метод вызывается непосредственно перед execute и сообщает
      ядру наш запрошенный набор символов для обмена данными внутри
      этого метода. Во время этого вызова контекст использует набор символов,
      полученный из ExternalEngine::getCharSet.

      @param(AStatus Статус вектор)
      @param(AContext Контекст выполнения внешней функции)
      @param(AName Имя набора символов)
      @param(AName Длина имени набора символов)
    }
    procedure getCharSet(AStatus: IStatus; AContext: IExternalContext;
      AName: PAnsiChar; ANameSize: Cardinal); override;

    { Выполнение внешней функции

      @param(AStatus Статус вектор)
      @param(AContext Контекст выполнения внешней функции)
      @param(AInMsg Указатель на входное сообщение)
      @param(AOutMsg Указатель на выходное сообщение)
    }
    procedure execute(AStatus: IStatus; AContext: IExternalContext;
      AInMsg: Pointer; AOutMsg: Pointer); override;
  end;

implementation

{ TSumArgsFunctionFactory }

procedure TSumArgsFunctionFactory.dispose;
begin
  Destroy;
end;

function TSumArgsFunctionFactory.newItem(AStatus: IStatus;
  AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalFunction;
begin
  Result := TSumArgsFunction.Create();
end;

procedure TSumArgsFunctionFactory.setup(AStatus: IStatus;
  AContext: IExternalContext; AMetadata: IRoutineMetadata;
  AInBuilder, AOutBuilder: IMetadataBuilder);
begin

end;

{ TSumArgsFunction }

procedure TSumArgsFunction.dispose;
begin
  Destroy;
end;

procedure TSumArgsFunction.execute(AStatus: IStatus; AContext: IExternalContext;
  AInMsg, AOutMsg: Pointer);
var
  xInput: PSumArgsInMsg;
  xOutput: PSumArgsOutMsg;
begin
  // преобразовываем указатели на вход и выход к типизированным
  xInput := PSumArgsInMsg(AInMsg);
  xOutput := PSumArgsOutMsg(AOutMsg);
  // по умолчанию выходной аргумент = NULL, а поэтому выставляем ему nullFlag
  xOutput^.resultNull := True;
  // если один из аргументов NULL значит и результат NULL
  // в противном случае считаем сумму аргументов
  with xInput^ do
  begin
    if not (n1Null or n2Null or n3Null) then
    begin
      xOutput^.result := n1 + n2 + n3;
      // раз есть результат, то сбрасываем NULL флаг
      xOutput^.resultNull := False;
    end;
  end;
end;

procedure TSumArgsFunction.getCharSet(AStatus: IStatus;
  AContext: IExternalContext; AName: PAnsiChar; ANameSize: Cardinal);
begin
end;

end.

Фабрика внешней функции должна реализовать интерфейсIUdrFunctionFactory. Для упрощения просто наследуем классIUdrFunctionFactoryImpl. Для каждой внешней функции нужна своя фабрика.Впрочем, если фабрики не имеют специфики для создания некоторой функции,то можно написать обобщённую фабрику с помощью дженериков. Позже мыприведём пример как это сделать.

Метод dispose вызывается при уничтожении фабрики, в нём мы должныосвободить ранее выделенные ресурсы. В данном случае просто вызываемдеструктор.

Метод setup выполняется каждый раз при загрузке внешней функции в кешметаданных. В нём можно делать различные действия которые необходимыперед созданием экземпляра функции, например изменить формат для входныхи выходных сообщений. Более подробно поговорим о нём позже.

Метод newItem вызывается для создания экземпляра внешней функции. В этотметод передаётся указатель на статус вектор, контекст внешней функции иметаданные внешней функции. С помощью IRoutineMetadata вы можетеполучить формат входного и выходного сообщения, тело внешней функции идругие метаданные. В этом методе вы можете создавать различныеэкземпляры внешней функции в зависимости от её объявления в PSQL.Метаданные можно передать в созданный экземпляр внешней функции если этонеобходимо. В нашем случае мы просто создаём экземпляр внешней функцииTSumArgsFunction.

Экземпляр функции

Внешняя функция должна реализовать интерфейс IExternalFunction. Дляупрощения просто наследуем класс IExternalFunctionImpl.

Метод dispose вызывается при уничтожении экземпляра функции, в нём мыдолжны освободить ранее выделенные ресурсы. В данном случае простовызываем деструктор.

Метод getCharSet используется для того, чтобы сообщить контексту внешней функциинабор символов, который мы хотим использовать при работе с соединением из текущего контекста.По умолчанию соединение из текущего контекста работает в кодировке текущего подключения, что не всегда удобно.

Метод execute обрабатывает непосредственно сам вызов функции. В этотметод передаётся указатель на статус вектор, указатель на контекствнешней функции, указатели на входное и выходное сообщение.

Контекст внешней функции может потребоваться нам для получения контекстатекущего соединения или транзакции. Даже если вы не будете использоватьзапросы к базе данных в текущем соединении, то эти контексты всё равномогут потребоваться вам, особенно при работе с типом BLOB. Примерыработы с типом BLOB, а также использование контекстов соединения итранзакции будут показаны позже.

Входные и выходные сообщения имеют фиксированную ширину, которая зависитот типов данных декларируемых для входных и выходных переменныхсоответственно. Это позволяет использовать типизированные указатели наструктуры фиксированный ширины, члены который должны соответствоватьтипам данных. Из примера видно, что для каждой переменной в структуреуказывается член соответствующего типа, после чего идёт член, которыйявляется признаком специального значения NULL (далее Null флаг). Помимоработы с буферами входных и выходных сообщений через структуры,существует ещё один способ с использованием адресной арифметики науказателях с использованием смещениях, значения которых можно получитьиз интерфейса IMessageMetadata. Подробнее о работе с сообщениями мыпоговорим далее, а сейчас просто поясним что делалось в методе execute.

Первым делом мы преобразовываем не типизированные указатели ктипизированным. Для выходного значение устанавливаем Null флаг взначение True (это необходимо чтобы функция возвращала NULL, если одиниз входных аргументов равен NULL). Затем проверяем Null флаги у всехвходных аргументов, если ни один из входных аргументов не равен NULL, товыходное значение будет равно сумме значений аргументов. Важно не забытьсбросить Null флаг выходного аргумента в значение False.

Регистрация процедур

Пришло время добавить в наш UDR модуль хранимую процедуру. Как известнохранимые процедуры бывают двух видов: выполняемые хранимые процедуры ихранимые процедуры для выборки данных. Сначала добавим выполняемуюхранимую процедуру, т.е. такую хранимую процедуру, которая может бытьвызвана с помощью оператора EXECUTE PROCEDURE и может вернуть не болееодной записи.

Вернитесь в модуль UdrInit и измените функцию firebird_udr_plugin так,чтобы она выглядела следующим образом.

function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr;
  AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl;
begin
  // регистрируем наши функции
  AUdrPlugin.registerFunction(AStatus, 'sum_args',
    TSumArgsFunctionFactory.Create());
  // регистрируем наши процедуры
  AUdrPlugin.registerProcedure(AStatus, 'sum_args_proc',
    TSumArgsProcedureFactory.Create());
  //AUdrPlugin.registerProcedure(AStatus, 'gen_rows', TGenRowsFactory.Create());
  // регистрируем наши триггеры
  //AUdrPlugin.registerTrigger(AStatus, 'test_trigger',
  //  TMyTriggerFactory.Create());

  theirUnloadFlag := AUnloadFlagLocal;
  Result := @myUnloadFlag;
end;
Note
Замечание

Не забудьте добавить в список uses модуль SumArgsProc, в котором и будетрасположена наша процедура.