diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index d0f8612..e39d870 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -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、框架设计] @@ -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语法快速参考] diff --git a/CN/modules/ROOT/pages/master/6.3.7.adoc b/CN/modules/ROOT/pages/master/6.3.7.adoc new file mode 100644 index 0000000..6328405 --- /dev/null +++ b/CN/modules/ROOT/pages/master/6.3.7.adoc @@ -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 参数信息。 diff --git a/CN/modules/ROOT/pages/master/7.19.adoc b/CN/modules/ROOT/pages/master/7.19.adoc new file mode 100644 index 0000000..b35a578 --- /dev/null +++ b/CN/modules/ROOT/pages/master/7.19.adoc @@ -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; +---- diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index b046ca0..b6cb43c 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -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 @@ -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] @@ -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] diff --git a/EN/modules/ROOT/pages/master/6.3.7.adoc b/EN/modules/ROOT/pages/master/6.3.7.adoc new file mode 100644 index 0000000..5ea1eb7 --- /dev/null +++ b/EN/modules/ROOT/pages/master/6.3.7.adoc @@ -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. diff --git a/EN/modules/ROOT/pages/master/7.19.adoc b/EN/modules/ROOT/pages/master/7.19.adoc new file mode 100644 index 0000000..601766c --- /dev/null +++ b/EN/modules/ROOT/pages/master/7.19.adoc @@ -0,0 +1,83 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += Oracle-Compatible Nested Subfunctions + +== Objective + +- Using Oracle-style nested subfunctions in IvorySQL. + +== Feature Description + +- Allows declaring and invoking subfunctions or subprocedures inside anonymous blocks, functions, or procedures, with scope limited to the parent block. +- Nested subfunctions can read and update variables defined in the parent scope while introducing their own local variables; the parent scope cannot directly access the subfunction's internal state. +- Supports overloading resolution that distinguishes homonymous subfunctions by argument count, data type, or named parameters. + +== Test Cases + +[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; +----