PostgreSQL DDL事件触发器

标签

PostgreSQL , event trigger , ddl


背景

PostgreSQL 9.3 将引入事件触发器, 与普通触发器不同的是, 事件触发器是数据库全局的触发器, 可以由DDL事件来触发.

例如可以用来实施DDL的操作审计,以及防止某些用户执行某些DDL,在某些表上面执行DDL等等。

Unlike regular triggers, which are attached to a single table and capture only DML events, event triggers are global to a particular database and are capable of capturing DDL events.

事件触发器同样可以使用C, plpgsql或者其他的过程语言的函数来编写, 但是不能使用SQL语言函数来编写.

由于事件触发器涉及的权限较大, 例如能禁止DDL操作等, 所以只能使用超级用户创建事件触发器.

在创建事件触发器之前必须先创建触发器函数, 触发器函数的返回类型为event_trigger. (注意区分我们以前所熟悉的普通触发器函数的返回类型为trigger.)

事件触发器的语法

Command:     CREATE EVENT TRIGGER
Description: define a new event trigger
Syntax:
CREATE EVENT TRIGGER name
  ON event
  [ WHEN filter_variable IN (filter_value [, ... ]) [ AND ... ] ]
  EXECUTE PROCEDURE function_name()

语法解释 :

name : 触发器名称
event : 事件名称, 现在支持的事件为ddl_command_start 和 ddl_command_end.

支持触发事件触发器的DDL如下(包括select into) :

http://www.postgresql.org/docs/devel/static/event-trigger-matrix.html

但是触发事件中不包括对系统共享对象的CREATE, ALTER, DROP操作, 如 :

databases, roles, and tablespaces

同样对事件触发器本身的DDL操作也不会触发事件触发器.

The ddl_command_start event occurs just before the execution of a CREATE, ALTER, or DROP command.
As an exception, however, this event does not occur for DDL commands targeting shared objects - databases, roles, and tablespaces - or for command targeting event triggers themselves.
The event trigger mechanism does not support these object types.
ddl_command_start also occurs just before the execution of a SELECT INTO command, since this is equivalent to CREATE TABLE AS.   

The ddl_command_end event occurs just after the execution of this same set of commands.  

filter_variable目前只支持TAG
filter_value是http://www.postgresql.org/docs/devel/static/event-trigger-matrix.html这里定义的DDL
function_name就是我们创建好的事件触发器函数.

以plpgsql函数语言为例讲解事件触发器函数的创建方法

PL/pgSQL can be used to define event triggers.
PostgreSQL requires that a procedure that is to be called as an event trigger must be declared as a function with no arguments and a return type of event_trigger.
When a PL/pgSQL function is called as a event trigger, several special variables are created automatically in the top-level block. They are:  

TG_EVENT
Data type text; a string representing the event the trigger is fired for.  

TG_TAG
Data type text; variable that contains the command tag for which the trigger is fired.

事件触发器函数的返回类型为event_trigger, 同时事件触发器的顶级块带入了两个特殊变量, TG_EVENT和TG_TAG.

TG_EVENT表示EVENT信息, 如现在支持的为ddl_command_start 和 ddl_command_end.

TG_TAG表示的是DDL信息, 信息在 http://www.postgresql.org/docs/devel/static/event-trigger-matrix.html查询.

如果同一个事件上建立了多个事件触发器, 执行顺序按触发器名字的字母先后顺序来执行, 这个和普通触发器的触发规则是一样的.

如下 :

创建两个触发器函数, 返回event_trigger类型 :

CREATE OR REPLACE FUNCTION etgr1()
  RETURNS event_trigger
 LANGUAGE plpgsql
  AS $$
BEGIN
  RAISE NOTICE 'this is etgr1, event:%, command:%', tg_event, tg_tag;
 END;
$$;  

CREATE OR REPLACE FUNCTION etgr2()
  RETURNS event_trigger
 LANGUAGE plpgsql
  AS $$
BEGIN
  RAISE NOTICE 'this is etgr2, event:%, command:%', tg_event, tg_tag;
 END;
$$;

