华为云云数据库GaussDBC语言函数(实验室特性)_云淘科技
当前特性是实验室特性,使用时请联系华为工程师提供技术支持。
用户定义的函数可以使用C编写。这类函数被编译成动态库并且由服务器在需要的时候载入。在一个会话第一次调用一个特定的用户定义函数时,数据库进程会把动态库文件载入到内存中以便该函数被调用。用户在定义C函数的时候必须为该函数指定两块信息:动态库文件的名称,以及要在该动态库中调用的特定的C名称。用户自定义函数的正确性和安全性由开发者保证,上线前需经过充分测试,避免影响数据库正常业务。
支持的数据类型以及接口
数据库支持数字,字符串,时间等多种类型。目前C函数支持的类型,以及各种类型的C类型和SQL类型的对应关系,类型的参数获取以及返回结果的接口,见下表:
SQL类型 |
C类型 |
获取参数 |
返回结果 |
---|---|---|---|
varchar |
VarChar* |
PG_GETARG_DATUM |
PG_RETURN_VARCHAR_P |
text |
text* |
PG_GETARG_DATUM |
PG_RETURN_TEXT_P |
char |
BpChar* |
PG_GETARG_DATUM |
PG_RETURN_BPCHAR_P |
date |
DateADT |
PG_GETARG_DATUM |
PG_RETURN_DATEADT |
timestamp |
Timestamp |
PG_GETARG_TIMESTAMP |
PG_RETURN_TIMESTAMP |
smallint |
int16 |
PG_GETARG_INT16 |
PG_RETURN_INT16 |
integer |
int32 |
PG_GETARG_INT32 |
PG_RETURN_INT32 |
bigint |
int64 |
PG_GETARG_INT64 |
PG_RETURN_INT64 |
常用函数及说明见下表:
函数名 |
功能 |
---|---|
TextDatumGetCString |
传入text/varchar/bpchar的Datum类型,返回一个char*字符串 |
cstring_to_text |
转换char*字符串到text/varchar类型 |
date_pl_interval |
date类型加一个时间间隔 |
timestamp_pl_interval |
timestamp类型加一个时间间隔 |
int4_numeric |
integer数据转换成numeric类型 |
palloc |
内存申请 |
pfree |
内存释放 |
PointerGetDatum |
指针类型强制转换成Datum类型 |
DirectFunctionCall1 |
调用1个参数的函数 |
DirectFunctionCall2 |
调用2个参数的函数 |
DirectFunctionCall3 |
调用3个参数的函数 |
PG_ARGISNULL(N) |
判断函数的第n个参数是否为NULL |
编写代码
C语言函数的编写需要遵守基本的规则:
函数声明语法,Datum funcname(PG_FUNCTION_ARGS)。
申明函数是版本1格式,调用宏PG_FUNCTION_INFO_V1(funcname)。不使用宏则默认为版本0格式。
C文件中声明PG_MODULE_MAGIC,标记数据库的版本信息,防止动态库被加载到一个不兼容的服务器。
在分配内存时,使用函数palloc和pfree,而不是使用对应的C库函数malloc和free。在每个事务结束是会自动释放通过palloc申请的内存,以免内存泄露。
C文件中定义的符号名不能相互冲突或者与服务器中可执行程序中定义的符号冲突。如果有关于此的编译错误消息,你必须重命名你的函数或者变量。
开发者应充分了解要调用的内核函数接口功能及入参范围,在调用前应检查参数合法性,避免出现空指针等可能导致程序crash的问题。
自定义函数上线前应经过充分测试,避免引入问题影响数据库正常业务。
示例
示例1:函数功能,返回两个时间中的较大的,文件名maxtimestamp.cpp,文件内容如下。
其中,PG_GETARG_TIMESTAMP(0)、PG_GETARG_TIMESTAMP(1)分别获取timestamp类型的第一个参数和第二个参数。PG_ARGISNULL(0)、PG_ARGISNULL(1)返回参数1、参数2是否为NULL。PG_RETURN_TIMESTAMP返回timestamp结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include "postgres.h" #include "fmgr.h" #include "utils/timestamp.h" PG_MODULE_MAGIC; extern "C" Datum maxdate(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(maxdate); Datum maxdate(PG_FUNCTION_ARGS) { Timestamp para1 = PG_GETARG_TIMESTAMP(0); Timestamp para2 = PG_GETARG_TIMESTAMP(1); bool arg1_is_null = PG_ARGISNULL(0); bool arg2_is_null = PG_ARGISNULL(1); Timestamp result; if (arg1_is_null == true && arg2_is_null == true) { PG_RETURN_NULL(); } else if (arg1_is_null == true) { PG_RETURN_TIMESTAMP(para2); } else if (arg2_is_null == true) { PG_RETURN_TIMESTAMP(para1); } if (para1 < para2) { result = para2; } else { result = para1; } PG_RETURN_TIMESTAMP(result); } |
示例2:函数功能,返回next date,文件名nextdate.cpp,文件内容如下。
其中,函数timestamp_pl_interval在theDate的基础上增加一个时间间隔spanTime(1天)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include "postgres.h" #include "fmgr.h" #include "utils/timestamp.h" PG_MODULE_MAGIC; extern "C" Datum getLatterDate(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(getLatterDate); Datum getLatterDate(PG_FUNCTION_ARGS) { Timestamp theDate = PG_GETARG_TIMESTAMP(0); Timestamp result; Interval spanTime; spanTime.time = 0; spanTime.day = 1; spanTime.month = 0; result = DirectFunctionCall2(timestamp_pl_interval, TimestampGetDatum(theDate), PointerGetDatum(&spanTime)); PG_RETURN_TIMESTAMP(result); } |
示例3,函数功能,判断字符串中的内容是否全是数字,文件名isNumber.cpp,文件内容如下。其中,PG_GETARG_DATUM(0) 获取text类型的参数。TextDatumGetCString将参数转换为char*字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include "postgres.h" #include "fmgr.h" #include "utils/builtins.h" PG_MODULE_MAGIC; extern "C" Datum ISNUMBER(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(ISNUMBER); Datum ISNUMBER (PG_FUNCTION_ARGS) { Datum source = PG_GETARG_DATUM(0); char *q = TextDatumGetCString(source); int len = strlen(q); for (int i = 0; i = '0' && q[i] <= '9') { continue; } else { return 0; } } return 1; } |
编译生成动态库
在使用用户定义的C代码之前,必须编译链接生成一个能被服务器动态载入的文件。确切的说,需要生成一个共享库文件。
首先源文件被编译成一个目标文件,然后目标文件被连接起来。目标文件需要被创建成position-independent code (PIC),这意味着当它们被载入时,可以被放置在内存中的任意位置。
下面例子中,我们以文件isNumber.c为例,并且创建一个共享库isNumber.so。
Linux创建PIC的编译器标志是-fpic。在不同平台上的,需要参考GCC手册。创建一个共享库的编译器标志是-shared。一个完整的例子:
gcc -fpic -c isNumber.cpp -I include/postgresql/server/ gcc -shared -o isNumber.so isNumber.o
上述命令也可以连在一起:
gcc -shared -fpic -o isNumber.so isNumber.cpp -I include/postgresql/server/
其中include/postgresql/server/ 为服务器对外发布的头文件路径,在安装目录下面。
gcc版本要求在7.3.0或者7.3.0之上。
为保证C函数兼容性,若涉及数据库升级,用户需要基于升级后的头文件重新编译C函数共享库。
在服务端加载过动态库后,不可在环境上手动修改该动态库,否则可能出现段错误或其他未知问题。
创建C函数
以ISNUMBER为例:
1 2 3 4 |
create or replace function isnumber(text) returns integer as '...../isNumber.so', 'ISNUMBER' language c strict fenced IMMUTABLE SHIIPABLE; |
…../isNumber.so 指定了库文件的路径。当enable_default_cfunc_libpath打开时,只需要指定文件名即可,数据库会自动在默认目录($libdir/proc_srclib)下查找该文件。当enable_default_cfunc_libpath关闭时,这里必须指定库文件的绝对路径。enable_default_cfunc_libpath参数默认打开。
属性strict,表示只要其中任意参数为NULL值,该函数就会返回空值,当有NULL参数时该函数不会被执行,而是自动返回一个空值结果。也就是说,如果函数创建时没有指定strict属性,则函数的C语言实现一定要对参数是否为NULL特殊处理,例如:maxdate的实现。否则,对NULL的不正确的使用可能引起进程的crash。
属性fenced,如果指定函数为fenced模式,则函数会在worker进程中被调用,防止C代码实现错误导致服务器crash,不建议使用not fenced模式。
属性IMMUTABLE,表示函数的结果只倚赖于它的输入参数。
属性SHIPPABLE,表示这个函数可以下推到DN执行。对于IMMUTABLE类型的函数,如果函数的返回值类型不是record,则可以下推到DN上执行。
对于STABLE/VOLATILE类型的函数,仅当函数的属性是SHIPPABLE的时候,函数可以下推到DN执行。
函数属性在CREATE FUNCTION章节会有详细介绍。
执行create函数时指定的参数类型和返回值类型,一定要和C代码实现中的类型一致。
如果函数创建时没有指定strict属性,则C代码的实现一定要对参数是否为NULL做判断。
C函数支持所有用户创建,而Java和Internal只支持拥有sysadmin权限的用户创建。
拥有sysadmin权限的用户可以通过grant语法来将创建C函数的权限授权给普通用户。
创建C函数为not fenced模式执行时,开发者需要保证C代码的正确性,否则会导致Gaussdb进程crash。
建议一个函数使用一个库文件。
如果使用fenced模式,可以通过GUC参数fencedUDFMemoryLimit控制C函数使用的内存大小, 避免内存过度使用或内存泄露。
出于函安全性考虑,对于.so文件上传权限仅限于omm用户(数据库初始化用户),其他用户不允许进行上传操作,并严格审核文件来源以及函数源码的安全性,该安全管理约束建议严格遵守。
父主题: 用户自定义函数
同意关联代理商云淘科技,购买华为云产品更优惠(QQ 78315851)
内容没看懂? 不太想学习?想快速解决? 有偿解决: 联系专家