摘要:用户在使用数据库过程中,受限于内置函数的功能,部分业务不易实现时,可以使用自定义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进程异常等严重问题。

2. C函数编写时,需要注意注册时的参数类型和返回类型,系统会根据创建函数时指定的内容传给实际执行的函数,所以如果函数内部处理和创建时指定类型不一致容易出现不可预测的异常

3. C函数编写时,需要注意对空值进行特殊处理,或者再创建函数时指定为strict属性的函数

4. C函数实现时都是底层实现,应该严格控制不可靠C函数的创建,坚持慎重使用自定义C函数的原则

5. C函数创建只能由具有sysadmin权限的用户进行创建,可以通过grant操作赋予其他用户执行权限

总结

自定义C函数的存在给用户提供了直接实现底层逻辑的机会,一个实现完善的自定义C函数,往往可以给业务带来极强的定制性和大幅度的性能提升。但定制性也是一把双刃剑,如果编写自定义C函数时做的不够完善存在逻辑问题,甚至内存溢出等严重问题,也极可能使系统崩溃。因此可以使用自定义C函数作为一个强有力的工具为实际业务增光添彩,但也需要谨慎的创建使用任意一个C函数避免误伤其他业务。

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

点击关注,第一时间了解华为云新鲜技术~

案例展示自定义C函数的实现过程的更多相关文章

  1. SQL Server如何定位自定义标量函数被那个SQL调用次数最多浅析

    前阵子遇到一个很是棘手的问题,监控系统DPA发现某个自定义标量函数被调用的次数非常高,高到一个离谱的程度.然后在Troubleshooting这个问题的时候,确实遇到了一些问题让我很是纠结,下文是解决 ...

  2. 自定义UDF函数应用异常

    自定义UDF函数应用异常 版权声明:本文为yunshuxueyuan原创文章.如需转载请标明出处: http://www.cnblogs.com/sxt-zkys/QQ技术交流群:299142667 ...

  3. 关于jqGrig如何写自定义格式化函数将JSON数据的字符串转换为表格各个列的值

    首先介绍一下jqGrid是一个jQuery的一个表格框架,现在有一个需求就是将数据库表的数据拿出来显示出来,分别有id,name,details三个字段,其中难点就是details字段,它的数据是这样 ...

  4. 权限管理之基于ACL的实现:自定义JSTL函数实现即时认证

    实现即时认证(即只有拥有相应的权限,才能做相应的操作) 经常用在,在JSP页面上,调用JSTL自定义函数做判断,显示相应的菜单或者功能按钮,比如只有管理员登陆时才显示“删除”按钮,从而完成权限的即时认 ...

  5. PHPCMS快速建站系列之自定义分页函数

    内容分页的实现方法:{pc:content action="lists" catid="$catid" order="id DESC" nu ...

  6. mysql的函数与储存过程与pymysql的配合使用

    现在mysql上定义一个函数,一个储存过程 函数: delimiter \\ CREATE FUNCTION f2 ( num2 INT, num1 INT ) RETURNS INT BEGIN D ...

  7. 自定义 serializeJSON() 函数

    说明:jQuery框架提供了serialize()方法, 能够将DOM元素内容序列化为json格式字符串,用于ajax请求.通过使用serialize()方法,可以提交本页面的所有域. 但是此方法具有 ...

  8. Java进阶SQL函数、网页定时刷新与自定义JSTL函数

    一.SQL函数 能够在SQL语句中调用的函数(方法) ,用来实现一些小功能 聚合函数 能够把多行数据聚合成一个值(统计) count()    计数,计算数据条数 max()      计算最大值 m ...

  9. beego 自定义模板函数

    beego支持的模板函数不是很多,有时候前端展现数据的时候,要对数据进行格式化,所以要用到自定义模板函数 比如我的前端模板上有时间和模板大小这2个数据,原始数据都是int的时间戳和byte单位的数据, ...

  10. hive自定义UDTF函数叉分函数

    hive自定义UDTF函数叉分函数 1.介绍 从聚合体日志中需要拆解出来各子日志数据,然后单独插入到各日志子表中.通过表生成函数完成这一过程. 2.定义ForkLogUDTF 2.1 HiveUtil ...

随机推荐

  1. HDU1702 ACboy needs your help again! 题解

    #include <iostream> #include <string> #include <queue> #include <stack> usin ...

  2. 高性能日志脱敏组件:已支持 log4j2 和 logback 插件

    项目介绍 日志脱敏是常见的安全需求.普通的基于工具类方法的方式,对代码的入侵性太强,编写起来又特别麻烦. sensitive提供基于注解的方式,并且内置了常见的脱敏方式,便于开发. 同时支持 logb ...

  3. webwork学习

    学习了H5中的webworker 主机 > 程序 > 进程 > 线程 > 纤程 多进程(重) 多线程(轻) 开销 创建.销毁开销大 创建.销毁开销小 安全性 进程之间是隔离 线 ...

  4. Flask解决跨域问题

    什么是跨域问题 跨域问题指的是浏览器限制了从一个源(协议.域名.端口)访问另一个源的资源的行为,这个限制是浏览器的一个安全机制.如果一个网页从一个源加载了另一种类型的资源(例如 HTML.CSS.脚本 ...

  5. 改变element dialog弹窗的关闭按钮样式

    .el-dialog__headerbtn { top: 8px !important; background: url('https://你路径资源的url图片') left no-repeat; ...

  6. this.$router 与this.$route的区别

    this.$router是Vue-Router的实例,需要导航到不同路由则用this.$router.push方法 this.$route为当前路由的跳转对象,包含当前路由的name.path.que ...

  7. 简单地聊一聊Spring Boot的构架

    本文由葡萄城技术团队发布.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 本文小编将详细解析Spring Boot框架,并通过代码举例说明每个层的作用 ...

  8. Langchain使用自己定义的tool

    Langchain使用自己定义的tool 快速开始 tool是agent可用于与世界交互的功能.这些工具可以是通用实用程序(例如搜索).其他链,甚至是其他代理. 目前,可以使用以下代码片段加载工具: ...

  9. 【Spring Boot】【外包杯】学习day01 | 项目目录结构划分以及代码分层

    起因:扒了一个开源的项目,但是啃起来很硬,所以决定开始学习相关的知识. 我们之前的SSM项目,搭建过程较为繁琐: 1)配置 web.xml,加载 spring 和 spring mvc 2)配置数据库 ...

  10. 神经网络入门篇:详解搭建神经网络块(Building blocks of deep neural networks)

    搭建神经网络块 这是一个层数较少的神经网络,选择其中一层(方框部分),从这一层的计算着手.在第\(l\)层有参数\(W^{[l]}\)和\(b^{[l]}\),正向传播里有输入的激活函数,输入是前一层 ...