创建事件触发器, 这里未使用WHEN, 也就是所有的DDL都触发这些事件触发器(除了前面提到的触发器本身的DDL和共享对象的DDL) :

CREATE EVENT TRIGGER b ON ddl_command_start EXECUTE PROCEDURE etgr1();
CREATE EVENT TRIGGER a ON ddl_command_start EXECUTE PROCEDURE etgr2();

同一个事件类型ddl_command_start下创建了2个事件触发器, 事件触发器的名称分别为a和b, 调用的先后顺序按字母顺序来, 如下 :

digoal=# create table digoal(id int);
NOTICE:  this is etgr2, event:ddl_command_start, command:CREATE TABLE
NOTICE:  this is etgr1, event:ddl_command_start, command:CREATE TABLE
CREATE TABLE

查询当前数据库中有哪些事件触发器 :

digoal=# select * from pg_event_trigger ;
 evtname |     evtevent      | evtowner | evtfoid | evtenabled | evttags
---------+-------------------+----------+---------+------------+---------
 b       | ddl_command_start |       10 |   16669 | O          |
 a       | ddl_command_start |       10 |   16671 | O          |
(2 rows)

evtowner是创建事件触发器的用户, 例如上面两个事件触发器我是用postgres用户创建的。

digoal=# select rolname from pg_roles where oid=10;
 rolname
----------
 postgres
(1 row)

evtfoid指事件触发器函数的oid,

digoal=# select proname from pg_proc where oid=16669;
 proname
---------
 etgr1
(1 row)
digoal=# select proname from pg_proc where oid=16671;
 proname
---------
 etgr2
(1 row)

事件触发器和DDL语句本身是在同一个事务中处理的, 所以任何事件触发器抛出异常的话, 整个事务都会回滚, 并且后续的操作也不会执行下去.

例如 :

创建事件触发器函数, 函数直接抛出异常.

digoal=# create or replace function abort1() returns event_trigger as $$
declare
begin
  raise exception 'event:%, command:%. abort.', TG_EVENT, TG_TAG;
end;
$$ language plpgsql;

创建ddl_command_end 事件触发器

digoal=# create event trigger tg_abort1 on ddl_command_end execute procedure abort1();
CREATE EVENT TRIGGER

执行DDL语句, 如下, 在调用了a和b事件触发器后, 最后调用ddl_command_end的触发器, 抛出异常

digoal=# create table digoal1(id int);
NOTICE:  this is etgr2, event:ddl_command_start, command:CREATE TABLE
NOTICE:  this is etgr1, event:ddl_command_start, command:CREATE TABLE
ERROR:  event:ddl_command_end, command:CREATE TABLE. abort.

异常导致表创建失败

digoal=# \d digoal1
Did not find any relation named "digoal1".

再创建1个事件触发器, 放在ddl_command_start 事件中

digoal=# create event trigger tg_abort2 on ddl_command_start execute procedure abort1();
CREATE EVENT TRIGGER
digoal=# create table digoal1(id int);
NOTICE:  this is etgr2, event:ddl_command_start, command:CREATE TABLE
NOTICE:  this is etgr1, event:ddl_command_start, command:CREATE TABLE
ERROR:  event:ddl_command_start, command:CREATE TABLE. abort.

同样会导致DDL执行失败. 这就达到了禁止执行DDL的目的.

digoal=# create event trigger abort2 on ddl_command_start execute procedure abort1();
CREATE EVENT TRIGGER
digoal=# create table digoal1(id int);
NOTICE:  this is etgr2, event:ddl_command_start, command:CREATE TABLE
ERROR:  event:ddl_command_start, command:CREATE TABLE. abort.
digoal=# \d digoal1
Did not find any relation named "digoal1".

当前数据库中的事件触发器如下

digoal=# select * from pg_event_trigger ;
  evtname  |     evtevent      | evtowner | evtfoid | evtenabled | evttags
-----------+-------------------+----------+---------+------------+---------
 b         | ddl_command_start |       10 |   16669 | O          |
 a         | ddl_command_start |       10 |   16671 | O          |
 tg_abort1 | ddl_command_end   |       10 |   16676 | O          |
 tg_abort2 | ddl_command_start |       10 |   16676 | O          |
 abort2    | ddl_command_start |       10 |   16676 | O          |
