第 58 章编写过程语言处理器
所有对以当前“版本 1”以外的语言编写的函数的调用(包括用户定义过程语言中的函数和用 SQL 编写的函数)都会通过特定语言的调用处理器函数进行。调用处理器的职责是以有意义的方式执行函数,例如通过解释提供的源文本。本章概述了如何编写新的过程语言的调用处理器。
过程语言的调用处理器是一个“普通”函数,必须使用版本 1 接口用 C 等编译语言编写,并向PostgreSQL注册为不接受任何参数并返回language_handler
类型的函数。此特殊伪类型将函数标识为调用处理器,并防止在 SQL 命令中直接调用它。有关 C 语言调用约定和动态加载的更多详细信息,请参阅第 38.10 节。
调用处理器的调用方式与任何其他函数相同:它接收一个指向包含参数值和有关被调用函数的信息的FunctionCallInfoBaseData``struct
的指针,并且它预计返回一个Datum
结果(并且可能设置FunctionCallInfoBaseData
结构的isnull
字段,如果它希望返回一个 SQL null 结果)。调用处理器和普通被调用函数之间的区别在于,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
命令足以安装该语言。有关编写扩展的信息,请参阅第 38.17 节。
在尝试编写自己的语言处理程序时,标准发行版中包含的过程语言是良好的参考。查看源代码树的src/pl
子目录。CREATE LANGUAGE参考页也有一些有用的详细信息。