暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

探一探 GBase 8a UDF

原创 灵魂摆渡者 2021-02-22
1094

注意啦!注意啦!
由于UDF以C/C++语言编写,并以动态库的形式存在,UDF函数稳定性会影响到数据库服务的稳定性。所有编写的UDF函数在上生产前必须经过严格测试,否则就跑路吧。

UDF(User Defined Function),既用户自定义函数,是一种通过程序语言来实现特定功能的函数存在形式。可以弥补数据库内嵌函数的不足。

1、UDF使用约束
所有函数必须是线程安全的(不仅是主函数,还有初始化和结束函数)。不允许在函数中改变全局共享或静态的变量。如果需要内存,应该在func_init()中分配它并且在func_deinit()中释放它。
==1)主func()必需 ==
主函数。具体功能实现在该函数中,每行调用一次。SQL 类型与C/C++函数返回类型的对应关系如下:
图片.png
2)初始化func_init()必需
func()的初始化函数,只在开始调用一次,它可用于:

  • 检查传到func()的参数个数
  • 检查参数类型是否正确或者当主函数被调用时将参数强制转换成需要的类型
  • 分配主函数所需的内存
  • 指定返回结果的最大长度
  • 指定返回REAL类型的函数的最大小数位
  • 指定结果是否允许为NULL

3)结束接口func_deinit()可选
func()的结束函数,只在所有行结束后调用一次,它可用于释放初始化函数分配的内存。当一条SQL语句调用func()时,GBase调用初始化函数func_init(),执行所需的初始化工作,例如参数检查或内存分配。如果func_init()返回一个错误,SQL语句返回一条错误消息同时不会调用主函数和结束函数。否则,为每行调用主函数func()一次。在所有行被处理完后,调用结束函数func_deinit(),执行必要的清理工作。

2、函数参数形式
主函数返回类型和参数的不同取决于CREATE FUNCTION语句中声明SQL函数func()返回类型
1)对SQL中返回STRING的函数,形式如下:
char *func(UDF_INIT *initid, UDF_ARGS *args,char *result, unsigned long *length,char *is_null, char *error);
2)对SQL中返回INTEGER函数,形式如下:
long long func(UDF_INIT *initid, UDF_ARGS *args,char *is_null, char *error);
3)对SQL中返回REAL函数,形式如下:
double func(UDF_INIT *initid, UDF_ARGS *args,char *is_null, char *error);

初始化函数和结束函数的声明形式如下:
my_bool func_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
void func_deinit(UDF_INIT *initid);

3、重要参数
1)initid参数
这个参数被传给所有3个函数,它指向一个UDF_INIT结构,被用来在函数之间传递信息。UDF_INIT结构成员列在下面。初始化函数应该初始化它想要改变的任何成员。(对一个成员使用缺省值,不改变它。)

  • my_bool maybe_null
    如果func()能返回NULL,func_init()应该设置maybe_null为1。如果任何一个参数被声明为maybe_null,缺省值是1。
  • 2 unsigned int decimals
    小数位数目。缺省值是在被传给主函数的参数中小数位的最大数目。
  • 3 unsigned int max_length
    返回结果的最大长度。缺省值不同,取决于函数的结果类型。对字符串函数,缺省是最长的参数的长度。对整数函数,缺省是21位。对实数函数,缺省是13加上由initid->decimals指出的小数位数。(对数字函数,长度包括任何符号位或小数点字符。)
  • 4 char *ptr
    函数可以自己使用的一个指针。例如,函数能使用initid->ptr在函数之间传递分配的内存。在func_init()中,分配内存并将它赋给这个指针:
    initid->ptr = allocated_memory;
    在func()和func_deinit()中,使用initid->ptr并释放内存。

2)args参数

