章 56.编写过程语言处理程序

用当前编译版本的“版本 1”接口以外的语言编写的函数的所有调用(包括用户定义的过程语言的函数和用 SQL 编写的函数)都通过特定语言的“调用处理程序”函数进行调用。调用处理程序有责任以有意义的方式执行功能,例如通过解释提供的源文本。本章概述了如何编写新的过程语言的调用处理程序。

程序语言的调用处理程序是“常规”函数,必须使用版本 1 接口以编译语言(例如 C)编写,并在 PostgreSQL 中注册为不带参数且返回类型language_handler。这种特殊的伪类型将函数标识为调用处理程序,并防止在 SQL 命令中直接调用该函数。有关 C 语言调用约定和动态加载的更多详细信息,请参见Section 38.10

调用处理程序的调用方式与其他任何函数相同:它接收指向FunctionCallInfoData struct的指针,该指针包含参数值和有关被调用函数的信息,并且期望返回Datum结果(并可能设置isnull字段)。 FunctionCallInfoData结构(如果希望返回 SQL 空结果)。调用处理程序和普通被调用函数之间的区别是FunctionCallInfoData结构的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相同。但是处理程序还可以选择使用寿命更长的内存上下文,以便它可以跨查询缓存功能定义信息。

当将过程语言函数作为触发器调用时,不会以通常的方式传递任何参数,但是FunctionCallInfoDatacontext字段指向TriggerData结构,而不是像普通函数调用中那样是NULL。语言处理程序应提供程序语言功能的机制,以获取触发信息。

这是用 C 编写的过程语言处理程序的模板:

#include "postgres.h"
#include "executor/spi.h"
#include "commands/trigger.h"
#include "fmgr.h"
#include "access/heapam.h"
#include "utils/syscache.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(plsample_call_handler);

Datum
plsample_call_handler(PG_FUNCTION_ARGS)
{
    Datum          retval;

    if (CALLED_AS_TRIGGER(fcinfo))
    {
        /*
         * Called as a trigger function
         */
        TriggerData    *trigdata = (TriggerData *) fcinfo->context;

        retval = ...
    }
    else
    {
        /*
         * Called as a function
         */

        retval = ...
    }

    return retval;
}

只需添加几千行代码即可代替点来完成调用处理程序。

将处理程序功能编译为可加载模块(请参见Section 38.10.5)后,以下命令将注册示例过程语言:

CREATE FUNCTION plsample_call_handler() RETURNS language_handler
    AS 'filename'
    LANGUAGE C;
CREATE LANGUAGE plsample
    HANDLER plsample_call_handler;

尽管提供呼叫处理程序足以创建最小的过程语言,但是可以选择提供两个其他功能,以使该语言更方便使用。这些是* validator inline handler *。可以提供验证器,以允许在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命令足以安装该语言。有关编写扩展程序的信息,请参见Section 38.16

尝试编写自己的语言处理程序时,标准发行版中包含的过程语言是很好的参考。查看源代码树的src/pl子目录。 CREATE LANGUAGE参考页还包含一些有用的详细信息。