将MySQL 5.7/向MySQL添加函数/添加自定义功能

6.2添加自定义功能

要使UDF机制工作,函数必须用C或c++编写,而且操作系统必须支持动态加载。MySQL源发行版包含一个文件sql / udf_example.cc它定义了五个UDF函数。参考这个文件,了解UDF调用约定是如何工作的。的包括/ mysql_com.h头文件定义了udf相关的符号和数据结构,尽管你不需要直接包括这个头文件;它包含在mysql.h

UDF包含成为正在运行的服务器一部分的代码,因此当您编写UDF时,您将受到应用于编写服务器代码的任何和所有约束的约束。方法中的函数可能会出现问题libstdc + +图书馆。这些约束在服务器的未来版本中可能会发生变化,因此服务器升级可能需要对最初为旧服务器编写的udf进行修订。有关这些约束的信息,请参见MySQL源配置选项,处理编译MySQL时的问题

为了能够使用udf,您必须链接mysqld动态。如果您想使用UDF,需要访问符号从mysqld(例如,变音位函数sql / udf_example.cc使用default_charset_info),您必须将该程序与-rdynamic(见男人dlopen).

对于希望在SQL语句中使用的每个函数,应该定义相应的C(或c++)函数。在下面的讨论中,名称xxx用于示例函数名。为了区分SQL和C/ c++的用法,XXX ()(大写)表示SQL函数调用,和xxx ()(小写)表示C/ c++函数调用。

请注意

当使用c++时,将你的C函数封装在这个构造中:

extern "C"{…}

这确保了c++函数名在完成的UDF中保持可读。

  • 自定义功能

    下面的列表描述了编写这些C/ c++函数来实现名为XXX ().的主要功能,xxx (),是必需的。此外,UDF至少需要这里描述的其他函数中的一个,原因在

当SQL语句调用时XXX (), MySQL调用初始化函数xxx_init ()让它执行任何必需的设置,例如参数检查或内存分配。如果xxx_init ()返回一个错误,MySQL终止SQL语句并返回一个错误消息,并且不调用main函数或去初始化函数。否则,MySQL调用main函数xxx ()每一行一次。在处理完所有行之后,MySQL调用去初始化函数xxx_deinit ()这样它就可以执行任何需要的清理。

对于聚合函数,就像SUM (),你必须同时提供以下功能:

  • xxx_clear ()

    重置当前聚合值,但不插入参数作为新组的初始聚合值。

  • xxx_add ()

    将参数添加到当前聚合值。

MySQL处理聚合udf的方式如下:

  1. 调用xxx_init ()让聚合函数分配存储结果所需的任何内存。

  2. 根据表的集团表达式。

  3. 调用xxx_clear ()对于每个新组的第一行。

  4. 调用xxx_add ()对于属于同一组的每一行。

  5. 调用xxx ()在组更改时或在处理最后一行之后获取聚合的结果。

  6. 重复步骤3至5,直到处理完所有行

  7. 调用xxx_deinit ()让UDF释放它所分配的所有内存。

所有函数都必须是线程安全的。这不仅包括主函数,还包括初始化和反初始化函数,以及聚合函数所需的附加函数。这一要求的结果是,您不允许分配任何全局或静态变量的变化!如果你需要内存,你必须把它装进去xxx_init ()把它释放出来xxx_deinit ()

简单函数的UDF调用序列

本节描述创建简单UDF必须定义的不同函数。有关MySQL调用这些函数的顺序,请参见xxx ()函数的声明应如本节所示。注意,根据是否声明SQL函数,返回类型和参数不同XXX ()返回字符串整数,或真正的创建函数声明:

字符串功能:

char *xxx(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error);

整数功能:

long long xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);

真正的功能:

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);空白xxx_deinit (UDF_INIT * initid);

