12.7. 过程语言处理器 #
对以编译语言当前“版本 1”接口以外的语言编写的函数的所有调用(包括用户自定义过程语言中的函数、用 SQL 编写的函数以及使用版本 0 编译语言接口的函数),都要经过该特定语言的调用处理器函数。调用处理器的职责是以有意义的方式执行函数,例如解释所提供的源文本。本节描述如何编写语言调用处理器。这并不是一项常见任务——事实上,在 PostgreSQL 的历史上它只被完成过寥寥几次——但这一主题自然属于本章,而且这些材料也许能让人对 PostgreSQL 系统的可扩展本质有所认识。
过程语言的调用处理器是一个“普通”函数,它必须用一种编译语言(如
C)编写,并向
PostgreSQL
注册为不带参数并返回
opaque
类型(未指定或未定义类型的占位符)。这防止调用处理器在查询中作为函数被直接调用。(不过,当要执行该处理器所提供语言中的函数时,在实际调用处理器时可以提供参数。)
注意
在 PostgreSQL 7.1 及之后的版本中,调用处理器必须遵循“版本 1”函数管理器接口,而不是旧风格接口。
调用处理器的调用方式与其他函数相同:它收到一个指向
FunctionCallInfoData
结构(包含参数值和所调用函数的信息)的指针,并应返回一个
Datum
结果(如果它想返回
SQL
NULL
结果,还可以设置
FunctionCallInfoData
结构的
isnull
字段)。调用处理器与普通被调函数的区别在于,FunctionCallInfoData
结构的
flinfo->fn_oid
字段将包含要调用的实际函数的
OID,而不是调用处理器自身的
OID。调用处理器必须使用此字段来确定要执行哪个函数。此外,传递的参数列表是按照目标函数(而不是调用处理器)的声明建立的。
获取
pg_proc
条目并分析被调用过程的参数和返回类型是调用处理器的职责。过程
CREATE FUNCTION
的
AS
子句可在
pg_proc
表条目的
prosrc
属性中找到。它可能是过程语言本身的源文本(如
PL/Tcl),也可能是指向文件的路径名,或者是任何能详细告诉调用处理器该做什么的东西。
同一函数在一个
SQL
语句中往往被调用多次。调用处理器可以利用
flinfo->fn_extra
字段避免对被调用函数信息的重复查找。该字段最初为
NULL,但调用处理器可以把它设置为指向有关
PL
函数的信息。在后续调用中,如果
flinfo->fn_extra
已非
NULL,就可以直接使用它并跳过信息查找步骤。调用处理器必须小心让
flinfo->fn_extra
指向至少能存活到当前查询结束的内存,因为
FmgrInfo
数据结构可能被保存那么久。一种做法是在
flinfo->fn_mcxt
指定的内存上下文中分配这些额外数据;这样的数据通常与
FmgrInfo
本身具有相同的生存期。但处理器也可以选择使用生存期更长的上下文,以便跨查询缓存函数定义信息。
当
PL
函数作为触发器被调用时,不传递显式参数,但
FunctionCallInfoData
的
context
字段指向一个
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))
{
/*
* Called as a trigger procedure
*/
TriggerData *trigdata = (TriggerData *) fcinfo->context;
retval = ...
}
else {
/*
* Called as a function
*/
retval = ...
}
return retval;
}
只需再加上几千行代码代替这些省略号即可完成调用处理器。关于如何把它编译成可装载模块,参见第 12.5 节。
下面的命令随后注册这个示例过程语言:
CREATE FUNCTION plsample_call_handler () RETURNS opaque
AS '/usr/local/pgsql/lib/plsample'
LANGUAGE C;
CREATE LANGUAGE plsample
HANDLER plsample_call_handler;