|
| 1 | +:sectnums: |
| 2 | +:sectnumlevels: 5 |
| 3 | + |
| 4 | +:imagesdir: ./_images |
| 5 | + |
| 6 | += Nested Subfunctions |
| 7 | + |
| 8 | +== Objective |
| 9 | + |
| 10 | +- Nested subfunctions refer to functions or procedures defined inside another function, stored procedure, or anonymous block; they are also called subprocs or inner functions. |
| 11 | +- Parent functions are the outer functions, stored procedures, or anonymous blocks that host nested subfunctions and are responsible for invoking them during execution. |
| 12 | + |
| 13 | +== Implementation Notes |
| 14 | + |
| 15 | +=== 1. Syntax Recognition for Nested Subfunctions |
| 16 | + |
| 17 | +==== 1. Detecting Nested Definitions |
| 18 | + |
| 19 | +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`): |
| 20 | + |
| 21 | +. Create a `PLiSQL_subproc_function` entry in the parent `PLiSQL_function`'s `subprocfuncs[]` array to store the name, arguments, return type, and other attributes, and record the index `fno` as the identifier of this subfunction. |
| 22 | +. Call `plisql_check_subprocfunc_properties()` to validate the combination of declaration and definition attributes. |
| 23 | + |
| 24 | +==== 2. Storing Datum Entries |
| 25 | + |
| 26 | +Nested subfunctions share the parent's datum table. During compilation, `PLiSQL_function->datums` describes variables and record fields inside the subfunction, while `PLiSQL_execstate->datums` keeps the runtime values. |
| 27 | + |
| 28 | +==== 3. Preserving Polymorphic Templates |
| 29 | + |
| 30 | +If the subfunction uses polymorphic parameters, the parser stores its source code in `subprocfunc->src` and sets `has_poly_argument` to `true` so that the executor can recompile it for each distinct argument type. |
| 31 | + |
| 32 | +=== 2. Recompiling the Parent Program |
| 33 | + |
| 34 | +. The parent `PLiSQL_function` gains a `subprocfuncs` array, each element being the `PLiSQL_subproc_function` created earlier. |
| 35 | +. Each `PLiSQL_subproc_function` has a `HTAB *poly_tab` pointer that is initialized on the first compilation when `has_poly_argument` is `true`. The hash key is `PLiSQL_func_hashkey`, which records the subfunction's `fno` and input argument types; the value is the compiled `PLiSQL_function *` execution context. |
| 36 | + |
| 37 | +=== 3. Name Resolution During Invocation |
| 38 | + |
| 39 | +. PostgreSQL builds a `ParseState` structure during compilation. `plisql_subprocfunc_ref()` locates the parent `PLiSQL_function` through `ParseState->p_subprocfunc_hook()` and calls `plisql_ns_lookup()` to gather all `fno` values for subfunctions sharing the same name, then selects the best match based on argument count and types. |
| 40 | +. When `FuncExpr` nodes are created, the subfunction call is tagged for later execution: `function_from = FUNC_FROM_SUBPROCFUNC`, `parent_func` points to the parent `PLiSQL_function`, and `funcid = fno`. |
| 41 | +. In `plisql_call_handler()`, when `function_from == FUNC_FROM_SUBPROCFUNC`, the runtime fetches the appropriate `PLiSQL_subproc_function` via the pair `(parent_func, fno)`: |
| 42 | +.. For non-polymorphic subfunctions, reuse the precompiled action tree stored in `subprocfunc->function`. |
| 43 | +.. For polymorphic subfunctions, probe `poly_tab`; if there is no cached plan, call `plisql_dynamic_compile_subproc()` to compile one and store it in the cache. |
| 44 | +. 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. |
| 45 | + |
| 46 | +== Module Design |
| 47 | + |
| 48 | +=== PL/iSQL Grammar Extensions |
| 49 | + |
| 50 | +- `pl_gram.y` adds productions for subprocedure declarations and nested definitions, and records metadata such as `lastoutvardno` and subprocedure descriptors. |
| 51 | +- Nested subfunctions can reference variables from the parent scope, other subprocedures, and user-defined types. |
| 52 | + |
| 53 | +Whenever a `function ... is/as begin ... end` construct is seen inside a `DECLARE` block, `pl_gram.y` invokes `plisql_build_subproc_function()`: |
| 54 | + |
| 55 | +. Insert a `PLiSQL_subproc_function` entry into the parent `PLiSQL_function->subprocfuncs[]`, storing the name, arguments, return type, and other attributes, and assign an index `fno`. |
| 56 | +. Call `plisql_check_subprocfunc_properties()` to verify that declarations and definitions are consistent and to prevent duplicate or missing declarations from introducing semantic errors. |
| 57 | + |
| 58 | +=== Datum Storage |
| 59 | + |
| 60 | +The parent program's datum tables hold the variables accessible to nested subfunctions during compilation and execution: |
| 61 | + |
| 62 | +. `PLiSQL_function->datums` preserves the variable and record metadata visible during compilation. |
| 63 | +. `PLiSQL_execstate->datums` carries the live values at runtime. |
| 64 | + |
| 65 | +=== Polymorphic Templates |
| 66 | + |
| 67 | +When a subfunction contains polymorphic arguments, the parser will: |
| 68 | + |
| 69 | +. Copy the subfunction source text into `subprocfunc->src`. |
| 70 | +. Set `has_poly_argument = true` to prepare for dynamic recompilation based on actual argument types. |
| 71 | + |
| 72 | +=== Parent Recompilation |
| 73 | + |
| 74 | +- The parent `PLiSQL_function` includes a `subprocfuncs` array, with each element corresponding to a `PLiSQL_subproc_function`. |
| 75 | +- Each `PLiSQL_subproc_function` maintains an optional `HTAB *poly_tab`; when `has_poly_argument` is `true`, the cache is initialized on the first compile. Keys are `PLiSQL_func_hashkey` (subfunction `fno` plus argument types), and values are the compiled `PLiSQL_function` plans. |
| 76 | + |
| 77 | +=== Parser Hooks |
| 78 | + |
| 79 | +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. |
| 80 | + |
| 81 | +=== FuncExpr Annotation |
| 82 | + |
| 83 | +When constructing `FuncExpr` nodes, the compiler attaches metadata so the executor can recognize nested calls: |
| 84 | + |
| 85 | +- `function_from = FUNC_FROM_SUBPROCFUNC`. |
| 86 | +- `parent_func` references the owning `PLiSQL_function`. |
| 87 | +- `funcid = fno`, enabling direct lookup of the subfunction definition. |
| 88 | + |
| 89 | +=== Nested Subfunction Lookup |
| 90 | + |
| 91 | +- `plisql_subprocfunc_ref()` implements `ParseState->p_subprocfunc_hook` and reuses the namespace search to find nested subfunctions. |
| 92 | +- `plisql_get_subprocfunc_detail()` applies matching rules for argument count, type, and naming to pick the optimal overload. |
| 93 | + |
| 94 | +=== Execution Path |
| 95 | + |
| 96 | +. `plisql_call_handler()` checks `function_from`; if it is a nested subfunction, the handler locates `PLiSQL_subproc_function` via `(parent_func, fno)`. |
| 97 | +. For regular subfunctions, reuse the cached plan stored in `subprocfunc->function`. |
| 98 | +. For polymorphic subfunctions, consult `poly_tab`; on a miss, call `plisql_dynamic_compile_subproc()` to build and cache a specialized plan. |
| 99 | + |
| 100 | +=== Variable Synchronization |
| 101 | + |
| 102 | +- `plisql_init_subprocfunc_globalvar()` copies the relevant entries from the parent datum table before the subfunction runs to expose the latest state. |
| 103 | +- `plisql_assign_out_subprocfunc_globalvar()` writes back OUT/INOUT variables after execution to keep parent and child scopes consistent without mutual pollution. |
| 104 | + |
| 105 | +=== Statement Dispatch in psql |
| 106 | + |
| 107 | +- `psqlscan.l` adjusts the push/pop logic of `proc_func_define_level` and `begin_depth` so the nested subfunction body is transmitted to the SQL engine as a whole. |
| 108 | +- Statements are sent only when the nesting depth returns to zero and a semicolon is reached, avoiding partial dispatch of subfunction blocks. |
| 109 | + |
| 110 | +=== Retrieving Return Information on the SQL Side |
| 111 | + |
| 112 | +- Regular functions obtain metadata via `funcid` from `pg_proc`; nested subfunctions rely on `FuncExpr.parent_func`, which holds the parent `PLiSQL_function`. |
| 113 | +- 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. |
0 commit comments