35. Функции, определяемые пользователем

35.1. Понятие функции, определяемой пользователем

Состав встроенных функций InterBase весьма небогат - в него входят функции вычисления агрегированных значений (MIN, MAX, SUM, A VG), функция преобразования букв UPPER и функция приведения типа CAST.

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

Специально для таких целей в InterBase существует аппарат функции, определяемых пользователем (User Defined Functions, UDF).

Разработка UDF может осуществляться на любом алгоритмическом языке, позволяющем создавать DLL (dynamic link library, динамически загружаемые библиотеки). Каждая UDF оформляется в виде функции, входящей в состав DLL. Таким образом, одна динамически загружаемая библиотека состоит минимум из одной функции.

После того как DLL разработана, она либо перемещается в подкаталог BIN каталога размещения InterBase, либо располагается в ином каталоге, путь к которому известен в операционной системе.

Каждая функция определяется оператором DECLARE EXTERNAL FUNCTION. Этот оператор устанавливает связь между функцией из DLL и ее описанием в БД. После этого функция может использоваться в SQL-операторах наряду со стандартными функциями InterBase.

Преимущества UDF:

• динамически загружаемая библиотека и определенные в ней функции могут использоваться более чем одной БД и более чем одним приложением; таким образом осуществляется повторное использование однажды написанного кода;

• пользователь может реализовать в UDF достаточно сложные алгоритмы.

35.2. Разработка DLL и UDF в Delphi

35.2.1. Общие положения

Чтобы создать с помощью Delphi динамически загружаемую библиотеку, необходимо в главном меню выбрать режим New и затем на странице New выбрать пиктограмму DLL для построения шаблона DLL. Текст модуля DLL должен содержать:

• заголовок Library имя, где имя - имя создаваемой DLL;

• в разделе реализации модуля нужно разместить один или несколько блоков exports, в которых через запятую перечисляются имена экспортируемых функций; каждая функция должна описываться выше блока по тексту модуля;

• каждая экспортируемая функция объявляется с использованием директив export и cdecl(последняя указывает компилятору, что функция использует соглашения для передачи параметров, принятые в C/C++).

Пример определения функции:

function SomeName(parami : Integer) : Integer; cdecl; export;

begin

end;

exports

SomeName;

35.2.2. Совместимость типов параметров

При описании параметров в БД (оператор DECLARE EXTERNAL FUNCTION) и параметров функций в DLL следует помнить о совместимости типов Object Pascal и InterBase:

Тип InterBase Тип Object Pascal

INTEGER Integer

DOUBLE PRECISION Double

CSTRING PChar

DATE IBDateTime = record

// нужно дополнительное преобразование значений

Days : Integer;

Msec: Cardinal;

end;

Данное соответствие типов верно для случая, когда результат функции передается в базу данных по значению. Для того чтобы результат передавался по значению, необходимо в операторе DECLARE EXTERNAL FUNCTION после слова RETURNS указать слово BY VALUE. В следующем примере объявляется функция DEN типа DATE, содержащаяся в DLL с именем UDF_DLL:

DECLARE EXTERNAL FUNCTION DEN DATE

RETURNS INTEGER BY VALUE

ENTRY_POINT "Den"

MODULE_NAME "udf_dll";

В том случае, если результат работы UDF передается в БД по ссылке, необходимо использовать указатели на соответствующие типы:

InterBase Object Pascal

INTEGER integer

DOUBLE PRECISION double

DATE IBDateTime

Тип PChar всегда передается по ссылке.

Будем использовать только параметры, передаваемые по значению, поскольку вызовы UDF предполагается осуществлять в SQL-операторах типа SELECT, INSERT, UPDATE, DELETE, а эти операторы не будут изменять содержимое параметров.

35.2.3. Особенности использования в UDF параметров типа PChar

Параметры типа PChar используются для совместимости с форматом представления строк C/C++, однако в Object Pascal со строками, передаваемыми как PChar, в теле функции лучше работать как с длинной строкой Pascal (String), воспользовавшись преобразованием из типа PChar в String и обратно.

Пример. Функция принимает строку типа PChar и отсекает хвостовые и ведущие пробелы:

function TrimChar(InString : PChar) : PChar; cdecl; export;

begin

Result := PChar(Trim(AnsiString(InString)));

end;

35.2.4. Особенности использования в UDF параметров типа даты и времени