(5 rows)

事件触发器应用举例

1. 禁止postgres用户在数据库digoal中执行CREATE TABLE和DROP TABLE命令.

首先把已有的事件触发器删除, 方便观看测试效果.

digoal=# drop event trigger tg_abort1;
DROP EVENT TRIGGER
digoal=# drop event trigger tg_abort2;
DROP EVENT TRIGGER
digoal=# drop event trigger abort2;
DROP EVENT TRIGGER
digoal=# drop event trigger a;
DROP EVENT TRIGGER
digoal=# drop event trigger b;
DROP EVENT TRIGGER
digoal=# select * from pg_event_trigger ;
 evtname | evtevent | evtowner | evtfoid | evtenabled | evttags
---------+----------+----------+---------+------------+---------
(0 rows)

创建触发器函数 :

CREATE OR REPLACE FUNCTION abort()
  RETURNS event_trigger
 LANGUAGE plpgsql
  AS $$
BEGIN
  if current_user = 'postgres' then
    RAISE EXCEPTION 'event:%, command:%', tg_event, tg_tag;
  end if;
 END;
$$;

创建触发器 :

digoal=# create event trigger a on ddl_command_start when TAG IN ('CREATE TABLE', 'DROP TABLE') execute procedure abort();
CREATE EVENT TRIGGER
digoal=# select * from pg_event_trigger ;
 evtname |     evtevent      | evtowner | evtfoid | evtenabled |            evttags
---------+-------------------+----------+---------+------------+-------------------------------
 a       | ddl_command_start |       10 |   16683 | O          | {"CREATE TABLE","DROP TABLE"}
(1 row)

测试postgres用户是否可以使用create table和drop table .

digoal=# \c digoal postgres
You are now connected to database "digoal" as user "postgres".

无法新建表了

digoal=# create table new(id int);
ERROR:  event:ddl_command_start, command:CREATE TABLE
digoal=# \d new
Did not find any relation named "new".
digoal=# \dt
          List of relations
 Schema |  Name   | Type  |  Owner
--------+---------+-------+----------
 public | digoal  | table | postgres
 public | digoal1 | table | postgres
 public | test    | table | postgres
(3 rows)

无法删表了

digoal=# drop table digoal;
ERROR:  event:ddl_command_start, command:DROP TABLE
digoal=# \d digoal
    Table "public.digoal"
 Column |  Type   | Modifiers
--------+---------+-----------
 id     | integer |

测试其他用户是否会有影响

digoal=# \c digoal digoal
You are now connected to database "digoal" as user "digoal".
digoal=> create table tbl(id int);
CREATE TABLE
digoal=> drop table tbl;
DROP TABLE

未受到影响.

其他

1. 事件触发器还可以结合会话参数session_replication_role来使用, 例如仅针对replica角色生效, 其他不生效.

Command:     ALTER EVENT TRIGGER
Description: change the definition of an event trigger
Syntax:
ALTER EVENT TRIGGER name DISABLE
ALTER EVENT TRIGGER name ENABLE [ REPLICA | ALWAYS ]
ALTER EVENT TRIGGER name OWNER TO new_owner
ALTER EVENT TRIGGER name RENAME TO new_name

具体用法可参见trigger的用法介绍 :

http://blog.163.com/digoal@126/blog/static/1638770402013283547959/

http://blog.163.com/digoal@126/blog/static/1638770402013211102130526/

2. 我们知道PostgreSQL没有像Oracle里面的DBA_OBJECTS表, 无法得知创建时间, ALTER时间.

使用事件触发器这个将会变成可能, 但是目前的事件触发器函数仅仅支持TG_EVENT和TG_TAG变量, 如果能加入TG_RELID, 那么就可以在DDL的时候记录这个事件到一个对象表中. 从而达到跟踪对象被执行DDL的时间的目的.

3. 事件触发器实际上是通过钩子实现的,例如 InvokeObjectPostCreateHook 在创建对象结束时调用。

src/backend/catalog/objectaccess.c

/*
 * RunObjectPostCreateHook
 *
 * It is entrypoint of OAT_POST_CREATE event
 */
