diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index e39d870..b82b558 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -28,6 +28,7 @@ **** xref:master/6.3.5.adoc[NLS 参数] **** xref:master/6.3.6.adoc[函数与存储过程] **** xref:master/6.3.7.adoc[嵌套子函数] +**** xref:master/6.3.8.adoc[Force View] *** xref:master/6.4.adoc[国标GB18030] ** Oracle兼容功能列表 *** xref:master/7.1.adoc[1、框架设计] @@ -47,6 +48,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.18.adoc[18、Force View] *** xref:master/7.19.adoc[19、嵌套子函数] ** IvorySQL贡献指南 *** xref:master/8.1.adoc[社区贡献指南] diff --git a/CN/modules/ROOT/pages/master/6.3.8.adoc b/CN/modules/ROOT/pages/master/6.3.8.adoc new file mode 100644 index 0000000..3029707 --- /dev/null +++ b/CN/modules/ROOT/pages/master/6.3.8.adoc @@ -0,0 +1,126 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += Force View + +== 目的 + +- Force View 提供 Oracle 兼容的 `CREATE [OR REPLACE] FORCE VIEW` 与 `ALTER VIEW ... COMPILE` 行为,允许开发者先落地视图占位,再在依赖补齐后恢复正常执行。 +- 系统将原始 SQL 及标识符大小写模式写入专属目录项,依赖恢复时可自动或手动编译为普通视图,且保持 Oracle 风格的告警与错误提示。 + +== 实现说明 + +PG在删除基表时,如果有视图依赖于此基表,则不允许删除,需要使用cascade 关键字全部删除;Oracle删除基表时,允许直接删除基表,而保留force view并将force view 转为无效状态,且创建普通视图后,也可以删除此视图的基表,但把视图转为无效状态。 + +为了支持新的语法,需要增加对应的语法规则。且FORCE VIEW的状态在多个session之间是共享的,新增一张系统表用来存储FORCE VIEW的相关信息。 + +=== 语法与解析入口 + +==== Force View 语法支持 + +- `src/backend/oracle_parser/ora_gram.y` 注册 Force View 关键字。 +- 语法阶段为 `ViewStmt->force` 赋值,并在 `AlterTableStmt` 阶段生成 `AT_ForceViewCompile` 选项。 +- 解析阶段保留 `ViewStmt->stmt_literal`,后续占位时需要复用原始文本。 + +``` +/* Insert or update the pg_force_view catalog as needed */ +if (need_store) +{ + ...... + + StoreForceViewQuery(address.objectId, stmt->replace, ident_case, stmt->stmt_literal ? stmt->stmt_literal : queryString); +} +``` + +==== AST 字段扩展 + +- `src/include/nodes/parsenodes.h` 为 `ViewStmt` 增加 `bool force`、`char *stmt_literal` 字段。 +- `AT_ForceViewCompile` 在 `parsenodes.h` 与 `tablecmds.c` 成对定义,确保 `ALTER VIEW ... COMPILE` 进入 AlterTable 流程。 +- `parse_analyze` 仍按普通路径执行,Force 模式只在解析报错后介入。 + +==== 解析入口 + +- `DefineView()`(`src/backend/commands/view.c`)统一处理普通与 Force 视图。 +- 代码在 `PG_TRY/PG_CATCH` 包裹下先尝试普通解析,捕获语义错误后根据 `stmt->force` 决定是否进入Force View 流程。 +- 成功解析继续调用 `StoreViewQuery()` 写入 `_RETURN` 规则,失败则进入 Force View 分支,保证视图对象仍然存在。 + +=== Force View 元数据 + +==== `pg_force_view` 目录 + +- `src/include/catalog/pg_force_view.h` 定义目录表,字段包含视图 OID (`fvoid`)、标识符大小写模式 (`ident_case`)、原始 SQL 文本 (`source`)。 +- 唯一索引 `pg_force_view_fvoid_index` 搭配 syscache `FORCEVIEWOID`(`src/include/catalog/syscache_info.h`)实现按视图 OID 查找。 +- 目录表开启 TOAST,长 SQL 文本不会被截断,可覆盖复杂迁移脚本。 + +``` +CATALOG(pg_force_view,9120,ForceViewRelationId) +{ + /* oid of force view */ + Oid fvoid; + + /* see IDENT_CASE__xxx constants below */ + char ident_case; + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + + /* sql definition */ + text source; +#endif +} FormData_pg_force_view; +``` +==== 视图状态 + +- `bool rel_is_force_view(Oid relid)` 位于 `src/backend/commands/view.c`,通过判断 `_RETURN` 规则是否存在确认视图是否处于 Force 状态。 +- Force View 仍登记为 `relkind = RELKIND_VIEW`,避免额外兼容分支。 +- `pg_class.relhasrules` 在占位状态设置为 `false`,配合作为判断依据。 + +=== 创建与替换流程 + +==== 普通 view + +- `DefineView()` 在解析成功后调用 `DefineVirtualRelation()` 写入 `pg_class`、`pg_attribute` 等结构。 +- `StoreViewQuery()` 生成 `_RETURN` 规则并维护依赖关系。 +- 该路径不会触及 `pg_force_view`,视图完成后立即可用。 + +==== Force view + +- `CreateForceVirtualPlaceholder()`(`src/backend/commands/view.c`)负责生成或复用占位视图: + - 若视图不存在,调用 `DefineVirtualRelation()` 建立基础对象,但不写入 `_RETURN` 规则。 + - 若已有 Force View,复用当前记录,更新列定义或清理旧元数据。 + - 若已有普通视图且声明了 `OR REPLACE`,调用 `make_view_invalid()` 失效原视图,再写入新的占位定义。 +- `StoreForceViewQuery()` 将 `stmt_literal` 与当前 `ivorysql.identifier_case_switch` 持久化到 `pg_force_view`,以便后续恢复大小写语义。 +- 视图占位完成后向客户端返回 `WARNING: View created with compilation errors`,提示仍处于不可用状态。 + +=== 依赖失效与回退 + +==== 主动失效逻辑 + +- `make_view_invalid()` 在依赖对象被 DROP/ALTER 等操作影响时调用。 +- 函数删除 `_RETURN` 规则、清理 `pg_depend` 依赖、重置 `pg_class.relhasrules` 并清空 `pg_attribute` 列信息。 +- 同步生成 `CREATE FORCE VIEW ... AS ` 文本写入 `pg_force_view`,`ident_case` 设置为 `IDENT_CASE_UNDEFINE` 表示该文本由系统构造。 + +==== 失效后的可见行为 + +- 视图仍然存在并可在元数据中枚举,但实际访问会触发 Force 判定。 +- `_RETURN` 缺失导致执行器在打开视图时调用 `compile_force_view()`,若编译仍失败则抛出 `view "." has errors`。 +- 用户可通过 `ALTER VIEW ... COMPILE` 或再次 `CREATE OR REPLACE FORCE VIEW` 来恢复。 + +=== 自动与主动编译 + +==== 自动编译触发点 + +- `parse_relation.c` 的 `addRangeTableEntry()` 和 `parse_clause.c` 的目标关系打开逻辑在判定 Force View 后调用 `compile_force_view()`。 +- 该函数重新执行 `raw_parser` 与 `parse_analyze`,成功则再安装 `_RETURN` 规则,失败直接终止当前语句。 + +==== 主动编译 + +- `AT_ForceViewCompile` 在 `tablecmds.c` Phase 2 阶段执行,先申请 `AccessExclusiveLock` 再调用 `compile_force_view()`。 +- 成功编译返回常规 `ALTER VIEW` 完成信息;失败打印 `WARNING: View altered with compilation errors`,视图仍保持占位。 + +==== 列校验与元数据更新 + +- `compile_force_view()` 读取 `pg_force_view.source` 并构造 `ViewStmt`,随后调用 `compile_force_view_internal()`。 +- 函数通过 `checkViewColumns()` 比对旧列,允许新增列但禁止类型不兼容的替换;新增列由 `AT_AddColumnToView()` 完成。 +- `_RETURN` 规则由 `StoreViewQuery()` 重新生成,最后 `DeleteForceView()` 删除目录项,视图恢复为普通状态。 diff --git a/CN/modules/ROOT/pages/master/7.18.adoc b/CN/modules/ROOT/pages/master/7.18.adoc new file mode 100644 index 0000000..49cb1d1 --- /dev/null +++ b/CN/modules/ROOT/pages/master/7.18.adoc @@ -0,0 +1,66 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += Force View + +== 目的 + +- 本文档解释 IvorySQL 中 Force View 的用途,帮助用户在依赖对象尚未就绪时仍可创建强制视图占位并保持与 Oracle 行为一致。 +- Force View 可支持在依赖表尚未准备好的情况下进行视图迁移,在依赖满足后通过自动或显式编译快速恢复为普通视图。 + +== 功能说明 + +- `CREATE [OR REPLACE] FORCE VIEW`:即使查询引用了不存在的表或函数,也会创建视图占位对象并保存原始 SQL,提示“View created with compilation errors”。 +- 自动编译:访问 Force View 时会尝试重新编译;成功后变为普通视图,失败则报错 `view "." has errors`。 +- 依赖失效回退:当普通视图因依赖对象被删除或结构变更而失效时,系统会自动将其转化为 Force View,保留最后一次有效定义以便后续恢复。 + +== 测试用例 + +=== 创建缺失依赖的 Force View + +[source,sql] +---- +-- 引用不存在的基础表,验证可成功创建占位 +CREATE FORCE VIEW fv_customer AS +SELECT c_id, c_name FROM missing_customer; +-- 期望输出:WARNING: View created with compilation errors +---- + +=== 自动编译并恢复为普通视图 + +[source,sql] +---- +-- 先补齐依赖对象 +CREATE TABLE missing_customer( + c_id int primary key, + c_name text +); +INSERT INTO missing_customer VALUES (1, 'Alice'); + +-- 访问 Force View 时自动尝试编译 +SELECT * FROM fv_customer; +-- 成功时视图转换为普通视图并返回数据 +---- + +=== 显式编译与失败回退 + +[source,sql] +---- +-- 再次让视图失效:重建定义为 Force View +CREATE OR REPLACE FORCE VIEW fv_customer AS +SELECT c_id, upper(c_name) AS c_name FROM missing_customer_v2; + +-- 显式编译:依赖仍缺少,保持 Force View 状态并输出 WARNING +ALTER VIEW fv_customer COMPILE; +-- 期望输出:WARNING: View altered with compilation errors + +-- 补齐依赖后再次编译 +CREATE TABLE missing_customer_v2( + c_id int, + c_name text +); +ALTER VIEW fv_customer COMPILE; +-- 期望输出:ALTER VIEW 成功,视图恢复为普通视图 +---- diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index b6cb43c..e3a8dac 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -27,6 +27,7 @@ *** 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.3.8.adoc[Force View] ** xref:master/6.4.adoc[GB18030 Character Set] * List of Oracle compatible features ** xref:master/7.1.adoc[1、Ivorysql frame design] @@ -46,6 +47,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.18.adoc[18、Force View] ** xref:master/7.19.adoc[19、Nested Subfunctions] * xref:master/8.adoc[Community contribution] * xref:master/9.adoc[Tool Reference] diff --git a/EN/modules/ROOT/pages/master/6.3.8.adoc b/EN/modules/ROOT/pages/master/6.3.8.adoc new file mode 100644 index 0000000..dc490b7 --- /dev/null +++ b/EN/modules/ROOT/pages/master/6.3.8.adoc @@ -0,0 +1,125 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += Force View + +== Purpose + +- 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. +- 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. + +== Implementation Description + +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. + +=== Grammar and Parsing Entry Points + +==== Force View Syntax Support + +- Register the Force View keywords in `src/backend/oracle_parser/ora_gram.y`. +- Set `ViewStmt->force` during the grammar phase, and generate the `AT_ForceViewCompile` option when parsing `AlterTableStmt`. +- Preserve `ViewStmt->stmt_literal` for later reuse when the placeholder is materialized. + +``` +/* Insert or update the pg_force_view catalog as needed */ +if (need_store) +{ + ...... + + StoreForceViewQuery(address.objectId, stmt->replace, ident_case, stmt->stmt_literal ? stmt->stmt_literal : queryString); +} +``` + +==== AST Field Extensions + +- Add `bool force` and `char *stmt_literal` to `ViewStmt` in `src/include/nodes/parsenodes.h`. +- Define `AT_ForceViewCompile` consistently between `parsenodes.h` and `tablecmds.c` so that `ALTER VIEW ... COMPILE` flows into the regular alter-table machinery. +- `parse_analyze` still follows the native path; Force mode intervenes only after a parsing error occurs. + +==== Parsing Entry + +- `DefineView()` in `src/backend/commands/view.c` now handles both normal and Force views. +- 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. +- On success it continues to `StoreViewQuery()`; on failure it falls back to the Force View logic so that the view object still exists. + +=== Force View Metadata + +==== `pg_force_view` Catalog + +- `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`). +- 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. +- The catalog enables TOAST so lengthy SQL definitions are preserved in full, covering complex migration scripts. + +``` +CATALOG(pg_force_view,9120,ForceViewRelationId) +{ + /* oid of force view */ + Oid fvoid; + + /* see IDENT_CASE__xxx constants below */ + char ident_case; + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + + /* sql definition */ + text source; +#endif +} FormData_pg_force_view; +``` + +==== View State + +- `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. +- Force views still register as `relkind = RELKIND_VIEW`, avoiding extra compatibility branches. +- `pg_class.relhasrules` is set to `false` while in placeholder mode, which becomes part of the detection logic. + +=== Creation and Replacement Flow + +==== Normal View + +- After successful parsing, `DefineView()` calls `DefineVirtualRelation()` to populate `pg_class`, `pg_attribute`, and related catalogs. +- `StoreViewQuery()` generates the `_RETURN` rule and records dependencies. +- This path never touches `pg_force_view`; the view is immediately ready for use. + +==== Force View + +- `CreateForceVirtualPlaceholder()` in `src/backend/commands/view.c` creates or reuses a placeholder view: + - If the view does not exist, it calls `DefineVirtualRelation()` to create the base object without a `_RETURN` rule. + - If a Force View already exists, it reuses the current record and updates column definitions or cleans up legacy metadata. + - If a normal view exists and `OR REPLACE` is specified, it invokes `make_view_invalid()` to invalidate the old definition before installing the placeholder. +- `StoreForceViewQuery()` persists `stmt_literal` and the current `ivorysql.identifier_case_switch` to `pg_force_view` so identifier case semantics can be restored later. +- After the placeholder is created, the client receives `WARNING: View created with compilation errors`, indicating the view cannot yet be used. + +=== Dependency Invalidations and Rollback + +==== Active Invalidation Logic + +- `make_view_invalid()` is triggered when dependencies are dropped, altered, or otherwise compromised. +- The routine removes the `_RETURN` rule, clears `pg_depend` entries, resets `pg_class.relhasrules`, and truncates `pg_attribute` column metadata. +- It also captures `CREATE FORCE VIEW ... AS ` and saves it in `pg_force_view`, while setting `ident_case` to `IDENT_CASE_UNDEFINE` to indicate the SQL is system-generated. + +==== Observable Behavior After Invalidation + +- The view remains visible in metadata catalogs but runtime access detects the Force flag. +- Because `_RETURN` is missing, the executor calls `compile_force_view()` when opening the view; if compilation fails, it raises `view "." has errors`. +- Users can recover by issuing `ALTER VIEW ... COMPILE` or `CREATE OR REPLACE FORCE VIEW` once dependencies are ready. + +=== Automatic and Manual Compilation + +==== Automatic Compilation Triggers + +- `addRangeTableEntry()` in `parse_relation.c` and the target-relation open logic in `parse_clause.c` call `compile_force_view()` after detecting a Force view. +- 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. + +==== Manual Compilation + +- `AT_ForceViewCompile` executes during phase 2 in `tablecmds.c`, acquiring `AccessExclusiveLock` before invoking `compile_force_view()`. +- Successful compilation behaves like a normal `ALTER VIEW`; failures emit `WARNING: View altered with compilation errors`, and the view stays in placeholder mode. + +==== Column Checks and Metadata Updates + +- `compile_force_view()` reads `pg_force_view.source`, rebuilds a `ViewStmt`, and calls `compile_force_view_internal()`. +- The routine uses `checkViewColumns()` to compare legacy columns, allowing additions but rejecting incompatible type changes; new columns are applied through `AT_AddColumnToView()`. +- `_RETURN` is regenerated via `StoreViewQuery()`, and `DeleteForceView()` removes the catalog record so the view becomes a standard one again. diff --git a/EN/modules/ROOT/pages/master/7.18.adoc b/EN/modules/ROOT/pages/master/7.18.adoc new file mode 100644 index 0000000..7e6eb5a --- /dev/null +++ b/EN/modules/ROOT/pages/master/7.18.adoc @@ -0,0 +1,67 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += Force View + +== Purpose + +- This document explains the purpose of Force View in IvorySQL and shows how it enables users to create placeholder views when dependencies are not yet ready while keeping the behavior aligned with Oracle. +- Force View supports migrating views even when dependent tables are not yet ready, and can swiftly revert to regular views through automatic or explicit compilation once + dependencies are satisfied. + +== Feature Description + +- `CREATE [OR REPLACE] FORCE VIEW`: Creates a view object even when the query references missing tables or functions. The system retains the SQL definition and returns the message `WARNING: View created with compilation errors`. +- Automatic compilation: When a Force view is accessed, IvorySQL attempts recompilation. If it succeeds, the view becomes a normal view; if it fails, it raises `view "." has errors`. +- Dependency fallback: When a normal view is invalidated because dependencies are removed or altered, IvorySQL automatically converts it into a Force view, preserving the latest valid definition so it can be restored quickly once dependencies are rebuilt. + +== Test Cases + +=== Create a Force View with Missing Dependencies + +[source,sql] +---- +-- The base table does not exist, but the Force view placeholder is created successfully. +CREATE FORCE VIEW fv_customer AS +SELECT c_id, c_name FROM missing_customer; +-- Expected output: WARNING: View created with compilation errors +---- + +=== Automatic Compilation and Recovery + +[source,sql] +---- +-- Provide the missing dependency. +CREATE TABLE missing_customer( + c_id int primary key, + c_name text +); +INSERT INTO missing_customer VALUES (1, 'Alice'); + +-- Accessing the Force view triggers automatic compilation. +SELECT * FROM fv_customer; +-- On success, the view becomes a normal view and returns data. +---- + +=== Explicit Compilation and Failure Fallback + +[source,sql] +---- +-- Recreate the view definition as a Force view to make it invalid again. +CREATE OR REPLACE FORCE VIEW fv_customer AS +SELECT c_id, upper(c_name) AS c_name FROM missing_customer_v2; + +-- Explicit compilation fails because dependencies are still missing. +ALTER VIEW fv_customer COMPILE; +-- Expected output: WARNING: View altered with compilation errors + +-- Supply the required dependency and compile again. +CREATE TABLE missing_customer_v2( + c_id int, + c_name text +); +ALTER VIEW fv_customer COMPILE; +-- Expected output: ALTER VIEW succeeds and the view returns to normal. +----