0%

PostgreSQL中添加插件

PostgreSQL中添加插件

背景

最近一个客户将SQLServer迁移到PostgreSQL中,性能得到了整体提升并省去每年接近百万的License费用,迁移中用户提出一个需求,需要将以前应用一些加解密的函数封装成PG的函数但是由于之前这些函数引入了C++第三方的动态库我们无法使用数据库中这种函数方式,所以我们通过PG中特有的插件功能来解决。

添加插件流程

文件结构

  • 封装好的C++类库
    1
    2
    CryptoPG.h
    CryptoPG.cpp
  • 对外接口文件
    • 由于编译是发现直接调用C++类库与PG源码中宏定义会有重名问题,所以将C++类库在进行一次包装供C进行调用
      1
      2
      cs_cryptopp.h
      cs_cryptopp.cpp
  • PG中插件实现的源文件
    1
    cs_cryptopp_wrapper.c

    源码实现

    对外结构文件

  • 在cs_cryptopp.cpp文件中我们进行一次封装,调用C++类库中的函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //cs_cryptopp.cpp
    int generateecdsakey_wrapper(char ** strPrivate, char ** strPublic)
    {
    //调用C++函数
    CryptoPG cry;
    cry.GenerateECDSAKey(strPrivate, strPublic);
    return 0;
    }
    //同时在cs_cryptopp.h中声明函数,extern 声明代表以C形式进行编译
    #ifdef __cplusplus
    extern "C" int generateecdsakey_wrapper(char ** strPrivate, char ** strPublic);
    #endif

PG中插件实现文件

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//cs_cryptopp_wrapper.c
extern int generateecdsakey_wrapper(char ** strPrivate, char ** strPublic); //引入外部声明函数

PG_FUNCTION_INFO_V1(generateecdsakey); //注册函数,此函数是PG中我们需要使用的函数名

PG_MODULE_MAGIC; //必须要添加证明是PG的模块

/*
* signmessage
*
* 生成电子签名信息
*/
Datum
signmessage(PG_FUNCTION_ARGS){

FuncCallContext *funcctx;
TupleDesc tupdesc;
CallCtx *myCtx;

if (SRF_IS_FIRSTCALL()) {
MemoryContext oldcontext;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

myCtx = (CallCtx *) palloc(sizeof(CallCtx));
myCtx->CallTime = 0;
tupdesc = CreateTemplateTupleDesc(2, false);
TupleDescInitEntry(tupdesc, 1, "Message", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, 2, "Signature", TEXTOID, -1, 0);
funcctx->user_fctx = myCtx;
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
MemoryContextSwitchTo(oldcontext);
}

funcctx = SRF_PERCALL_SETUP();
myCtx = funcctx->user_fctx;
tupdesc = funcctx->tuple_desc ;

if (myCtx->CallTime == 0) {
char* strPGMessage = PG_GETARG_CSTRING(0); //获取函数传入的值
char* strSignature = NULL ;
signmessage_wrapper(strPGMessage, &strSignature); //调用对应封装好的函数
HeapTuple tuple = NULL;
Datum values[2];
bool isnull[2] = { 0,0 };

//放入返回值
values[0] = CStringGetTextDatum(strPGMessage);
values[1] = CStringGetTextDatum(strSignature);
free(strSignature);
myCtx->CallTime++;
tuple = heap_form_tuple(tupdesc, values, isnull);
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); //返回
//PostgreSQL中针对返回不同类型有专门宏定义 具体请参考源码src/include/fmgr.h文件
} else {
SRF_RETURN_DONE(funcctx);

}
}

SQL文件编写

编写完对应的代码之后我们还需要添加一个SQL文件,这样在PG中 CREATE EXTENSION会执行对应的SQL语句创建

1
2
3
4
5
//创建一个函数对应cs_cryptopp_wrapper.c中函数名
CREATE FUNCTION signmessage(cstring) //接受类型定义为cstring
RETURNS TABLE (Message text, signature text) //返回值我们定义为一个表因为会返回多列
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

编译安装与使用

编译安装

一般PG的插件我们都会放在源码目录下的contrib/项目名称/ 目录中,进入目录执行:

1
2
3
4
5
6
7
make //编译
su - postgres //切换到postgres用户
pg_ctl stop //关闭数据库
exit //退回到root用户
make install //安装
su - postgres //切换到postgres用户
pg_ctl start //启动数据库

使用

登录pg数据库中执行:

1
2
3
4
5
6
7
8
9
postgres=# drop EXTENSION cs_cryptopp;  //如果之前已创建过EXTENSION
postgres=# CREATE EXTENSION cs_cryptopp;
postgres=# select * from signmessage('2018-01-01 测试');
message | signature
-----------------+------------------------------------
2018-01-01 测试 | )\x06Ū +
| \x0E\x05\x0C\x0E +
| SBHLc?)!\x0EÒ:A\x03h\x1C\72n\x13={
(1 row)

参考链接