所有非当前“版本 1”接口编译语言(包括用户定义的语言以及用 SQL 编写的函数)的函数调用,都会通过特定语言的调用处理程序。调用处理程序的职责是以有意义的方式执行函数,例如通过解释提供的源代码。本章概述了如何编写新的过程语言的调用处理程序。
过程语言的调用处理程序是一个“普通”函数,必须使用版本 1 接口用 C 等编译语言编写,并向 PostgreSQL 注册为不接受任何参数并返回 language_handler
类型。这个特殊的伪类型标识该函数为调用处理程序,并防止它在 SQL 命令中被直接调用。有关 C 语言调用约定和动态加载的更多详细信息,请参阅 第 36.10 节。
调用处理程序以与其他任何函数相同的方式调用:它接收指向包含参数值和有关被调用函数信息的 FunctionCallInfoBaseData
struct
的指针,并期望返回一个 Datum
结果(如果希望返回 SQL null 结果,还可以设置 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
命令中是否存在明显错误。)虽然具体检查什么主要留给验证器函数自行决定,但请注意,核心 CREATE FUNCTION
代码仅在 check_function_bodies
开启时执行附加到函数的 SET
子句。因此,结果可能受 GUC 参数影响的检查在 check_function_bodies
关闭时绝对应该跳过,以避免在恢复转储时出现误报。
如果过程语言提供了内联处理程序,它必须被声明为一个接受单个 internal
类型参数的函数。内联处理程序的返回值被忽略,因此通常声明其返回 void
。当执行指定了过程语言的 DO
语句时,将调用内联处理程序。实际传递的参数是指向 InlineCodeBlock
结构的指针,该结构包含有关 DO
语句参数的信息,特别是要执行的匿名代码块的文本。内联处理程序应该执行此代码并返回。
建议将所有这些函数声明以及 CREATE LANGUAGE
命令本身包装在一个扩展中,以便使用一个简单的 CREATE EXTENSION
命令即可安装该语言。有关编写扩展的信息,请参阅 第 36.17 节。
标准发行版中包含的过程语言是编写自己的语言处理程序的良好参考。查看源树的 src/pl
子目录。CREATE LANGUAGE 参考页面也包含一些有用的细节。
如果您在文档中发现任何不正确、与您对特定功能的体验不符或需要进一步澄清的内容,请使用 此表单 报告文档问题。