void
RunObjectPostCreateHook(Oid classId, Oid objectId, int subId,
                                                bool is_internal)
{
        ObjectAccessPostCreate pc_arg;  

        /* caller should check, but just in case... */
        Assert(object_access_hook != NULL);  

        memset(&pc_arg, 0, sizeof(ObjectAccessPostCreate));
        pc_arg.is_internal = is_internal;  

        (*object_access_hook) (OAT_POST_CREATE,
                                                   classId, objectId, subId,
                                                   (void *) &pc_arg);
}

src/include/catalog/objectaccess.h

/* Core code uses these functions to call the hook (see macros below). */
extern void RunObjectPostCreateHook(Oid classId, Oid objectId, int subId,
                                                bool is_internal);
extern void RunObjectDropHook(Oid classId, Oid objectId, int subId,
                                  int dropflags);
extern void RunObjectPostAlterHook(Oid classId, Oid objectId, int subId,
                                           Oid auxiliaryId, bool is_internal);
extern bool RunNamespaceSearchHook(Oid objectId, bool ereport_on_volation);
extern void RunFunctionExecuteHook(Oid objectId);
......
/*
 * The following macros are wrappers around the functions above; these should
 * normally be used to invoke the hook in lieu of calling the above functions
 * directly.
 */  

#define InvokeObjectPostCreateHook(classId,objectId,subId)                      \
        InvokeObjectPostCreateHookArg((classId),(objectId),(subId),false)
#define InvokeObjectPostCreateHookArg(classId,objectId,subId,is_internal) \
        do {                                                                                                                    \
                if (object_access_hook)                                                                         \
                        RunObjectPostCreateHook((classId),(objectId),(subId),   \
                                                                        (is_internal));                                 \
        } while(0)  

......

在函数中执行DDL,同样被审查,因为HOOK不是语义层面的,而是执行层面的。

例如:

postgres=# create or replace function fe() returns event_trigger as $$
declare
begin
  if current_user = 'digoal' then
    raise exception 'can not execute ddl';
  end if;
end;
$$ language plpgsql strict;
CREATE FUNCTION  

postgres=# CREATE EVENT TRIGGER a ON ddl_command_start EXECUTE PROCEDURE fe();
CREATE EVENT TRIGGER  

postgres=# \c postgres digoal
You are now connected to database "postgres" as user "digoal".  

postgres=> create table tbl(id int);
ERROR:  can not execute ddl  

postgres=> do language plpgsql $$
postgres$> declare
postgres$> begin
postgres$>   execute 'create table tbl (id int)';
postgres$> end;
postgres$> $$;
ERROR:  can not execute ddl
CONTEXT:  SQL statement "create table tbl (id int)"
PL/pgSQL function inline_code_block line 4 at EXECUTE statement

参考

1. http://www.postgresql.org/docs/devel/static/event-triggers.html

2. http://www.postgresql.org/docs/devel/static/plpgsql-trigger.html

3. http://www.postgresql.org/docs/devel/static/sql-createeventtrigger.html

4. http://www.postgresql.org/docs/devel/static/catalog-pg-event-trigger.html

5. http://blog.163.com/digoal@126/blog/static/1638770402013283547959/

6. http://blog.163.com/digoal@126/blog/static/1638770402013211102130526/

7. http://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=3a0e4d36ebd7f477822d5bae41ba121a40d22ccc

时间: 2025-01-30 15:08:28

PostgreSQL DDL事件触发器的相关文章

DDL审计 , DDL逻辑复制 , 打造DDL统一管理入口 - PostgreSQL DDL 事件触发器应用

标签 PostgreSQL , event trigger , 事件触发器 , 用法详解 背景 DDL是非常重的操作,(锁大,或者会导致TABLE REWRITE导致消耗大量资源,影响大,例如DROP,TRUNCATE)也是数据库的使用过程中需要关注的. 通常企业会对DDL做一定的限制,不允许某些用户执行,或者不允许从网络登录的用户执行DDL. 同时DDL还有被审计的需求,所有的DDL可能都要记录到日志中. 构建逻辑复制的备库时,由于DDL不记录在REDO中,所以如果要复制DDL,通常的做法是使

