Skip to content

Commit 4fdaced

Browse files
authored
Merge branch 'master' into master
2 parents c682e9a + 1f73d65 commit 4fdaced

File tree

10 files changed

+785
-1
lines changed

10 files changed

+785
-1
lines changed

CN/modules/ROOT/nav.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
**** xref:master/6.3.4.adoc[%TYPE、%ROWTYPE]
2828
**** xref:master/6.3.5.adoc[NLS 参数]
2929
**** xref:master/6.3.6.adoc[函数与存储过程]
30+
**** xref:master/6.3.7.adoc[嵌套子函数]
31+
**** xref:master/6.3.8.adoc[Force View]
3032
**** xref:master/6.3.9.adoc[大小写转换]
3133
*** xref:master/6.4.adoc[国标GB18030]
3234
** Oracle兼容功能列表
@@ -47,6 +49,8 @@
4749
*** xref:master/7.15.adoc[15、OUT 参数]
4850
*** xref:master/7.16.adoc[16、%TYPE、%ROWTYPE]
4951
*** xref:master/7.17.adoc[17、NLS 参数]
52+
*** xref:master/7.18.adoc[18、Force View]
53+
*** xref:master/7.19.adoc[19、嵌套子函数]
5054
** IvorySQL贡献指南
5155
*** xref:master/8.1.adoc[社区贡献指南]
5256
*** xref:master/8.2.adoc[asciidoc语法快速参考]
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= 嵌套子函数
7+
8+
== 目的
9+
10+
- 嵌套子函数:定义在函数、存储过程或匿名块内部的函数或存储过程,也称为 subproc 或 inner 函数。
11+
- 父函数:承载嵌套函数的外层函数、存储过程或匿名块,执行过程中负责实际触发子函数调用。
12+
13+
== 实现说明
14+
15+
=== 嵌套子函数语法识别
16+
17+
==== 识别嵌套写法
18+
19+
当 `DECLARE` 块里出现 `function ... is/as begin ... end` 结构时,`pl_gram.y` 会调用 `plisql_build_subproc_function()`(对应创建普通函数,这一阶段相当于在 `pg_proc` 中更新 catalog 中的注册信息):
20+
21+
. 在 `PLiSQL_function` 的 `subprocfuncs[]` 数组中创建 `PLiSQL_subproc_function` 结构,记录名称、参数、返回类型、属性等,获得一个下标 `fno` 作为该子函数的标识。
22+
. 调用 `plisql_check_subprocfunc_properties()` 校验声明与定义的合法组合。
23+
24+
==== 数据项保存
25+
26+
保存到父函数的 `datum` 表:编译期的 `PLiSQL_function->datums` 描述子函数里的变量、记录字段,`PLiSQL_execstate->datums` 保存着执行过程中的变量。
27+
28+
==== 保存多态函数模板
29+
30+
如果子函数里使用了多态参数,在语法阶段保存到 `subprocfunc->src`,同时将 `has_poly_argument` 设成 `true`,执行时按不同实参类型重新编译。
31+
32+
=== 父函数重新编译
33+
34+
. 父函数的 `PLiSQL_function` 结构多了一个 `subprocfuncs` 数组,里面每个元素就是刚才创建的 `PLiSQL_subproc_function`。
35+
. 子函数结构体 `PLiSQL_subproc_function` 有一个哈希表指针 `HTAB *poly_tab`,默认为空。当子函数里使用了多态函数时,`has_poly_argument` 为 `true`,则会在初次编译时初始化 `poly_tab`。`poly_tab` 的 key 是 `PLiSQL_func_hashkey`,记录着子函数的 `fno`、输入参数类型等; value 是编译好的 `PLiSQL_function *`(`plisql` 函数的执行上下文)。
36+
37+
=== 调用时解析
38+
39+
. 编译过程中,`pg` 解析器会生成一个 `ParseState` 结构,`plisql_subprocfunc_ref()` 会通过 `ParseState->p_subprocfunc_hook()` 找到父函数的 `PLiSQL_function`,调用 `plisql_ns_lookup()` 找到所有同名子函数的 `fno`,根据参数个数、类型找到最合适的多态函数。
40+
. `FuncExpr` 结构构造时会对子函数进行标记,方便后期执行阶段识别:`function_from = FUNC_FROM_SUBPROCFUNC`,`parent_func` 指向父级 `PLiSQL_function`,`funcid = fno`。
41+
. `plisql_call_handler()` 当 `function_from == FUNC_FROM_SUBPROCFUNC`,会用 `parent_func + fno` 找到对应的 `PLiSQL_subproc_function`:
42+
.. 如果不是多态:直接复用 `subprocfunc->function` 里的动作树。
43+
.. 如果是多态:先在 `poly_tab` 查有没有编译结果;没有就调用 `plisql_dynamic_compile_subproc()` 编译,放进 `poly_tab` 缓存。
44+
. 子函数开始执行之前,`plisql_init_subprocfunc_globalvar()` 会把父函数的 `datum` 表中有关的变量 fork 一份,这样子函数可以获取到父函数的变量值,也不会污染父函数的变量空间;执行后由 `plisql_assign_out_subprocfunc_globalvar()` 把需要回写的变量更新到父函数的 `datum` 表。
45+
46+
== 模块设计
47+
48+
=== PL/iSQL 语法扩展
49+
50+
- `pl_gram.y` 新增子过程声明、嵌套定义的产生式,并在创建过程中记录 `lastoutvardno`、子过程信息等元数据。
51+
- 支持在子函数内引用父过程变量、子过程以及自定义类型。
52+
53+
当 DECLARE 块内出现 `function ... is/as begin ... end` 结构时,`pl_gram.y` 会调用 `plisql_build_subproc_function()` 进行编译:
54+
55+
. 在 `PLiSQL_function` 的 `subprocfuncs[]` 中创建 `PLiSQL_subproc_function` 结构,记录名称、参数、返回类型和属性,分配下标 `fno` 作为子函数标识。
56+
. 调用 `plisql_check_subprocfunc_properties()` 校验声明与定义属性组合是否合法,防止重复或缺失声明造成的语义错误。
57+
58+
=== 数据项保存
59+
60+
父函数的 Datum 表在编译期和执行期分别缓存子函数能访问的变量:
61+
62+
. `PLiSQL_function->datums` 保存子函数编译阶段可见的变量与记录字段信息。
63+
. `PLiSQL_execstate->datums` 在执行阶段持有实时的变量数值,实现运行期访问。
64+
65+
=== 多态函数模板
66+
67+
若子函数包含多态参数,语法阶段会:
68+
69+
. 将子函数源文本拷贝到 `subprocfunc->src`。
70+
. 设置 `has_poly_argument = true`,为后续按实参类型动态编译做好准备。
71+
72+
=== 父函数重新编译
73+
74+
- 父函数的 `PLiSQL_function` 结构新增 `subprocfuncs` 数组,每个元素对应一个 `PLiSQL_subproc_function`。
75+
- `PLiSQL_subproc_function` 持有 `HTAB *poly_tab` 指针;当 `has_poly_argument` 为 `true` 时,在首次编译时初始化该缓存,键为 `PLiSQL_func_hashkey`(子函数 `fno` + 实参类型),值为编译后的 `PLiSQL_function`。
76+
77+
=== 解析器钩子
78+
79+
编译期间 PostgreSQL 解析器会构造 `ParseState`,`plisql_subprocfunc_ref()` 通过 `ParseState->p_subprocfunc_hook()` 连接父函数,调用 `plisql_ns_lookup()` 找到同名子函数的全部 `fno`,并依据参数个数与类型挑选最佳候选,实现重载分发。
80+
81+
=== FuncExpr 标记
82+
83+
构造 `FuncExpr` 时会标记嵌套调用信息,便于执行阶段识别:
84+
85+
- `function_from = FUNC_FROM_SUBPROCFUNC`。
86+
- `parent_func` 指向父级 `PLiSQL_function`。
87+
- `funcid = fno`,用于快速定位子函数定义。
88+
89+
=== 嵌套函数查找机制
90+
91+
- `plisql_subprocfunc_ref()` 作为 `ParseState->p_subprocfunc_hook` 实现入口,复用名称空间查询逻辑。
92+
- `plisql_get_subprocfunc_detail()` 依据参数数量、类型与命名匹配规则挑选最优候选,是嵌套函数重载的关键。
93+
94+
=== 执行路径
95+
96+
. `plisql_call_handler()` 判断 `function_from` 后,通过 `parent_func + fno` 找到目标 `PLiSQL_subproc_function`。
97+
. 对普通子函数,直接复用 `subprocfunc->function` 缓存;
98+
. 对多态子函数,先查询 `poly_tab`,未命中时调用 `plisql_dynamic_compile_subproc()` 动态编译并写入缓存。
99+
100+
=== 变量同步
101+
102+
- `plisql_init_subprocfunc_globalvar()` 在子函数执行前拷贝父函数 Datum 表中的相关变量,保证子函数读取到外层最新状态。
103+
- `plisql_assign_out_subprocfunc_globalvar()` 在返回前回写 OUT/INOUT 变量,确保父子函数数据一致性且互不污染。
104+
105+
=== PSQL 端语句发送
106+
107+
- `psqlscan.l` 调整 `proc_func_define_level` 和 `begin_depth` 的入栈/出栈逻辑,确保嵌套函数体整体发送至 SQL 端。
108+
- 只有当嵌套层级回到 0 且遇到分号时,才触发发送,避免子函数块被拆分。
109+
110+
=== SQL 层返回值获取
111+
112+
- 普通函数通过 `funcid` 访问 `pg_proc`;嵌套函数依赖 `FuncExpr.parent_func` 承载的 `PLiSQL_function`。
113+
- 为此实现一组函数指针(`plisql_register_internal_func()` 注册)供 SQL 层回调,按需获取嵌套函数名称、返回类型与 OUT 参数信息。
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= Force View
7+
8+
== 目的
9+
10+
- Force View 提供 Oracle 兼容的 `CREATE [OR REPLACE] FORCE VIEW` 与 `ALTER VIEW ... COMPILE` 行为,允许开发者先落地视图占位,再在依赖补齐后恢复正常执行。
11+
- 系统将原始 SQL 及标识符大小写模式写入专属目录项,依赖恢复时可自动或手动编译为普通视图,且保持 Oracle 风格的告警与错误提示。
12+
13+
== 实现说明
14+
15+
PG在删除基表时,如果有视图依赖于此基表,则不允许删除,需要使用cascade 关键字全部删除;Oracle删除基表时,允许直接删除基表,而保留force view并将force view 转为无效状态,且创建普通视图后,也可以删除此视图的基表,但把视图转为无效状态。
16+
17+
为了支持新的语法,需要增加对应的语法规则。且FORCE VIEW的状态在多个session之间是共享的,新增一张系统表用来存储FORCE VIEW的相关信息。
18+
19+
=== 语法与解析入口
20+
21+
==== Force View 语法支持
22+
23+
- `src/backend/oracle_parser/ora_gram.y` 注册 Force View 关键字。
24+
- 语法阶段为 `ViewStmt->force` 赋值,并在 `AlterTableStmt` 阶段生成 `AT_ForceViewCompile` 选项。
25+
- 解析阶段保留 `ViewStmt->stmt_literal`,后续占位时需要复用原始文本。
26+
27+
```
28+
/* Insert or update the pg_force_view catalog as needed */
29+
if (need_store)
30+
{
31+
......
32+
33+
StoreForceViewQuery(address.objectId, stmt->replace, ident_case, stmt->stmt_literal ? stmt->stmt_literal : queryString);
34+
}
35+
```
36+
37+
==== AST 字段扩展
38+
39+
- `src/include/nodes/parsenodes.h` 为 `ViewStmt` 增加 `bool force`、`char *stmt_literal` 字段。
40+
- `AT_ForceViewCompile` 在 `parsenodes.h` 与 `tablecmds.c` 成对定义,确保 `ALTER VIEW ... COMPILE` 进入 AlterTable 流程。
41+
- `parse_analyze` 仍按普通路径执行,Force 模式只在解析报错后介入。
42+
43+
==== 解析入口
44+
45+
- `DefineView()`(`src/backend/commands/view.c`)统一处理普通与 Force 视图。
46+
- 代码在 `PG_TRY/PG_CATCH` 包裹下先尝试普通解析,捕获语义错误后根据 `stmt->force` 决定是否进入Force View 流程。
47+
- 成功解析继续调用 `StoreViewQuery()` 写入 `_RETURN` 规则,失败则进入 Force View 分支,保证视图对象仍然存在。
48+
49+
=== Force View 元数据
50+
51+
==== `pg_force_view` 目录
52+
53+
- `src/include/catalog/pg_force_view.h` 定义目录表,字段包含视图 OID (`fvoid`)、标识符大小写模式 (`ident_case`)、原始 SQL 文本 (`source`)。
54+
- 唯一索引 `pg_force_view_fvoid_index` 搭配 syscache `FORCEVIEWOID`(`src/include/catalog/syscache_info.h`)实现按视图 OID 查找。
55+
- 目录表开启 TOAST,长 SQL 文本不会被截断,可覆盖复杂迁移脚本。
56+
57+
```
58+
CATALOG(pg_force_view,9120,ForceViewRelationId)
59+
{
60+
/* oid of force view */
61+
Oid fvoid;
62+
63+
/* see IDENT_CASE__xxx constants below */
64+
char ident_case;
65+
66+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
67+
68+
/* sql definition */
69+
text source;
70+
#endif
71+
} FormData_pg_force_view;
72+
```
73+
==== 视图状态
74+
75+
- `bool rel_is_force_view(Oid relid)` 位于 `src/backend/commands/view.c`,通过判断 `_RETURN` 规则是否存在确认视图是否处于 Force 状态。
76+
- Force View 仍登记为 `relkind = RELKIND_VIEW`,避免额外兼容分支。
77+
- `pg_class.relhasrules` 在占位状态设置为 `false`,配合作为判断依据。
78+
79+
=== 创建与替换流程
80+
81+
==== 普通 view
82+
83+
- `DefineView()` 在解析成功后调用 `DefineVirtualRelation()` 写入 `pg_class`、`pg_attribute` 等结构。
84+
- `StoreViewQuery()` 生成 `_RETURN` 规则并维护依赖关系。
85+
- 该路径不会触及 `pg_force_view`,视图完成后立即可用。
86+
87+
==== Force view
88+
89+
- `CreateForceVirtualPlaceholder()`(`src/backend/commands/view.c`)负责生成或复用占位视图:
90+
- 若视图不存在,调用 `DefineVirtualRelation()` 建立基础对象,但不写入 `_RETURN` 规则。
91+
- 若已有 Force View,复用当前记录,更新列定义或清理旧元数据。
92+
- 若已有普通视图且声明了 `OR REPLACE`,调用 `make_view_invalid()` 失效原视图,再写入新的占位定义。
93+
- `StoreForceViewQuery()` 将 `stmt_literal` 与当前 `ivorysql.identifier_case_switch` 持久化到 `pg_force_view`,以便后续恢复大小写语义。
94+
- 视图占位完成后向客户端返回 `WARNING: View created with compilation errors`,提示仍处于不可用状态。
95+
96+
=== 依赖失效与回退
97+
98+
==== 主动失效逻辑
99+
100+
- `make_view_invalid()` 在依赖对象被 DROP/ALTER 等操作影响时调用。
101+
- 函数删除 `_RETURN` 规则、清理 `pg_depend` 依赖、重置 `pg_class.relhasrules` 并清空 `pg_attribute` 列信息。
102+
- 同步生成 `CREATE FORCE VIEW ... AS <pg_get_viewdef>` 文本写入 `pg_force_view`,`ident_case` 设置为 `IDENT_CASE_UNDEFINE` 表示该文本由系统构造。
103+
104+
==== 失效后的可见行为
105+
106+
- 视图仍然存在并可在元数据中枚举,但实际访问会触发 Force 判定。
107+
- `_RETURN` 缺失导致执行器在打开视图时调用 `compile_force_view()`,若编译仍失败则抛出 `view "<schema>.<name>" has errors`。
108+
- 用户可通过 `ALTER VIEW ... COMPILE` 或再次 `CREATE OR REPLACE FORCE VIEW` 来恢复。
109+
110+
=== 自动与主动编译
111+
112+
==== 自动编译触发点
113+
114+
- `parse_relation.c` 的 `addRangeTableEntry()` 和 `parse_clause.c` 的目标关系打开逻辑在判定 Force View 后调用 `compile_force_view()`。
115+
- 该函数重新执行 `raw_parser` 与 `parse_analyze`,成功则再安装 `_RETURN` 规则,失败直接终止当前语句。
116+
117+
==== 主动编译
118+
119+
- `AT_ForceViewCompile` 在 `tablecmds.c` Phase 2 阶段执行,先申请 `AccessExclusiveLock` 再调用 `compile_force_view()`。
120+
- 成功编译返回常规 `ALTER VIEW` 完成信息;失败打印 `WARNING: View altered with compilation errors`,视图仍保持占位。
121+
122+
==== 列校验与元数据更新
123+
124+
- `compile_force_view()` 读取 `pg_force_view.source` 并构造 `ViewStmt`,随后调用 `compile_force_view_internal()`。
125+
- 函数通过 `checkViewColumns()` 比对旧列,允许新增列但禁止类型不兼容的替换;新增列由 `AT_AddColumnToView()` 完成。
126+
- `_RETURN` 规则由 `StoreViewQuery()` 重新生成,最后 `DeleteForceView()` 删除目录项,视图恢复为普通状态。
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= Force View
7+
8+
== 目的
9+
10+
- 本文档解释 IvorySQL 中 Force View 的用途,帮助用户在依赖对象尚未就绪时仍可创建强制视图占位并保持与 Oracle 行为一致。
11+
- Force View 可支持在依赖表尚未准备好的情况下进行视图迁移,在依赖满足后通过自动或显式编译快速恢复为普通视图。
12+
13+
== 功能说明
14+
15+
- `CREATE [OR REPLACE] FORCE VIEW`:即使查询引用了不存在的表或函数,也会创建视图占位对象并保存原始 SQL,提示“View created with compilation errors”。
16+
- 自动编译:访问 Force View 时会尝试重新编译;成功后变为普通视图,失败则报错 `view "<schema>.<name>" has errors`。
17+
- 依赖失效回退:当普通视图因依赖对象被删除或结构变更而失效时,系统会自动将其转化为 Force View,保留最后一次有效定义以便后续恢复。
18+
19+
== 测试用例
20+
21+
=== 创建缺失依赖的 Force View
22+
23+
[source,sql]
24+
----
25+
-- 引用不存在的基础表,验证可成功创建占位
26+
CREATE FORCE VIEW fv_customer AS
27+
SELECT c_id, c_name FROM missing_customer;
28+
-- 期望输出:WARNING: View created with compilation errors
29+
----
30+
31+
=== 自动编译并恢复为普通视图
32+
33+
[source,sql]
34+
----
35+
-- 先补齐依赖对象
36+
CREATE TABLE missing_customer(
37+
c_id int primary key,
38+
c_name text
39+
);
40+
INSERT INTO missing_customer VALUES (1, 'Alice');
41+
42+
-- 访问 Force View 时自动尝试编译
43+
SELECT * FROM fv_customer;
44+
-- 成功时视图转换为普通视图并返回数据
45+
----
46+
47+
=== 显式编译与失败回退
48+
49+
[source,sql]
50+
----
51+
-- 再次让视图失效:重建定义为 Force View
52+
CREATE OR REPLACE FORCE VIEW fv_customer AS
53+
SELECT c_id, upper(c_name) AS c_name FROM missing_customer_v2;
54+
55+
-- 显式编译:依赖仍缺少,保持 Force View 状态并输出 WARNING
56+
ALTER VIEW fv_customer COMPILE;
57+
-- 期望输出:WARNING: View altered with compilation errors
58+
59+
-- 补齐依赖后再次编译
60+
CREATE TABLE missing_customer_v2(
61+
c_id int,
62+
c_name text
63+
);
64+
ALTER VIEW fv_customer COMPILE;
65+
-- 期望输出:ALTER VIEW 成功,视图恢复为普通视图
66+
----

0 commit comments

Comments
 (0)