postgresql内核开发之add 您所在的位置:网站首页 linkedlist类型 postgresql内核开发之add

postgresql内核开发之add

2023-03-11 03:25| 来源: 网络整理| 查看: 265

postgresql内核内部函数实现详解

pg前文通过实现的helloworld入门,实际上就是实现了一个函数,但这个函数只有演示意义,也没有详细介绍函数实现细节,本文将详细介绍在postgresql内部如何完成一个函数的实现。 在oracle中,add_months函数能基于当前时间增加或减去几个月得出一个新日期,postgresql没有这样的函数,我们就以add_months有例子介绍下postgresql的函数增加方法。在C语言中,实现一个函数很简单,只要把函数主体实现就行,在其它文件中使用的话include头文件中声名就可以了,这两个步骤在postgresql内核中自然也要有,但还要增加一个动作,注册到pg_proc系统表中,pg_proc是postgresql中函数的系统表。 首先实现函数,因为是时间相关的函数,可以放到src/backend/utils/adt/date.c中,add_months_date和add_months_timestamp,头文件src/include/utils/date.h 增加声名,代码如下:

Datum add_months_date(PG_FUNCTION_ARGS) {DateADT date = PG_GETARG_DATEADT(0);int added_mon = PG_GETARG_INT32(1);struct pg_tm tt;struct pg_tm * tm = &tt;bool isLastDay = false;int LastDay;int year;int mon;int day;DateADT result = 0;/* check range */if (DATE_NOT_FINITE(date)){PG_RETURN_DATEADT(date);}j2date((date + POSTGRES_EPOCH_JDATE), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));year = tm->tm_year;mon = tm->tm_mon;day = tm->tm_mday;isLastDay = (day == day_tab[isleap(year)][mon - 1]);mon += added_mon;if (mon > 12){year += ((mon - 1) / 12);mon = (((mon - 1) % 12) + 1);}else if (mon < 1){year += ((mon / 12) - 1);mon = ((mon % 12) + 12);}LastDay = day_tab[isleap(year)][mon - 1];day = isLastDay ? LastDay : ((day > LastDay) ? LastDay : day);if (!IS_VALID_JULIAN(year, mon, day)){ereport(ERROR,(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),errmsg("date out of range")));}result = (DateADT)(date2j(year, mon, day) - POSTGRES_EPOCH_JDATE);PG_RETURN_DATEADT(result); }Datum add_months_timestamp(PG_FUNCTION_ARGS) {Timestamp timestamp = PG_GETARG_TIMESTAMP(0);int added_mon = PG_GETARG_INT32(1);struct pg_tm tt;struct pg_tm * tm = &tt;bool isLastDay = FALSE;int LastDay;int year;int mon;int day;int tz;fsec_t fsec;Timestamp result = 0;if (TIMESTAMP_NOT_FINITE(timestamp)){PG_RETURN_TIMESTAMP(timestamp);}if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0){ereport(ERROR,(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),errmsg("timestamp out of range")));}year = tm->tm_year;mon = tm->tm_mon;day = tm->tm_mday;isLastDay = (day == day_tab[isleap(year)][mon - 1]);mon += added_mon;if (mon > 12){year += ((mon - 1) / 12);mon = (((mon - 1) % 12) + 1);}else if (mon < 1){year += ((mon / 12) - 1);mon = ((mon % 12) + 12);}LastDay = day_tab[isleap(year)][mon - 1];day = isLastDay ? LastDay : ((day > LastDay) ? LastDay : day);tm->tm_year = year;tm->tm_mon = mon;tm->tm_mday = day;if (tm2timestamp(tm, fsec, NULL, &result) != 0){ereport(ERROR,(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),errmsg("timestamp out of range")));}PG_RETURN_TIMESTAMP(result); }

src/include/utils/date.h

extern Datum add_months_date(PG_FUNCTION_ARGS); extern Datum add_months_timestamp(PG_FUNCTION_ARGS);

这里不对add_months函数内部逻辑深入,有兴趣的可以研究下oracle该函数表现对照下,重点在于完成了函数体实现,只要再注册到pg_proc中即可实现新增加函数。如何注册呢? 在src/include/catalog/pg_proc.h 新增代码如下:

