Skip to content

Commit 6cea0b2

Browse files
committed
Add Force View documentation for Oracle compatibility.
1 parent b03f58e commit 6cea0b2

File tree

6 files changed

+391
-1
lines changed

6 files changed

+391
-1
lines changed

CN/modules/ROOT/nav.adoc

Lines changed: 3 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+
31+
**** xref:master/6.3.8.adoc[Force View]
3032
*** xref:master/6.4.adoc[国标GB18030]
3133
** Oracle兼容功能列表
3234
*** xref:master/7.1.adoc[1、框架设计]
@@ -46,6 +48,7 @@
4648
*** xref:master/7.15.adoc[15、OUT 参数]
4749
*** xref:master/7.16.adoc[16、%TYPE、%ROWTYPE]
4850
*** xref:master/7.17.adoc[17、NLS 参数]
51+
*** xref:master/7.18.adoc[18、Force View]
4952
** IvorySQL贡献指南
5053
*** xref:master/8.1.adoc[社区贡献指南]
5154
*** xref:master/8.2.adoc[asciidoc语法快速参考]
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+
== 1.目的
9+
10+
- 本文档解释 IvorySQL 中 Force View 的用途,帮助用户在依赖对象尚未就绪时仍可创建强制视图占位并保持与 Oracle 行为一致。
11+
- Force View 支持 Oracle 迁移,可通过自动或显式编译在依赖满足后快速恢复为普通视图。
12+
13+
== 2.功能说明
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+
== 3.测试用例
20+
21+
=== 3.1 创建缺失依赖的 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+
=== 3.2 自动编译并恢复为普通视图
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+
=== 3.3 显式编译与失败回退
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+
----

