openGauss 系统函数添加指导
1、函数架构简介
openGauss 内函数的可以分为两个部分:
- 身份注册声明:openGauss 中存在一个系统表 pg_proc,这个表存放了所有函数的基本元信息,相当于函数的“户口本”,只有在其中可以查到的函数,才可以在 SQL 语句中进行调用,才有“数据库函数”的身份。常见的注册方式有:builtin、升级脚本、CREATE FUNCTION 语句、EXTENSION。
- 底层功能实现:实现其功能的具体逻辑代码,可以根据其所用的语言分为四类:INTERNAL, SQL, PLPGSQL、C。
四中常见的函数注册创建方式,分别对应着着不同的场景:
- builtin:源代码中存在一个名为 builtin_funcs.ini 的文件,存放着一系列内置函数的元信息,在初始化安装数据库时,会通过某些方式,全量扫描此文件,将里面罗列的函数批量注册到 pg_proc 系统表。
- 升级脚本:数据库由老版本升级到新版本的场景下,不会也不能遍历重刷 builtin_funcs.ini 到 pg_proc,因此若新版本有新增函数,就需要编写一个升级脚本,在升级过程中通过升级脚本将新增函数注册到 pg_proc 之中。
- CREATE FUNCTION: 通过
CREATE FUNCTION ... BEGIN ... END
语句,一把完成注册和实现。 - EXTENSION:随着 extension 进行注册和加载。
四类语言实现方案分别有不同的注册声明方式以及实现特征:
- INTERNAL: 通过 builtin 或升级脚本进行注册,底层功能通过 C 语言实现的函数,也是数据库最常见的内置函数,如 pg_sleep()。其底层功能函数函数名可以再 pg_proc 的 prosrc 列查到。
- SQL: 通过 builtin 或者升级脚本进行注册,底层功能通过一句 SQL 实现的函数,也是数据库内置函数的一种。如 to_char() ,在数据库底层会转换为一句
select CAST(... AS VARCHAR2);
,这一句在 pg_proc 的 prosrc 列可以查到,通常是为了复用已有功能模块来适配新接口而采用这种实现方案。 - PLPGSQL: 这个就是我们所熟知的,使用 plpgsql 进行编写创建的函数了,通过语句一次完成声明与实现。pg_proc 的 prosrc 列存放了这个语句的源代码。
- C: 出现在各种 extension 之中,内部功能使用 C 语言实现。这个和 INTERNAL 比较类似,区别在于其具体注册方式为通过 extension 进行注册,并且底层代码是在外部 lib 之中,而 INTERNAL 是在 gaussdb 二进制内的。可以在 pg_proc 的 prosrc、probin 列查到其 lib 路径以及函数符号信息。
其中 INTERNAL、SQL 类的函数,因为都可以通过 builtin 的方式整,因此也都常被统称为 builtin 函数。
一个普通函数调用流程大致为:
1、解析 SQL 语句,获取到函数名以及参数值与类型等信息。
2、根据以上信息,在 pg_proc 中检索到这个函数的元数据,元数据中包含默认值、实现语言、底层函数、估算代价等所有信息。
3、根据其实现语言,调用其具体底层接口模块。如:INTERNAL 类型会直接调用其元数据中的底层 C 语言代码函数;C 类型会根据元数据信息加载相关 lib 后调用 lib 中的 C 语言代码函数;SQL 类型会直接转而执行元数据 prosrc 中存放的 sql 语句;PLPGSQL 类型会转而走过程语言模块解释执行 prosrc 中存放的源代码。
另外还有一种稍微特殊的函数——聚集函数,它其实是在普通函数的架构基础上做的功能变更与扩展,其架构和添加流程与普通函数有些差异。我们分两章介绍如何添加普通的 INTERNAL 函数和聚集函数。
2、如何添加一个普通的 INTERNAL 函数
了解了上面的架构与流程后,不难得出,添加一个普通的 INTERNAL 函数,可分为四个步骤:
1、声明函数身份。将我们已经提前设计好的函数的各种属性,如参数数量类型、返回值类型、稳定性等等,按照特定的格式添加进 buitin_funcs.ini 文件之中。
2、实现功能代码。在内核代码合适位置,实现一个 C 语言的函数,来实现对应的功能。
3、关联声明实现。将上一个步骤编写函数的函数名,添加到 builtin_funcs.ini 对应条目的对应位置。
4、编写升级脚本。用于在升级流程之中注册 SQL 函数身份。
2.1 声明函数身份
在这之前我们需要已经提前设计好自己的函数属性以及功能,如参数数量类型、返回值类型、稳定性等等,将这些信息按照特定的格式和顺序填写到./src/common/backend/catalog/builtin_funcs.ini
文件之中。
这个文件中需要按照如下结构进行书写:
AddFuncGroup(
"pg_sleep", 1,
AddBuiltinFunc(_0(2626), _1("pg_sleep"), _2(1), _3(true), _4(false), _5(pg_sleep), _6(2278), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(1, 701), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_sleep"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false))
),
可以看到其有内外两层组成。
外层为AddFuncGroup("pg_sleep", 1, AddBuiltinFunc(...))
,其第一个成员变量为函数名,第二个成员变量为重载数量,后面的 AddBuiltinFunc 结构为函数元信息。这个结构会匹配内核代码中的结构体struct FuncGroup
。需要十分注意的是,这个结构需要按照第一个成员也就是函数名的 ASCII 大小升序,添加到对应的位置。
内层AddBuiltinFunc
为函数元信息,其目前一共含有 38 个属性,与内核代码结构体struct Builtin_func
和系统表 pg_proc 都有对应关系。我们根据如下表每个属性含义,完善 AddBuiltinFunc 结构(可暂不关注属性 5 与 25)。
编号 | 含义 | 对应 Builtin_func |
---|---|---|
0 | oid,函数的唯一标识 id,需要小于 10000 且不可以和已有函数重复。 | Oid foid; |
1 | 函数名 | const char* funcName |
2 |