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