EN/modules/ROOT/nav.adoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
** xref:master/4.3.adoc[Developer]
1212
** xref:master/4.4.adoc[Operation Management]
1313
** xref:master/4.5.adoc[Migration]
14-
* IvorySQL Ecosystem
14+
* IvorySQL Ecosystem
1515
** xref:master/5.1.adoc[PostGIS]
1616
** xref:master/5.2.adoc[pgvector]
1717
* IvorySQL Architecture Design
@@ -26,6 +26,8 @@
2626
*** xref:master/6.3.4.adoc[%Type & %Rowtype]
2727
*** xref:master/6.3.5.adoc[NLS Parameters]
2828
*** xref:master/6.3.6.adoc[Function and stored procedure]
29+
30+
*** xref:master/6.3.8.adoc[Force View]
2931
** xref:master/6.4.adoc[GB18030 Character Set]
3032
* List of Oracle compatible features
3133
** xref:master/7.1.adoc[1、Ivorysql frame design]
@@ -45,6 +47,7 @@
4547
** xref:master/7.15.adoc[15、OUT Parameter]
4648
** xref:master/7.16.adoc[16、%Type & %Rowtype]
4749
** xref:master/7.17.adoc[17、NLS Parameters]
50+
** xref:master/7.18.adoc[18、Force View]
4851
* xref:master/8.adoc[Community contribution]
4952
* xref:master/9.adoc[Tool Reference]
5053
* xref:master/10.adoc[FAQ]
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= Force View
7+
8+
== Purpose
9+
10+
- Force View offers Oracle-compatible `CREATE [OR REPLACE] FORCE VIEW` and `ALTER VIEW ... COMPILE` behavior, allowing developers to persist a placeholder view even when dependencies are missing and then switch it back to a normal view once dependencies are available.
11+
- The system records the original SQL text and identifier case mode in a dedicated catalog entry so that automatic or manual recompilation can restore the view with Oracle-style warnings and error reporting.
12+
13+
== Implementation Description
14+
15+
In PostgreSQL, a base table referenced by a view cannot be dropped unless `CASCADE` is used. Oracle, however, allows the base table to be dropped while retaining the Force View and marking it as invalid. To align with Oracle semantics, IvorySQL introduces new grammar rules and a catalog that stores Force View metadata so that the Force state can be shared across sessions.
16+
17+
=== Grammar and Parsing Entry Points
18+
19+
==== Force View Syntax Support
20+
21+
- Register the Force View keywords in `src/backend/oracle_parser/ora_gram.y`.
22+
- Set `ViewStmt->force` during the grammar phase, and generate the `AT_ForceViewCompile` option when parsing `AlterTableStmt`.
23+
- Preserve `ViewStmt->stmt_literal` for later reuse when the placeholder is materialized.
24+
25+
```
26+
/* Insert or update the pg_force_view catalog as needed */
27+
if (need_store)
28+
{
29+
......
30+
31+
StoreForceViewQuery(address.objectId, stmt->replace, ident_case, stmt->stmt_literal ? stmt->stmt_literal : queryString);
32+
}
33+
```
34+
35+
==== AST Field Extensions
36+
37+
- Add `bool force` and `char *stmt_literal` to `ViewStmt` in `src/include/nodes/parsenodes.h`.
38+
- Define `AT_ForceViewCompile` consistently between `parsenodes.h` and `tablecmds.c` so that `ALTER VIEW ... COMPILE` flows into the regular alter-table machinery.
39+
- `parse_analyze` still follows the native path; Force mode intervenes only after a parsing error occurs.
40+
41+
==== Parsing Entry
42+
43+
- `DefineView()` in `src/backend/commands/view.c` now handles both normal and Force views.
44+
- The function first attempts normal parsing inside a `PG_TRY/PG_CATCH`; if the semantic analysis fails, it checks `stmt->force` to decide whether to enter the Force View branch.
45+
- On success it continues to `StoreViewQuery()`; on failure it falls back to the Force View logic so that the view object still exists.
46+
47+
=== Force View Metadata
48+
49+
==== `pg_force_view` Catalog
50+
51+
- `src/include/catalog/pg_force_view.h` defines the catalog table, whose fields include the view OID (`fvoid`), identifier case (`ident_case`), and the original SQL text (`source`).
52+
- The unique index `pg_force_view_fvoid_index` plus the syscache `FORCEVIEWOID` (declared in `src/include/catalog/syscache_info.h`) enable lookups by view OID.
53+
- The catalog enables TOAST so lengthy SQL definitions are preserved in full, covering complex migration scripts.
54+
55+
```
56+
CATALOG(pg_force_view,9120,ForceViewRelationId)
57+
{
58+
/* oid of force view */
59+
Oid fvoid;
60+
61+
/* see IDENT_CASE__xxx constants below */
62+
char ident_case;
63+
64+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
65+
66+
/* sql definition */
67+
text source;
68+
#endif
69+
} FormData_pg_force_view;
70+
```
71+
72+
==== View State
73+
74+
- `bool rel_is_force_view(Oid relid)` in `src/backend/commands/view.c` determines whether a view is in Force state by checking whether the `_RETURN` rule exists.
75+
- Force views still register as `relkind = RELKIND_VIEW`, avoiding extra compatibility branches.
76+
- `pg_class.relhasrules` is set to `false` while in placeholder mode, which becomes part of the detection logic.
77+
78+
=== Creation and Replacement Flow
79+
80+
==== Normal View
81+
82+
- After successful parsing, `DefineView()` calls `DefineVirtualRelation()` to populate `pg_class`, `pg_attribute`, and related catalogs.
83+
- `StoreViewQuery()` generates the `_RETURN` rule and records dependencies.
84+
- This path never touches `pg_force_view`; the view is immediately ready for use.
85+
86+
==== Force View
87+
88+
- `CreateForceVirtualPlaceholder()` in `src/backend/commands/view.c` creates or reuses a placeholder view:
89+
- If the view does not exist, it calls `DefineVirtualRelation()` to create the base object without a `_RETURN` rule.
90+
- If a Force View already exists, it reuses the current record and updates column definitions or cleans up legacy metadata.
91+
- If a normal view exists and `OR REPLACE` is specified, it invokes `make_view_invalid()` to invalidate the old definition before installing the placeholder.
92+
- `StoreForceViewQuery()` persists `stmt_literal` and the current `ivorysql.identifier_case_switch` to `pg_force_view` so identifier case semantics can be restored later.
93+
- After the placeholder is created, the client receives `WARNING: View created with compilation errors`, indicating the view cannot yet be used.
94+
95+
=== Dependency Invalidations and Rollback
96+
97+
==== Active Invalidation Logic
98+
99+
- `make_view_invalid()` is triggered when dependencies are dropped, altered, or otherwise compromised.
100+
- The routine removes the `_RETURN` rule, clears `pg_depend` entries, resets `pg_class.relhasrules`, and truncates `pg_attribute` column metadata.
101+
- It also captures `CREATE FORCE VIEW ... AS <pg_get_viewdef>` and saves it in `pg_force_view`, while setting `ident_case` to `IDENT_CASE_UNDEFINE` to indicate the SQL is system-generated.
102+
103+
==== Observable Behavior After Invalidation
104+
105+
- The view remains visible in metadata catalogs but runtime access detects the Force flag.
106+
- Because `_RETURN` is missing, the executor calls `compile_force_view()` when opening the view; if compilation fails, it raises `view "<schema>.<name>" has errors`.
107+
- Users can recover by issuing `ALTER VIEW ... COMPILE` or `CREATE OR REPLACE FORCE VIEW` once dependencies are ready.
108+
109+
=== Automatic and Manual Compilation
110+
111+
==== Automatic Compilation Triggers
112+
113+
- `addRangeTableEntry()` in `parse_relation.c` and the target-relation open logic in `parse_clause.c` call `compile_force_view()` after detecting a Force view.
114+
- The function reruns `raw_parser` and `parse_analyze`; on success it reinstalls the `_RETURN` rule, and on failure it aborts the statement with the encountered error.
115+
116+
==== Manual Compilation
117+
118+
- `AT_ForceViewCompile` executes during phase 2 in `tablecmds.c`, acquiring `AccessExclusiveLock` before invoking `compile_force_view()`.
119+
- Successful compilation behaves like a normal `ALTER VIEW`; failures emit `WARNING: View altered with compilation errors`, and the view stays in placeholder mode.
120+
121+
==== Column Checks and Metadata Updates
122+
123+
- `compile_force_view()` reads `pg_force_view.source`, rebuilds a `ViewStmt`, and calls `compile_force_view_internal()`.
124+
- The routine uses `checkViewColumns()` to compare legacy columns, allowing additions but rejecting incompatible type changes; new columns are applied through `AT_AddColumnToView()`.
125+
- `_RETURN` is regenerated via `StoreViewQuery()`, and `DeleteForceView()` removes the catalog record so the view becomes a standard one again.

0 commit comments

Comments
 (0)