Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
**** xref:master/6.3.4.adoc[%TYPE、%ROWTYPE]
**** xref:master/6.3.5.adoc[NLS 参数]
**** xref:master/6.3.6.adoc[函数与存储过程]
**** xref:master/6.3.7.adoc[嵌套子函数]
*** xref:master/6.4.adoc[国标GB18030]
** Oracle兼容功能列表
*** xref:master/7.1.adoc[1、框架设计]
Expand All @@ -46,6 +47,7 @@
*** xref:master/7.15.adoc[15、OUT 参数]
*** xref:master/7.16.adoc[16、%TYPE、%ROWTYPE]
*** xref:master/7.17.adoc[17、NLS 参数]
*** xref:master/7.19.adoc[19、嵌套子函数]
** IvorySQL贡献指南
*** xref:master/8.1.adoc[社区贡献指南]
*** xref:master/8.2.adoc[asciidoc语法快速参考]
Expand Down
113 changes: 113 additions & 0 deletions CN/modules/ROOT/pages/master/6.3.7.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
:sectnums:
:sectnumlevels: 5

:imagesdir: ./_images

= 嵌套子函数

== 目的

- 嵌套子函数:定义在函数、存储过程或匿名块内部的函数或存储过程,也称为 subproc 或 inner 函数。
- 父函数:承载嵌套函数的外层函数、存储过程或匿名块,执行过程中负责实际触发子函数调用。

== 实现说明

=== 嵌套子函数语法识别

==== 识别嵌套写法

当 `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()` 校验声明与定义的合法组合。

==== 数据项保存

保存到父函数的 `datum` 表:编译期的 `PLiSQL_function->datums` 描述子函数里的变量、记录字段,`PLiSQL_execstate->datums` 保存着执行过程中的变量。

==== 保存多态函数模板

如果子函数里使用了多态参数,在语法阶段保存到 `subprocfunc->src`,同时将 `has_poly_argument` 设成 `true`,执行时按不同实参类型重新编译。

=== 父函数重新编译

. 父函数的 `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` 函数的执行上下文)。

=== 调用时解析

. 编译过程中,`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` 表。

== 模块设计

