PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

Chapter 57. 编写过程语言处理器

对以非当前“版本 1”编译语言接口编写的函数(这包括用户定义过程语言中的函数以及用 SQL 编写的函数)的所有调用,都会经由该特定语言的 调用处理器 函数。调用处理器的职责是以有意义的方式执行该函数,例如解释所提供的源文本。本章概述如何编写新的过程语言的调用处理器。

过程语言的调用处理器是一个 “普通” 函数,必须用 C 等编译语言编写,使用版本 1 接口,并以不接收任何参数、返回 language_handler 类型的形式注册到 PostgreSQL。这一特殊伪类型把该函数标识为调用处理器,并防止它在 SQL 命令中被直接调用。关于 C 语言调用约定与动态加载的更多细节,请参见 Section 36.10。

调用处理器的调用方式与其他任何函数相同:它接收一个指向 FunctionCallInfoBaseData 结构 的指针,其中包含参数值与被调用函数的信息,并且应返回一个 Datum 结果(如果希望返回 SQL 空值结果,还应设置 FunctionCallInfoBaseData 结构的 isnull 字段)。调用处理器与普通被调用函数的区别在于:FunctionCallInfoBaseData 结构的 flinfo->fn_oid 字段包含的是要调用的实际函数的 OID,调用处理器自身的 OID。调用处理器必须使用该字段来确定要执行哪个函数。此外,传入的参数列表是按照目标函数(而非调用处理器)的声明设置的。

由调用处理器自行从 pg_proc 系统目录取回该函数的条目,并分析被调用函数的参数与返回类型。函数的 CREATE FUNCTION 命令中的 AS 子句可在 pg_proc 行的 prosrc 列中找到。它通常是过程语言的源文本,但理论上也可以是其他内容,例如文件路径名,或任何能让调用处理器明确知道该做什么的信息。

同一个函数常常在一条 SQL 语句中被多次调用。调用处理器可以通过使用 flinfo->fn_extra 字段避免反复查找被调用函数的信息。该字段初始为 NULL,但调用处理器可以把它设置为指向关于被调用函数的信息。在后续调用中,如果 flinfo->fn_extra 已非 NULL,就可以直接使用它并跳过信息查找步骤。调用处理器必须确保 flinfo->fn_extra 所指向的内存在至少当前查询结束之前一直存活,因为 FmgrInfo 数据结构可能会保留那么久。一种做法是在 flinfo->fn_mcxt 指定的内存上下文中分配这些额外数据;这类数据通常与 FmgrInfo 本身具有相同的生命周期。但处理器也可以选择使用存活期更长的内存上下文,以便跨查询缓存函数定义信息。

当过程语言函数作为触发器被调用时,不会以常规方式传入参数,但 FunctionCallInfoBaseData 的 context 字段指向一个 TriggerData 结构,而非像普通函数调用那样为 NULL。语言处理器应当提供机制,让过程语言函数能够获取触发器信息。

src/test/modules/plsample 提供了一个以 C 扩展形式编写的过程语言处理器模板。这是一个可运行的示例,演示了创建过程语言处理器、处理参数并返回值的一种方式。

虽然提供调用处理器就足以创建一个最小的过程语言,但还可以选择提供另外两个函数,使该语言更便于使用。它们是 验证器 与 内联处理器。提供验证器是为了允许在 CREATE FUNCTION 期间执行语言特定的检查。提供内联处理器是为了让语言支持通过 DO 命令执行的匿名代码块。

如果过程语言提供了验证器,它必须被声明为接受单个 oid 类型参数的函数。验证器的结果会被忽略,因此惯例上声明为返回 void。在 CREATE FUNCTION 命令创建或更新了一个以该过程语言编写的函数之后,验证器会在该命令的末尾被调用。传入的 OID 是函数的 pg_proc 行的 OID。验证器必须以常规方式取回这一行,并执行任何适当的检查。 首先,调用 CheckFunctionValidatorAccess() 以诊断用户通过 CREATE FUNCTION 无法达成的对验证器的显式调用。随后的典型检查包括:验证函数的参数与结果类型受该语言支持,以及函数体在该语言中语法正确。如果验证器认定函数没有问题,它应当直接返回。如果发现错误,应当通过常规的 ereport() 错误报告机制报告。抛出错误会强制事务回滚,从而防止错误的函数定义被提交。

验证器函数通常应遵循 check_function_bodies 参数:如果它被关闭,则应跳过任何开销大或依赖上下文的检查。如果该语言支持在编译时执行代码,验证器必须抑制会引发这类执行的检查。特别地,pg_dump 会关闭该参数,以便在加载过程语言函数时无需担心函数体对其他数据库对象的副作用或依赖。 (基于这一要求,调用处理器应当避免假定验证器已完整检查过函数。拥有验证器的意义不是让调用处理器省略检查,而是在 CREATE FUNCTION 命令存在明显错误时立即通知用户。) 虽然具体检查什么大多留给验证器函数自行裁量,但请注意:只有当 check_function_bodies 开启时,核心 CREATE FUNCTION 代码才会执行附加到函数的 SET 子句。因此,结果可能受 GUC 参数影响的检查在 check_function_bodies 关闭时一定要跳过,以避免恢复转储时出现错误失败。

如果过程语言提供了内联处理器,它必须被声明为接受单个 internal 类型参数的函数。内联处理器的结果会被忽略,因此惯例上声明为返回 void。当执行指定了该过程语言的 DO 语句时,内联处理器会被调用。实际传入的参数是指向 InlineCodeBlock 结构的指针,其中包含关于 DO 语句参数的信息,特别是要执行的匿名代码块的文本。内联处理器应当执行这段代码然后返回。

建议你把所有这些函数声明以及 CREATE LANGUAGE 命令本身包装成一个 扩展,这样一条简单的 CREATE EXTENSION 命令就足以安装该语言。关于编写扩展的信息,请参见 Section 36.17。

标准发行版附带的过程语言是编写你自己的语言处理器时的良好参考。请查看源码树的 src/pl 子目录。CREATE LANGUAGE 参考页也提供了一些有用的细节。