iis服务器助手广告广告
返回顶部
首页 > 资讯 > 数据库 >PostgreSQL 源码解读(180)- 内核研发#4(如何实现自定义系统函数)
  • 663
分享到

PostgreSQL 源码解读(180)- 内核研发#4(如何实现自定义系统函数)

2024-04-02 19:04:59 663人浏览 泡泡鱼
摘要

本节以实现oracle中的add

本节以实现oracle中的add_months函数为例介绍如何通过改造内核实现自定义系统函数.

一、基础知识

在实现之前有必要先行介绍一些基础知识,包括Oid/函数注册等.
Oid
Oid即Object identifier,对象标识符,在postgresql中,每个对象都一个Oid,系统表对象之间以Oid进行关联.
函数作为Postgresql中的一种对象,每个函数都存在Oid,通过查询pg_proc可获得相关信息:



postgres=# select oid,proname from pg_proc order by oid;
  oid  |                   proname                    
-------+----------------------------------------------
    31 | byteaout
    33 | charout
    34 | namein
    35 | nameout
    38 | int2in
    39 | int2out
    40 | int2vectorin
    41 | int2vectorout
    42 | int4in
    43 | int4out
    44 | regprocin
    45 | regprocout
    46 | textin
    47 | textout
    48 | tidin
    49 | tidout
    50 | xidin
    51 | xidout
    52 | cidin
    53 | cidout
    54 | oidvectorin
    55 | oidvectorout
    56 | boollt
    57 | boolgt
    60 | booleq
--More--

函数注册
假设我们已经实现了一个自定义系统函数,比如add_months,PostgreSQL如何才能感知该函数的存在?答案是通过函数注册实现.
PostgreSQL在编译的时候,会用perl脚本根据预置的记录,生成src/backend/catalog/postgres.bki文件,该文件在initdb时被解析成一条条的SQL,插入到系统表中.因此自定义的系统函数,需要在通过initdb新建的数据库实例中才能被”感知”.

二、实现步骤

有了上面的基础知识,接下来我们step by step的实现add_months自定义函数.
1.获取函数Oid
PostgreSQL提供了unused_oids工具用于快速检索未使用的Oid,该文件位于src/include/catalog目录下



find -name unused_oids
./src/include/catalog/unused_oids
[root@localhost pg11]# ./src/include/catalog/unused_oids
2 - 9
3423 - 3436
3996
3998
4001 - 4013
4142 - 4199
4217 - 4565
4572 - 4999
5017 - 5027
5029 - 5999
6015 - 6099
6103
6105
6107 - 6109
6116
6122 - 9999

我们选择了Oid = 5100

2.注册函数
在文件pg_proc.dat中添加add_months函数



#src/include/catalog/pg_proc.dat
...
{ oid => '5100', descr => 'oracle-like add_months function',
  proname => 'add_months', provariadic => '0',
  proisstrict => 'f', prorettype => 'date', proargtypes => 'date int4',
  prosrc => 'add_months'},

该文件中的条目对应结构体FORM_pg_proc




CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,ProcedureRelation_Rowtype_Id) BKI_SCHEMA_MacRO
{
    
    //过程名称
    NameData    proname;
    
    //系统OID
    Oid            pronamespace BKI_DEFAULT(PGNSP);
    
    //拥有者Owner
    Oid            proowner BKI_DEFAULT(PGUID);
    
    //实现语言调用接口,pg_language中的OID.
    //默认为12-internal,其他选项包括13-C语言,14-sql,13275-plpgsql
    Oid            prolang BKI_DEFAULT(12);
    
    //估算的执行成本,默认为1
    float4        procost BKI_DEFAULT(1);
    
    //估算的结果行数,默认为0
    float4        prorows BKI_DEFAULT(0);
    
    //可变数组参数元素类型,默认为0
    Oid            provariadic BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
    
    //在计划期间的转换调用,默认为0
    //可通过此列指定的函数来简化
    regproc        protransform BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);
    
    //详见下面的PROKIND_XXX
    char        prokind BKI_DEFAULT(f);
    
    //安全定义器
    bool        prosecdef BKI_DEFAULT(f);
    
    //弱认证函数?除了返回值,没有关系参数的信息被传播
    bool        proleakproof BKI_DEFAULT(f);
    
    //NULLs的处理(严格还是不严格)
    bool        proisstrict BKI_DEFAULT(t);
    
    //返回集合?默认为F
    bool        proretset BKI_DEFAULT(f);
    
    //详见下面的PROVOLATILE_XXX
    char        provolatile BKI_DEFAULT(i);
    
    //详见下面的PROPARALLEL_XXX
    char        proparallel BKI_DEFAULT(s);
    
    
    //参数个数
    //注意:不需要在pg_proc.dat中指定,genbki.pl会自动计算
    int16        pronargs;
    
    //有默认值的参数个数
    int16        pronargdefaults BKI_DEFAULT(0);
    
    //结果类型OID
    Oid            prorettype BKI_LOOKUP(pg_type);
    
    
    //参数类型(剔除了OUT参数)
    //只包括输入参数(含INOUT和VARIADIC参数
    oidvector    proargtypes BKI_LOOKUP(pg_type);
