Chapter 45. 书写一个过程语言句柄

调用函数的时候,如果函数的书写语言不是目前的"版本 1"的编译语言接口(这包括用户定义的过程语言写的函数, 用 SQL 写的函数,以及用版本 0 的编译语言接口写的函数),都会通过一个 调用句柄处理具体的语言。 调用句柄有责任用一种有意义的方法执行这个函数,比如说解析所提供的文本等等。本章简介如何书写一个新的过程语言调用句柄。

过程语言的调用句柄是一个"普通"的函数, 必须使用一种编译语言来写,比如 C,使用版本-1的接口,并且在 PostgreSQL 里注册成接受零个参数并且返回类型 language_handler。 这个特殊的伪类型标识该函数为一个调用句柄并且避免它直接在 SQL 命令中被调用。

调用句柄的调用方式和其它函数一样:它接受一个指向一个 FunctionCallInfoData struct 的指针,这个指针包含参数值和有关被调用的函数的信息, 并且预期它返回一个 Datum 结果(如果它希望返回一个 SQL 的空结果, 那么可能设置 isnull 字段)。 调用句柄和普通的被调函数的区别是 FunctionCallInfoData 结构的 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 自己有一样的生命期。 但是句柄也可以同样选择使用一个更长生存期的环境,这样它就可以跨查询缓存函数定义。

在过程语言函数以触发器的形式调用的时候,就没有什么参数以通常的方式传递进来, 而是 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_FUNCTION_INFO_V1(plsample_call_handler);

Datum
plsample_call_handler(PG_FUNCTION_ARGS)
{
    Datum          retval;

    if (CALLED_AS_TRIGGER(fcinfo))
    {
        /*
         * 以触发器过程调用
         */
        TriggerData    *trigdata = (TriggerData *) fcinfo->context;

        retval = ...
    }
    else
    {
        /*
         * 以函数调用
         */

        retval = ...
    }

    return retval;
}

在打点的地方放上几千行代码就可以完成调用句柄。

在把句柄函数编译成一个可装载的模块(参阅 Section 31.9.6)之后, 下面的命令就可以注册这个例子过程语言:

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

如果想书写自己的调用句柄,那么包含在标准发布里面的过程语言是很好的例子。 参考一下源代码树中的 src/pl 子目录。