这个参数指向一个UDF_ARGS成员,其结构如下:

  • unsigned int arg_count
    参数个数。如果函数有固定数量的参数,在初始化函数中检查这个值。例如:
    if (args->arg_count != 1)
    {
    strcpy(message," func() requires one arguments");
    return 1;
    }
  • enum Item_result *arg_type
    每个参数的类型。可能的类型值是STRING_RESULT、INT_RESULT和REAL_RESULT。确保参数是所需类型,如果不是,返回错误,在初始化函数中检查arg_type数组。例如:
    if (args->arg_type[0] != STRING_RESULT
    && args->arg_type[1] != INT_RESULT)
    {
    strcpy(message," func() requires a string and an integer");
    return 1;
    }
    另外也可以使用初始化函数把arg_type成员设置成所需类型。这样GBase为每个func()调用强制将参数转换成所需类型。例如,为了指定前两个参数是字符串和整数,可以在func_init()中这样做:
    args->arg_type[0] = STRING_RESULT;
    args->arg_type[1] = INT_RESULT;
  • char **args
    args将函数的参数传递给初始化函数和主函数。函数引用第i个参数的方式如下:
    一个STRING_RESULT类型的参数由一个字符串指针加一个长度给出,允许处理任意的长度的数据。字符串内容可由args->args[i]得到并且字符串长度是args->lengths[i]。不用考虑字符串是否以空(null)结束
    对于一个INT_RESULT类型的参数,必须强制转换args->args[i]为一个long long值:
    long long int_val;
    int_val = ((long long) args->args[i]);
    对一个REAL_RESULT类型的参数,必须强制转换args->args[i]为一个double值:
    double real_val;
    real_val = ((double) args->args[i]);
    对一个DECIMAL_RESULT类型的参数,处理方式同STRING_RESULT一样。
  • unsigned long *lengths
    在初始化函数中,lengths数组指出每个参数的最大字符串长度。对于主函数调用,lengths为当前正在处理的行的任何字符串参数的实际长度。对INT_RESULT或REAL_RESULT类型的参数,lengths仍然包含参数的最大长度。
  • char *maybe_null
    在初始化函数中,maybe_null指出每个参数是否允许为空(NULL)(1表示允许,0表示不允许);
  • char **attributes
    每个参数的别名,如果不存在别名,就是参数实际名称,如:
    select 1 from t1 group by udf_func(1+2);则args->attributes[0]为”1+2”,select 1+2 as plus from t1 group by udf_func(plus);则args->attributes[0]为“plus”。
  • unsigned long *attribute_lengths
    每个attributes的长度

3、返回及错误处理
1)函数返回
对long long和double函数,主函数func()的返回值即是函数返回值。对字符串函数,字符串可以在result和length参数中被返回。result是255个字节长的一个缓冲区,如果返回结果不超过255,就可以把返回结果放到result中,这样做的一个好处就是不用去管理result的内存。例如:
memcpy(result, “result value”, 12);
*length = 12;
return result;
但是如果返回结果超过255个字节,就需要在func_init()或func()中申请空间并在func_deinit()中释放了,注意不要产生内存泄露。例如:
在func_init中:
initid->ptr = (char *) malloc(MAX_LEN);
在func_deinit中:
free(initid->ptr);
为了在主函数中表明一个NULL返回值,设定is_null为1:
*is_null = 1;
为了在函数中表明一个错误返回,设定error参数为1:
error = 1;
如果,某一行func()设置
error为1,则当前行函数值是NULL,但是并不影响后续行的结果,func ()将继续被调用。
2)错误处理
如果没有出现错误,初始化函数应该返回0,否则返回1。如果发生一个错误,func_init()应该在message参数中存储错误信息返回给客户。错误信息缓冲区是GBASE_ERRMSG_SIZE(目前在GBase中这个长度是512字符)个字符长,该缓冲区长度不宜设置过大,一般不要超过80字符。

4、编译及创建函数

  • 把C或C程序编译成共享库,使用如下的命令:
    shell> gcc -fPIC func.c -shared -o func.so -I head_file_path 或
    shell> gcc -fPIC func.cc -shared -o func.so -I head_file_path 或
    shell> g
    -fPIC func.cc -shared -o func.so -I head_file_path
    其中head_file_path是func.c中用到的gbase头文件存放路径,一般是GBase安装目录的include/gbase。
  • 把编译好的共享库(一般以.so结尾,如上面的func.so)拷贝到GBase服务的plugin目录下。
    可以通过系统变量plugin_dir得到plugin目录,show variables like ‘plugin_dir’;
    注意:有些系统只会识别lib开头的.so文件,这是需要把.so改名,如func.so改为libfunc.so
  • 在共享库被拷贝以后,就可以创建想要的函数了:
    gbase> CREATE FUNCTION func RETURNS STRING SONAME ‘func.so’;
    可以使用DROP FUNCTION删除函数:
    gbase> DROP FUNCTION func;
    CREATE FUNCTION和DROP FUNCTION语句在gbase数据库中更新系统表func。函数名、类型和共享库名被保存在该表中。当前用户必须有insert和delete权限才能创建和删除函数。