DATA(insert OID = 9998 (add_months PGNSP PGUID 12 1 0 0 0 f f f f t f i f 2 0 1082 "1082 23" _null_ _null_ _null_ _null_ _null_ add_months_date _null_ _null_ _null_)); DATA(insert OID = 9997 (add_months PGNSP PGUID 12 1 0 0 0 f f f f t f i f 2 0 1114 "1114 23" _null_ _null_ _null_ _null_ _null_ add_months_timestamp _null_ _null_ _null_)); DATA(insert OID = 9996 (add_months PGNSP PGUID 14 1 0 0 0 f f f f t f i f 2 0 1114 "1184 23" _null_ _null_ _null_ _null_ _null_ "select add_months($1::timestamp,$2)" _null_ _null_ _null_));

OK,全部需要增加的代码全部完成,编译,安装,重新初始化库,再运行测试如下:

postgres=# \d a Table "public.a" Column | Type | Modifiers --------+-----------------------------+----------- a | date | b | timestamp without time zone |postgres=# select *from a;a | b ------------+----------------------------2016-12-04 | 2016-12-04 22:17:28.4478292016-02-28 | 2016-02-29 00:00:00 (2 rows) postgres=# select a,add_months(a,3),b,add_months(b,4) from a; a | add_months | b | add_months ------------+------------+----------------------------+---------------------------- 2016-12-04 | 2017-03-04 | 2016-12-04 22:17:28.447829 | 2017-04-05 06:17:28.447829 2016-02-28 | 2016-05-28 | 2016-02-29 00:00:00 | 2016-06-30 08:00:00 (2 rows)

add_months功能正常,至此一个内部函数增加完毕,下面再以提问的方式详细介绍下一些可能的疑问。 第一个问题,为什么要增加两个函数,不是只一个add_months吗? 事实上,一共增加了三个。这三个外部调用都是add_months函数,根据参数不相同实现了重载,在pg_proc.h注册时说明了这些参数。(后面还有详细说明) 第二个问题,注册是什么意思? 在模板库中插入记录,使任何库一创建就有。对于系统表来说,在它的头文件里有对这个系统表的详细定义说明,在这个头文件下面能按格式预置一些记录,这些记录在initdb时会插入到模板库的对应系统表中。具体来说,这些预置的记录,在编译过程中,会被perl脚本转换到postgres.bki中,这个bki文件在安装目录的share文件夹,当initdb时,会加载这个bki并解析成一条条sql运行,创建出一个个系统表,插入初始记录,把初始的信息准备好。有兴趣者可以看看initdb模块中 bootstrap_template1函数,初始化模块时第1件事就是加载postgres.bki文件翻译解析执行。 第三个问题,在pg_proc.h中插入的记录是什么含义? 以第一行为例详细说明如下: DATA(insert OID = 9998 (add_months PGNSP PGUID 12 1 0 0 0 f f f f t f i f 2 0 1082 “1082 23” null null null null null add_months_date null null null));

9998–OID使用内核中未使用的OID即可(src/include/catalog下unused_oids,可以显示未使用的oid) postgres内部预留了1W多个oid给系统用,选一个没有的就行,如果不知道哪些可用,在\src\include\catalog\ 下有个脚本文件unused_oids,运行一下就能找出哪些oid可用,但要这是一个linux脚本,需要在linux下运行。

add_months–函数名,我们在SQL中用的外部函数,但对应内部功能函数实现可能不止一个

PGNSP–函数所属的名字空间的OID,PGNSP即pg_catalog(oid=11),内置函数添加此值固定

PGUID–函数的拥有者OID,PGUID及initdb时指定用户(oid=10),内置函数添加此值固定

12–实现语言或该函数的调用接口,内置函数使用12(internal),SQL用14,如第三个函数实现

1–估计的执行代价,如果proretset为真,这是每行返回的代价

0–估计的结果行数量(如果proretset为假,该值为0)

0–可变数组参数的元素的数据类型,如果函数没有可变参数则为0

0–调用该函数时可以通过此列指定的函数来简化

f–函数是否为一个聚集函数

f–函数是否为一个窗口函数

