27.2. 为MySQL添加新函数有两个途径来为MySQL添加新函数:
每种途径都有其优点和缺点:
无论你使用哪种方法去添加新函数,它们都可以被SQL声明调用,就像 ABS() 或 SOUNDEX()这样的固有函数一样。 另一个添加函数的方法时创建存储函数。这些函数时用SQL声明编写的,而不是编译目标代码。编写存储函数的语法在第20章:存储程序和函数 中描述。 下面的小节描述UDF接口的特性,给出编写UDF的指令,并讨论MySQL为防止UDF被误用而采取的安全预防措施。 给出源代码的例子来说明如何编写UDF,看一看MySQL源码分发版中提供的sql/udf_example.cc 文件。 MySQL自定义函数接口有如下特性和功能:
CREATE [AGGREGATE] FUNCTION function_name RETURNS {STRING|INTEGER|REAL} SONAME shared_library_name DROP FUNCTION function_name 一个自定义函数 (UDF)就是用一个象ABS() 或 CONCAT()这样的固有(内建)函数一样作用的新函数去扩展MySQL。 function_name 是 用在SQL声明中以备调用的函数名字。RETURNS 子句说明函数返回值的类型。 shared_library_name 是共享目标文件的基本名,共享目标文件含有实现函数的代码。该文件必须位于一个能被你系统的动态连接者搜索的目录里。 你必须有mysql 数据库的INSERT 权限才能创建一个函数,你必须有mysql 数据库的DELETE权限才能撤销一个函数。这是因为CREATE FUNCTION 往记录函数名字,类型和共享名的mysql.func系统表里添加了一行,而DROP FUNCTION则是从表中删掉这一行。如果你没有这个系统表,你应该运行mysql_fix_privilege_tables脚本来创建一个。请参阅2.10.2节,“升级授权表”。 一个有效的函数是一个用CREATE FUNCTION加载且没有用DROP FUNCTION移除的函数。每次服务器启动的时候会重新加载所有有效函数,除非你使用--skip-grant-tables参数启动mysqld。在这种情况下, 将跳过UDF的初始化,UDF不可用。 要了解编写自定义函数的说明,请参阅27.2.3节,“添加新的自定义函数”。要使得UDF机制能够起作用,必须使用C或者C++编写函数,你的系统必须支持动态加载,而且你必须是动态编译的mysqld(非静态)。 一个AGGREGATE函数就像一个MySQL固有的集合(总和)函数一样起作用,比如,SUM或COUNT()函数。要使得AGGREGATE 起作用,你的mysql.func表必须包括一个type列。如果你的mysql.func表没有这一 列,你应该运行mysql_fix_privilege_tables脚本来创建此 列。 要使得UDF机制能够起作用,必须使用C或者C++编写函数,你的系统必须支持动态加载。MySQL 源码分发版包括一个sql/udf_example.cc 文件,此文件定义了5个新函数。可以参考这个文件,看UDF是如何调用常规工作。 为了能使用UDF,你需要动态链接mysqld。不要配置MySQL使用--with-mysqld-ldflags=-all-static参数。如果你想使用一个需要从mysqld 访问符号的UDF(例如在使用default_charset_info的sql/udf_example.cc文件中的metaphone函数),你必须使用-rdynamic参数来链接程序(参阅man dlopen)。如果你计划使用UDF, 一个经验法则就是,用with-mysqld-ldflags=-rdynamic设定MySQL,除非你有很好的理由不去这么做。 如果你使用的是预编译分发版的MySQL, 请使用MySQL-Max,其中含有一个动态链接了的服务器,它可以支持动态加载。 对于每个你想要使用在SQL声明中的函数,你应该定义相应的C (或 C++)函数。在下面的讨论中,xxx用来表示范例函数的名字,为了区分使用SQL还是C/C++,xxx()(上标)表示SQL函数调用,xxx()(下标)表示C/C++函数调用。 你为xxx()编写来实现接口的C/C++ 函数如下:
当SQL声明调用XXX()时,MySQL调用初始化函数xxx_init(),让它执行必要的设置,比如,检查 参量或分配内存。如果xxx_init() 返回一个错误,SQL声明会退出并给出错误信息,而主函数和去初始化函数并没有被调用。 否则,主函数xxx() 对每一行都被调用一次。所有行都处理完之后,调用去初始化函数xxx_deinit() 执行必要的清除。 对于象SUM()一样工作的集合函数,你也必须提供如下的函数:
MySQL 按下列操作来处理集合UDF:
所有函数必须时线程安全的,这不仅对主函数,对初始化和去初始化函数也一样,也包括集合函数要求的附加函数。这个要求的一个结果就是,你不能分配任何变化的全局或静态变量。如果你需要内存,你可以在xxx_init()函数分配内存,然后在xxx_deinit()函数释放掉。 下面介绍创建简单UDF时需要定义的不同函数。27.2.3节,“添加新的自定义函数”中介绍了MySQL调用这些函数的顺序。 如本节所示,应该说明主函数xxx()。注意返回值和参数会有所不同,这取决于你说明的SQL函数xxx()在CREATE FUNCTION声明中返回的是STRING,INTEGER类型还是REAL类型示: 对于STRING 型函数: char *xxx(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error); 对于INTEGER型函数: long long xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error); 对于REAL型函数: double xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error); 初始化和去初始化函数如下说明: my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message); void xxx_deinit(UDF_INIT *initid); initid 参数被传递给所有的三个函数。它指向一个UDF_INIT 结构,这个结构被用来在函数之间交换信息。UDF_INIT 结构项跟随着。初始化函数应该给任何它想要改变的项赋值。(要使用项的默认值,就让它不被改变)
本节介绍创建集合UDF之时需要定义的不同函数。27.2.3节,“添加新的自定义函数” 介绍了MySQL调用这些函数的顺序。
对集合UDF而言xxx() 函数应该用与非集合UDF一样的方法来说明。请参阅27.2.3.1节,“UDF调用简单函数的顺序”。 对一个集合UDF,MySQL 在组内所有行被处理之后调用xxx()函数。这里你应该一般不会接触到它的UDF_ARGS参量,但是取而代之地根据内部总和变量返回给你值。 在xxx()中处理的返回值应该用与对非集合UDF一样的方法来操作。请参阅27.2.3.4节,“UDF返回值和错误处理”。 xxx_reset() 和 xxx_add() 函数用与非集合UDF一样的方法来处理它们的UDF_ARGS 参量。请参阅27.2.3.3节,“UDF参量处理”。 到is_null和error的指针 参量和所有到xxx_reset(), xxx_clear(), xxx_add() 和 xxx()调用一样。你可以用这个来提醒你获取一个错误或无论xxx()是否返回NULL的一个结果。你不能把一个字符串存到*error!error指向单字节变量而不是字符串缓冲区。 *is_null 对每一个组都重置(调用xxx_clear()前), *error 从不重置。 如果 xxx()返回时,*is_null 或 *error 被设置,MySQL返回 NULL作为组函数的结果。 args 参数指向列着结构元的 UDF_ARGS 结构:
如果没有错误发生,初始化函数应该返回0,否则就返回1。如果有错误发生,xxx_init() 应该在message 参数存储一个以null结尾的错误消息。该消息被返回给客户端。消息缓冲区是 MYSQL_ERRMSG_SIZE 字符长度,但你应该试着把消息保持在少于80个字符,以便它能适合标准终端屏幕的宽度。 对于long long 和 double 类型的函数,主函数 xxx()的返回返回值是函数值。字符函数返回一个指向结果的指针,并且设置 *result 和 *length 为返回值的内容和长度。例如: memcpy(result, "result string", 13); *length = 13; 被传给 xxx() 函数的结果缓冲区是 255 字节长。如果你的结果适合这个长度,你就不需要担心对结果的内存分配。 如果字符串函数需要返回一个超过255字节的字符串,你必须用 malloc() 在你的 xxx_init() 函数或者 xxx() 函数里为字符串分配空间,并且在 xxx_deinit() 函数里释放此空间。你可以将已分配内存存储在 UDF_INIT 结构里的 ptr 位置以备将来 xxx() 调用。请参阅27.2.3.1节,“UDF 对简单函数的调用顺序”。 要在主函数中指明一个 NULL 的返回值,设置 *is_null 为 1 : *is_null = 1; 要在主函数中指明错误返回,设置 *error 为 1 : *error = 1; 如果 xxx() 对任意行设置 *error 为 1 ,对于任何 XXX()被调用的语句处理的当前行和随后的任意行,该函数值为 NULL (甚至都不为随后的行调用 xxx())。 实现UDF的文件必须在运行服务器的主机上编译和安装。这个步骤在下面介绍,以包含在MySQL源码分发版里的UDF文件sql/udf_example.cc 为例。 紧接着下面的指令是对Unix的,对Windows的指令在本节稍后给出。 udf_example.cc 文件包含下列函数:
一个可动态加载的文件应使用如下这样的命令编译为一个可共享的对象文件: shell> gcc -shared -o udf_example.so udf_example.cc 如果你使用gcc,你应该能用一个更简单的命令创建udf_example.so : shell> make udf_example.so 通过运行MySQL源码树下sql里的如下命令,你可以容易地为你的系统决定正确的编译器选项: shell> make udf_example.o 你应该运行一个类似于make所显示那样的编译命令,除了要在行尾附近删除-c选项,并在行尾加上加上 -o udf_example.so。(在某些系统上,你可能需要在命令行留着-c 选项)。 编译好一个包含有UDF的共享目标后,你必须安装它并通知MySQL。从udf_example.cc编译一个共享目标文件产生一个名字类似于udf_example.so 的文件(确切名字可能因平台而异)。把这个文件复制到 /usr/lib 这样被你系统的动态(运行时)链接器搜索到的目录下,或者 把你放共享目标文件的目录添加到链接器配置文件(如,/etc/ld.so.conf )。 动态链接器的名字时系统特定的(如,在FreeBSD上是ld-elf.so.1 ,在Linux上是 ld.so,在Mac OS X上是dyld )。查看一下你系统的文档,看看链接器的名字是什么及如何配置链接器。 在许多系统上,你也可以设置环境变量LD_LIBRARY 或 LD_LIBRARY_PATH 指向你放UDF的目录。dlopen 手册会告诉你,在你系统上用哪个变量名。你可以在mysql.server 或 mysqld_safe 启动脚本里设置这个然后重启 mysqld。 在一些系统上,配置动态链接器的ldconfig不能识别不是以lib做名字开头的共享目标。在这种情况下,你应该把udf_example.so 改名为 libudf_example.so。 在Windows系统上,你可以通过下列步骤编译自定义函数:
共享目标文件安装完以后,为新函数信息修改 mysqld ,做如下声明: mysql> CREATE FUNCTION metaphon RETURNS STRING SONAME 'udf_example.so'; mysql> CREATE FUNCTION myfunc_double RETURNS REAL SONAME 'udf_example.so'; mysql> CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME 'udf_example.so'; mysql> CREATE FUNCTION lookup RETURNS STRING SONAME 'udf_example.so'; mysql> CREATE FUNCTION reverse_lookup -> RETURNS STRING SONAME 'udf_example.so'; mysql> CREATE AGGREGATE FUNCTION avgcost -> RETURNS REAL SONAME 'udf_example.so'; 可以使用DROP FUNCTION删除函数: mysql> DROP FUNCTION metaphon; mysql> DROP FUNCTION myfunc_double; mysql> DROP FUNCTION myfunc_int; mysql> DROP FUNCTION lookup; mysql> DROP FUNCTION reverse_lookup; mysql> DROP FUNCTION avgcost; CREATE FUNCTION 和 DROP FUNCTION 声明更新mysql 数据库中的func 系统表。函数名,类型和共享库名存进表中。你必须有mysql 数据库的INSERT 和DELETE 权限来创建和移除函数。 你不能使用 CREATE FUNCTION 去田间一个先前已经被创建的函数。如果你需要重新安装一个函数,你可以用DROP FUNCTION移除它,然后再用CREATE FUNCTION重新安装它。你可能会需要这么做,比如你重新编译新版本的函数以便mysqld得到这个新版本。不然,服务器还继续使用旧的版本。 一个有效程序是已被 CREATE FUNCTION加载且没有被DROP FUNCTION移除的函数。所有有效函数在每次服务器启动时重新加载,除非你使用--skip-grant-tables选项来启动mysqld。这种情况下,UDF的初始化将被跳过,UDF不可用。 MySQL 采取下列措施来防止误用自定义函数。 你必须有 INSERT 权限才能使用 CREATE FUNCTION 及有 DELETE 权限才能使用 DROP FUNCTION。这是很必要的,因为这些声明在mysql.func表里添加合删除行。 除了对应主 xxx()函数的xxx 符号,UDF应该至少定义一个符号。这些辅助符号对应 xxx_init(), xxx_deinit(), xxx_reset(), xxx_clear() 和 xxx_add() 函数。mysqld 也支持一个控制仅有一个xxx符号的UDF是否被加载的--allow-suspicious-udfs。这个选项 默认是关,以防止从共享目标文件而不是从这些已包含的合法UDF加载的企图。如果你有仅含xxx符号的老版本UDF,以及不能重编译来包含辅助符号的老版本UDF,那就有必要选--allow-suspicious-udfs 选项。否则,你应该避免打开这个选项。 UDF 目标文件不能放在任意目录。它们必须位于动态链接器被配置来搜索到的一些系统目录。为强制执行这个限制并防止指定被动态链接器搜索到的目录之外的路径,MySQL在加载函数的时候检查在CREATE FUNCTION 中指定的共享目标文件名,以及存在mysql.func表中的文件的路径分隔符。这防止通过直接操作mysql.func表指定非法路径名。有关UDF和运行时链接器,请参阅27.2.3.5节,“编译和安装自定义函数”。 下面介绍添加新固有函数的步骤。要注意你不能添加固有函数到二进制分发版里,因为这个步骤包含修改MySQL源代码。你必须从源码分发版自己编译MySQL。另外要注意,如果你把MySQL移植到另一个版本(比如新版本放出来的时候),你需要用新版本重复这个添加 步骤。 采取下列步骤来添加MySQL新的固有函数:
所有函数都必须是线程安全的,换句话说就是,如果没有互斥体保护,不要在函数中使用任何全局或静态变量。 如果你想要从函数::val(), ::val_int()或::str()返回NULL,你应该设null_value为1,并返回0。 对于目标函数 ::str() 有一些需要而外考虑之处::
在MySQL中,你可以用C++定义一个步骤,在一个查询被发送到客户端之前访问和修改其中的数据。修改可以一行接一行地做,或者按照级别成组(GROUP)地做。 我们创建一个范例步骤来演示你可以做的。 此外,我们推荐你看一下mylua。通过它你可以用 LUA语言把运行时里的一个 步骤加载到mysqld中。 analyse([max_elements,[max_memory]]) 这个步骤在sql/sql_analyse.cc定义,这个步骤检查你查询的结果,并且返回对此结果的一个分析:
SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]]) |
|
来自: 322yangxinxing > 《MySQL》