|
| 1 | +:sectnums: |
| 2 | +: sectnumlevels: 5 |
| 3 | + |
| 4 | +:imagesdir: ./_images |
| 5 | + |
| 6 | += 视图只读属性 |
| 7 | + |
| 8 | +== 目的 |
| 9 | + |
| 10 | +Oracle 数据库支持在创建视图时使用 `WITH READ ONLY` 子句,将视图设置为只读状态,防止对视图执行任何数据修改操作(INSERT、UPDATE、DELETE、MERGE)。为了兼容 Oracle 的这一特性,IvorySQL 实现了视图只读属性功能。当视图被标记为只读后,任何试图修改视图数据的 DML 操作都会被拒绝,从而保证 Oracle 应用程序在 IvorySQL 上的行为一致性。 |
| 11 | + |
| 12 | +== 实现说明 |
| 13 | + |
| 14 | +=== 语法与解析 |
| 15 | + |
| 16 | +==== 语法规则扩展 |
| 17 | + |
| 18 | +在 `ora_gram.y` 文件中添加了 `READ_ONLY_OPTION` 枚举值,并扩展了 `opt_check_option` 语法规则: |
| 19 | + |
| 20 | +[source,yacc] |
| 21 | +---- |
| 22 | +opt_check_option: |
| 23 | + WITH CHECK OPTION { $$ = CASCADED_CHECK_OPTION; } |
| 24 | + | WITH CASCADED CHECK OPTION { $$ = CASCADED_CHECK_OPTION; } |
| 25 | + | WITH LOCAL CHECK OPTION { $$ = LOCAL_CHECK_OPTION; } |
| 26 | + | WITH READ ONLY { $$ = READ_ONLY_OPTION; } /* 新增 */ |
| 27 | + | /* EMPTY */ { $$ = NO_CHECK_OPTION; } |
| 28 | + ; |
| 29 | +---- |
| 30 | + |
| 31 | +==== ViewStmt 结构体扩展 |
| 32 | + |
| 33 | +在 `parsenodes.h` 文件中,`ViewCheckOption` 枚举新增 `READ_ONLY_OPTION` 选项,`ViewStmt` 结构体新增 `readOnly` 字段: |
| 34 | + |
| 35 | +[source,c] |
| 36 | +---- |
| 37 | +typedef enum ViewCheckOption { |
| 38 | + NO_CHECK_OPTION, |
| 39 | + LOCAL_CHECK_OPTION, |
| 40 | + CASCADED_CHECK_OPTION, |
| 41 | + READ_ONLY_OPTION, /* WITH READ ONLY (Oracle compat) */ |
| 42 | +} ViewCheckOption; |
| 43 | +
|
| 44 | +typedef struct ViewStmt { |
| 45 | + // ... 其他字段 |
| 46 | + bool readOnly; /* WITH READ ONLY (Oracle compat) */ |
| 47 | +} ViewStmt; |
| 48 | +---- |
| 49 | + |
| 50 | +解析时设置 `readOnly` 标志: |
| 51 | + |
| 52 | +[source,c] |
| 53 | +---- |
| 54 | +n->readOnly = ($10 == READ_ONLY_OPTION); |
| 55 | +n->withCheckOption = n->readOnly ? NO_CHECK_OPTION : $10; |
| 56 | +---- |
| 57 | + |
| 58 | +需要注意的是,`WITH READ ONLY` 与 `WITH CHECK OPTION` 是互斥的,不能同时使用。 |
| 59 | + |
| 60 | +=== 关系选项系统 |
| 61 | + |
| 62 | +==== read_only 关系选项 |
| 63 | + |
| 64 | +在 `reloptions.c` 文件中定义了 `read_only` 视图关系选项: |
| 65 | + |
| 66 | +[source,c] |
| 67 | +---- |
| 68 | +/* reloptions.c */ |
| 69 | +{ |
| 70 | + {"read_only", |
| 71 | + "Prevents INSERT, UPDATE, and DELETE on this view (Oracle compatibility)", |
| 72 | + RELOPT_KIND_VIEW, |
| 73 | + AccessExclusiveLock}, |
| 74 | + false |
| 75 | +}, |
| 76 | +
|
| 77 | +/* view_reloptions() 函数中 */ |
| 78 | +{"read_only", RELOPT_TYPE_BOOL, |
| 79 | + offsetof(ViewOptions, read_only)}, |
| 80 | +---- |
| 81 | + |
| 82 | +==== ViewOptions 结构体扩展 |
| 83 | + |
| 84 | +在 `rel.h` 文件中,`ViewOptions` 结构体新增 `read_only` 字段: |
| 85 | + |
| 86 | +[source,c] |
| 87 | +---- |
| 88 | +typedef struct ViewOptions { |
| 89 | + // ... 其他字段 |
| 90 | + bool read_only; /* WITH READ ONLY (Oracle compat) */ |
| 91 | +} ViewOptions; |
| 92 | +---- |
| 93 | + |
| 94 | +==== RelationIsReadOnlyView 宏 |
| 95 | + |
| 96 | +通过 `RelationIsReadOnlyView` 宏可以快速判断视图是否为只读: |
| 97 | + |
| 98 | +[source,c] |
| 99 | +---- |
| 100 | +#define RelationIsReadOnlyView(relation) \ |
| 101 | + (AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW), \ |
| 102 | + (relation)->rd_options && \ |
| 103 | + ((ViewOptions *) (relation)->rd_options)->read_only) |
| 104 | +---- |
| 105 | + |
| 106 | +=== 视图创建处理 |
| 107 | + |
| 108 | +在 `view.c` 文件的 `DefineView` 函数中处理 `WITH READ ONLY` 选项: |
| 109 | + |
| 110 | +[source,c] |
| 111 | +---- |
| 112 | +/* DefineView() 中处理 WITH READ ONLY */ |
| 113 | +if (stmt->readOnly) |
| 114 | +{ |
| 115 | + /* 设置 read_only 关系选项 */ |
| 116 | + options = transformViewOptions(RelationGetRelid(newRelationView), |
| 117 | + stmt->options, true); |
| 118 | + setRelOptions(newRelationView, options, true, AccessExclusiveLock); |
| 119 | +} |
| 120 | +---- |
| 121 | + |
| 122 | +同时在 `compile_force_view_internal` 函数中也支持 `WITH READ ONLY`,确保 FORCE VIEW 可以正常使用只读属性。 |
| 123 | + |
| 124 | +=== DML 执行拦截 |
| 125 | + |
| 126 | +==== 执行层拦截 |
| 127 | + |
| 128 | +在 `execMain.c` 文件的 `CheckValidResultRel()` 函数中,对只读视图的 DML 操作进行拦截: |
| 129 | + |
| 130 | +[source,c] |
| 131 | +---- |
| 132 | +/* execMain.c */ |
| 133 | +if (RelationIsReadOnlyView(resultRel)) |
| 134 | + ereport(ERROR, |
| 135 | + (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 136 | + errmsg("cannot modify view \"%s\"", |
| 137 | + RelationGetRelationName(resultRel)), |
| 138 | + errhint("The view is defined as read-only."))); |
| 139 | +---- |
| 140 | + |
| 141 | +当执行 INSERT、UPDATE、DELETE 或 MERGE 语句时,如果目标视图被标记为只读,将抛出错误。 |
| 142 | + |
| 143 | +==== 重写层拦截 |
| 144 | + |
| 145 | +在 `rewriteHandler.c` 文件的 `rewriteTargetView()` 函数中也添加了相同的检查: |
| 146 | + |
| 147 | +[source,c] |
| 148 | +---- |
| 149 | +/* rewriteHandler.c */ |
| 150 | +if (RelationIsReadOnlyView(view)) |
| 151 | + ereport(ERROR, |
| 152 | + (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 153 | + errmsg("cannot modify view \"%s\"", |
| 154 | + RelationGetRelationName(view)), |
| 155 | + errhint("The view is defined as read-only."))); |
| 156 | +---- |
| 157 | + |
| 158 | +这确保了在查询重写层面也能阻止对只读视图的修改操作。 |
| 159 | + |
| 160 | +=== pg_dump 支持 |
| 161 | + |
| 162 | +在 `pg_dump.c` 文件中添加了对 `WITH READ ONLY` 属性的导出和恢复支持: |
| 163 | + |
| 164 | +[source,c] |
| 165 | +---- |
| 166 | +/* pg_dump.h - TableInfo 结构体新增字段 */ |
| 167 | +typedef struct _tableInfo { |
| 168 | + // ... 其他字段 |
| 169 | + bool readOnly; /* WITH READ ONLY (Oracle compat) */ |
| 170 | + // ... 其他字段 |
| 171 | +} _tableInfo; |
| 172 | +
|
| 173 | +/* pg_dump.c - 提取 read_only 选项 */ |
| 174 | +if (strcmp(options[i].defname, "read_only") == 0) |
| 175 | + info->readOnly = defGetBoolean(options[i].def); |
| 176 | +---- |
| 177 | + |
| 178 | +导出时,`dumpTableSchema()` 和 `dumpRule()` 函数会在适当位置输出 `WITH READ ONLY` 子句,确保只读属性在数据库迁移过程中得以保留。 |
| 179 | + |
| 180 | +== 使用示例 |
| 181 | + |
| 182 | +=== 创建只读视图 |
| 183 | + |
| 184 | +[source,sql] |
| 185 | +---- |
| 186 | +-- 创建带 WITH READ ONLY 的视图 |
| 187 | +CREATE VIEW emp_view AS |
| 188 | +SELECT * FROM employees |
| 189 | +WITH READ ONLY; |
| 190 | +
|
| 191 | +-- 在只读视图上执行 DML 将被拒绝 |
| 192 | +INSERT INTO emp_view VALUES (1, 'John', 5000); |
| 193 | +-- ERROR: cannot modify view "emp_view" |
| 194 | +-- HINT: The view is defined as read-only. |
| 195 | +
|
| 196 | +UPDATE emp_view SET salary = 6000 WHERE id = 1; |
| 197 | +-- ERROR: cannot modify view "emp_view" |
| 198 | +-- HINT: The view is defined as read-only. |
| 199 | +
|
| 200 | +DELETE FROM emp_view WHERE id = 1; |
| 201 | +-- ERROR: cannot modify view "emp_view" |
| 202 | +-- HINT: The view is defined as read-only. |
| 203 | +---- |
| 204 | + |
| 205 | +=== 创建或替换只读视图 |
| 206 | + |
| 207 | +[source,sql] |
| 208 | +---- |
| 209 | +-- 使用 OR REPLACE 保留只读属性 |
| 210 | +CREATE OR REPLACE VIEW emp_view AS |
| 211 | +SELECT id, name, department FROM employees |
| 212 | +WITH READ ONLY; |
| 213 | +---- |
| 214 | + |
| 215 | +=== FORCE VIEW 与只读属性结合 |
| 216 | + |
| 217 | +[source,sql] |
| 218 | +---- |
| 219 | +-- 创建 FORCE VIEW 并设置为只读 |
| 220 | +CREATE OR REPLACE FORCE VIEW emp_force_view AS |
| 221 | +SELECT * FROM employees |
| 222 | +WITH READ ONLY; |
| 223 | +---- |
| 224 | + |
| 225 | +=== 与 WITH CHECK OPTION 的互斥性 |
| 226 | + |
| 227 | +[source,sql] |
| 228 | +---- |
| 229 | +-- 错误: WITH READ ONLY 和 WITH CHECK OPTION 不能同时使用 |
| 230 | +CREATE VIEW emp_view AS |
| 231 | +SELECT * FROM employees |
| 232 | +WITH CHECK OPTION |
| 233 | +WITH READ ONLY; |
| 234 | +-- ERROR: READ ONLY and CHECK OPTION are mutually exclusive |
| 235 | +---- |
0 commit comments