乐趣区

关于数据库:案例展示自定义C函数的实现过程

摘要:用户在应用数据库过程中,受限于内置函数的性能,局部业务不易实现时,能够应用自定义 C 函数实现非凡性能。本文通过两个示例展现自定义 C 函数的实现过程。

前言

用户在应用数据库过程中,经常受限于内置函数的性能,局部业务不易实现,或实现后性能较差,在这些场景呈现时能够思考应用 C 编写自定义函数来实现独立性能。

例如用户针对某些数据列须要应用 C 编写的特定算法进行计算,如果放在业务层所性能不能承受,就能够尝试有自定义 C 函数在实现性能的前提下保障实现效率。

粗略的来说,用户应用 C 编写的自定义函数会被被编译成动静库并且由数据库在须要的时候载入。在一个会话中第一次调用一个特定的用户定义函数时,数据库过程会把动静库文件载入到内存中以便该函数被调用。从这个角度来说,注册自定义函数时须要筹备编译好的动静库文件和函数定义,以下会以实际操作举例。

数据类型

首先须要先明确,数据库中反对的数据类型在应用自定义 C 函数操作时,必须将数据库中的数据类型转换为数据库内核能够解决的相干类型,实际上数据库内核在解决内置函数输出时也是相似的操作:

例如比拟常见的

常见的几种类型中,须要留神的是,对 text 类的函数,C 函数在理论解决时往往不是间接应用 gaussdb 外部的 text 类型进行解决的,而是应用 C 语言的规范类型 char 进行解决的。

这种状况下就能够先读取到入参的地址,再通过 gauss 提供的内置 C 函数 (比方简略罕用的 TextDatumGetCString) 转换为 char* 类型字符串进行操作。

利用实例

上面以两个简略的 HelloWorld 例子来阐明自定义 C 函数创立的整体流程。

1. 最大公约数

最大公约数的计算比较简单,但外部必然会波及循环,应用 SQL 实现只能通过相似 PL/pgSQL 自定义函数或存储过程的办法,如果要达到极限性能的话能够尝试应用自定义 C 函数实现。

如果以失常的 C /C++ 实现,咱们可能会这样来编写代码

int gcd_c(int a, int b){
    int c = a%b;
    while(c) {
        a = b;
        b = c;
        c = a%b;
    }
    return b;
}

如果转换成 gauss 可用的 C 函数须要进行革新,例如革新成如下文件,并命名未 gcd.cpp:

//postgres.h 和 fmgr.h 为 gauss 中 C 函数固定宏和根本定义的头文件
#include "postgres.h"
#include "fmgr.h"

//PG_MODULE_MAGIC 为固定调用宏,自定义 c 函数必须在文件开始地位蕴含
PG_MODULE_MAGIC;

