Skip to content

Commit 1273bed

Browse files
committed
read only view
1 parent c9799d6 commit 1273bed

6 files changed

Lines changed: 810 additions & 0 deletions

File tree

CN/modules/ROOT/nav.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
** xref:master/oracle_compatibility/compat_sys_guid.adoc[17、sys_guid 函数]
2727
** xref:master/oracle_compatibility/compat_empty_string_to_null.adoc[18、空字符串转null]
2828
** xref:master/oracle_compatibility/compat_call_into.adoc[19、CALL INTO]
29+
** xref:master/oracle_compatibility/compat_read_only_view.adoc[20、视图只读]
2930
* 容器化与云服务
3031
** 容器化指南
3132
*** xref:master/containerization/k8s_deployment.adoc[K8S部署]
@@ -90,6 +91,7 @@
9091
**** xref:master/compatibility_features_design/sys_guid_function.adoc[sys_guid 函数]
9192
**** xref:master/compatibility_features_design/empty_string_to_null.adoc[空字符串转null]
9293
**** xref:master/compatibility_features_design/call_into.adoc[CALL INTO]
94+
**** xref:master/compatibility_features_design/read_only_view.adoc[视图只读]
9395
*** 内置函数
9496
**** xref:master/oracle_builtin_functions/sys_context.adoc[sys_context]
9597
**** xref:master/oracle_builtin_functions/userenv.adoc[userenv]
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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

Comments
 (0)