initid参数传递给所有三个函数。它指向一个UDF_INIT结构,用于在函数之间通信信息。的UDF_INIT结构成员。初始化函数应该填充它希望更改的任何成员。(要对成员使用默认值,请保持不变。)

  • my_bool maybe_null

    xxx_init ()应该设定maybe_null1如果xxx ()可以返回.默认值为1如果声明了任何参数maybe_null

  • unsigned int小数

    小数点右边的小数位数。默认值是传递给main函数的参数的最大十进制位数。例如,如果传递了函数1.341.345,1.3,默认值为3,因为1.345有3位十进制数字。

    对于没有固定小数数的实参,则小数值设置为31,该值比允许的最大小数数多1小数浮动,数据类型。这个值可以作为常量使用NOT_FIXED_DECmysql_com.h头文件。

    一个小数值31用于诸如a浮动列声明时没有显式小数(例如,浮动而不是浮动(10,3))和浮点常数,如1345 e - 3.它也用于可能在函数内转换为数字形式的字符串和其他非数字参数。

    的值小数成员是否初始化只是一个默认值。可以在函数内更改它,以反映实际执行的计算。默认情况下,将使用参数的最大小数数。如果小数的个数是NOT_FIXED_DEC对于其中一个实参,那就是用于的值小数

  • unsigned int max_length

    结果的最大长度。默认的max_length值根据函数的结果类型而不同。对于字符串函数,默认值是最长参数的长度。对于整型函数,默认值是21位。对于实函数,默认值是13加上表示的十进制位数initid - >小数.(对于数值函数,长度包括任何符号或小数点字符。)

    如果你想返回一个blob值,你可以设置max_length至65KB或16MB。不分配此内存,但是如果需要临时存储数据,则使用该值来决定使用哪种数据类型。

  • char * ptr

    一个指针,函数可以使用它自己的目的。例如,函数可以使用initid - > ptr在它们之间通信已分配的内存。xxx_init ()应该分配内存并将其分配给这个指针:

    initid - > ptr = allocated_memory;

    xxx ()而且xxx_deinit (),请参考initid - > ptr使用或释放内存。

  • my_bool const_item

    xxx_init ()应该设定const_item1如果xxx ()总是返回相同的值和0否则。

聚合函数的UDF调用序列

本节描述在创建聚合UDF时需要定义的不同函数。有关MySQL调用这些函数的顺序,请参见

xxx ()函数的声明方式应该与非聚合UDF的声明方式相同。看到xxx ()处理完组中的所有行之后,函数。你通常不应该访问它UDF_ARGS参数,而是返回一个基于内部摘要变量的值。

中的返回值处理xxx ()应该与处理非聚合UDF的方法相同。看到xxx_reset ()而且xxx_add ()函数处理他们UDF_ARGS参数的方法与非聚合udf的函数相同。看到is_null而且错误所有的呼叫都是一样的吗xxx_reset ()xxx_clear ()xxx_add ()而且xxx ().你可以用它来记住你得到了一个错误或者是否xxx ()函数应该返回.不应该将字符串存储到*误差错误指向单字节变量,而不是字符串缓冲区。

* is_null是否为每个组重置(在调用xxx_clear ()).*误差永远不会重置。

如果* is_null*误差设置时xxx ()回报,MySQL的回报作为组函数的结果。

UDF参数处理