#ifdef CATALOG_VARLEN
    
    //所有参数类型(数组),包括所有参数(含OUT和INOUT参数)
    //如都为IN类型,则为NULL
    Oid            proallargtypes[1] BKI_DEFAULT(_null_) BKI_LOOKUP(pg_type);
    
    //参数模式数组(如都为IN参数,则为NULL)
    // i表示IN参数 , o表示OUT参数, b表示INOUT参数, v表示VARIADIC参数, t表示TABLE参数
    char        proargmodes[1] BKI_DEFAULT(_null_);
    
    //参数名称数组(如无则为NULL)
    //这里的下标对应着proallargtypes而不是proargtypes中的位置
    text        proargnames[1] BKI_DEFAULT(_null_);
    
    //参数默认值表达式树链表(如无则为NULL)
    //对应proargtypes
    pg_node_tree proargdefaults BKI_DEFAULT(_null_);
    
    //应用变换的类型
    Oid            protrftypes[1] BKI_DEFAULT(_null_);
    
    //过程实现文本(如为c,则可为函数名称)
    text        prosrc BKI_FORCE_NOT_NULL;
    
    //第二个过程信息,即附加信息(可为NULL)
    text        probin BKI_DEFAULT(_null_);
    
    //与过程相关的本地GUC设置
    text        proconfig[1] BKI_DEFAULT(_null_);
    
    //访问权限
    aclitem        proacl[1] BKI_DEFAULT(_null_);
#endif
} FormData_pg_proc;

typedef FormData_pg_proc *Form_pg_proc;
#ifdef EXPOSE_TO_CLIENT_CODE

#define PROKIND_FUNCTION 'f'
#define PROKIND_AGGREGATE 'a'
#define PROKIND_WINDOW 'w'
#define PROKIND_PROCEDURE 'p'

#define PROVOLATILE_IMMUTABLE    'i' 
#define PROVOLATILE_STABLE        's' 
#define PROVOLATILE_VOLATILE    'v' 

#define PROPARALLEL_SAFE        's' 
#define PROPARALLEL_RESTRICTED    'r' 
#define PROPARALLEL_UNSAFE        'u' 

#define PROARGMODE_IN        'i'
#define PROARGMODE_OUT        'o'
#define PROARGMODE_INOUT    'b'
#define PROARGMODE_VARIADIC 'v'
#define PROARGMODE_TABLE    't'
#endif                            

通过perl脚本,PG会把该定义文件中的条目生成postgres.bki文件,查看Makefile文件(src/backend/catalog/Makefile)中的注释:



# bki-stamp records the last time we ran genbki.pl.  We don't rely on
# the timestamps of the individual output files, because the Perl script
# won't update them if they didn't change (to avoid unnecessary recompiles).
# Technically, this should depend on Makefile.global which supplies
# $(MAJORVERSION); but then genbki.pl would need to be re-run after every
# configure run, even in distribution tarballs.  So depending on configure.in
# instead is cheating a bit, but it will achieve the Goal of updating the
# version number when it changes.
bki-stamp: genbki.pl Catalog.pm $(POSTGRES_BKI_SRCS) $(POSTGRES_BKI_DATA) $(top_srcdir)/configure.in
    $(PERL) -I $(catalogdir) $< --set-version=$(MAJORVERSION) $(POSTGRES_BKI_SRCS)
    touch $@

编译成功后,生成的src/backend/catalog/postgres.bki中包含了我们添加的条目(OID = 5100):



...
insert OID = 5028 ( satisfies_hash_partition 11 10 12 1 0 2276 0 f f f f f i s 4 0 16 "26 23 23 2276" _null_ "{i,i,i,v}" _null_ _null_ _null_ satisfies_hash_partition _null_ _null_ _null_ )
insert OID = 5100 ( add_months 11 10 12 1 0 0 0 f f f f f i s 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ _null_ add_months _null_ _null_ _null_ )
close pg_proc
...

3.实现功能
在src/backend/utils/adt/date.c文件的最后添加逻辑实现.
该实现使用了GitHub开源项目orafce中的实现( 感谢开源! )



Datum
add_months(PG_FUNCTION_ARGS)
{
    DateADT day = PG_GETARG_DATEADT(0);
    int n = PG_GETARG_INT32(1);
    int y, m, d;
    int    days;
    DateADT result;
    div_t    v;
    bool    last_day;
    j2date(day + POSTGRES_EPOCH_JDATE, &y, &m, &d);
    last_day = (d == days_of_month(y, m));
    v = div(y * 12 + m - 1 + n, 12);
    y = v.quot;
    if (y < 0)
        y += 1;    
    m = v.rem + 1;
    days = days_of_month(y, m);
    if (last_day || d > days)
        d = days;
    result = date2j(y, m, d) - POSTGRES_EPOCH_JDATE;
    PG_RETURN_DATEADT (result);
}
int
days_of_month(int y, int m)
{
    int month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int    days;
    if (m < 0 || 12 < m)
        ereport(ERROR,
                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                 errmsg("date out of range")));
    days = month_days[m - 1];
    if (m == 2 && (y % 400 == 0 || (y % 4 == 0 && y % 100 != 0)))
        days += 1;    
    return days;
}

在头文件src/include/utils/date.h中添加函数声明



extern Datum add_months(PG_FUNCTION_ARGS);

4.编译安装



make clean
make
make install

5.初始化数据库



initdb -D /data/pgsql/tmpdb
pg_ctl start -D /data/pgsql/tmpdb

6.检查验证



postgres=# select add_months(current_date,12);
 add_months 
------------
 2020-04-28
(1 row)

DONE!

三、参考资料

Oid
orafce

您可能感兴趣的文档:

--结束END--

本文标题: PostgreSQL 源码解读(180)- 内核研发#4(如何实现自定义系统函数)

本文链接: https://www.lsjlt.com/news/46636.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作