可更新视图
本项目旨在实现兼容 SQL92 的可更新视图。 目前,已有一个原型实现单关系视图,通过现有的规则重写系统,支持本地和级联检查选项。
建议的补丁已提交以包含在 PostgreSQL 8.2 版本中,但是,有一些严重的担忧,使得需要重新思考整个实现和规则重写系统。 附件中的讨论总结了一些最需要在最终实现中解决的重要问题。
创建此页面是为了展示开发过程,提交想法,并在设计新的核心功能时为开发人员提供支持。
可更新视图至少应该做什么?
绝对最小的目标是达到与 SQL-92 标准的兼容性。 它们描述如下
- 只允许一个基本关系。 如果找到一个联接视图,我们将该视图视为不可更新。
- 不允许分组、区分、联合或聚合
- 视图的目标列表应仅包含“真实”列,这些列直接从底层视图/关系派生而来。 我们可以区分“可插入”、“可能可插入”和“不可插入”,如 SQL-99 中建议的那样,但是一旦我们获得函数、常量或服务器端变量,我们将整个目标列表视为不可插入。
- 检查选项只能应用于可更新(可插入)视图。
到目前为止,我们应该能够执行以下操作
CREATE TABLE foo(id INTEGER PRIMARY KEY, name TEXT NOT NULL); CREATE VIEW vfoo AS SELECT id, name FROM foo WHERE id BETWEEN 1 AND 10000 WITH CHECK OPTION;
这将创建一个表 foo 和一个可更新视图 vfoo,允许插入元组,元组的 ID 值介于 1 和 10000 之间。 其他值将被拒绝,并且无法更新值以包含违反此条件的 id 值。 如图所示,我们应该将视图限定符与视图创建时的 WITH CHECK OPTION
选项一起考虑为视图约束,并根据该约束验证插入或更新的任何数据。 我认为我们不需要关心删除操作,因为我们无法从视图中删除不在视图中的数据。
SQL 标准为 CHECK OPTION
定义了另外两个关键字
级联检查选项
本地检查选项
CREATE VIEW vfoo_name AS SELECT id, name FROM vfoo WHERE name LIKE 'bernd%' WITH CASCADED CHECK OPTION;
CASCADED
强制检查所有底层视图定义定义的所有视图约束。 LOCAL
仅检查当前选定视图上定义的视图约束。 这使事情变得更加复杂,因为我们需要递归地检查视图更新链中是否有 CASCADED
或 LOCAL
检查选项。 上面的示例将根据视图 vfoo_name 和 vfoo 定义的视图限定符(视图约束)检查插入和更新操作。 如果我们将 CASCADED
替换为 LOCAL
,则仅根据选定视图 vfoo_name 的视图约束验证新元组。
当前实现
转换器
当前实现涵盖了 SQL-92 标准定义的所有要求,如上所述。 我们不支持联接视图和部分可更新列列表(如 SQL-99 中描述的可插入、可插入和不可插入视图)。
整个功能是在现有的规则重写系统之上实现的。 可更新视图功能的入口点是 src/backend/commands/view.c
,函数 DefineView()
。 这将创建必要的虚拟关系和内容,最后但并非最不重要的是通过 DefineViewRules()
创建可更新视图的规则。 这将级联到 CreateViewUpdateRules()
,它是 src/backends/rewrite/viewUpdate.c
中用于自动创建所有可更新视图规则的入口点。
我们在那里定义了三个转换器,它们将给定的 SELECT
查询树转换为其相应的 DELETE
、UPDATE
或 INSERT
查询树
- transform_select_to_delete()
- transform_select_to_update()
- transform_select_to_insert()
将给定的查询树转换为特定备用操作并非易事,但在第一眼看上去并不难。 PostgreSQL 的 Query 结构在那里定义了一些重要的字段,我们需要触碰它们
- targetList
- joinTree(包含
FROM
和WHERE
表达式/子句) - rtable
大多数工作是重写 varno 和 resno,并调整目标条目的顺序,因为视图可能具有不同的列“顺序”。 请注意,在 SQL 中没有“排序列”的定义,但所有列都以特定顺序存储在内部,因此我们必须注意这一点。
在进入任何转换器之前,我们调用 checkTree()
函数来检查给定的 SQL 查询树,以匹配 SQL-92 要求。 如果发现给定的查询树违反了上面描述的规则,我们将拒绝任何尝试将其转换为相应的更新操作。
这些操作的结果是三个规则,它们提供了可更新视图的必需 DML 功能(根据 SQL-92),命名如下
_INSERT
:插入规则_UPDATE
:更新规则_DELETE
:删除规则
当使用 REPLACE
关键字修改现有视图时,我们需要注意替换这些规则。
现有实现的问题
CHECK OPTION
和多表达式评估
建议的 CHECK OPTION
实现存在一些问题,主要是由于易失函数在规则中产生不正确的结果,因为表达式在规则重写系统 [2] 中被双重评估。
我们需要重新考虑现有实现,并以允许以安全方式评估任何视图条件的方式重新实现 CHECK OPTION
。 一个想法是将所有 CHECK OPTION
推入表约束 [4]。 然后,重写器负责收集所有必要的约束,并将它们应用于当前查询树。 但是,为了可靠地实现这一点,我们需要一种计划失效机制来调整缓存的查询计划,并向它们教授在那里收集的新约束。 Tom Lane 计划在 8.3 中实现此功能,所以让我们拭目以待。 另一个问题是,当我们想在检查选项中覆盖子查询时,就会出现。 SQL 标准似乎允许这样做,其他数据库也相应地做到了。 我们需要检查我们的约束代码是否能够处理此类表达式。
另一个想法是使用类似于 RI 触发器的东西(与 PostgreSQL 中实现外键检查的方式非常相似)。 我不知道 SQL 标准是否允许在 CHECK OPTION
中使用子查询,但如果是真的,这将很难在检查约束中实现。 我没有深入研究这个想法,但我认为值得考虑。
规则系统中的缺陷
事务可见性逻辑在规则上强制执行,就像在任何其他 SQL 命令上一样。 对于多操作规则,我们需要考虑在每个单独操作之间有一个 CommandCounterIncrement(),它迫使后续操作“看到”任何先前操作的修改。 当我们准备更新联接视图及其联接键(例如在 UPDATE 命令中)时,这一点变得很重要。