所有对用不是当前“版本 1”接口(用于编译型语言)语言编写的函数(这包括用户定义的过程语言中的函数、SQL 编写的函数)的调用都会流经一个用于指定语言的调用处理器。调用处理器负责以一种有意义的方式执行该函数,例如通过解释所提供的源文本。本章勾勒了如何编写一个新的过程语言调用处理器的轮廓。
一个过程语言的调用处理器是一个“正常的”函数,它必须以一种编译型语言(如 C)编写、使用版本-1接口并且在PostgreSQL中注册为无参数且返回类型language_handler
。这种特殊的伪类型标识该函数是一个调用处理器并且阻止它在 SQL 命令中被直接调用。关于 C 语言调用惯例和动态载入的更多细节,请见第 38.10 节。
调用处理器的调用方式和其他任何函数相同:它接收一个包含参数值和有关被调用函数信息的FunctionCallInfoBaseData
结构
,并且它被期望返回一个Datum
结果(并且如果它希望返回一个 SQL 空值结果,它可能设置FunctionCallInfoBaseData
结构的isnull
域)。一个调用处理器和一个普通被调用函数之间的区别是FunctionCallInfoBaseData
结构的flinfo->fn_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
(就像它在一个普通函数调用中那样)。一个语言处理器应该为过程语言函数提供机制来得到触发器信息。
编写为C扩展的过程-语言处理程序的模板,在src/test/modules/plsample
中提供。
这是一个工作示例,演示了一种方法,以创建过程语言处理程序、处理参数以及返回一个值。
尽管提供一个调用处理器对于创建一个最小过程语言已经足够,还可以提供其他两个可选的函数来让该函数更易用。它们是验证器和内联处理器。一个验证器可以被提供来允许在CREATE FUNCTION期间完成语言相关的检查。一个内联处理器可以被提供来允许语言支持通过DO命令执行匿名代码块。
如果一个验证器被一个过程语言提供,它必须被声明为一个采用一个单一oid
类型参数的函数。该验证器的结果被忽略,因为它通常被声明为返回void
。验证器将在一个已经创建了或更新了一个以该过程语言编写的函数的CREATE FUNCTION
命令之后被调用。被传入的 OID 是函数的pg_proc
行的 OID。验证器必须用通常方式取得这个行,并且做任何合适的检查。首先,调用CheckFunctionValidatorAccess()
来诊断对用户通过CREATE FUNCTION
无法达到的验证器的显式调用。典型的检查包括验证函数的参数和结果类型是否被该语言支持,以及该函数体在该语言中语法是否正确。如果验证器发现该函数是好的,它应该只是返回。如果它发现一个错误,它应该通过通常的ereport()
错误报告机制报告该错误。抛出一个错误将强制一次事务回滚并且因此阻止不正确的函数定义被提交。
验证函数通常应遵守check_function_bodies参数:如果关闭,则应跳过任何昂贵或上下文敏感的检查。
如果语言允许在编译时执行代码,则验证器必须禁止可能导致此类执行的检查。
特别是,pg_dump关闭此参数,以便可以加载存储过程语言函数,而无需担心函数体对其他数据库对象的副作用或依赖关系。
(由于此要求,调用处理程序应避免假设验证器已完全检查函数。
有验证器的目的不是让调用处理程序省略检查,而是在CREATE FUNCTION
命令中存在明显错误时立即通知用户。)
尽管要检查的内容主要由验证器函数自行决定,但请注意核心CREATE FUNCTION
代码仅在check_function_bodies
打开时执行附加到函数的SET
子句。
因此,在check_function_bodies
关闭时,绝对应跳过可能受GUC参数影响的检查,以避免在恢复转储时出现错误失败。
如果一个过程语言提供了一个内联处理器,它必须被声明为一个采用一个单一internal
类型参数的函数。内联处理器的结果会被忽略,因此它通常被声明为返回void
。当一个DO
语句被调用执行指定过程语言时,内联处理器将被调用。实际被传递的参数是一个指向一个InlineCodeBlock
结构的指针,它包含有关DO
语句参数的而信息,特别是将被执行的匿名代码块的文本。内联处理器应该执行该代码并返回。
我们推荐你包装所有这些函数声明,以及CREATE LANGUAGE
命令本身到一个extension中,这样一个简单的CREATE EXTENSION
命令就足以安装该语言。关于编写扩展的信息请见第 38.17 节。
在尝试编写你自己的语言处理器时,包括在标准发布中的过程语言是很好的参考。看看源码树中的src/pl
子目录。CREATE LANGUAGE参考页也有一些有用的细节。