arg游戏参数指向UDF_ARGS结构,其成员列在这里:

  • unsigned int arg_count

    参数的数量。如果需要用特定数量的参数调用函数,请在初始化函数中检查此值。例如:

    if (args->arg_count != 2) {strcpy(消息,"XXX()需要两个参数");返回1;}

    为其他UDF_ARGS数组成员值,数组引用是从零开始的。也就是说,使用从0到的索引值引用数组成员参数- > arg_count−1。

  • enum Item_result * arg_type

    一个指向数组的指针,该数组包含每个参数的类型。可能的类型值为STRING_RESULTINT_RESULTREAL_RESULT,DECIMAL_RESULT

    要确保实参是给定类型的,如果不是则返回错误,请检查arg_type数组在初始化函数中。例如:

    if (args->arg_type[0] != STRING_RESULT || args->arg_type[1] != INT_RESULT) {strcpy(消息,"XXX()需要一个字符串和一个整数");返回1;}

    参数的类型DECIMAL_RESULT都作为字符串传递,因此处理它们的方法与STRING_RESULT值。

    作为要求函数的参数为特定类型的替代方法,您可以使用初始化函数来设置arg_type元素转换为所需的类型。这导致MySQL在每次调用时将参数强制为这些类型xxx ().例如,要指定前两个参数应该分别强制为字符串和整数,请执行以下操作xxx_init ()

    参数- > arg_type [0] = STRING_RESULT;参数- > arg_type [1] = INT_RESULT;

    精确值的十进制参数,例如1.3小数的类型传递列值DECIMAL_RESULT.但是,值是作为字符串传递的。要接收一个数字,使用初始化函数指定实参应该强制为aREAL_RESULT值:

    参数- > arg_type [2] = REAL_RESULT;
  • char * * args

    参数- >参数将有关传递给函数的参数的一般性质的信息传递给初始化函数。对于一个持续的争论参数- >参数[我]指向实参值。(关于如何正确访问该值,请参阅后面的说明。)对于一个非常数参数,参数- >参数[我]0.常量实参是只使用常量的表达式,例如3.4 * 7 - 2罪(3.14).非常量实参是引用可能在行与行之间变化的值的表达式,例如用非常量实参调用的列名或函数。

    对于主函数的每次调用,参数- >参数包含为当前正在处理的行传递的实际参数。

    如果参数代表参数- >参数[我]是空指针(0)。如果参数不是,函数可以引用如下:

    • 类型的实参STRING_RESULT以字符串指针加上长度的形式给出,以支持处理二进制数据或任意长度的数据。字符串内容可用为参数- >参数[我]弦的长度是参数- >长度(我).不要假设字符串是以空结束的。

    • 对于类型的实参INT_RESULT,你必须投参数- >参数[我]到一个很久很久值:

      长长的int_val;Int_val = *((long long*) args->args[i]);
    • 对于类型的实参REAL_RESULT,你必须投参数- >参数[我]到一个值:

      双real_val;Real_val = *((double*) args->args[i]);
    • 对于类型的实参DECIMAL_RESULT,该值作为字符串传递,应该像处理STRING_RESULT价值。

    • ROW_RESULT参数不被实现。

  • 无符号长*长度

    对于初始化函数,长度数组表示每个参数的最大字符串长度。你不应该改变这些。对于主函数的每次调用,长度包含为当前正在处理的行传递的任何字符串参数的实际长度。对于类型的实参INT_RESULTREAL_RESULT长度仍然包含参数的最大长度(与初始化函数一样)。

  • char * maybe_null

    对于初始化函数,maybe_nullArray为每个参数指示参数值是否可能为空(否为0,是为1)。

  • char * *属性

    参数- >属性传递关于UDF参数名称的信息。为参数中的字符串形式提供属性名[我]参数- >属性属性长度是参数- > attribute_lengths[我].不要假设字符串是以空结束的。

    默认情况下,UDF参数的名称是用于指定参数的表达式的文本。对于udf,参数也可以有一个可选参数[,]alias_name子句,在这种情况下,参数名为alias_name.的属性因此,每个参数的值取决于是否给出了别名。

    假设一个UDFmy_udf ()调用方式如下:

    SELECT my_udf(expr1, expr2 AS alias1, expr3 alias2);

    在这种情况下,属性而且attribute_lengths数组将有这些值:

    Args ->attributes[0] = "expr1" Args ->attribute_length [0] = 5 Args ->attributes[1] = "alias1" Args ->attribute_length [1] = 6 Args ->attributes[2] = "alias2" Args ->attribute_length [2] = 6
  • 无符号长* attribute_lengths

    attribute_lengths数组表示每个参数名的长度。

UDF返回值和错误处理

初始化函数应该返回0如果没有发生错误,则1否则。如果发生错误,xxx_init ()应该将以空结束的错误消息存储在消息参数。消息返回给客户端。消息缓冲区为MYSQL_ERRMSG_SIZE字符长。尽量将消息控制在80个字符以内,以适应标准终端屏幕的宽度。

main函数的返回值xxx ()函数值是,for很久很久而且功能。字符串函数应该返回一个指向结果和集合的指针*长度返回值的长度(以字节为单位)。例如:

Memcpy (result, "result string", 13);*长度= 13;

MySQL将一个缓冲区传递给xxx ()函数使用结果参数。这个缓冲区足够长,可以容纳255个字符,可以是多字节字符。的xxx ()函数可以将适合的结果存储在这个缓冲区中,在这种情况下,返回值应该是一个指向缓冲区的指针。如果函数将结果存储在不同的缓冲区中,则应该返回指向该缓冲区的指针。

如果你的string函数没有使用提供的缓冲区(例如,如果它需要返回一个长于255个字符的字符串),你必须为你自己的缓冲区分配空间malloc ()xxx_init ()函数或xxx ()功能和释放它在你xxx_deinit ()函数。可以将分配的内存存储在ptr槽的UDF_INIT供将来重用的结构xxx ()调用。看到零在主函数中,设置* is_null1

* is_null = 1;

要在main函数中指示错误返回,请设置*误差1

*误差= 1;

如果xxx ()*误差1对于任意行,函数值为语句处理的当前行和任何后续行XXX ()被调用。(xxx ()对于后续的行甚至不调用。)

UDF编译和安装

必须在服务器运行的主机上编译和安装实现udf的文件。这里描述了示例UDF文件的过程sql / udf_example.cc它包含在MySQL源代码发行版中。有关UDF安装的更多信息,请参见安装和卸载自定义函数

如果将在复制到副本的语句中引用UDF,则必须确保每个副本也有可用的函数。否则,当副本试图调用该函数时,复制会在副本上失败。

