凡是不是使用当前针对编译型语言的“版本 1”接口编写的函数,在被调用时都会经过该语言专用的调用处理器函数。这包括用户定义过程语言中的函数,以及用 SQL 编写的函数。调用处理器负责以恰当的方式执行该函数,例如解释所提供的源文本。本章概述如何编写新的过程语言调用处理器。
过程语言的调用处理器是一个“普通”函数,必须使用诸如 C 这样的编译型语言、按照版本 1 接口编写,并在 PostgreSQL 中注册为不接受参数且返回 language_handler 类型。这个特殊伪类型会将该函数标识为调用处理器,并阻止它在 SQL 命令中被直接调用。有关 C 语言调用约定和动态装载的更多细节,见 Section 36.10。
调用处理器与其他函数一样被调用:它会接收一个指向 FunctionCallInfoBaseData struct 的指针,其中包含参数值以及被调用函数的相关信息;同时它还应返回一个 Datum 结果(如果希望返回 SQL 空值结果,还可以设置 isnull 字段,也就是 FunctionCallInfoBaseData 结构中的该字段)。调用处理器与普通被调函数之间的区别在于,flinfo->fn_oid 字段在 FunctionCallInfoBaseData 结构中保存的是实际要调用函数的 OID,而不是调用处理器本身的 OID。调用处理器必须使用这个字段来判定要执行哪个函数。另外,传入的参数列表是按照目标函数而不是调用处理器的声明来设置的。
调用处理器必须从系统目录 pg_proc 中取出该函数的条目,并分析被调用函数的参数类型和返回类型。该函数的 AS 子句可在其 CREATE FUNCTION 命令对应的 prosrc 列里找到,也就是 pg_proc 行中的该列。这里通常是一段过程语言源文本,但理论上也可以是其他内容,例如某个文件的路径名,或者任何能详细告诉调用处理器该做什么的信息。
同一个函数在执行一条 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 命令中存在明显错误时立即通知用户。)虽然究竟检查哪些内容主要由验证器函数自行决定,但要注意,核心 CREATE FUNCTION 代码只会执行附加到函数上的 SET 子句,前提是 check_function_bodies 打开。因此,当 check_function_bodies 关闭时,那些结果可能受 GUC 参数影响的检查肯定都应跳过,以避免在恢复转储时出现虚假的失败。
如果过程语言提供了内联处理器,它必须声明为一个接受单个 internal 参数的函数。内联处理器的返回值会被忽略,因此习惯上将其声明为返回 void。当执行指定了该过程语言的 DO 语句时,就会调用内联处理器。实际传入的参数是一个指向 InlineCodeBlock 结构的指针,其中包含 DO 语句参数的相关信息,特别是待执行的匿名代码块文本。内联处理器应执行这段代码并返回。
建议你将以上所有函数声明以及 CREATE LANGUAGE 命令本身都封装进一个扩展中,这样只需执行一条简单的 CREATE EXTENSION 命令就足以安装该语言。关于如何编写扩展,见 Section 36.17。
标准发行版中附带的各过程语言,在尝试编写自己的语言调用处理器时都是很好的参考资料。请查看源码树中的 src/pl 子目录。CREATE LANGUAGE 参考页中也包含一些有用的细节。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。