PostgreSQL 事件触发器应用 - DDL审计

标签 PostgreSQL , 事件触发器 , DDL审计 , 表结构变更 , 建表等审计 背景 DDL语句的审计是非常重要的,目前PG的DDL审计记录在日志文件中.不便于查看. 为了让DDL事件记录到表中,方便查看,我们可以通过事件触发器来达到这个效果. 事件触发器审计DDL操作 事件触发器语法: Command: CREATE EVENT TRIGGER Description: define a new event trigger Syntax: CREATE EVENT TRIGGER

Oracle触发器6(建立系统事件触发器)

Oracle触发器6(建立系统事件触发器) (2012-02-14 09:23:36) 转载▼ <SCRIPT. $tag="触发器,建立,用于,事件,例程" $tag_code="64e2a8d9ae7359def02feebdbe14d783" $r_quote_bligid="71806002010108pu" $worldcup="0" var $worldcupball="0" ; <

事件触发器监控网络更智能

"事件查看器"我想大家一定都非常熟悉了,通过事件查看器,我们可以查看服务器中发生的一些重要事件,如应用程序的运行情况.DNS服务器的运行情况,系统的资源调用.网络访问等详细信息.这些信息常常可以成为我们分析问题,解决问题以及发现潜在威胁重要参考资料,可以有效的帮助我们及时的对系统的故障.潜在威胁进行处理,确保服务器的正常运转,保障内部网络的安全.但是,通过事件查看器查看系统相关日志文件,必须是我们网络管理员主动去查看,并且还要细心的审核每一条记录,这样才能清楚的了解发生了什么事件.如果

xaml-c#wpf的样式的事件触发器问题

问题描述 c#wpf的样式的事件触发器问题 请问大神这个问题是什么意思?应该如何改正? 解决方案 WPF 样式触发器 解决方案二: 就是个事件触发器-在鼠标进入时-触发某个按钮透明度的变化

javascript的事件触发器介绍的实现_javascript技巧

事件触发器从字面意思上可以很好的理解,就是用来触发事件的,但是有些没有用过的朋友可能就会迷惑了,事件不是通常都由用户在页面上的实际操作来触发的吗?这个观点不完全正确,因为有些事件必须由程序来实现,如自定义事件,jQuery的ajax框架的一些自定义事件就必须由事件触发器来实现.当然,在一些特殊情况下,用事件触发器来触发事件比用户的实际操作来触发事件更方便. 对于实现事件触发器,浏览器都有原生的方法来支持,但是在兼容性上又有很大的出入,这种兼容性的问题完全在意料之中,IE有自己的方法,其他标准浏览

wpf请问这段事件触发器代码为什么会异常?

问题描述 wpf请问这段事件触发器代码为什么会异常? <Button Grid.Row="1" Grid.Column="1" Margin="45,30,45,40" Content="确定" Name="button1"> <Button.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter">

ORACLE数据库DDL审计触发器与隐藏参数_system_trig_enabled

  最近,在我们开发库要对一套实例做一个DDL审计触发器,触发器代码如下所示: ---- 存储DDL语句的表          create table audit_ddl ( opertime timestamp PRIMARY KEY, ip varchar2(20), hostname varchar2(30), operation varchar2(30), object_type varchar2(30), object_name varchar2(30), sql_stmt clob

潘金莲改变了历史吗 - PostgreSQL舆情事件分析应用

标签 PostgreSQL , 独立事件分析 , 舆情分析 , 舆情事件 , 相关事件 , 行为轨迹 , 独立事件的流水相关性分析 , PostgreSQL服务端编程实践 背景 潘金莲改变了历史吗? 网上的段子 潘金莲撑开窗户,撑窗户滴棍子掉下去了,于是西门庆看到了,于是他们相遇了.如果潘金莲同学当时没有开窗,那么她就不会遇到西门庆. 如果没有遇到西门庆,那么她就不会被迫出轨,那样武松哥哥就不会怒发冲冠为红颜,这样他就不会奔上梁山. 武松就不会奔上梁上,不会奔上梁山之后,哪怕水泊梁山107将依旧