// 上面两行也是固定调用,这两行能够指定出一个对外开放,并可在自定义 C 函数创立时被援用的函数
// 其中 gcd 为动静库对外可见接口,前面定义 C 函数时会用到
extern "C" Datum gcd(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(gcd);

// 理论处理函数
int gcd_c(int a, int b){
    int c = a%b;
    while(c) {
        a = b;
        b = c;
        c = a%b;
    }
    return b;
}
// 主入口函数
//PG_FUNCTION_ARGS 是一个固定宏,理论是一个入参出参相干信息的构造体
Datum
gcd(PG_FUNCTION_ARGS)
{    
    // 入参阶段
    // 查看 参数是否为空,如果为空,返回空,相当于对空做查看,避免创立函数时未指定 strict 属性,导致函数执行异样
    if(PG_ARGISNULL(0) || PG_ARGISNULL(1)){PG_RETURN_NULL();
    }

    //PG_GETARG_INT32(n)示意从 PG_FUNCTION_ARGS 构造体中抽取入参的第 n 个参数,并返回为 int32 类型
    int32        arg1 = PG_GETARG_INT32(0);
    int32        arg2 = PG_GETARG_INT32(1);

    // 调用理论执行函数
    int32 res = gcd_c(arg1, arg2);

    // 返回阶段
    PG_RETURN_INT32(res);
 }

编译动静库,编译时请保障 gcc/g++>=5.4

g++ -c -fpic -Wno-write-strings -fstack-protector-all gcd.cpp -I ${GAUSSHOME}/include/postgresql/server/cfunction
g++ -shared -fPIC -Wl,-z,now -o gcd.so gcd.o

执行创立 C 函数命令:

create or replace function gcd_my(integer,integer)
returns integer
as 'xxxxx/gcd.so', 'gcd'
language c strict not fenced immutable shippable;

这个命令中

  • gcd_my 示意前面 sql 中调用该 C 函数时应用的名字
  • xxxxxx/gcd.so 示意的是以后环境上编译生成动静库搁置的地位
  • language c 示意该自定义函数为 C 语言编写的函数
  • not fenced 示意该函数执行时应用的是非 fenced 模式(该模式前面大节会再次阐明)
  • strict 示意输出不能为空,否则返回也为空(上局部 C 函数尽管以对空值做了解决,以后为了阐明问题,此处也申明了 strict 属性)
  • 其余部分为 create function 的公共内容,具体能够参考手册中 create function 章节

执行函数

select gcd_my(12, 16);

2. 将字符串中的第一个字母大写,其余小写

内置的字符串处理函数中有大小写转换函数,但没有这种相似只有第一个字符进行大小写转换的定制化函数,如此,咱们就能够尝试应用自定义 C 函数进行实现。
如果以失常的 C /C++ 语言操作,咱们可能会这样来写

#include <string.h>
void upper_str(char* str){
    bool has_first = false;
    for(int i = 0; i< strlen(str); i++){if((str[i]>='a' && str[i]<='z') || (str[i]>='a' && str[i]<='z')) {if(has_first) {str[i]=str[i]%0x20 + 'a';
            } else {str[i]=str[i]%0x20 + 'A';
            }
        }
    }
}

如果转换成 gauss 可用的 C 函数能够革新成如下文件,并命名未 upper_str.cpp:

#include "postgres.h"
#include "fmgr.h"
//builtins.h 中存在上面应用的 TextDatumGetCString 的定义
#include "utils/builtins.h"
#include <string.h>

PG_MODULE_MAGIC;

extern "C" Datum upper_str(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(upper_str);

// 理论处理函数
void upper_str_c(char* str){
    bool has_first = false;
    for(int i = 0; i< strlen(str); i++){if((str[i]>='A' && str[i]<='Z') || (str[i]>='a' && str[i]<='z')) {if(has_first) {str[i]=str[i]%0x20 + 'a' - 1;
            } else {str[i]=str[i]%0x20 + 'A' - 1;
                has_first=true;
            }
        }
    }
}
// 主入口函数
Datum
upper_str(PG_FUNCTION_ARGS)
{    
    // 入参阶段
    if(PG_ARGISNULL(0)){PG_RETURN_NULL();
    }
    Datum source = PG_GETARG_DATUM(0);
    char *src = TextDatumGetCString(source);
 
    // 理论调用函数
    (void) upper_str_c(src);
 
    // 返回阶段
    //cstring_to_text 能够将 char* 类型转换为 gauss 内置的 text* 类型
    //PG_RETURN_TEXT_P 宏能够返回 text* 类型的构造,被下层调用获取
    PG_RETURN_TEXT_P(cstring_to_text(src));
 }

执行创立 C 函数命令:

create or replace function upper_str_my(text)
returns text
as 'xxxxxx/upper_str.so', 'upper_str'
language c strict not fenced immutable shippable;

执行函数

select upper_str_my('@hello World');

注意事项

  1. fenced/not fenced 模式

C 函数在注册时有一个选项时 fenced,这个模式是 gauss 提供的一种过程隔离机制。如果在 fenced 模式下理论执行的函数会放在一个独自启动的过程中执行,而 not fenced 模式则是在理论执行时和 gaussdb 同一过程。两种模式各有优劣,须要依据理论状况进行抉择。比拟倡议的形式是,受限创立为 fenced 模式函数,调试无问题后,再从新注册为 not fenced 模式,以高效率模式运行。

  • fenced 模式
    长处:执行更平安,如果函数执行出现异常,不会影响到 CN/DN 过程,保障整个节点的稳固运行;
    毛病:须要额定的过程开销,效率较低。
  • not fenced 模式正好相同
    长处:运行效率高,无额定开销;
    毛病:如果自定义 C 代码编码有问题,容易造成 CN/DN 过程异样等重大问题。
  1. C 函数编写时,须要留神注册时的参数类型和返回类型,零碎会依据创立函数时指定的内容传给理论执行的函数,所以如果函数外部解决和创立时指定类型不统一容易呈现不可预测的异样
  2. C 函数编写时,须要留神对空值进行非凡解决,或者再创立函数时指定为 strict 属性的函数
  3. C 函数实现时都是底层实现,应该严格控制不牢靠 C 函数的创立,保持谨慎应用自定义 C 函数的准则
  4. C 函数创立只能由具备 sysadmin 权限的用户进行创立,能够通过 grant 操作赋予其余用户执行权限

总结

自定义 C 函数的存在给用户提供了间接实现底层逻辑的机会,一个实现欠缺的自定义 C 函数,往往能够给业务带来极强的定制性和大幅度的性能晋升。但定制性也是一把双刃剑,如果编写自定义 C 函数时做的不够欠缺存在逻辑问题,甚至内存溢出等重大问题,也极可能使零碎解体。因而能够应用自定义 C 函数作为一个强有力的工具为理论业务增光添彩,但也须要审慎的创立应用任意一个 C 函数防止误伤其余业务。

本文分享自华为云社区《初窥自定义 C 函数》,原文作者:sincatter。

点击关注,第一工夫理解华为云陈腐技术~

退出移动版