嵌套子函数
1. 目的
-
嵌套子函数:定义在函数、存储过程或匿名块内部的函数或存储过程,也称为 subproc 或 inner 函数。
-
父函数:承载嵌套函数的外层函数、存储过程或匿名块,执行过程中负责实际触发子函数调用。
2. 实现说明
2.1. 嵌套子函数语法识别
2.1.1. 识别嵌套写法
当 DECLARE 块里出现 function … is/as begin … end 结构时,pl_gram.y 会调用 plisql_build_subproc_function()(对应创建普通函数,这一阶段相当于在 pg_proc 中更新 catalog 中的注册信息):
-
在
PLiSQL_function的subprocfuncs[]数组中创建PLiSQL_subproc_function结构,记录名称、参数、返回类型、属性等,获得一个下标fno作为该子函数的标识。 -
调用
plisql_check_subprocfunc_properties()校验声明与定义的合法组合。
2.2. 父函数重新编译
-
父函数的
PLiSQL_function结构多了一个subprocfuncs数组,里面每个元素就是刚才创建的PLiSQL_subproc_function。 -
子函数结构体
PLiSQL_subproc_function有一个哈希表指针HTAB *poly_tab,默认为空。当子函数里使用了多态函数时,has_poly_argument为true,则会在初次编译时初始化poly_tab。poly_tab的 key 是PLiSQL_func_hashkey,记录着子函数的fno、输入参数类型等; value 是编译好的PLiSQL_function *(plisql函数的执行上下文)。
2.3. 调用时解析
-
编译过程中,
pg解析器会生成一个ParseState结构,plisql_subprocfunc_ref()会通过ParseState→p_subprocfunc_hook()找到父函数的PLiSQL_function,调用plisql_ns_lookup()找到所有同名子函数的fno,根据参数个数、类型找到最合适的多态函数。 -
FuncExpr结构构造时会对子函数进行标记,方便后期执行阶段识别:function_from = FUNC_FROM_SUBPROCFUNC,parent_func指向父级PLiSQL_function,funcid = fno。 -
plisql_call_handler()当function_from == FUNC_FROM_SUBPROCFUNC,会用parent_func + fno找到对应的PLiSQL_subproc_function:-
如果不是多态:直接复用
subprocfunc→function里的动作树。 -
如果是多态:先在
poly_tab查有没有编译结果;没有就调用plisql_dynamic_compile_subproc()编译,放进poly_tab缓存。
-
-
子函数开始执行之前,
plisql_init_subprocfunc_globalvar()会把父函数的datum表中有关的变量 fork 一份,这样子函数可以获取到父函数的变量值,也不会污染父函数的变量空间;执行后由plisql_assign_out_subprocfunc_globalvar()把需要回写的变量更新到父函数的datum表。
3. 模块设计
3.1. PL/iSQL 语法扩展
-
pl_gram.y新增子过程声明、嵌套定义的产生式,并在创建过程中记录lastoutvardno、子过程信息等元数据。 -
支持在子函数内引用父过程变量、子过程以及自定义类型。
当 DECLARE 块内出现 function … is/as begin … end 结构时,pl_gram.y 会调用 plisql_build_subproc_function() 进行编译:
-
在
PLiSQL_function的subprocfuncs[]中创建PLiSQL_subproc_function结构,记录名称、参数、返回类型和属性,分配下标fno作为子函数标识。 -
调用
plisql_check_subprocfunc_properties()校验声明与定义属性组合是否合法,防止重复或缺失声明造成的语义错误。
3.2. 数据项保存
父函数的 Datum 表在编译期和执行期分别缓存子函数能访问的变量:
-
PLiSQL_function→datums保存子函数编译阶段可见的变量与记录字段信息。 -
PLiSQL_execstate→datums在执行阶段持有实时的变量数值,实现运行期访问。
3.3. 多态函数模板
若子函数包含多态参数,语法阶段会:
-
将子函数源文本拷贝到
subprocfunc→src。 -
设置
has_poly_argument = true,为后续按实参类型动态编译做好准备。
3.4. 父函数重新编译
-
父函数的
PLiSQL_function结构新增subprocfuncs数组,每个元素对应一个PLiSQL_subproc_function。 -
PLiSQL_subproc_function持有HTAB *poly_tab指针;当has_poly_argument为true时,在首次编译时初始化该缓存,键为PLiSQL_func_hashkey(子函数fno+ 实参类型),值为编译后的PLiSQL_function。
3.5. 解析器钩子
编译期间 PostgreSQL 解析器会构造 ParseState,plisql_subprocfunc_ref() 通过 ParseState→p_subprocfunc_hook() 连接父函数,调用 plisql_ns_lookup() 找到同名子函数的全部 fno,并依据参数个数与类型挑选最佳候选,实现重载分发。
3.6. FuncExpr 标记
构造 FuncExpr 时会标记嵌套调用信息,便于执行阶段识别:
-
function_from = FUNC_FROM_SUBPROCFUNC。 -
parent_func指向父级PLiSQL_function。 -
funcid = fno,用于快速定位子函数定义。
3.7. 嵌套函数查找机制
-
plisql_subprocfunc_ref()作为ParseState→p_subprocfunc_hook实现入口,复用名称空间查询逻辑。 -
plisql_get_subprocfunc_detail()依据参数数量、类型与命名匹配规则挑选最优候选,是嵌套函数重载的关键。
3.8. 执行路径
-
plisql_call_handler()判断function_from后,通过parent_func + fno找到目标PLiSQL_subproc_function。 -
对普通子函数,直接复用
subprocfunc→function缓存; -
对多态子函数,先查询
poly_tab,未命中时调用plisql_dynamic_compile_subproc()动态编译并写入缓存。
3.9. 变量同步
-
plisql_init_subprocfunc_globalvar()在子函数执行前拷贝父函数 Datum 表中的相关变量,保证子函数读取到外层最新状态。 -
plisql_assign_out_subprocfunc_globalvar()在返回前回写 OUT/INOUT 变量,确保父子函数数据一致性且互不污染。