=== 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()` 校验声明与定义属性组合是否合法,防止重复或缺失声明造成的语义错误。

=== 数据项保存

父函数的 Datum 表在编译期和执行期分别缓存子函数能访问的变量:

. `PLiSQL_function->datums` 保存子函数编译阶段可见的变量与记录字段信息。
. `PLiSQL_execstate->datums` 在执行阶段持有实时的变量数值,实现运行期访问。

=== 多态函数模板

若子函数包含多态参数,语法阶段会:

. 将子函数源文本拷贝到 `subprocfunc->src`。
. 设置 `has_poly_argument = true`,为后续按实参类型动态编译做好准备。

=== 父函数重新编译

- 父函数的 `PLiSQL_function` 结构新增 `subprocfuncs` 数组,每个元素对应一个 `PLiSQL_subproc_function`。
- `PLiSQL_subproc_function` 持有 `HTAB *poly_tab` 指针;当 `has_poly_argument` 为 `true` 时,在首次编译时初始化该缓存,键为 `PLiSQL_func_hashkey`(子函数 `fno` + 实参类型),值为编译后的 `PLiSQL_function`。

=== 解析器钩子

编译期间 PostgreSQL 解析器会构造 `ParseState`,`plisql_subprocfunc_ref()` 通过 `ParseState->p_subprocfunc_hook()` 连接父函数,调用 `plisql_ns_lookup()` 找到同名子函数的全部 `fno`,并依据参数个数与类型挑选最佳候选,实现重载分发。

=== FuncExpr 标记

构造 `FuncExpr` 时会标记嵌套调用信息,便于执行阶段识别:

- `function_from = FUNC_FROM_SUBPROCFUNC`。
- `parent_func` 指向父级 `PLiSQL_function`。
- `funcid = fno`,用于快速定位子函数定义。

=== 嵌套函数查找机制

- `plisql_subprocfunc_ref()` 作为 `ParseState->p_subprocfunc_hook` 实现入口,复用名称空间查询逻辑。
- `plisql_get_subprocfunc_detail()` 依据参数数量、类型与命名匹配规则挑选最优候选,是嵌套函数重载的关键。

=== 执行路径

. `plisql_call_handler()` 判断 `function_from` 后,通过 `parent_func + fno` 找到目标 `PLiSQL_subproc_function`。
. 对普通子函数,直接复用 `subprocfunc->function` 缓存;
. 对多态子函数,先查询 `poly_tab`,未命中时调用 `plisql_dynamic_compile_subproc()` 动态编译并写入缓存。

=== 变量同步

- `plisql_init_subprocfunc_globalvar()` 在子函数执行前拷贝父函数 Datum 表中的相关变量,保证子函数读取到外层最新状态。
- `plisql_assign_out_subprocfunc_globalvar()` 在返回前回写 OUT/INOUT 变量,确保父子函数数据一致性且互不污染。

=== PSQL 端语句发送

- `psqlscan.l` 调整 `proc_func_define_level` 和 `begin_depth` 的入栈/出栈逻辑,确保嵌套函数体整体发送至 SQL 端。
- 只有当嵌套层级回到 0 且遇到分号时,才触发发送,避免子函数块被拆分。

=== SQL 层返回值获取

- 普通函数通过 `funcid` 访问 `pg_proc`;嵌套函数依赖 `FuncExpr.parent_func` 承载的 `PLiSQL_function`。
- 为此实现一组函数指针(`plisql_register_internal_func()` 注册)供 SQL 层回调,按需获取嵌套函数名称、返回类型与 OUT 参数信息。
83 changes: 83 additions & 0 deletions CN/modules/ROOT/pages/master/7.19.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
:sectnums:
:sectnumlevels: 5

:imagesdir: ./_images

= 兼容Oracle 嵌套子函数

== 目的

- 在 IvorySQL 中使用 Oracle 风格嵌套子函数。

== 功能说明

- 支持在匿名块、函数或过程内部声明与调用子函数/子过程,作用域限定在父块内。
- 子函数可读取及更新父级变量,也可定义自身局部变量;父级无法直接访问子函数内部状态。
- 支持重载解析机制,按参数个数、类型或命名区分同名子程序。

== 测试用例

[source,sql]
----
DO $$
DECLARE
v_result integer;
FUNCTION inner_square(p_value integer) RETURN integer IS
BEGIN
RAISE NOTICE 'inner_square called';
RETURN p_value * p_value;
END;
BEGIN
v_result := inner_square(10);
RAISE NOTICE 'result=%', v_result;
END;
$$ LANGUAGE plisql;
----

[source,sql]
----
DO $$
DECLARE
v_base_multiplier integer := 20;
v_audit_counter integer := 0;
v_result integer;
FUNCTION inner_square(p_value integer) RETURN integer IS
BEGIN
RAISE NOTICE 'inner_square called';
v_audit_counter := v_audit_counter + 1;
RETURN v_base_multiplier * p_value;
END;
BEGIN
v_result := inner_square(10);
RAISE NOTICE 'result=%', v_result;
RAISE NOTICE 'v_audit_counter=%', v_audit_counter;
END;
$$ LANGUAGE plisql;
----

[source,sql]
----
-- Polymorphic nested function specializing on argument type
DO $$
DECLARE
v_last_notice text := 'none';

FUNCTION describe_value(p_input anyelement) RETURN text IS
BEGIN
v_last_notice := format('polymorphic dispatch with %s', pg_typeof(p_input));
RETURN v_last_notice;
END;

FUNCTION describe_value(p_input anyarray, p_element anyelement) RETURN text IS
BEGIN
v_last_notice := format('array dispatch with %s', pg_typeof(p_input)::text);
RETURN v_last_notice;
END;
BEGIN
RAISE NOTICE '%', describe_value(100);
RAISE NOTICE '%', describe_value('IvorySQL'::text); -- explicit cast avoids ambiguous literal
RAISE NOTICE '%', describe_value(ARRAY[1,2,3], NULL::int); -- extra arg guides anyarray resolution
RAISE NOTICE 'last notice=%', v_last_notice;
END;
$$ LANGUAGE plisql;
----
4 changes: 3 additions & 1 deletion EN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
** xref:master/4.3.adoc[Developer]
** xref:master/4.4.adoc[Operation Management]
** xref:master/4.5.adoc[Migration]
* IvorySQL Ecosystem
* IvorySQL Ecosystem
** xref:master/5.1.adoc[PostGIS]
** xref:master/5.2.adoc[pgvector]
* IvorySQL Architecture Design
Expand All @@ -26,6 +26,7 @@
*** xref:master/6.3.4.adoc[%Type & %Rowtype]
*** xref:master/6.3.5.adoc[NLS Parameters]
*** xref:master/6.3.6.adoc[Function and stored procedure]
*** xref:master/6.3.7.adoc[Nested Subfunctions]
** xref:master/6.4.adoc[GB18030 Character Set]
* List of Oracle compatible features
** xref:master/7.1.adoc[1、Ivorysql frame design]
Expand All @@ -45,6 +46,7 @@
** xref:master/7.15.adoc[15、OUT Parameter]
** xref:master/7.16.adoc[16、%Type & %Rowtype]
** xref:master/7.17.adoc[17、NLS Parameters]
** xref:master/7.19.adoc[19、Nested Subfunctions]
* xref:master/8.adoc[Community contribution]
* xref:master/9.adoc[Tool Reference]
* xref:master/10.adoc[FAQ]
113 changes: 113 additions & 0 deletions EN/modules/ROOT/pages/master/6.3.7.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
:sectnums:
:sectnumlevels: 5

:imagesdir: ./_images

= Nested Subfunctions

== 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.

== Implementation Notes

=== Syntax Recognition for Nested Subfunctions

==== 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_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.
. Call `plisql_check_subprocfunc_properties()` to validate the combination of declaration and definition attributes.

==== Storing Datum Entries

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.

==== Preserving Polymorphic Templates

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.

=== Recompiling the Parent Program

. The parent `PLiSQL_function` gains a `subprocfuncs` array, each element being the `PLiSQL_subproc_function` created earlier.
. 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.

=== Name Resolution During Invocation

. 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.
. 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`.
. In `plisql_call_handler()`, when `function_from == FUNC_FROM_SUBPROCFUNC`, the runtime fetches the appropriate `PLiSQL_subproc_function` via 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, call `plisql_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.

== Module Design

=== PL/iSQL Grammar Extensions

- `pl_gram.y` adds productions for subprocedure declarations and nested definitions, and records metadata such as `lastoutvardno` and 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_function` entry into the parent `PLiSQL_function->subprocfuncs[]`, storing the name, arguments, return type, and other attributes, and assign an index `fno`.
. Call `plisql_check_subprocfunc_properties()` to verify that declarations and definitions are consistent and to prevent duplicate or missing declarations from introducing semantic errors.

