C 中调用 PostgreSQL 内置动态加载函数的方法

普通函数

使用位于 fmgr.h 中的各类 DirectFunctionCall 函数

比如若想将一个时间间隔字符串转换为一个 PG 内置 interval 类型,PG 已经提供了一个 interval_in() 的动态加载函数,位于 timestamp.c,定义 snippet 如下:

Datum
interval_in(PG_FUNCTION_ARGS)
{
	char	   *str = PG_GETARG_CSTRING(0);

    #ifdef NOT_USED
	Oid			typelem = PG_GETARG_OID(1);
    #endif
	int32		typmod = PG_GETARG_INT32(2);
	Interval   *result;

可以看到它接收三个参数:输入的字符串,一个 oid,一个 typmod,但后两个参数却不解其意,这时可以在整个 PG 源码中搜索对 interval_in() 的调用,发现后两个参数似乎也可以不指定,那么这时就可以开始调用了。

因为 interval_in() 接收三个参数,所以使用 DirectFunctionCall3();

传入的参数全都都要使用 Datum 形式,需要调用各类准备好的宏将你的参数转换为 Datum;

Datum interval = DirectFunctionCall3(interval_in,
							         CStringGetDatum(1 year 3 months),
							         ObjectIdGetDatum(InvalidOid),
							         Int32GetDatum(-1));

调用完成后得到的 interval 也是 Datum 形式,可以转换为正常的 interval 类型,使用宏:

Interval *interval_p = DatumGetIntervalP(interval);

上面的例子其实是一个错误示例,因为根据官方邮件里的的说法:

You should be using InputFunctionCall to invoke any datatype inputfunction. There are plenty of examples to follow in the standard PLs.

所以对于输入函数,一般需要使用 InputFunctionCall 来调用,比如 array_in() 如果使用 DirectFunctionCall3() 的话,会遇到运行时错误,interval_in() 可能是歪打正着吧,不过其他的普通动态加载函数都可以如上的示例般使用

下面介绍我探索的 InputFunctionCall 调用方法,我觉得有些繁琐,若有更好的方法,欢迎指正。

数据类型输入函数

使用位于 fmgr.h 中的各类 OidInputFunctionCall 函数

想将一个 ‘{1,2}’ 形式的字符串,转换为 PG 内置的 int[] 类型,使用 array_in() 函数,定义如下:

Datum
array_in(PG_FUNCTION_ARGS)
{
	char	   *string = PG_GETARG_CSTRING(0);	/* external form */
	Oid			element_type = PG_GETARG_OID(1);		/* type of an array
														 * element */
	int32		typmod = PG_GETARG_INT32(2);	/* typmod for array elements */

需要传入的参数是:输入的字符串, array 中元素的类型 Oid 以及这个类型的 typmod。

对于元素的类型和 typmod 可以这样获得:

parseTypeString("integer", &typeid_p, &typmod_p, missing_ok);

integer 类型的 Oid 和 typmod 就在 typeid_p 和 typmod_p 中。

而 OidInputFunctionCall 函数的调用要求知道函数 Oid,

Datum
OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod);

所以通过 fmgr_internal_function 获取其 Oid:

Oid funcoid = fmgr_internal_function("array_in");

最后开始调用:

ArrayType *array = DatumGetArrayTypeP(OidInputFunctionCall(funcoid, "{1,2}",
					                         	           typeid_p,
								   			               typmod_p));