FirebirdSQL logo

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

Внешняя функция должна реализовать интерфейс 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, в котором и будетрасположена наша процедура.

Фабрика процедур

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

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

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

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

Фабрику процедуры, а также саму процедуру расположим в модулеSumArgsProc.

unit SumArgsProc;

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

interface

uses
  Firebird;

  { **********************************************************

    create procedure sp_sum_args (
      n1 integer,
      n2 integer,
      n3 integer
    ) returns (result integer)
    external name 'myudr!sum_args_proc'
    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;

  // Фабрика для создания экземпляра внешней процедуры TSumArgsProcedure
  TSumArgsProcedureFactory = class(IUdrProcedureFactoryImpl)
    // Вызывается при уничтожении фабрики
    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;

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

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

  TSumArgsProcedure = class(IExternalProcedureImpl)
  public
    // Вызывается при уничтожении экземпляра процедуры
    procedure dispose(); override;

    { Этот метод вызывается непосредственно перед open и сообщает
      ядру наш запрошенный набор символов для обмена данными внутри
      этого метода. Во время этого вызова контекст использует набор символов,
      полученный из 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 Указатель на выходное сообщение)
      @returns(Набор данных для селективной процедуры или
               nil для процедур выполнения)
    }
    function open(AStatus: IStatus; AContext: IExternalContext; AInMsg: Pointer;
      AOutMsg: Pointer): IExternalResultSet; override;
  end;

implementation

{ TSumArgsProcedureFactory }

procedure TSumArgsProcedureFactory.dispose;
begin
  Destroy;
end;

function TSumArgsProcedureFactory.newItem(AStatus: IStatus;
  AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalProcedure;
begin
  Result := TSumArgsProcedure.create;
end;

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

end;

{ TSumArgsProcedure }

procedure TSumArgsProcedure.dispose;
begin
  Destroy;
end;

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

end;

function TSumArgsProcedure.open(AStatus: IStatus; AContext: IExternalContext;
  AInMsg, AOutMsg: Pointer): IExternalResultSet;
var
  xInput: PSumArgsInMsg;
  xOutput: PSumArgsOutMsg;
begin
  // Набор данных для выполняемых процедур возращать не надо
  Result := nil;
  // преобразовываем указатели на вход и выход к типизированным
  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;

end.

Экземпляр процедуры

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

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

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

Метод open обрабатывает непосредственно сам вызов процедуры. В этотметод передаётся указатель на статус вектор, указатель на контекствнешней процедуры, указатели на входное и выходное сообщение. Если у васвыполняемая процедура, то метод должен вернуть значение nil, в противномслучае должен вернуться экземпляр набора выходных данных для процедуры.В данном случае нам не нужно создавать экземпляр набора данных. Простопереносим логику из метода TSumArgsFunction.execute.