PostgreSQL C风格函数TEXT优化

使用PostgreSQL C风格函数注意事项

1.在C函数中不检查参数是否为NULL,NULL参数检查在create function sql中设置strict实现.
2.当用PG_GETARG_xx获取参数后,根据需要检查参数值是否有效.
3.如不能编译是因为缺少头文件,自己添加缺失的头文件即可.
4.PostgreSQL分配和释放内存使用palloc,palloc0和pfree.palloc0分配后会将内存设置为0.其它和palloc一样,在源码backend/utils/mmgr/mcxt.c中查看.
5.palloc分配的内存无需再检查是否分配成功(NULL==ptr),在palloc内部已经检查.
6.内存使用原则是谁分配谁释放(函数返回值除外),这点和c/c++一致.
7.当palloc分配的内存做为函数的返回值时,不需要pfree释放内存,由PostgreSQL自动释放.

常规办法

为更好说明问题,以下步骤比较繁琐,

#include "postgres.h"
#include "fmgr.h"
#include "utils/builtins.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PGDLLEXPORT Datum string_test(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(string_test);

Datum string_test(PG_FUNCTION_ARGS) {
    int32 val = PG_GETARG_INT32(0);
    char *ptmp, *pint;
    const char *pcsrc = "汉字测试";
    size_t all_size,int_str_size, src_size = strlen(pcsrc);
    /*
    *   计算拼接字符和整数所需的内存大小
    *   为提升性能int转字符不设置单独的缓冲区
    *   直接在分配时多加16个字节存储int转换为字符后的内容
    */
    size_t alloc_size = (src_size + 16 + sizeof(0));
    char *buffer = (char *)malloc(alloc_size);
    if (NULL == buffer)
        ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("memory out of range")));
    ptmp = buffer;
    /*复制字符串*/
    memcpy(ptmp, pcsrc, src_size);
    ptmp += src_size;
    /*将int转换为10进制字符后和字符拼接*/
    pint = itoa(val, ptmp, 10);
    /*计算int转换为字符后所需的内存大小*/
    int_str_size = strlen(pint);
    ptmp += int_str_size;
    /*计算实际所需的内存*/
    all_size = ptmp - buffer;
    buffer[all_size] = '\0';
    text *result = cstring_to_text(buffer);
    free(buffer);
    /*返回拼接后的数据,cstring_to_text又复制了一次数据,所以内存一共分配和复制了两次*/
    PG_RETURN_TEXT_P(PointerGetDatum(result));
}

函数cstring_to_text内部直接调用了cstring_to_text_with_len(const char *s, int len),cstring_to_text_with_len实现如下:

text* cstring_to_text_with_len(const char *s, int len){
    text *result = (text *) palloc(len + VARHDRSZ);
    SET_VARSIZE(result, len + VARHDRSZ);
    memcpy(VARDATA(result), s, len);
    return result;
}

一次分配内存完成上述功能

Datum string_test(PG_FUNCTION_ARGS) {
    int32 val = PG_GETARG_INT32(0);
    char *pbuf,*ptmp, *pint;
    const char *pcsrc = "汉字测试";
    size_t all_size,int_str_size, src_size = strlen(pcsrc);
    /*
    *   内存缓冲区
    *   计算拼接字符和整数所需的内存大小
    *   为提升性能int转字符不设置单独的缓冲区
    *   直接在分配内存时多加16个字节存储int转换为字符后的内容
    */
    size_t alloc_size = (src_size + 16 + VARHDRSZ);
    /*一次性分配内存,返回值也用这个函数*/
    text *result = (text *)palloc(alloc_size);
    /*获取分配的内存空间*/
    pbuf = VARDATA(result);
    ptmp = pbuf;
    /*复制字符串*/
    memcpy(ptmp, pcsrc, src_size);
    ptmp += src_size;
    /*将int转换为10进制字符后和字符拼接*/
    pint = itoa(val, ptmp, 10);
    /*计算int转换为字符后所需的内存大小*/
    int_str_size = strlen(pint);
    ptmp += int_str_size;
    /*计算实际所需的内存*/
    all_size = ptmp - pbuf;
    /*PostgreSQL的字符串不是以NULL结尾的,在开头有4字节说明字符串的长度,类似BSTR的定义,所以不需要字符结束标记*/
    //buffer[all_size] = '\0';  
    /*
    *   注意,注意,注意
    *   在函数返回前设置实际的内存大小
    *   SET_VARSIZE必须是实际使用的内存大小加上VARHDRSZ,否则PostgreSQL不能正确显示文本
    *   SET_VARSIZE必须是实际使用的内存大小加上VARHDRSZ,否则PostgreSQL不能正确显示文本
    *   SET_VARSIZE必须是实际使用的内存大小加上VARHDRSZ,否则PostgreSQL不能正确显示文本
    */
    SET_VARSIZE(result, all_size + VARHDRSZ);
    /*返回拼接后的数据内存分配和复制了一次*/
    PG_RETURN_TEXT_P(PointerGetDatum(result));
}

小结

当使用PostgreSQL的text类型时要注意以几点:
1.在text类型开头用VARHDRSZ字节说明字符串的长度.

截止目前VARHDRSZ定义为4字节,将来有可能变化 ,所以不要直接使用4,应该使用VARHDRSZ

2.因此分配内存时,内存大小必须包含VARHDRSZ.
size_t alloc_size = (src_size + 16 + VARHDRSZ);

src_size + 16是实际的数据存储空间 ,VARHDRSZ字节说明字符串的长度

3.获取分配的数据内存空间使用 VARDATA.
pbuf = VARDATA(result);
4.如果明确知道实际的内存大小,在palloc完成后,就立刻调用SET_VARSIZE设置内存的大小
text *result = (text *)palloc(alloc_size);
 SET_VARSIZE(result, all_size + VARHDRSZ);
5.如果在分配时不知道需要的内存大小,可以在palloc时分配足够大的内存空间,然后计算实际的内存大小在返回前调用SET_VARSIZE设置内存的大小

例如: 28个UTF-8格式的字符要返回,但是在生产字符串之间我并不知道28个UTF-8字符实际占用的内存大小,因些在分配前一次性分配足够的空间

UTF8单个字符最多使用6字节
text *result = (text *)palloc(28 * 6  + VARHDRSZ);

现在生成28个UTF-8字符,并在生成过程中或生成完成后计算实际的内存大小.
现在我们已经知道28个UTF-8字符实际使用的内存大小,因此在函数返回前调用SET_VARSIZE设置实际使用的内存大小

SET_VARSIZE(result, all_size + VARHDRSZ);
PG_RETURN_TEXT_P(PointerGetDatum(result));
SET_VARSIZE必须是实际使用的内存大小加上VARHDRSZ,否则PostgreSQL不能正确显示文本
SET_VARSIZE必须是实际使用的内存大小加上VARHDRSZ,否则PostgreSQL不能正确显示文本
SET_VARSIZE必须是实际使用的内存大小加上VARHDRSZ,否则PostgreSQL不能正确显示文本
6.PostgreSQL的字符串不是以NULL结尾的,在开头有4字节说明字符串的长度,类似BSTR的定义,所以不需要字符结束标记NULL

不需要这个

 //buffer[all_size] = '\0'; 
在实践使用过程中字符串结束符有没有都可以,如果为了和C函数兼容,可以保留
7.PostgreSQL的TEXT操作函数总结

VARHDRSZ获取TEXT类型头的大小
在分析内存和设置内存大小时一定要加上VARHDRSZ,主要是palloc,palloc0和SET_VARSIZE.
VARDATA获取分配的数据内存空间

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kmblack1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值