Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -26,6 +26,7 @@
**** xref:master/6.3.2.adoc[OUT 参数]
**** xref:master/6.3.4.adoc[%TYPE、%ROWTYPE]
**** xref:master/6.3.5.adoc[NLS 参数]
**** xref:master/6.3.7.adoc[嵌套子函数]
*** xref:master/6.4.adoc[国标GB18030]
** Oracle兼容功能列表
*** xref:master/7.1.adoc[1、框架设计]
Expand All @@ -45,6 +46,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 函数。
- 父函数:承载嵌套函数的外层函数、存储过程或匿名块,执行过程中负责实际触发子函数调用。

== 实现说明

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

= interpreted as section level, so we can remove the section number '一' ,'1.' ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

= interpreted as section level, so we can remove the section number '一' ,'1.' ...

done


==== 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. 数据项保存

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

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

如果子函数里使用了多态参数,在语法阶段保存到 `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 嵌套子函数

== 1.目的

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

== 2.功能说明

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

== 3.测试用例

[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 @@ -25,6 +25,7 @@
*** xref:master/6.3.2.adoc[OUT Parameter]
*** xref:master/6.3.4.adoc[%Type & %Rowtype]
*** xref:master/6.3.5.adoc[NLS Parameters]
*** 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 @@ -44,6 +45,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

=== 1. Syntax Recognition for Nested Subfunctions

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

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

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

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

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