译者叙:
由于InterBase性能方面表现突出,并且它是开源和跨平台的,有很多使用Delphi,C++Builder的程序员开始采用它做为其后数据库平台。但是InterBase的中文资料简直太少了。昨日,一网友在CSDN上提到InterBase的UDF(user defined functions 用户自定义函数),我顺手一查,网上竟然查不到这方面的中文资料。于是,我今天将一篇如何编写和使用InterBase UDF的文章翻译给大家,希望使用Interbase,C++Builder,Delphi的朋友能喜欢!
-----warton 2003.01.21
什么是UDF?
UDF—-( user defined functions)用户定义函数,是InterBase中采用任何一种语言编写(一般用C/C++,也可以用其它语言如pascal)并编译成共享库的函数。在windows平台下,共享库一般指动态链接库(DLL)。
为什么编写UDF?
毕竟存储过程自身可以实现相当多的功能。那为什么还要用UDF呢?
然而,事实是InterBase并没有提供相当丰富的内置函数。一些普通的函数、字符串操作、日期操作等相关的函数据都没有提供。
于是这样的事发生了,像Delphi,C这样的编程语言可以提供执行速度特别快的模块算法、日期变量处理、浮点数格式化、及字符中操作程序。
编写UDF是个狂简单的任务,这可以说是从所周知了。然而,没经验的编写者可能对编写DLL/共享库感觉太难和不安…
使用Delphi编写Windows平台的UDF
首先启动一个Delphi工程
1.启动车个delphi dll工程(一个特殊类型的工程,当你选择“File”,“New”)
为你的函数生成一个新的单元
2.选择”File”,”New”…UNIT
3.聪明的你好现在保存所有文件…把你的工程保存到一个你认为合适的地方。
生成一个模块程序
4.在新生成的单元文件:
5. 在接口段定义你的函数:
function Modulo(var i, j: Integer): Integer; cdecl; export;
6.实现这个函数:
function Modulo(var i, j: Integer): Integer;
begin
if (j = 0) then
result := -1 // just check the boundary condition, and
// return a reasonably uninteresting answer.
else
result := i mod j;
end;
7.在新生成的工程源代码中,直接将下面的代码写到”begin end.”之上:
exports
Modulo;
8.现在编译工程,你将得到一个可工作的动态链接库。
9.现在,我必须做的是将这个DLL复制到InterBase可以找到的UDF目录下,它可能是:
c:/Program Files/Borland/InterBase/UDF
10.如何使用UDF….按如下的操作。使用ISQL连接到一个己存在的新的数据库
11.写上如下的代码:
declare external function f_Modulo
integer, integer
returns
integer by value
entry_point 'Modulo' module_name 'dll name minus ".dll"';
12提交你的改变。
13.现在测试它…
select f_Modulo(3, 2) from rdb$database
吆…这简直太简单了,不是吗?
但是关于字符串和日期型怎么处理呢?
怎么编写字符串和日期型处理函数的UDF呢?
下面编写一个“Left“函数
内存分配问题:
如果你使用IB(InterBase)5.1以下版本的话,在你的单元文件中键入下面的声明:
function malloc(Bytes: Integer): Pointer; cdecl; external 'msvcrt.dll';
如果你有5.5以上的版本,你就不需要这么做了。这样,先确定ib_util.pas 文件在你的编译器可以找到的路径中,并且ib_util.dll也在你的搜索路径之中。
简单的方法是放到的库可搜索到的路径。
c:/Program Files/InterBase Corp/InterBase/include
然后复制
c:/Program Files/InterBase Corp/InterBase/lib/ib_util.dll
到你的windows系统目录下(典型的是:c:/Windows/System)。后在你的单元文件的use子句中将ib_util.pas加进去
uses
...,
ib_util;
为什么有如此奇特的内存分配?为什么我不能用AllocMem分配内存,或者其它方法?问题很简单:你不能,所以不要问!更复杂的问题是每一个编译器使用它喜欢的算法来进行内存管理,这是被操作系统控制着的。例如,VC和Delphi管理内存的方式不同,猜这是为什么?IB是在VC下编译的,在5.5以前的版本中,你必须直接指定运行库。在IB5.5之后,IB给了一个IB调用使得这成为可能。既然这样,那我们继续进行编写字符串操作的函数。
构建函数
在新的单元文件中,作如下声明:
function Left(sz: PChar; Cnt: Integer): PChar; cdecl; export;
在函数实现部分:
(* Return the Cnt leftmost characters of sz *)
function Left(sz: PChar; var Cnt: Integer): PChar;
var
i: Integer;
begin
if (sz = nil) then
result := nil
else begin
i := 0;
while ((sz[i] <> #0) and (i < cnt)) do Inc(i);
result := ib_util_malloc(i+1);
Move(sz[0], result[0], i);
result[i] := #0;
end;
end;
在你的工程文件中,在“exports“部分键入:
exports
Modulo,
Left;
Now,再次编译工程项目….
现在,为了使用函数,在ISQL中重新连接数据库,输入:
declare external function f_Left
cstring(64), integer
returns cstring(64) free_it
entry_point 'Left' module_name 'dll name minus ".dll"';
测试这个函...
select f_Left('Hello', 3) from rdb$database
仍然相当简单,哈?
上我们来编写一个时间函数
在IB6中,有三种不同的时期类型被支持,DATE,TIME,和TIMESTAMP,与IB5.5及以前老版本中的相比,TIMESTAMP 的数据类型实质上相当于IB5.5的DATE类型。
为了在你的程序中”decode”和”encode”这些类型。你应该了解一点IB API相关的信息。输入以下代码:
interface
...
type
TM = record
tm_sec : integer; // Seconds
tm_min : integer; // Minutes
tm_hour : integer; // Hour (0--23)
tm_mday : integer; // Day of month (1--31)
tm_mon : integer; // Month (0--11)
tm_year : integer; // Year (calendar year minus 1900)
tm_wday : integer; // Weekday (0--6) Sunday = 0)
tm_yday : integer; // Day of year (0--365)
tm_isdst : integer; // 0 if daylight savings time is not in effect)
end;
PTM = ^TM;
ISC_TIMESTAMP = record
timestamp_date : Long;
timestamp_time : ULong;
end;
PISC_TIMESTAMP = ^ISC_TIMESTAMP;
implementation
...
procedure isc_encode_timestamp (tm_date: PTM;
ib_date: PISC_TIMESTAMP);
stdcall; external IBASE_DLL;
procedure isc_decode_timestamp (ib_date: PISC_TIMESTAMP;
tm_date: PTM);
stdcall; external IBASE_DLL;
procedure isc_decode_sql_date (var ib_date: Long;
tm_date: PTM);
stdcall; external IBASE_DLL;
procedure isc_encode_sql_date (tm_date: PTM;
var ib_date: Long);
stdcall; external IBASE_DLL;
procedure isc_decode_sql_time (var ib_date: ULong;
tm_date: PTM);
stdcall; external IBASE_DLL;
procedure isc_encode_sql_time (tm_date: PTM;
var ib_date: ULong);
stdcall; external IBASE_DLL;
现在我们写日期UDF:
在单元文件的interface部分,输入声明:
function Year(var ib_date: Long): Integer; cdecl; export;
function Hour(var ib_time: ULong): Integer; cdecl; export;
在implementation(实现)部分,输入:
function Year(var ib_date: Long): Integer;
var
tm_date: TM;
begin
isc_decode_sql_date(@ib_date, @tm_date);
result := tm_date.tm_year + 1900;
end;
function Hour(var ib_time: ULong): Integer;
var
tm_date: TM;
begin
isc_decode_sql_time(@ib_time, @tm_date);
result := tm_date.tm_hour;
end;
好,在你的工程文件中:在““部分,输入:
exports
Modulo,
Left,
Year,
Hour;
现在编译工程...
为了使用这个函数,就像上面那样:用ISQL连接数据库,输入:
declare external function f_Year
date
returns integer by value
entry_point 'Year' module_name 'dll name minus ".dll"';
declare external function f_Hour
time
returns integer by value
entry_point 'Hour' module_name 'dll name minus ".dll"';
后再测试一下看!
select f_Year(cast('7/11/00' as date)) from rdb$database
这一部分并非像前面字符串和整数那样操作哪样了,但是还是相当...simple,哼!
编写Linux/Unix平台的UDF
编写Linux/Unix平台的UDF
(译者注:原用大量篇幅讲述了linux下的SO(shared object)文件和windows下的DLL文件,其实它们都是一种动态链接库,在此不做详细翻译!)
怎么在Linux 下生成一个shared library(共享库)?
1.建立一个C文件(扩展名为.c)
2.建立模块函数:int modulo(int *, int *);
int modulo(a, b)
int *a;
int *b;
{
if (*b == 0)
return -1; // return something suitably stupid.
else
return *a % *b;
}
编译和使用它
在命令行下:
gcc -c -O -fpic -fwritable-strings <your udf>.c
ld -G <your udf>.o -lm -lc -o <your udflib>.so
cp <your udflib>.so /usr/interbase/udf
然后在ISQL中:
declare external function f_Modulo
integer, integer
returns
integer by value
entry_point 'modulo' module_name 'name of shared library';
commit;
select f_Modulo(3, 2) from rdb$database;
真是太简单了!!
结论:
看来编写UDF真的不难!看,它包含的内容也不太多!毕竟Linux开发者也不为难了。
后:
如果你想要更好的编程例子,可以下载FreeUDFLib—一个Delphi的UDF库和一个FreeUDFLibC―一个基于C的UDF库,它可以运行在Solaris,Linux,Windows等
[UDF系列之四]:传递和返回数据到一个Delphi编写的UDF
Warton译
作者: Chris Levesque, Tina Grubbe, Brett Bandy
--------------------------------------------------------------------------------
[译者叙]:
前面我已经翻译了几篇关于编写UDF的文章,虽然一些朋友可能也从中得到了一点帮助,但是可能对UDF的
认识还存在一些问题。今天,我再翻译两文章,这两篇文章都是来自MER System (http://www.mers.com)
的,有兴趣的朋友可以查看原文。
[论点]:
当动态链接库没有为受保护的数据值做特殊的预防时,我们的UDF带有参数值或返回值的数据结果
可能处在一个受保护的异常或错误结果之中。
[解决方案]:
每一个日期值被保存在两个32位的整数类型之中:一个表示日期的signed integer,和一个表示
时间的unsigned integer。使用Delphi代码来定义这个结构(ISC_QUAD)和结构的指针(PISC_QUAD):
type
{InterBase Date/Time Record}
ISC_QUAD = record
isc_quad_high : Integer ; // Date
isc_quad_low : Cardinal ; // Time
end;
PISC_QUAD = ^ISC_QUAD;
为了保护返回值,在函数定义的外部申明一个线程安全的ISC_QUAD变量,使它保存返回值(如果返回值
是一个日期型的数据)。
threadvar
tempquad : ISC_QUAD;
然后编写你的函数以便结果指向线程变量。
// 定义函数
// This function adds a number of days to an existing date.
function DayAdd( var Days: Integer; IBDate PISC_QUAD) : PISC_QUAD; cdecl; export;
begin
tempquad.isc_quad_high := IBDate^.isc_quad_high + Days;
tempquad.isc_quad_low := IBDate^.isc_quad_low;
Result := @tempquad;
end;
[概述]:
用户定义函数(UDF)是一种采用编译语言编写的函数,是用户为执行自定义的功能和常用的任务面设计的。UDF 允许程序员模块化一个数据库应用程序,并能嵌入到数据库以增强数据库自身的功能。UDF 也总是在数据库服务器端执行。这可以减少网络通信量。
UDF可以执行事务比如获得服务器上可用硬盘空间,清理字符串的空格,为一系列值计算标准偏差等等。UDF能完成任何功能只要编程语言能够表达出来。这种编程语言能常是在C和C++之间选择(当然,用户可以用其它语言,比如pascal 译者注)。
UDF在提供SQL语言不能处理的功能、查询、更新数据库,以及为异源客户工作站提供通用函数方面非常有效。
与其它功能相比,使用UDF要花费一些代价。这主要表现在两方面:,在UNIX或VMS平台,UDF是模块化AST程序,这意谓着当UDF在执行时,没有其它的存取操作可以发生。这就要求我们尽量使UDF尽可能的小和有效率。第二,如果数据库服务器崩溃,你需要将数据转移到另一台机器上。你必须首先在新的服务器上安装UDF库。如果在相同的操作系统下,这并不难。但是当转移到另一操作系统时,你只少需要编译一遍UDF库的源代码。
[UDF示例]:
InterBase包含几个内置SQL 函数:UPPER,GEN_ID,CAST 。UPPER将一个字符串转为大写。GEN_ID生成一个长整型值,因为一个特殊的生成器已经在数据库中定义。这在生成主键时是非常有用的,比如客户编号或职员编号。CAST将一种类型列转化为另一种类型的列。
InterBase 也提供了另外一些UDF的源代码,在examples目录下。它们包含在udflib.c文件中。这里定义的UDF有:lower, strcat, substr, trim, trunc, doy, moy, dow, sysdate, add2, mul, fact, abs, maxnum, sqrt, blob_linecount, blob_bytecount, substr_blob。Lower是将字符串转化为小写串。Strcat连接两个字符串。Substr返回字符串的一部分。Trim清空字符串中的空格。Trunc返回删节后的串。doy(day of year),moy(month of year),dow(day of week)。Sysdate返回当前日期以字符串的形式(“mmm-dd-yyyy”).add2将两整数相加在一起。Mul将两double数相乘.maxnum返回两数中较大者。Sqrt为取平方。blob_bytecount返回blob的大小。Substr_blob,取blob的一部分文本。
我们将增加几个新的UDF:rtrim, left, right, swapcase, imonth, iday, iyear。Rtrim去掉字符串右边的空格。Left返回一个输入串前n个字符的串。Right则是返回右过n个字符的串。Swapcase将小写转大写并将大写转小写。imonth返回月份的值(1-12)。Iday返回day的值(1-31)。Iyear返回年的值如:2002。
[编写UDF]:
一旦你编写的UDF,你必须创建一个动态链接库,以便上UDF可以使用。然后你必须在数据库中定义它。
在数据定义语言中定义 UDFs 非常简单,基本的语法是:
DEFINE EXTERNAL FUNCTION name [<datatype> | CSTRING (int)
[, <datatype> | CSTRING (int) ...]]
RETURNS {<datatype> [BY VALUE] | CSTRING (int)}
ENTRY_POINT "<entryname>"
MODULE_NAME "<modulename>" ;
Name是指函数的名称,它可达到31个字符长。个datatype是输入参数。Datatype指标准的interbase数据类型:INTEGER, CHAR, VARCHAR等。或者,你可以使用CSTRING这个C风格没用结束的字符数组。Entry_point指实际的函数名。在InterBase提供示例中,SQL函数名是lower而实际函数名在udflib.c中为fn_lower_c。模块名指的是函数被编译后的输出库名。例如:这里用SQL 定义函数lower和substr。
DEFINE EXTERNAL FUNCTION lower
VARCHAR (256)
RETURNS CSTRING (80)
ENTRY_POINT "fn_lower_c" MODULE_NAME "funclib";
DEFINE EXTERNAL FUNCTION substr
CSTRING (256), SMALLINT, SMALLINT
RETURNS CSTRING (80)
ENTRY_POINT "fn_substr" MODULE_NAME "funclib";
为了将函数加入到函数库中,在NT上使用Borland C++,我们应该让lib模块有有一个本地的拷贝:
implib mygds32.lib /interbas/bin/gds32.dll
然后连接必要的选项生成我们新的库funclib.dll。
bcc32 -v -a4 -DWIN32 -tWM -tWCD -efunclib.dll udf.c mygds32.lib
为了在本地使用DLL,它必须在BIN目录下(或你环境变的路径中)。如果要在远程使用,它必须在能运行InterBase服务的用户的环境变量路径之中,默认情况下,它在系统账户的PATH中。
[使用UDF]:
一但编译,连接,定义之后,一个UDF就可以在SQL 语句中使用了。
它们可以做如下使用:定义计算后字段做为表定义的一部分,在view定义中中做为列表达式或在存储过程和触发器中做为SELECT, INSERT, UPDATE, DELETE操作的一部分。
例如,使用计算结果做为字段:
CREATE TABLE name ( FIRST_NAME VARCHAR(20), LAST_NAME VARCHAR(20),
FULL_NAME_UPPER COMPUTED BY
(upper(FIRST_NAME) | " " | upper(LAST_NAME)));
在view中做为一个列表达式:
CREATE VIEW upper_names (FIRST_NAME, LAST_NAME) AS
SELECT upper(n.first_name), upper(n.last_name) FROM name n;
在选择操作中:
SELECT substr(n.FIRST_NAME, 2, 4) FROM name n WHERE
upper(n.LAST_NAME) = 'MOORE';
在插入操作中:
INSERT INTO name (FIRST_NAME, LAST_NAME)
VALUES (rtrim(:new_fname), rtrim(:new_lname));
在更新操作中:
UPDATE name SET LAST_NAME = rtrim(:new_lname) WHERE
upper(n.LAST_NAME) = 'JONES';
在删除操作中:
DELETE FROM name WHERE left(LAST_NAME, 3) = 'SMI';