9.8. 过程语言句柄

调用所有不是用目前的 "版本 1" 的编译语言接口写的 函数的时候,(包括调用那些由用户定义的过程语言写的函数, 以及那些用 SQL 写的或者是用版本 0 编译语言接口写的函数), 这些调用都是通过调用句柄为指定的语言 金星调用的.将函数以一种有意义的方式执行(比如通过解释所提供的源文本) 是调用句柄的责任.本节描述如何书写一个语言调用句柄. 这个课题可不是个普通的活,实际上, 在 PostgreSQL 的历史上,这件事只干过 区区几次,但是这个话题本来就属于这一章,而且这些材料可能会给我们 一个关于 PostgreSQL 系统可扩展秉性的 比较深入的知识.

过程语言的调用句柄是一个"普通"函数, 它必须用编译语言书写,比如 C,并且在 PostgreSQL 里注册为不接受参数并且 返回 language_handler 类型。 这个特殊的伪类型把这个句柄标识为一个调用句柄,因而可以避免 它被直接在查询中调用。

注意: PostgreSQL 7.1 以及以后的版本中, 调用句柄必须使用"版本 1"函数管理器的接口,而不是 老式的接口.

这个调用句柄是用和调用其它函数相同的方法调用的∶ 它接受一个指向 FunctionCallInfoData struct的指针,该结构包含参数数值和有关被调用函数的信息, 并且它的预期返回是一个 Datum 结果(并且可能设置 FunctionCallInfoData 结构的 isnull 字段 --- 如果它希望返回 一个 SQL NULL 结果的话).调用句柄和普通被调函数的区别是 FunctionCallInfoData 结构的 flinfo->fn_oid 字段将包含实际上被 调用的函数的 OID,而不是调用句柄本身.调用句柄必须用这个字段 判断要执行哪个函数.同样,传入的参数列表是根据目标函数的声明 设置的,而不是根据调用句柄设置.

调用句柄有责任抓取 pg_proc 里的记录并且 分析被调函数的参数和返回它的类型.来自创建该过程的 CREATE FUNCTION 里的 AS 子句是在 pg_proc 表对应记录的 prosrc 字段.该字段可能是过程语言自身的源程序 (比如 PL/Tcl),一个指向某文件的路径名,或者任何其它告诉调用句柄 处理细节的信息.

通常,每条 SQL 语句要调用同一个函数许多次.一个调用句柄可以 通过使用 flinfo->fn_extra 字段 来避免重复查找有关被调用函数的信息.该字段初始值是 NULL,但是 可以由调用句柄设置为指向与该 PL 函数相关的信息.在随后的调用过程中, 如果 flinfo->fn_extra 已经是非空了, 那么就可以直接使用它并且忽略信息查找过程. 调用句柄必须注意的是 flinfo->fn_extra 指向的内存的生存期将至少延伸到当前查询的结尾,因为一个 FmgrInfo 数据结构可以保留那么长时间. 实现这个目地的一个方法是在 flinfo->fn_mcxt 声明的内存环境中分配额外的数据;这样的数据通常拥有和 FmgrInfo 本身一样的生命周期. 但是该句柄也可以选用更长的生命周期环境,这样它就可以跨查询地 缓存函数定义信息.

当一个 PL 函数被当做一个触发器调用的时候,不会给它传递 明确的参数,但是 FunctionCallInfoDatacontext 字段指向一个 TriggerData 节点,而不是象在普通函数 调用中那样的 NULL. 一个语言句柄应该为 PL 函数提供获取触发器 信息的机制.

这里是一个用 C 写的 PL 句柄的模板∶

#include "postgres.h"
#include "executor/spi.h"
#include "commands/trigger.h"
#include "utils/elog.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 9.5 获取如何把它编译成可装载模块的 信息.

下面的命令接着装载这个简单的过程语言∶

CREATE FUNCTION plsample_call_handler () RETURNS language_handler
    AS '/usr/local/pgsql/lib/plsample'
    LANGUAGE C;
CREATE LANGUAGE plsample
    HANDLER plsample_call_handler;