|
| 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()` 删除目录项,视图恢复为普通状态。 |
0 commit comments