PostgreSQL7.0手册-程序员手册 -47. 触发器
内容
创建触发器
与触发器管理器交互
数据改变的可视性
例子
Postgres 拥有多种客户接口,象Perl,Tcl,Python 和 C,还有两种 过程语言 (PL).同样也可能把 C 函数的调用作为触发器的动作.要注意当前版本还不支持语句级(STATEMENT-level)的触发器事件.目前你可以在 INSERT,DELETE 或 UPDATE 一条记录上声明 BEFORE 或 AFTER (之前或之后)作为触发器事件.
创建触发器
如果发生了触发器事件,触发器管理器(由执行器调用)初始化全局结构 TriggerData *CurrentTriggerData (下面描述)并调用触发器函数来操作事件.
触发器函数必须作为一个没有参数并且返回 opaque 的函数在创建触发器之前创建.
创建触发器的语法如下:
CREATE TRIGGER trigger [ BEFORE AFTER ] [ INSERT DELETE UPDATE [ OR ... ] ]
ON relation FOR EACH [ ROW STATEMENT ]
EXECUTE PROCEDURE procedure
(args);
这里的参数是:
trigger
如果你想删除触发器,那么这是所使用的触发器的名称.它被当做 DROP TRIGGER 命令的一个参数.
BEFORE, AFTER
决定函数是在事件之前还是之后调用.
INSERT, DELETE, UPDATE
命令的下一元素决定在什么事件上触发该函数.多个事件可以用 OR 分隔声明.
relation
关系名,决定该事件应用于哪个表.
ROW, STATEMENT
FOR EACH 子句决定该触发器是为每个受影响的行触发还是在整个语句完成之前(或之后)触发.
procedure
过程名就是调用的 C 函数.
args
参数是放在 CurrentTriggerData 结构里面传给函数的.传递参数给函数的目的是为了允许类似要求的不同的触发器调用同样的函数.
同样,函数可以被用于触发不同的关系(这些函数被命名为"通用触发器函数")。
做为使用上面两个特性的例子,可以有一个通用函数把两个字段名称作为参数:把当前用户作为一个参数而把当前时标做为另一个参数.这样就允许我们在 INSERT 事件上写一个触发器来自动跟踪一个事务表里的记录的创建.如果用于一个 UPDATE 事件,同样我们可以当 "最后更新"(last updated)函数来用.
触发器函数返回 HeapTuple 给调用它的执行器.这个返回在那些在 INSERT,DELETE 或 UPDATE 操作之后执行的触发器上被忽略,但它允许那些 BEFORE 触发器用来:
返回 NULL 以忽略对当前记录的操作(这样该记录就将不会被插入/更新/删除).
返回一个指向另一个记录的指针(只用于 INSERT 和 UPDATE ),该指针所指记录将代替原始记录被插入(或者作为在 UPDATE 中记录的新版本).
注意,CREATE TRIGGER 句柄将不进行任何初始化工作.这一点将在以后进行修改.同样,如果多于一个触发器为同样的事件定义在同样的关系上,触发器触发的顺序将不可预料.这一点以后也会修改.
如果一个触发器函数执行 SQL-查询(使用 SPI)那么这些查询可能再次触发触发器.这就是所谓的嵌套触发器.对嵌套触发器的嵌套深度没有显式的限制.
如果一个触发器是被 INSERT 触发并且插入一个新行到同一关系中,然后该触发器将被再次触发.目前对这种情况没有提供任何同步(等)的措施,这一点也可能会修改.目前,回归测试里有一个函数 funny_dup17() 使用了一些技巧避免对自身的递归(嵌套)调用...
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
与触发器管理器交互
如我们前面所说,当触发器管理器调用函数时,结构 TriggerData *CurrentTriggerData 是 NOT NULL (非空)的并且初始化过的.所以最好检查 CurrentTriggerData 结构以防止在开始时就是 NULL (空)的并且在获取信息之后把它清空以避免从非触发器管理器来的触发器函数.
结构(struct)TriggerData 在 src/include/commands/trigger.h 里定义:
typedef struct TriggerData
{
TriggerEvent tg_event;
Relation tg_relation;
HeapTuple tg_trigtuple;
HeapTuple tg_newtuple;
Trigger *tg_trigger;
} TriggerData;
这些成员的定义如下:
tg_event
描述调用函数的事件.你可以使用下面的宏来检验 tg_event:
TRIGGER_FIRED_BEFORE(tg_event)
returns TRUE if trigger fired BEFORE.(触发器由 BEFORE 触发返回 TRUE)
TRIGGER_FIRED_AFTER(tg_event)
Returns TRUE if trigger fired AFTER.(触发器由 AFTER 触发返回 TRUE)
TRIGGER_FIRED_FOR_ROW(event)
Returns TRUE if trigger fired for a ROW-level event.(触发器行级(ROW-level)触发返回 TRUE)
TRIGGER_FIRED_FOR_STATEMENT(event)
Returns TRUE if trigger fired for STATEMENT-level event.(触发器语句级(ROW-level)触发返回 TRUE)
TRIGGER_FIRED_BY_INSERT(event)
Returns TRUE if trigger fired by INSERT.(触发器由 INSERT 触发返回 TRUE)
TRIGGER_FIRED_BY_DELETE(event)
Returns TRUE if trigger fired by DELETE.(触发器由 DELETE 触发返回 TRUE)
TRIGGER_FIRED_BY_UPDATE(event)
Returns TRUE if trigger fired by UPDATE.(触发器由 UPDATE 触发返回 TRUE)
tg_relation
是一个指向描述被触发的关系的结构的指针.请参考src/include/utils/rel.h 获取关于此结构的详细信息.最让人感兴趣的事情是 tg_relation->rd_att (关系记录的描述) 和 tg_relation->rd_rel->relname (关系名.这个变量的类型不是 char*,而是 NameData.用 SPI_getrelname(tg_relation) 获取 char* ,如果你需要一份名字的拷贝的话).
tg_trigtuple
是一个指向触发触发器的记录的指针.这是一个正在被 插入(INSERT),删除(DELETE)或更新(UPDATE)的记录.如果是 INSERT/DELETE ,那么这就是你将返回给执行器的东西--如果你不想用另一条记录覆盖此记录(INSERT)或忽略操作.
tg_newtuple
如果是 UPDATE,这是一个指向新版本的记录的指针,如果是 INSERT 或 DELETE,就是 NULL这就是你将返回给执行器的东西-- 如果你是 UPDATE 并且你不想用另一条记录替换这条记录或忽略操作.
tg_trigger
是一个指向结构 Trigger 的指针,该结构在 src/include/utils/rel.h 里定义:
typedef struct Trigger
{
Oid tgoid;
char *tgname;
Oid tgfoid;
FmgrInfo tgfunc;
int16 tgtype;
bool tgenabled;
bool tgisconstraint;
bool tgdeferrable;
bool tginitdeferred;
int16 tgnargs;
int16 tgattr[FUNC_MAX_ARGS];
char **tgargs;
} Trigger;
tgname 是触发器的名称,tgnargs 是在 tgargs 里参数的数量,tgargs 是一个指针数组,数组里每个指针指向在 CREATE TRIGGER 语句里声明的参数.其他成员只在内部使用.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
数据改变的可视性
Postgres 数据修改的可视性规则:在查询执行过程中,由查询本身造成的数据修改(通过 SQL-函数,SPI-函数,触发器)对查询扫描而言是不可见的.例如,在查询
INSERT INTO a SELECT * FROM a
里,插入的记录对 SELECT 的扫描是不可见的.实际上,这么做在数据库内部形成非递归的数据库表的复制(当然是要受到唯一索引规则的制约的)
但是请记住在 SPI 文挡里关于可视性的注释:
由查询 Q 造成的改变可以为查询 Q 以后运行的查询可见,不管这些查询
是在查询 Q 内部开始运行(在 Q 运行期间)的还是Q运行完毕后开始运行的
这些对触发器而言也是正确的,尽管被插入的记录 (tg_trigtuple)对 BEFORE 触发器是不可见的,这个刚被插入的记录却可以被一个 AFTER 触发器看到,并且对所有这个(触发器)以后的所有 BEFORE/AFTER 触发器均可见!
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
例子
在 src/test/regress/regress.c 和 contrib/spi 里有更复杂的例子.
这里是一个非常简单的触发器使用的例子.函数 trigf 报告在被触发的关系 ttest 中记录数量,并且如果查询试图把 NULL 插入到 x 里(例如 -它做为一个 NOT NULL 约束但不退出事务的约束)时略过操作.
#include "executor/spi.h" /* this is what you need to work with SPI */
#include "commands/trigger.h" /* -"- and triggers */
HeapTuple trigf(void);
HeapTuple
trigf()
{
TupleDesc tupdesc;
HeapTuple rettuple;
char *when;
bool checknull = false;
bool isnull;
int ret, i;
if (!CurrentTriggerData)
elog(WARN, "trigf: triggers are not initialized");
/* tuple to return to Executor */
if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event))
rettuple = CurrentTriggerData->tg_newtuple;
else
rettuple = CurrentTriggerData->tg_trigtuple;
/* check for NULLs ? */
if (!TRIGGER_FIRED_BY_DELETE(CurrentTriggerData->tg_event) &&
TRIGGER_FIRED_BEFORE(CurrentTriggerData->tg_event))
checknull = true;
if (TRIGGER_FIRED_BEFORE(CurrentTriggerData->tg_event))
when = "before";
else
when = "after ";
tupdesc = CurrentTriggerData->tg_relation->rd_att;
CurrentTriggerData = NULL;
/* Connect to SPI manager */
if ((ret = SPI_connect()) < 0)
elog(WARN, "trigf (fired %s): SPI_connect returned %d", when, ret);
/* Get number of tuples in relation */
ret = SPI_exec("select count(*) from ttest", 0);
if (ret < 0)
elog(WARN, "trigf (fired %s): SPI_exec returned %d", when, ret);
i = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
elog (NOTICE, "trigf (fired %s): there are %d tuples in ttest", when, i);
SPI_finish();
if (checknull)
{
i = SPI_getbinval(rettuple, tupdesc, 1, &isnull);
if (isnull)
rettuple = NULL;
}
return (rettuple);
}
然后,编译和创建表 ttest (x int4):
create function trigf () returns opaque as
'...path_to_so' language 'c';
vac=> create trigger tbefore before insert or update or delete on ttest
for each row execute procedure trigf();
CREATE
vac=> create trigger tafter after insert or update or delete on ttest
for each row execute procedure trigf();
CREATE
vac=> insert into ttest values (null);
NOTICE:trigf (fired before): there are 0 tuples in ttest
INSERT 0 0
-- Insertion skipped and AFTER trigger is not fired
vac=> select * from ttest;
x
-
(0 rows)
vac=> insert into ttest values (1);
NOTICE:trigf (fired before): there are 0 tuples in ttest
NOTICE:trigf (fired after ): there are 1 tuples in ttest
^^^^^^^^
remember what we said about visibility.
INSERT 167793 1
vac=> select * from ttest;
x
-
1
(1 row)
vac=> insert into ttest select x * 2 from ttest;
NOTICE:trigf (fired before): there are 1 tuples in ttest
NOTICE:trigf (fired after ): there are 2 tuples in ttest
^^^^^^^^
remember what we said about visibility.
INSERT 167794 1
vac=> select * from ttest;
x
-
1
2
(2 rows)
vac=> update ttest set x = null where x = 2;
NOTICE:trigf (fired before): there are 2 tuples in ttest
UPDATE 0
vac=> update ttest set x = 4 where x = 2;
NOTICE:trigf (fired before): there are 2 tuples in ttest
NOTICE:trigf (fired after ): there are 2 tuples in ttest
UPDATE 1
vac=> select * from ttest;
x
-
1
4
(2 rows)
vac=> delete from ttest;
NOTICE:trigf (fired before): there are 2 tuples in ttest
NOTICE:trigf (fired after ): there are 1 tuples in ttest
NOTICE:trigf (fired before): there are 1 tuples in ttest
NOTICE:trigf (fired after ): there are 0 tuples in ttest
^^^^^^^^
remember what we said about visibility.
DELETE 2
vac=> select * from ttest;
x
-
(0 rows)
--------------------------------------------------------------------------------