udf_example.ccFile包含以下函数:

  • metaphon ()返回字符串参数的变径字符串。这有点像soundex字符串,但更适合于英语。

  • myfunc_double ()返回参数中字符的ASCII值的和,除以参数长度的和。

  • myfunc_int ()返回其参数长度的和。

  • 序列((const int))返回从给定数字开始的序列,如果没有给定数字则返回1。

  • 查询()返回主机名的IP地址。

  • reverse_lookup ()返回IP地址的主机名。该函数可以用该形式的单个字符串参数调用“xxx.xxx.xxx.xxx”或者四个数字。

  • avgcost ()返回平均成本。这是一个聚合函数。

在Unix和类Unix系统上,使用以下过程编译用户定义函数:

一个动态加载的文件应该被编译成一个共享库文件,使用如下命令:

GCC -shared -o udf_example。所以udf_example.cc

如果你正在使用海湾合作委员会CMake(这就是MySQL本身的配置方式),您应该能够创建udf_example.so用一个更简单的命令:

使udf_example

编译一个包含udf的共享对象后,必须安装它并告知MySQL。编译共享对象udf_example.cc使用海湾合作委员会直接生成一个名为udf_example.so.将共享对象复制到服务器的插件目录并命名它udf_example.so.的值给出该目录plugin_dir系统变量。

在某些系统上,ldconfig配置动态链接器的程序不识别共享对象,除非其名称以自由.在这种情况下,您应该将文件重命名为udf_example.solibudf_example.so

在Windows环境下,编译自定义函数的步骤如下:

  1. 获取MySQL源代码发行版。看到如何获取MySQL

  2. 获得CMake构建实用程序(如果需要的话)http://www.cmake.org.(需要版本2.6或更高版本)。

  3. 在源树中,查看sql文件名的目录udf_example.def而且udf_example.cc.将此目录中的两个文件复制到您的工作目录。

  4. 创建一个CMakemakefileCMakeLists.txt),内容如下:

    项目(udf_example) # MySQL include目录INCLUDE_DIRECTORIES("c:/ MySQL /include") ADD_DEFINITIONS("-DHAVE_DLOPEN") ADD_LIBRARY(udf_example模块udf_example. txt)TARGET_LINK_LIBRARIES(udf_example wsock32)
  5. 创建VC项目和解决方案文件,替换一个合适的发电机值:

    cmake - g”发电机

    调用cmake——帮助显示有效生成器的列表。

  6. 创建udf_example.dll

    devenv udf_example。sln /构建版本

在所有平台上,将共享库文件复制到plugin_dir目录,通知mysqld用以下语句介绍新函数。每个平台的文件名后缀不同(例如,所以对于Unix和类Unix系统,. dll对于Windows),因此调整所以根据需要为平台添加后缀。

返回STRING SONAME 'udf_example.so';创建函数myfunc_double返回实SONAME 'udf_example.so';创建函数myfunc_int返回整数SONAME 'udf_example.so';创建函数序列返回整数SONAME 'udf_example.so';创建函数查询返回STRING SONAME 'udf_example.so';返回SONAME 'udf_example.so';创建聚合函数avgcost返回真正的SONAME 'udf_example.so';

函数一旦安装,将保持安装状态,直到卸载为止。

要删除函数,使用删除函数

下降函数metaphon;下降函数myfunc_double;下降函数myfunc_int;下降函数序列;下降函数查找;下降函数reverse_lookup;下降函数avgcost;

创建函数而且删除函数报表更新mysql.func作为UDF注册表的系统表。这些陈述要求插入而且删除特权,分别为mysql数据库。

在正常启动顺序中,服务器加载在mysql.func表格参数启动服务器——skip-grant-tables选项,在表中注册的udf将不会被加载并且不可用。

UDF安全预防措施

MySQL采取了一些措施来防止用户定义函数的滥用。

UDF库文件不能放置在任意目录中。它们必须位于服务器的插件目录中。的值给出该目录plugin_dir系统变量。

使用创建函数删除函数,你必须有插入删除特权,分别为mysql数据库。这是必要的,因为这些语句从mysql.func表格

的符号之外,udf应该至少有一个定义的符号xxx与主相对应的符号xxx ()函数。这些辅助符号对应于xxx_init ()xxx_deinit ()xxx_reset ()xxx_clear (),xxx_add ()功能。mysqld还支持一个——allow-suspicious-udfs选项,该选项控制udf是否只有xxx符号可以加载。默认情况下,该选项是禁用的,以防止试图从包含合法udf以外的共享库文件加载函数。如果您的旧udf只包含xxx符号,且不能重新编译以包含辅助符号时,可能需要指定——allow-suspicious-udfs选择。否则,您应该避免启用它。