事件触发器
事件触发器
事件触发器补丁系列的主要目标是允许用户调整 DDL 命令,而无需挂钩到 ProcessUtility()。该设计已变得更加通用,以便我们可以支持重要的用例。
与 ProcessUtility 挂钩的主要优势
事件触发器的第一个版本提供的技术功能很少,是我们通过 ProcessUtility 挂钩无法实现的,那么,有什么区别呢?
- 您可以在 PL/pgSQL 中编写事件触发器函数,无需用 C 编写,构建扩展,将其发送到服务器的文件系统并最终安装它;
- 您实际上可以通过在 psql 中执行 \dy 来列出当前安装的事件触发器;
- 在单用户模式下,事件触发器被禁用,当您忙于修复生产服务时,它们不会妨碍您;
- 可以安装多个事件触发器,然后由 DBA 决定以何种顺序运行事件触发器,而无需弄清楚代码是如何实现的以及共享对象库将以何种顺序被_Init()初始化。
使用案例
我们试图能够解决这些用例
- 逻辑复制
- 审计
- 扩展的 DDL 支持
- 影响分析/性能
逻辑复制
我们希望以一种允许在其他地方以灵活的方式重播 DDL 更改的方式记录 DDL 更改。因此,我们需要访问原始文本和完全限定文本(一旦所有搜索路径都已解析),使两者都可用以允许复制决定使用哪个文本。我们不需要为每个可能的子语句触发操作,只需要足以准确地再现发生的事情即可。例如,DROP CASCADE 可能只需要作为 DROP CASCADE 重播。查询文本可从实用程序钩子获取,但需要完全完成的 SQL 事件信息。
审计
我们需要能够记录谁对谁/哪个对象做了什么,何时做的以及是否成功。因此,我们需要访问用户 ID、用户名、操作类别、操作文本、主题类别、主题 ID、操作时间、rc
性能
我们持有锁的时间有多长?操作花费了多长时间?
预期功能
事件触发器的第一个版本将在 PostgreSQL 9.3 中发布,并且在该版本中我们将有一组受限制的功能。
功能
因此,我们希望提供这些功能
- 在 DDL 命令之前或之后运行的事件触发器
- 用户函数应提供详细的信息
- 事件名称
- 命令标签
- 操作(创建、修改或删除)
- 对象类型(表、函数、视图等)
- 对象 OID
- 对象名称
- 对象所在的模式名称
- 支持 DROP CASCADE
- 支持删除多个对象
- 支持生成的命令
必需品与期望
其中一些项目是必不可少的;其他可能不是。
例如,操作、对象类型和对象 OID 似乎非常重要;如果您不知道事件的性质,那么用事件做有趣的事情会非常困难。
另一方面,对象名称和模式名称的必要性更加模糊,因为它们可能从 pg_catalog 中的对象表中查询。
- 如果要删除一个对象,那么可以在 ddl_command_start 事件中从 pg_catalog 获取其名称/模式,但一旦进程到达 ddl_command_end 事件,该数据将从 pg_catalog 中删除。
- 如果要更改对象的名称,那么 pg_catalog 中的名称/模式数据在 ddl_command_start 时的值将与进程到达 ddl_command_end 时的值不同。在这种情况下,要在每个事件中返回的名称/模式值可能在 ddl_command_(start|end) 事件之间不同;要在每个事件中提供的哪些值代表一个语义,应该确定下来。
已提交的补丁系列
以下是我们已有的内容列表
- 事件触发器的语法支持和文档。
- 让新的事件触发器功能真正发挥作用。
- http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=3a0e4d36ebd7f477822d5bae41ba121a40d22ccc
- 现在您可以实际调用您的函数了
- 它只知道tg_event和tg_tag
- 而且您只能用 PLpgSQL 编写
- 调整许多后端函数以返回 OID 而不是 void。
- 为事件触发器添加 ddl_command_end 支持。
9.3 中仍在进行中的功能
我们需要的并且尚未在 9.3 中提交的主要功能是
- 关于命令目标对象没有特定信息
- 无法访问解析树或命令字符串,无论是原始的还是规范化的
- 不支持每个命令有多个目标
- 一次只能让 DROP 针对多个对象
- DROP CASCADE 是这种情况的另一种特殊情况
- 不支持生成的命令
下一部分详细说明了我们尚未提交的这些功能的讨论内容。
讨论
为了找到最佳设计,我们仍然在讨论一些功能,以便能够实现它们。
作为一般说明,值得记住的是,大多数这些功能已经至少编码过一次(其中一些在 9.2 和 9.3 周期中经过审查后,已经进行了一次或多次重新设计和重构)。因此,这不是关于添加之前没有预见到的新功能,而是关于完成一个补丁系列以匹配几年前提出的功能子集。
目标是改进我们准备接受的功能子集,并评估我们是否可以将该子集作为本身有意义的东西发布。
例如,如果根本没有关于命令所针对对象的具体信息,那么很难看到您实际上可以使用事件触发器解决哪些用例。
生成的和内部的命令
为了上下文,以下命令将实际执行多个操作
CREATE TABLE foo(id serial primary key, f1 text);
我们将有CREATE SEQUENCE,然后是一个新的CREATE TABLE语句,使用我们刚刚创建的序列的名称,然后是ALTER SEQUENCE,最后是一个CREATE INDEX命令。PostgreSQL 后端的当前实现是从头开始创建一个解析节点,并以递归的方式将其再次发送到 ProcessUtility()。
当前的补丁提案是通过GENERATED的CONTEXT公开这些,该CONTEXT默认情况下不会与任何事件触发器匹配,并允许用户在对该实现细节感兴趣时选择加入。
如果将该实现细节公开给用户可能并不明智,因为这将使我们无法在以后清理混乱,假设任何开发人员将来有时间或动力这样做。
一个想法是只公开愿意用 C 编码事件触发器的用户,另一个想法是只通过钩子公开它们。钩子方法的问题在于,您需要构建和公开相同数量的信息。
DROP CASCADE / DROP OWNED 支持
已发送一个补丁以允许为每个作为命令的一部分被删除的对象调用事件触发器:https://postgresql.ac.cn/message-id/[email protected]
如何向事件触发器函数公开信息
目前的提议是公开一些神奇的 PL 变量。已提交的代码公开TG_EVENT和TG_TAG。
我们建议在此基础上添加最常见的几部分信息作为变量,并添加那些构建成本非常低的变量(它是一个常量字符串)
- 变量
- TG_OBJECTID
- TG_OBJECTNAME
- TG_SCHEMANAME
- TG_OPERATION
- TG_OBTYPENAME
- TG_CONTEXT
- 访问器
- pg_get_event_command_string()
关于TG_CONTEXT,请参见上一节。
命令字符串规范化
命令字符串的主要用例是逻辑复制。在这种用例中,非常重要的是要直接知道在哪些模式中创建了对象(分别删除、更改),以及在一定程度上了解自动命名对象(索引、约束、序列)的名称。
参见
- https://postgresql.ac.cn/message-id/CAFNqd5VuowUqXrHfWf_Ld-_szCUxaN3=RZD=XiVmNr_Yd=53QQ@mail.gmail.com
- https://postgresql.ac.cn/message-id/[email protected]
下一版本的功能
我们原本打算在 9.2 中提供的某些功能,将不得不等到我们准备好才能提供,这在 9.3 的时间范围内是不可能的。
INSTEAD OF
想法是能够安装一个事件触发器,它可以控制现有命令,以便能够重新定义它,也许用命令本身来定义它。
create event trigger my_create_extension instead of 'create extension' execute procedure my_create_extension();
create function my_create_extension() returns event_trigger language plpgsql as $$ begin alter event trigger my_create_extension disable; -- do some stuff here create extension tg_objectid; -- do some more stuff here, presumably end; $$;
不幸的是,已经很清楚的是,INSTEAD OF功能的实现很难在事件触发器的第一个版本中就实现正确,因为后端代码中的调用点必须非常小心地放置。
table_rewrite
想法是提供一个名为table_rewrite的事件,该事件将在每次要执行将重写整个表的命令时触发,以便 DBA 可以安装关于该事件的本地策略(例如,只在晚上没有满月时接受重写)。
插入时创建表
如今,一些开发人员正在习惯使用无模式数据库,并且希望在执行以下操作时具有以下行为:
INSERT INTO look_me_i_dont_exist(key, value) VALUES(1, 'foo');
PostgreSQL 会自动创建表look_me_i_dont_exist,它有两个列,key 类型为integer(可能是),value 类型为text。