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

华为GaussDB A C语言函数

墨天轮 2019-10-12
4186

C语言函数

用户定义的函数可以使用C编写。这类函数被编译成动态库并且由服务器在需要的时候载入。在一个会话第一次调用一个特定的用户定义函数时,数据库进程会把动态库文件载入到内存中以便该函数被调用。用户在定义C函数的时候必须为该函数指定两块信息:动态库文件的名称,以及要在该动态库中调用的特定的C名称。

支持的数据类型以及接口

数据库支持数字,字符串,时间等多种类型。目前C函数支持的类型,以及各种类型的C类型和SQL类型的对应关系,类型的参数获取以及返回结果的接口,见下表:

表1

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

对外提供的接口见下表:

表2 对外接口

函数名

功能

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文件中定义的符号名不能相互冲突或者与服务器中可执行程序中定义的符号冲突。如果有关于此的编译错误消息,你必须重命名你的函数或者变量。

示例

示例1:函数功能,返回两个时间中的较大的,文件名maxtimestamp.c,文件内容如下。

其中,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.c,文件内容如下。

其中,函数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.c,文件内容如下。其中,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 < len; i++)
    {
        if (q[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.c -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版本要求在5.4.0或者5.4.0之上。

创建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 指定了库文件的绝对路径。
  • 属性strict,表示只要其中任意参数为NULL值,该函数就会返回空值,当有NULL参数时该函数不会被执行,而是自动返回一个空值结果。也就是说,如果函数创建时没有指定strict属性,则函数的C语言实现一定要对参数是否为NULL特殊处理,例如:maxdate的实现。否则,对NULL的不正确的使用可能引起进程的crash。
  • 属性fenced,如果指定函数为fenced模式,则函数会在worker进程中被调用,防止C代码实现错误导致服务器crash。
  • 属性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用户(数据库初始化用户),其他用户不允许进行上传操作,并严格审核文件来源以及函数源码的安全性,该安全管理约束建议严格遵守。


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

评论