Nested Subfunctions
1. Objective
-
Nested subfunctions refer to functions or procedures defined inside another function, stored procedure, or anonymous block; they are also called subprocs or inner functions.
-
Parent functions are the outer functions, stored procedures, or anonymous blocks that host nested subfunctions and are responsible for invoking them during execution.
2. Implementation Notes
2.1. Syntax Recognition for Nested Subfunctions
2.1.1. Detecting Nested Definitions
When a DECLARE block contains a function … is/as begin … end construct, pl_gram.y calls plisql_build_subproc_function() (similar to creating a regular function and updating the entry in pg_proc):
-
Create a
PLiSQL_subproc_functionentry in the parentPLiSQL_function’s `subprocfuncs[]array to store the name, arguments, return type, and other attributes, and record the indexfnoas the identifier of this subfunction. -
Call
plisql_check_subprocfunc_properties()to validate the combination of declaration and definition attributes.
2.2. Recompiling the Parent Program
-
The parent
PLiSQL_functiongains asubprocfuncsarray, each element being thePLiSQL_subproc_functioncreated earlier. -
Each
PLiSQL_subproc_functionhas aHTAB *poly_tabpointer that is initialized on the first compilation whenhas_poly_argumentistrue. The hash key isPLiSQL_func_hashkey, which records the subfunction’sfnoand input argument types; the value is the compiledPLiSQL_function *execution context.
2.3. Name Resolution During Invocation
-
PostgreSQL builds a
ParseStatestructure during compilation.plisql_subprocfunc_ref()locates the parentPLiSQL_functionthroughParseState→p_subprocfunc_hook()and callsplisql_ns_lookup()to gather allfnovalues for subfunctions sharing the same name, then selects the best match based on argument count and types. -
When
FuncExprnodes are created, the subfunction call is tagged for later execution:function_from = FUNC_FROM_SUBPROCFUNC,parent_funcpoints to the parentPLiSQL_function, andfuncid = fno. -
In
plisql_call_handler(), whenfunction_from == FUNC_FROM_SUBPROCFUNC, the runtime fetches the appropriatePLiSQL_subproc_functionvia the pair(parent_func, fno):-
For non-polymorphic subfunctions, reuse the precompiled action tree stored in
subprocfunc→function. -
For polymorphic subfunctions, probe
poly_tab; if there is no cached plan, callplisql_dynamic_compile_subproc()to compile one and store it in the cache.
-
-
Before execution,
plisql_init_subprocfunc_globalvar()forks relevant entries from the parent’s datum table so the subfunction can access the latest parent variables without polluting the parent scope. After execution,plisql_assign_out_subprocfunc_globalvar()writes back the necessary variables.
3. Module Design
3.1. PL/iSQL Grammar Extensions
-
pl_gram.yadds productions for subprocedure declarations and nested definitions, and records metadata such aslastoutvardnoand subprocedure descriptors. -
Nested subfunctions can reference variables from the parent scope, other subprocedures, and user-defined types.
Whenever a function … is/as begin … end construct is seen inside a DECLARE block, pl_gram.y invokes plisql_build_subproc_function():
-
Insert a
PLiSQL_subproc_functionentry into the parentPLiSQL_function→subprocfuncs[], storing the name, arguments, return type, and other attributes, and assign an indexfno. -
Call
plisql_check_subprocfunc_properties()to verify that declarations and definitions are consistent and to prevent duplicate or missing declarations from introducing semantic errors.
3.2. Datum Storage
The parent program’s datum tables hold the variables accessible to nested subfunctions during compilation and execution:
-
PLiSQL_function→datumspreserves the variable and record metadata visible during compilation. -
PLiSQL_execstate→datumscarries the live values at runtime.
3.3. Polymorphic Templates
When a subfunction contains polymorphic arguments, the parser will:
-
Copy the subfunction source text into
subprocfunc→src. -
Set
has_poly_argument = trueto prepare for dynamic recompilation based on actual argument types.
3.4. Parent Recompilation
-
The parent
PLiSQL_functionincludes asubprocfuncsarray, with each element corresponding to aPLiSQL_subproc_function. -
Each
PLiSQL_subproc_functionmaintains an optionalHTAB *poly_tab; whenhas_poly_argumentistrue, the cache is initialized on the first compile. Keys arePLiSQL_func_hashkey(subfunctionfnoplus argument types), and values are the compiledPLiSQL_functionplans.
3.5. Parser Hooks
During compilation, PostgreSQL creates a ParseState. plisql_subprocfunc_ref() plugs into ParseState→p_subprocfunc_hook, reusing the namespace lookup logic to gather candidates. plisql_get_subprocfunc_detail() then chooses the best match based on argument count, types, and named parameters, enabling overloaded dispatch.
3.6. FuncExpr Annotation
When constructing FuncExpr nodes, the compiler attaches metadata so the executor can recognize nested calls:
-
function_from = FUNC_FROM_SUBPROCFUNC. -
parent_funcreferences the owningPLiSQL_function. -
funcid = fno, enabling direct lookup of the subfunction definition.
3.7. Nested Subfunction Lookup
-
plisql_subprocfunc_ref()implementsParseState→p_subprocfunc_hookand reuses the namespace search to find nested subfunctions. -
plisql_get_subprocfunc_detail()applies matching rules for argument count, type, and naming to pick the optimal overload.
3.8. Execution Path
-
plisql_call_handler()checksfunction_from; if it is a nested subfunction, the handler locatesPLiSQL_subproc_functionvia(parent_func, fno). -
For regular subfunctions, reuse the cached plan stored in
subprocfunc→function. -
For polymorphic subfunctions, consult
poly_tab; on a miss, callplisql_dynamic_compile_subproc()to build and cache a specialized plan.
3.9. Variable Synchronization
-
plisql_init_subprocfunc_globalvar()copies the relevant entries from the parent datum table before the subfunction runs to expose the latest state. -
plisql_assign_out_subprocfunc_globalvar()writes back OUT/INOUT variables after execution to keep parent and child scopes consistent without mutual pollution.
3.10. Statement Dispatch in psql
-
psqlscan.ladjusts the push/pop logic ofproc_func_define_levelandbegin_depthso the nested subfunction body is transmitted to the SQL engine as a whole. -
Statements are sent only when the nesting depth returns to zero and a semicolon is reached, avoiding partial dispatch of subfunction blocks.
3.11. Retrieving Return Information on the SQL Side
-
Regular functions obtain metadata via
funcidfrompg_proc; nested subfunctions rely onFuncExpr.parent_func, which holds the parentPLiSQL_function. -
A set of callback pointers (registered through
plisql_register_internal_func()) allows the SQL layer to fetch nested subfunction names, return types, and OUT parameter information on demand.