可以设置下列变量:
MODULES
- 一个需要从同词根(同名,不同后缀)的源代码上制作的共享对象的列表(不要在这个列表里包含后缀)
DATA
- 安装到prefix/share/contrib的随机文件
DATA_built
- 需要先制作的,安装到prefix/share/contrib里面的随机文件。
DOCS
- 安装到prefix/doc/contrib里面的随机文件
SCRIPTS
- 安装到prefix/bin里面的脚本文件(非二进制)
SCRIPTS_built
- 安装到prefix/bin里面的,需要先制作的脚本文件(非二进制)。
REGRESS
- 回归测试案例的列表(没有后缀)
或者最多声明下面两个之一:
PROGRAM
- 一个需要制作的二进制文件(在OBJS里面列出目标文件)
MODULE_big
- 一个需要制作的共享对象(在OBJS里列出目标文件)
还可以设置下列变量:
EXTRA_CLEAN
- 在make clean里删除的额外的文件
PG_CPPFLAGS
- 将增加到CPPFLAGS
PG_LIBS
- 将增加到PROGRAM链接行里
SHLIB_LINK
- 将增加到MODULE_big连接行里
PG_CONFIG
- 需要安装的包指向的 PostgreSQL 安装的 pg_config 程序的路径(通常是在你的 PATH 中的第一个 pg_config)
把这个 makefile 以Makefile的名字放在保存你的扩展的目录里。然后你就可以运行make来编译,然后用make install安装你的模块。这个扩展是为你的路径里找到的第一个 pg_config 命令对应的 PostgreSQL 安装编译和安装的。你可以通过在 makefile 里或者在 make 命令行上将PG_CONFIG设置成指向对应的 pg_config 程序来为不同的 PostgreSQL 版本安装。
- 警告: 修改 PG_CONFIG 的方法只能在 PostgreSQL 8.3 或者更高版本上使用。老版本除了可以设置为 pg_config 之外,啥用也没有;你必须更改你的路径,选择你需要的那个安装版本。
在 REGRESS 变量中列出用于对你的模块进行回归测试的脚本,就像 PostgreSQL 服务器的 make installcheck 一样。要想这部分能运转,你必须在你的扩展目录里有一个 sql/ 子目录,在其中你要为每个需要运行的测试组写一个文件,文件的扩展名应该是 .sql,扩展名不应该包含在 makefile 的 REGRESS 列表中。每个测试都应该在一个叫 expected/ 的子目录中包含一个结果文件,其扩展名是 .out。测试是通过运行 make installcheck 执行的,结果会和与其文件进行比较。区别将会以 diff -c 格式写入到 regression.diffs里。请主义,如果试图在缺乏预期文件的情况下运行测试,那么就会报告为“trouble”(问题),所以你要确保自己已经书写了所有预期文件。
- 提示: 创建预期文件的最简单方法就是创建一个空文件,然后在执行完测试运行之后,仔细检查结果文件(可以在 result/ 目录找到),然后,如果它们符合你的预期,把它们拷贝到 expected/。
复合类型的 C 语言函数
复合类型不象 C 结构那样有固定的布局。 复合类型的实例可能包含空(null)域。 另外,一个属于继承层次一部分的复合类 型可能和同一继承范畴的其他成员有不同的域/字段。 因此,PostgreSQL 提供一个过程接口用于从 C 里面访问复合类型。
假设我们为下面查询写一个函数
SELECT name, c_overpaid(emp, 1500) AS overpaid FROM emp WHERE name = 'Bill' OR name = 'Sam';
在上面的查询里,用版本 0 的调用接口,我们可以这样定义c_overpaid:
#include "postgres.h"
#include "executor/executor.h" /* 用 GetAttributeByName() */
bool
c_overpaid(HeapTupleHeader t, /* emp 的当前行*/
int32 limit)
{
bool isnull;
int32 salary;
salary = DatumGetInt32(GetAttributeByName(t, "salary", &isnull));
if (isnull)
return (false);
return salary > limit;
}
在版本-1编码,上面的东西会写成下面这样:
#include "postgres.h"
#include "executor/executor.h" /* 用 GetAttributeByName() */
PG_FUNCTION_INFO_V1(c_overpaid);
Datum
c_overpaid(PG_FUNCTION_ARGS)
{
HeapTupleHeader t = PG_GETARG_HEAPTUPLEHEADER(0);
int32 limit = PG_GETARG_INT32(1);
bool isnull;
Datum salary;
salary = GetAttributeByName(t, "salary", &isnull);
if (isnull)
PG_RETURN_BOOL(false);
/* 另外,我们可能更希望将 PG_RETURN_NULL() 用在空薪水上 */
PG_RETURN_BOOL(DatumGetInt32(salary) > limit);
}
GetAttributeByName 是 PostgreSQL 系统函数, 用来返回当前记录的字段。它有三个参数:类型为 HeapTupleHeader 的传入函数的参数,你想要的字段名称, 以及一个用以确定字段是否为空(null)的返回参数。 GetAttributeByName 函数返回一个Datum值, 你可以用对应的 DatumGetXXX() 宏把它转换成合适的数据类型。 请注意,如果设置了空标志,那么返回值是无意义的, 在准备对结果做任何处理之前,总是要先检查空标志。
还有一个 GetAttributeByNum,它用字段编号而不是字段名选取目标字段。
下面的命令在 SQL 里声明 c_overpaid 函数:
CREATE FUNCTION c_overpaid(emp, integer) RETURNS bool AS 'DIRECTORY/funcs', 'c_overpaid' LANGUAGE C STRICT;
请注意我们使用了 STRICT,这样我们就不需要检查输入参数是否有 NULL。
返回行(复合类型)
要从一个 C 语言函数里返回一个行或者一个复合类型的数值,我们可以使用一个特殊的 API, 它提供了许多宏和函数来消除大多数制作复合数据类型的复杂性。 要使用该 API,源代码必须包含:
#include "funcapi.h"
制作一个复合类型数据值(也就是一个"元组")有两种方法:你可以从一个Datum值的数组里制作,也可以从一个 C 字串数组里制作:我们可以把这个数组传递给该元组的字段类型的输入转换函数。不管是哪种方式,你首先都需要为元组结构获取或者制作一个TupleDesc描述符。在使用 Datum的时候,你把这个TupleDesc传递给BlessTupleDesc然后为每行调用heap_form_tuple。 在使用 C 字串的时候,你把TupleDesc传递给TupleDescGetAttInMetadata,然后为每行调用BuildTupleFromCStrings。如果是一个函数返回一个元组集合的场合,所有设置步骤都可以在第一次调用该函数的时候一次性完成。
有几个便利函数可以用于设置所需要的TupleDesc。 在大多数返回复合类型给调用者的函数里建议的做法是这样的:
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
把传递给调用函数自己的fcinfo传递给他。(这个当然要求你使用版本 1 的调用习惯。)resultTypeId 可以声明为 NULL 或者是接收函数的结果类型 OID 的局部变量的地址(指针)。resultTupleDesc 应该是一个局部的TupleDesc变量的地址(指针)。检查结果是否 TYPEFUNC_COMPOSITE;如是,resultTupleDesc就已经填充好需要的TupleDesc了。(如不是,你可以报告一个类似"返回记录的函数在一个不接受记录的环境中被调用"的错误。)
- 提示: get_call_result_type 可以把一个多形的函数结果解析为实际类型;因此它在返回多形的标量结果的函数里也很有用,不仅仅是返回复合类型的函数里。resultTypeId 输出主要是用于那些返回多形的标量类型的函数的。
- 注意: get_call_result_type 有一个同胞弟兄 get_expr_result_type,可以用于给一个用表达式树表示的函数调用解析输出。 它可以用于视图从函数本身外边判断结果类型的场合。还有一个 get_func_result_type,可以用在只能拿到函数的 OID 的场合。不过,这些函数不能处理那些声明为返回 record 的函数,并且 get_func_result_type 不能解析多形的类型,因此你最好还是使用 get_call_result_type。
旧的,现在已经废弃的获取 TupleDesc 的函数是
TupleDesc RelationNameGetTupleDesc(const char *relname)
它可以从一个命名的关系里为行类型获取一个 TupleDesc,还有是
TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)
可以基于类型 OID 获取一个 TupleDesc。它可以用于给一个基本类型或者一个复合类型获取 TupleDesc。不过它不能处理返回 record 的函数,并且不能解析多形的类型。
一旦你有了一个 TupleDesc,如果你想使用 Datum,那么调用
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
如果你想用 C 字串,那么调用
AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)
如果你在写一个返回集合的函数,那么你可以吧这些函数的结果保存在 FuncCallContext 结构里 — 分别使用 tuple_desc 或者 attinmeta 字段。
在使用 Datum 的时候,使用
HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, char *nulls)
制作一个 HeapTuple,它把数据以 Datum 的形式交给用户。
在使用 C 字串的时候,用
HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
制作一个 HeapTuple,以 C 字串的形式给出用户数据。"values" 是一个 C 字串的数组,返回行的每个字段对应其中一个。每个 C 字串都应该是字段数据类型的输入函数预期的形式。为了从其中一个字段中返回一个空值,values 数组中对应的指针应该设置为 NULL。这个函数将会需要为你返回的每个元组调用一次。
一旦你制作了一个从你的函数中返回的元组,那么该元组必须转换成一个 Datum。使用
HeapTupleGetDatum(HeapTuple tuple)
把一个 HeapTuple 转换为一个有效的 Datum。如果你想只返回一行,那么这个 Datum 可以用于直接返回,或者是它可以用作在一个返回集合的函数里的当前返回值。
例子在下面给出。
从 C 语言函数里返回集合
还有一个特殊的 API 用于提供从 C 语言函数中返回集合(多行)的支持。一个返回集合的函数必须遵循版本-1的调用方式。同样,源代码必须包含 funcapi.h,就像上面说的那样。
一个返回集合的函数(SRF)通常为它返回的每个项都调用一次。因此 SRF 必须保存足够的状态用于记住它正在做的事情以及在每次调用的时候返回下一个项。PostgreSQL 提供了 FuncCallContext 结构用于帮助控制这个过程。fcinfo->flinfo->fn_extra 用于保存一个跨越多次调用的指向 FuncCallContext 的指针。
typedef struct
{
/*
* 我们前面已经被调用的次数
*
* 初始的时候,call_cntr 被 SRF_FIRSTCALL_INIT() 置为里 0,并且
* 每次你调用 SRF_RETURN_NEXT() 的时候都递增
*/
uint32 call_cntr;
/*
* 可选的最大调用数量
*
* 这里的 max_calls 只是为了方便,设置它也是可选的
* 如果没有设置,你必须提供可选的方法来知道函数何时结束
*
*/
uint32 max_calls;
/*
* 指向结果槽位的可选指针
*
* 这个数据类型已经过时,只用于向下兼容。也就是那些使用
* 废弃的 TupleDescGetSlot() 的用户定义 SRF
*/
TupleTableSlot *slot;
/*
* 可选的指向用户提供的杂项环境信息的指针
*
* user_fctx 用做一个指向你自己的结构的指针,包含任意提供给你的函数的调用间的环境信息
*
*/
void *user_fctx;
/*
* 可选的指向包含属性类型输入元信息的结构数组的指针
*
*
* attinmeta 用于在返回元组的时候(也就是说返回复合数据类型)
* 在只返回基本(也就是标量)数据类型的时候并不需要。
* 只有在你准备用 BuildTupleFromCStrings() 创建返回元组的时候才需要它
*
*/
AttInMetadata *attinmeta;
/*
* 用于必须在多次调用间存活的结构的内存环境
*
* multi_call_memory_ctx 是由 SRF_FIRSTCALL_INIT() 为你设置的,并且由
* SRF_RETURN_DONE() 用于清理。它是用于存放任何需要跨越多次调用 SRF 之间重复使用的内存
*
*
*/
MemoryContext multi_call_memory_ctx;
/*
* 可选的指针,指向包含元组描述的结构
*
* tuple_desc 用于返回元组(也就是说复合数据类型)
* 并且只是在你想使用 heap_form_tuple() 而不是 BuildTupleFromCStrings() 制作元组的
* 时候需要。请注意这里存储的 TupleDesc 指针通常应该先用heap_form_tuple()
* BlessTupleDesc() 处理。
*/
TupleDesc tuple_desc;
} FuncCallContext;
一个 SRF 使用好几个函数和宏来把操作 FuncCallContext 结构的事情自动化(我们可以通过 fn_extra 找到它)。用
SRF_IS_FIRSTCALL()
来判断你的函数是第一次调用还是后继的调用。(只有)在第一次调用的时候,用
SRF_FIRSTCALL_INIT()
初始化 FuncCallContext。在每次函数调用时(包括第一次),使用
SRF_PERCALL_SETUP()
为使用 FuncCallContext 做恰当的设置以及清理任何前面的回合里面剩下的已返回的数据。
如果你的函数有数据要返回,使用
SRF_RETURN_NEXT(funcctx, result)
返回给调用者。(result 必须是个 Datum,要么是单个值,要么是象前面介绍的那样准备的元组。)最后,如果你的函数结束了数据返回,使用
SRF_RETURN_DONE(funcctx)
清理并结束SRF。
在 SRF 被调用的时候的内存环境是一个临时的环境,在调用之间将会被清理掉。这意味着你不需要 pfree 所有你 palloc 的东西;它会自动消失的。不过,如果你想分配任何跨越调用存在的数据结构,那你就需要把它们放在其它什么地方。被 multi_call_memory_ctx 引用的环境适合用于保存那些需要直到 SRF 结束前都存活的数据。在大多数情况下,这意味着你在做第一次调用的设置的时候应该切换到 multi_call_memory_ctx。
一个完整的伪代码例子看起来像下面这样:
Datum
my_Set_Returning_Function(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
Datum result;
MemoryContext oldcontext;
''还有更多的声明''
if (SRF_IS_FIRSTCALL())
{
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* 这里放出现一次的设置代码:*/
用户定义代码
if 返回复合
制作 TupleDesc,以及可能还有 AttInMetadata
endif 返回复合
用户定义代码
MemoryContextSwitchTo(oldcontext);
}
/* 每次都执行的设置代码在这里出现:*/
用户定义代码
funcctx = SRF_PERCALL_SETUP();
用户定义代码
/* 这里只是用来测试我们是否完成的一个方法:*/
if (funcctx->call_cntr < funcctx->max_calls)
{
/* 这里我们想返回另外一个条目:*/
用户代码
获取结果 Datum
SRF_RETURN_NEXT(funcctx, result);
}
else
{
/* 这里我们完成返回条目的工作了,只需要清理就OK了:*/
用户代码
SRF_RETURN_DONE(funcctx);
}
}
一个返回复合类型的完整 SRF 例子看起来象这样:
PG_FUNCTION_INFO_V1(retcomposite);
Datum
retcomposite(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
int call_cntr;
int max_calls;
TupleDesc tupdesc;
AttInMetadata *attinmeta;
/* 只是在第一次调用函数的时候干的事情 */
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
/* 创建一个函数环境,用于在调用间保持住 */
funcctx = SRF_FIRSTCALL_INIT();
/* 切换到适合多次函数调用的内存环境 */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* 要返回的元组总数 */
funcctx->max_calls = PG_GETARG_UINT32(0);
/*
* 为我们的结果类型制作一个元组描述
*/
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
"that cannot accept type record")));
/*
* 生成稍后从裸 C 字串生成元组的属性元数据
*
*/
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta;
MemoryContextSwitchTo(oldcontext);
}
/* 每次函数调用都要做的事情 */
funcctx = SRF_PERCALL_SETUP();
call_cntr = funcctx->call_cntr;
max_calls = funcctx->max_calls;
attinmeta = funcctx->attinmeta;
if (call_cntr < max_calls) /* 在还有需要发送的东西时继续处理 */
{
char **values;
HeapTuple tuple;
Datum result;
/*
* 准备一个数值数组用于版本的返回元组。
* 它应该是一个 C 字串数组,稍后可以被合适的类型输入函数处理。
*
*/
values = (char **) palloc(3 * sizeof(char *));
values[0] = (char *) palloc(16 * sizeof(char));
values[1] = (char *) palloc(16 * sizeof(char));
values[2] = (char *) palloc(16 * sizeof(char));
snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1));
snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1));
snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1));
/* 制作一个元组 */
tuple = BuildTupleFromCStrings(attinmeta, values);
/* 把元组做成 datum */
result = HeapTupleGetDatum(tuple);
/* 清理(这些实际上并非必要) */
pfree(values[0]);
pfree(values[1]);
pfree(values[2]);
pfree(values);
SRF_RETURN_NEXT(funcctx, result);
}
else /* 在没有数据残留的时候干的事情 */
{
SRF_RETURN_DONE(funcctx);
}
}