注意:不能使用CREATE FUNCTION创建一个已经被创建的函数。如果需要重新创建函数,应该用DROP FUNCTION删除它,然后用CREATE FUNCTION重新创建它。例如,如果重新编译函数的一个新版本,以便GBase获得新版本,需要删除函数并重新创建,否则GBase将继续使用旧版本。新增函数在每次服务器启动时再次装载,除非使用–skip-grant-tables选项启动GBase。在这种情况下,用户自定义函数的初始化被跳过,新增函数将失效。

5、实例
1)实现oracle中months_between函数,计算日期之间的月份数,代码如下:

#include <gbase_sys.h>
#include <m_string.h>
#include <gbase.h>
#include <string.h>
#include <time.h>
#include <gbase.h>

int is_end(struct tm timestru)
{
	unsigned char x[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
	int y = timestru.tm_year;
	int m = timestru.tm_mon;
	int d = timestru.tm_mday;

	if(y%4==0 && y%100!=0 || y%400==0)
		x[2]=29;

	if( m == 0)
		return 0;
	else
		if(x[m] == d)
			return 1;
		else
			return 0;	
}

void strtodatetime(char *timeptr,struct tm *timestru)
{
	char yy[4]={'\0'},mm[2]={'\0'},day[2]={'\0'};

	memset(yy,0,4);
	while(*timeptr ==' ' || *timeptr=='\t')
		timeptr++;
	strncpy(yy,timeptr,4);
	timeptr+=5;
	strncpy(mm,timeptr,2);
	timeptr+=3;
	strncpy(day,timeptr,2);
		
	timestru->tm_year=atoi(yy);
	timestru->tm_mon= atoi(mm);
	timestru->tm_mday= atoi(day);
	
}

my_bool months_between_init(UDF_INIT *initid,UDF_ARGS *args,char *message)
{
   if(args->arg_count != 2)
   {
     strcpy(message,"months_between() must be three arguments");
	 return 1;
   }

   initid->extension=NULL;
   initid->maybe_null=1;
  
   return 0;
}

double months_between(UDF_INIT *initid,UDF_ARGS *args,char *result,unsigned long *length,char *is_null,char *error)
{
        char v_first[20]={'\0'};
        char v_second[20]={'\0'} ;
    
	struct tm first_tm;
	struct tm second_tm;
	
	int v_first_end=0;
	int v_second_end =0;
	double vm_result=0;
	
 	strcpy(v_first,args->args[0]);
 	strcpy(v_second,args->args[1]);
	strtodatetime(v_first,&first_tm);
	strtodatetime(v_second,&second_tm);
	
	v_first_end= is_end(first_tm);
	v_second_end= is_end(second_tm);	

	if(v_first_end && v_second_end)
		vm_result= (first_tm.tm_year-second_tm.tm_year)*12 +(first_tm.tm_mon-second_tm.tm_mon);
	else
		vm_result= (first_tm.tm_year-second_tm.tm_year)*12 +(first_tm.tm_mon-second_tm.tm_mon)+(first_tm.tm_mday-second_tm.tm_mday)/31.0 ;
		
	return vm_result;
	
}

void months_between_deinit(UDF_INIT *initid)
{

}

注意保存的.c文件名,一定与主函数的名称一致,这里就是months_between.c
2)获取编译头文件库
www.gbase.cn的社区中发帖索要头文件库,需要说明是GBase V8还是V9。假定获取后解压到/home/gbase/udf目录中,加压后的include目录结构为:/home/gbase/udf/include/ 下面会有很多头文件。

3)编译(CentOS下)
gcc -fPIC months_between.c -shared -o months_between.so -I /home/gbase/udf/include/

4)拷贝months_between.so到gcluster、gnode的plugin目录,如果是集群版本,每个集群节点都需拷贝,具体位置如下:
假设集群或者单机安装在了/opt/目录下,则路径信息为:
/opt/gcluster/server/lib/gbase/plugin
/opt/gnode/server/lib/gbase/plugin

5)创建函数
create function months_between returns real soname ‘months_between.so’;
注意返回值一定是real。

6)测试函数
gbase> select months_between(date’20201231’,date’20200812’);
±----------------------------------------------+
| months_between(date’20201231’,date’20200812’) |
±----------------------------------------------+
| 4.61290322580645 |
±----------------------------------------------+
1 row in set (Elapsed: 00:00:00.00)

7)删除函数
drop function months_between;

谢谢朋友们阅读!

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论