=== Datum Storage

The parent program's datum tables hold the variables accessible to nested subfunctions during compilation and execution:

. `PLiSQL_function->datums` preserves the variable and record metadata visible during compilation.
. `PLiSQL_execstate->datums` carries the live values at runtime.

=== Polymorphic Templates

When a subfunction contains polymorphic arguments, the parser will:

. Copy the subfunction source text into `subprocfunc->src`.
. Set `has_poly_argument = true` to prepare for dynamic recompilation based on actual argument types.

=== Parent Recompilation

- The parent `PLiSQL_function` includes a `subprocfuncs` array, with each element corresponding to a `PLiSQL_subproc_function`.
- 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.

=== 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.

=== FuncExpr Annotation

When constructing `FuncExpr` nodes, the compiler attaches metadata so the executor can recognize nested calls:

- `function_from = FUNC_FROM_SUBPROCFUNC`.
- `parent_func` references the owning `PLiSQL_function`.
- `funcid = fno`, enabling direct lookup of the subfunction definition.

=== Nested Subfunction Lookup

- `plisql_subprocfunc_ref()` implements `ParseState->p_subprocfunc_hook` and 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.

=== Execution Path

. `plisql_call_handler()` checks `function_from`; if it is a nested subfunction, the handler locates `PLiSQL_subproc_function` via `(parent_func, fno)`.
. For regular subfunctions, reuse the cached plan stored in `subprocfunc->function`.
. For polymorphic subfunctions, consult `poly_tab`; on a miss, call `plisql_dynamic_compile_subproc()` to build and cache a specialized plan.

=== 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.

=== Statement Dispatch in psql

- `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.
- Statements are sent only when the nesting depth returns to zero and a semicolon is reached, avoiding partial dispatch of subfunction blocks.

=== Retrieving Return Information on the SQL Side

- Regular functions obtain metadata via `funcid` from `pg_proc`; nested subfunctions rely on `FuncExpr.parent_func`, which holds the parent `PLiSQL_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.
Loading