Значения InterBase типа DATE в Object Pascal интерпретируются как запись, состоящая из двух полей - целочисленного знакового и беззнакового.

IBDateTime = record

Days : Integer;

Msec : Cardinal;

end;

Для того чтобы перевести значение из формата IBDateTime в формат даты и времени Delphi TDateTime, необходимо произвести следующее преобразование:

ДaтаDelphi =ДатаInterBase.Days -15018 +ДamaInlerBase.MSec/(MSecsPerDay * 10);

где константа MSecsPerDay определена в Delphi как число миллисекунд в сутках.

35.3. Объявление UDF в БД InterBase

Для объявления функции, определенной пользователем, в БД InterBase, необходимо выполнить оператор

DECLARE EXTERNAL FUNCTION ИмяФункции

[<Тип данных> | CSTRING (число) [, <Тип данных> | CSTRING (число) ...]]

RETURNS {< Тип данных > [BY VALUE] | CSTRING (число)}

ENTRY_POINT "<Имя функции в DLL>"

MODULE_NAME "< Имя DLL >";

ИмяФункции - имя функции, под которой функция будет известна в БД. Это имя может отличаться от имени UDF в DLL. После имени функции следует список типов входных параметров функции. Это либо тип данных, разрешенный в InterBase, либо CSTRING (число для строковых значений. Число определяет размер строкового значения в символах. Если число меньше действительного размера строки, строка при передаче в UDF усекается.

После слова RETURNS указывается тип возвращаемого параметра функции. Это либо тип данных, разрешенный в InterBase, либо CSTRING (число) для строковых значений. Слова BY VALUE означают, что результат функции возвращается по значению, а не по ссылке.

После ENTR Y_POINTa кавычках указывается имя функции в DLL, а после слов MODULE_NAME - имя модуля DLL (без расширения).

Удалить из БД объявление функции, определенной пользователем, можно при помощи оператора

DROP EXTERNAL FUNCTION ИмяФункции;

35.4. Пример создания DLL с несколькими UDF и объявления их в БД

Создадим DLL с именем 'UDF_DLL', в состав которой входят три функции:

library udf_dll;

uses

SysUtils,

Classes;

type

TIBDateTime = record

Days,

MSecIO : Cardinal;

end;

PInteger = ^Integer;

// функция усекает ведущие и хвостовые пробелы у строкового // значения, передаваемого как параметр function TrimChar(InString : PChar) : PChar; cdecl; export;

begin

Result := PChar(Trim(AnsiString(InString)));

end;

//функция возвращает (по значению) номер дня передаваемой в качестве параметра даты

function Den(var InDate : TIBDateTime) : Integer; cdecl; export;

var DT : TDateTime;

Gd,Ms,Dn : Word;

begin

DT := InDate.Days - 15018 + InDate.MSecIO / (MSecsPerDay * 10);

DecodeDate(DT,Gd,Ms,Dn) ;

Result := Integer(Dn);

end;

//функция возвращает (по ссылке) номер месяца передаваемой в качестве параметра даты

function Mes(var InDate : TIBDateTime) : PInteger; cdecl; export;

var DT : TDateTime;

Gd,Ms,Dn : Word;

R : Integer;

begin

DT := InDate.Days - 15018 + InDate.MSecIO / (MSecsPerDay * 10);

DecodeDate(DT,Gd,Ms,Dn);

R := Ms;

Result := @R;

end;

exports

TrimChar,

Den,

Mes;

begin

end.

После генерации модуля UDF_DLL.DLL переместим его в подкаталог BIN каталога на диске, в котором расположен сервер InterBase. Затем объявим функции в БД:

DECLARE EXTERNAL FUNCTION TRIMCHAR CSTRING(256)

RETURNS CSTRING(256)

ENTRY_POINT "TrimChar"

MODULE_NAME "udf_dll";

DECLARE EXTERNAL FUNCTION DEN DATE

RETURNS INTEGER BY VALUE

ENTRY_POINT "Den"

MODULE_NAME "udf_dll";

DECLARE EXTERNAL FUNCTION MES DATE

RETURNS INTEGER

ENTRY_POINT "Mes"

MODULE_NAME "udf_dll";

Примеры использования объявленных в БД функций пользователя в операторе SELECT:

SELECT TRIMCHAR (POKUP) || ' ' II GOROD

FROM POKUPATELI

SELECT *

FROM RASHOD

WHERE DEN(DAT_RASH) > 10;