UDF就是user define function。在mysql内部,提供了很多内建函数,如 abs()、count()等,但根据实际情况,内建的函数并不一定能满足我们的需要,这时就可以使用mysql提供的udf了,构建我们自己的函数,然后加到mysql中。如其他函数一样,udf有参数,也有输出,udf函数类型有两类:单一函数如abs(),聚集函数如count()、sum()等,创建udf函数使用的语言是C/C++。
在编写udf的过程中,有几个重要api,也是固定流程,以下将介绍各个api以及编写过程中的注意事项。
该函数在sql语句之前执行,做一些初始化的工作,如参数类型检查,参数个数判断,内存分配,同时在该函数中还可以做参数类型的强制转换,name表示实际要取的函数名字。
UDF_INIT:
参数名称 |
参数类型 |
说明 |
maybe_null |
my_bool |
如果为1,表示udf可以返回NULL |
decimals |
unsigned int |
若返回值是实数,表示精度,范围0—30 |
max_length |
unsigned long |
返回值最大允许的长度 |
ptr |
char* |
参数指针,可以自定义分配内存,并传给其他api使用 |
const_item |
my_bool |
为1表示函数总是返回相同的值 |
extension |
void* |
用于扩展 |
UDF_ARGS:
参数名称 |
参数类型 |
说明 |
arg_count |
unsigned int |
参数个数 |
arg_type |
enum Item_result* |
参数类型数组,记录每一个参数的类型,枚举值有:STRING_RESULT、DECIMAL_RESULT、REAL_RESULT、INT_RESULT |
args |
char** |
存储sql语句执行后返回的结果,对于STRING_RESULT、DECIMAL_RESULT是char*,INT_RESULT是long long*,REAL_RESULT是double*,也可能是NULL指针 |
maybe_null |
char* |
该数组表示每个参数是否可以为空 |
lengths |
unsigned int* |
存储每个参数的长度 |
attributes |
char** |
存储每个参数的名字 |
attribute_lengths |
unsigned long* |
存储每个参数名字的长度 |
extension |
void* |
用于扩展 |
message:用来存储要打印的错误信息
返回值:1表示出错返回,0表示正常返回
该函数在sql语句后执行,用于释放在name_init函数中分配的内存,如果无内存分配,该函数可放空。
在该函数中,实现了对具体逻辑的处理过程,在name_init函数后执行,如果对于单一函数,对于返回结果的每一行记录该函数被执行一次,如果是聚集函数,则是当每一分组结束时该函数被执行一次
针对不同的返回值类型,该函数原型有以下三种形式:
返回类型 |
函数原型 |
STRING_RESULT、DECIMAL_RESULT |
char *name(UDF_INIT *inited, UDF_ARGS *args, char *result, unsigned long* length, char *is_null, char *error) |
INT_RESULT |
long long name(UDF_INIT *inited, UDF_ARGS *args, char *is_null, char *error) |
REAL_RESULT |
double name(UDF_INIT *inited, UDF_ARGS *args, char *is_null, char *error) |
对于char *类型,参数:
*result,指向存储返回结果的地址,不超过255字节,如果返回的字符串结果长度超过这个值,则需要自定义分配内存存储结果
*length,存储返回结果的长度
*is_null,当值为1时,表明返回的结果为NULL值
*error, 当值为1时,表明有错发生
该函数用在聚合函数中,对于由group by 子句分组的每一分组内的每个记录调用该函数对结果值进行累计。
该函数也用在聚合函数内,对于用group by子句分组的每一分组的开始该函数会被调用清空变量值。
注意:所有函数必须是线程安全的,这不仅对主函数,对初始化和反初始化函数,包括add和clear函数都一样。这个要求的结果就是,你不能分配任何变化的全局或静态变量。如果你需要内存,可以在name_init()函数中来分配,在name_deinit()函数中释放。
#include
#include
#include
#include
#include
#include
/*****************
*
*init, to check arg
*
****************/
my_bool add_5_init(UDF_INIT *inited,UDF_ARGS *args, char *message)
{
if (args->arg_count != 1)
{
strcpy(message, "add_5() can only accept one arg");
return 1;
}
if (args->arg_type[0] != INT_RESULT)
{
strcpy(message, "add_5() can only operate with a int");
return 1;
}
return 0;
}
/*****************
*
*deinit, to free resources
*
****************/
void add_5_deinit(UDF_INIT *inited)
{
}
/*****************
*
*toimplement function
*
****************/
long long add_5(UDF_INIT *inited, UDF_ARGS*args, char *is_null, char *error)
{
long long res = (*(long long *)args->args[0]);
return res + 5;
}
该函数实现将每一行结果记录自增5的功能。首先在add_5_init()函数中检查参数类型及个数,如果有错返回1,函数sql语句将自动停止执行,无错返回0;在add_5()函数中,将结果转换后整型后,自增5并返回。由于在初始化函数中并没有内存分配,所以在反初始化函数中什么都不做。
#include
#include
#include
#include
#include
#include
#include
/*****************
*
*init, to check arg
*
****************/
typedef long long myint;
typedef struct my {
double res;
myint count;
}Val;
my_bool my_avg_init(UDF_INIT *inited,UDF_ARGS *args, char *message)
{
int ret = 0;
//check arg num
if (args->arg_count != 1)
{
strcpy(message, "my_avg() can only accept one arg");
ret = 1;
goto _RETURN;
}
//check arg type
if (args->arg_type[0] != INT_RESULT && args->arg_type[0]!= REAL_RESULT)
{
strcpy(message, "my_avg() can only operate with a int or areal");
ret = 1;
goto _RETURN;
}
//long long*total = (long long*)malloc(sizeof(long long));
Val *sp = (Val*)malloc(sizeof(Val));
if (sp == NULL)
{
strcpy(message, "no memory for malloc");
ret = 1;
goto _RETURN;
}
inited->maybe_null = 0; //返回结果可能为NULL
inited->decimals = 2; //结果保留两位小数
inited->max_length = 10; //结果长度为10
sp->res = 0.0;
sp->count = 0;
args->arg_type[0] = REAL_RESULT; //类型强制转换为实数型
inited->ptr = (char*)sp;
_RETURN:
return ret;
}
/*****************
*
*deinit, to free resources
*
****************/
void my_avg_deinit(UDF_INIT *inited)
{
if (inited->ptr) {
Val *tp = (Val*)inited->ptr;
free(tp);
}
}
/*****************
*
*for every row of query results, this function will be invoked
*
****************/
void my_avg_add(UDF_INIT *inited, UDF_ARGS*args, char *is_null, char *error)
{
Val *value= (Val*)inited->ptr;
value->res += (*(double*)args->args[0]);
value->count += 1;
}
/*****************
*
*toimplement function
*
****************/
double my_avg(UDF_INIT *inited, UDF_ARGS*args, char *is_null, char *error)
{
Val *result = (Val*)inited->ptr;
if (!result->count) //如果计数为0,即除数为0,就返回NULL
{
*is_null = 1;
return 0.0;
}
*is_null = 0;
return result->res / (double)result->count;
}
/*****************
*
*before next group calc, clear variebles;
*
****************/
void my_avg_clear(UDF_INIT *inited, char*is_null, char *error)
{
Val *sum = (Val*)inited->ptr;
sum->res = 0.0;
sum->count = 0;
}
该函数实现的是将结果记录按group by分组求每一分组的平均值。同样,在my_avg_init()函数中,检查参数个数及类型,同时由于需要保留结果,在初始化时,自定义分配内存来存储每一分组累计的结果值。当分组开始时,首先调用my_avg_clear()函数初始化相关变量,然后当对于分组内每一记录时,循环调用my_avg_add()函数进行结果值的累加,当分组结束时,调用my_avg()函数进行计算并返回结果值。sql语句执行结束后,my_avg_deinit()函数被调用,用来释放在my_avg_init函数中分配的内存。
当我们完成udf后,就要进行编译,对于上述实例单一函数,编译命令如下:
root>gcc –fPIC –shared –DMYSQL_DYNAMIC_PLUGIN`mysql_config --cflags` -o add_5.so my_add_5.c `mysql_config --libs`
将源文件编译成动态库,将生成的共享文件添加到本地系统动态链接器能够动态加载的目录下,如/usr/lib目录,或者在/etc/ld.so.conf链接器配置文件中添加上存放你共享文件的目录。
当我们编译好一个共享文件并将共享文件放到指定目录下后,首先要注意:为了使用UDF,需要动态链接mysqld,不要配置MYSQL使--with-mysqld-ldflags=-all-static参数,经验告诉我们应当使用--with-mysqld-ldflags=-rdynamic参数。
当一切准备好后,需要将动态库添加到mysql,以便我们在sql语句中能够调用UDF。在这单一函数和聚合函数有点不一样,单一函数这样添加:
mysql>create function add_5 returns integersoname ‘add_5.so’;
聚合函数如下添加:
mysql>create aggregate function my_avg returnsreal soname ‘my_avg.so’;
多了关键字:aggregate,即告诉mysql添加的是聚合函数。
注意:不管是添加单一函数还是添加聚合函数,都需要指明该函数的返回值,否则将不会得到正确的结果。SQL函数数据类型与C/C++返回的数据类型对应关系如下:
SQL类型 |
C/C++类型 |
STRING |
char* |
INTEGER |
long long |
REAL |
double |
我们可以通过sql语句查询我们添加UDF,如下:
当我们不想使用某个UDF,可以通过如下语句删除:
mysql>drop function add_5;
mysql>drop function my_avg;
如果我们要在udf中调用本地函数,这里就有一个问题要注意了。因为udf只能编译成动态库,在编译时必须加上 –fPIC 选项,该选项表示生成位置无关代码。而如果我们要在udf中调用静态库,同样被调用的静态库在编译时也必须加上 –fPIC选项。如:有a.h,a.c源文件,在udf.c中调用a.c中的函数。通过静态库调用:
root>gcc –g –Wall –fPIC –c a.c
通过上面的命令得到a.o文件,然后编译成静态库:
root>ar –rv liba.a a.o
编译成静态库中,就可以在udf.c中调用自定义的本地函数了。