f–函数是一个安全性定义者(即,一个”setuid”函数)

f–该函数没有副作用。除了通过返回值,没有关于参数的信息被传播。任何会抛出基于其参数值的错误信息的函数都不是泄露验证的。

t–当任意调用函数为空时,函数是否会返回空值。在那种情况下函数实际上根本不会被调用。非”strict”函数必须准备好处理空值输入。

f–函数是否返回一个集合(即,指定数据类型的多个值)

i–provolatile说明函数是仅仅只依赖于它的输入参数,还是会被外部因素影响。值i表示”不变的”函数,它对于相同的输入总是输出相同的结果。值s表示”稳定的”函数,它的结果(对于固定输入)在一次扫描内不会变化。值v表示”不稳定的”函数,它的结果在任何时候都可能变化(使用v页表示函数具有副作用,所以对它们的调用无法得到优化)

f–函数类型(此项为我们开发过程中新增字段,表示函数类型,f代表函数,p代表过程,t代表触发器),postgresql内核没有此参数,可忽略

2–输入参数的个数,对应后面的1082 23两个参数

0–具有默认值的参数个数

1082–返回值的数据类型

“1082 23”–函数参数的数据类型的数组,这只包括输入参数(含INOUT和VARIADIC参数),因此也表现了函数的调用特征 重载函数也凭这区别,如add_months函数,如果有多个,参数肯定不同,这个不同即可以是数量不同,也可以是类型不同,1082 23 就是代表类型,如下:

postgres=# select oid,typname from pg_type where oid in (1082,23,1114,1184) ; oid | typname ------+-------------23 | int4 1082 | date 1114 | timestamp 1184 | timestamptz (4 rows)

_null_–函数参数的数据类型的数组,这包括所有参数(含OUT和INOUT参数)。但是,如果所有参数都是IN参数,这个域将为空。注意下标是从1开始 ,然而由于历史原因proargtypes的下标是从0开始

_null_–函数参数的模式的数组。编码为: i表示IN参数 , o表示OUT参数, b表示INOUT参数, v表示VARIADIC参数, t表示TABLE参数。 如果所有的参数都是IN参数,这个域为空。注意这里的下标对应着proallargtypes而不是proargtypes中的位置

_null_–函数参数的名字的数组。没有名字的参数在数组中设置为空字符串。如果没有一个参数有名字,这个域为空。注意这里的下标对应着proallargtypes而不是proargtypes中的位置

_null_–默认值的表达式树(按照nodeToString()的表现方式)。这是一个pronargdefaults元素的列表,对应于最后N个input参数(即最后N个proargtypes位置)。如果没有一个参数具有默认值,这个域为空

_null_–数据类型OID为了应用转换

add_months_date–函数处理者如何调用该函数。它可能是针对解释型语言的真实源码、一个符号链接、一个文件名或任何其他东西,这取决于实现语言/调用习惯,简单来说,就是add_months真正运行的东西,第一个是add_months_date ,第二个是调了add_months_timestamp函数,第三个是一个sql语句select add_months(1::timestamp,2)。虽然我们写sql语句时,并不是太关心相关的类型,但是在执行时,肯定是精确调某个函数来完成功能。第一个处理的是add_months(date,int), 第二个处理的是add_months(timestamp,int),第三个处理的是add_months(timestamptz,int),通过这样的方式实现sql函数重载。

_null_–关于如何调用函数的附加信息。其解释是与语言相关的

_null_–函数对于运行时配置变量的本地设置值

_null_–访问权限

第四个问题,Datum,PG_FUNCTION_ARGS,PG_GETARG_DATEADT,PG_RETURN_DATEADT,分别是什么东西? Datum是通用的类型,存什么类型的值是什么由接收的人自己解析。PG_FUNCTION_ARGS是参数的宏,里面有函数的信息。PG_GETARG_DATEADT是取到前面的参数里的对应位置的参数值。PG_RETURN_DATEADT将值转成对应的类型返回,会将多余的字节清理掉。这些详细的可以看看代码,尤其PG_FUNCTION_ARGS的定义用法,对理解函数的实现有很大帮助。 ok,这次就到这,希望能对大家有所帮助。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有