如何在PostgreSQL C扩展中调用可能返回NULL的SQL函数
在PostgreSQL C扩展中安全调用返回NULL的SQL函数
你可以使用PostgreSQL内部的FunctionCallInvoke函数实现需求——它和DirectFunctionCall系列用法类似,但不会在被调用函数返回NULL时抛出错误,而是通过标记位告知调用者自行处理NULL结果。
核心实现步骤与代码示例
以下是调用to_regprocedure获取函数OID,并在函数存在时调用它的完整示例:
#include "postgres.h" #include "funcapi.h" #include "catalog/pg_proc.h" #include "utils/syscache.h" #include "utils/builtins.h" PG_FUNCTION_INFO_V1(my_extension_func); Datum my_extension_func(PG_FUNCTION_ARGS) { // 用户传入的目标函数签名文本(比如"my_test_func(int)") text *target_func_sig = PG_GETARG_TEXT_P(0); Oid target_func_oid = InvalidOid; // 初始化函数调用信息结构体 FunctionCallInfoData fcinfo; InitFunctionCallInfoData(fcinfo, NULL, 1, InvalidOid, NULL, NULL); // 1. 获取to_regprocedure函数的OID(避免硬编码OID适配不同版本) Oid to_regproc_oid = LookupFuncName( list_make2(makeString("pg_catalog"), makeString("to_regprocedure")), 1, TEXTOID, false ); if (!OidIsValid(to_regproc_oid)) { PG_RETURN_NULL(); } // 2. 设置to_regprocedure的调用参数 fcinfo.arg[0] = PointerGetDatum(target_func_sig); fcinfo.argnull[0] = false; fcinfo.fncollation = DEFAULT_COLLATION_OID; // 3. 调用函数并处理结果 Datum regproc_result = FunctionCallInvoke(&fcinfo); if (fcinfo.isnull) { // to_regprocedure返回NULL,说明目标函数不存在 PG_RETURN_NULL(); } target_func_oid = DatumGetObjectId(regproc_result); // ------------------------------ // 若目标函数存在,调用该函数(同样用FunctionCallInvoke) // ------------------------------ if (OidIsValid(target_func_oid)) { FunctionCallInfoData user_func_fcinfo; // 这里假设目标函数无参数,实际需根据函数元数据调整参数数量 InitFunctionCallInfoData(user_func_fcinfo, NULL, 0, InvalidOid, NULL, NULL); // 获取目标函数的元数据 HeapTuple func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(target_func_oid)); if (!HeapTupleIsValid(func_tuple)) { PG_RETURN_NULL(); } Form_pg_proc func_form = (Form_pg_proc) GETSTRUCT(func_tuple); // 调用目标函数 Datum user_func_result = FunctionCallInvoke(&user_func_fcinfo); ReleaseSysCache(func_tuple); if (user_func_fcinfo.isnull) { // 目标函数返回NULL,按需求处理 PG_RETURN_NULL(); } // 返回目标函数的非NULL结果 PG_RETURN_DATUM(user_func_result); } PG_RETURN_NULL(); }
关键细节说明
FunctionCallInvoke的特性:它会完整保留被调用函数的返回行为,若函数返回NULL,仅会将fcinfo.isnull设为true,不会触发错误中断流程,完全由你控制后续逻辑。- 函数OID的可靠获取:用
LookupFuncName获取系统函数OID,避免硬编码OID导致的版本兼容性问题。 - 内存上下文注意:确保传入的参数(如文本类型的函数签名)分配在合适的内存上下文中,避免内存泄漏或提前释放。
- 用户函数调用适配:调用用户自定义函数时,需通过
PROCOID系统缓存获取函数元数据,确保参数数量、类型与函数定义匹配。
为什么不用SPI?
SPI适合执行复杂SQL语句,但对于简单的函数调用场景,FunctionCallInvoke更轻量——不需要开启SPI事务,性能开销更低,也更贴合C扩展的底层调用逻辑。
内容的提问来源于stack exchange,提问作者Serpent7776
相关产品推荐
相关产品推荐

