PostgreSQL7.0手冊(cè)-程序員手冊(cè) -47. 觸發(fā)器
2019-09-08 23:34:18
供稿:網(wǎng)友
第四十七章. 觸發(fā)器
內(nèi)容
創(chuàng)建觸發(fā)器
與觸發(fā)器管理器交互
數(shù)據(jù)改變的可視性
例子
Postgres 擁有多種客戶接口,象Perl,Tcl,Python 和 C,還有兩種 過(guò)程語(yǔ)言?。≒L).同樣也可能把 C 函數(shù)的調(diào)用作為觸發(fā)器的動(dòng)作.要注意當(dāng)前版本還不支持語(yǔ)句級(jí)(STATEMENT-level)的觸發(fā)器事件.目前你可以在 INSERT,DELETE 或 UPDATE 一條記錄上聲明 BEFORE 或 AFTER (之前或之后)作為觸發(fā)器事件.
創(chuàng)建觸發(fā)器
如果發(fā)生了觸發(fā)器事件,觸發(fā)器管理器(由執(zhí)行器調(diào)用)初始化全局結(jié)構(gòu) TriggerData *CurrentTriggerData?。ㄏ旅婷枋觯┎⒄{(diào)用觸發(fā)器函數(shù)來(lái)操作事件.
觸發(fā)器函數(shù)必須作為一個(gè)沒(méi)有參數(shù)并且返回 opaque 的函數(shù)在創(chuàng)建觸發(fā)器之前創(chuàng)建.
創(chuàng)建觸發(fā)器的語(yǔ)法如下:
CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT | DELETE | UPDATE [ OR ... ] ]
ON relation FOR EACH [ ROW | STATEMENT ]
EXECUTE PROCEDURE procedure
(args);
這里的參數(shù)是:
trigger
如果你想刪除觸發(fā)器,那么這是所使用的觸發(fā)器的名稱.它被當(dāng)做 DROP TRIGGER 命令的一個(gè)參數(shù).
BEFORE, AFTER
決定函數(shù)是在事件之前還是之后調(diào)用.
INSERT, DELETE, UPDATE
命令的下一元素決定在什么事件上觸發(fā)該函數(shù).多個(gè)事件可以用 OR 分隔聲明.
relation
關(guān)系名,決定該事件應(yīng)用于哪個(gè)表.
ROW, STATEMENT
FOR EACH 子句決定該觸發(fā)器是為每個(gè)受影響的行觸發(fā)還是在整個(gè)語(yǔ)句完成之前(或之后)觸發(fā).
procedure
過(guò)程名就是調(diào)用的 C 函數(shù).
args
參數(shù)是放在 CurrentTriggerData 結(jié)構(gòu)里面?zhèn)鹘o函數(shù)的.傳遞參數(shù)給函數(shù)的目的是為了允許類似要求的不同的觸發(fā)器調(diào)用同樣的函數(shù).
同樣,函數(shù)可以被用于觸發(fā)不同的關(guān)系(這些函數(shù)被命名為"通用觸發(fā)器函數(shù)")。
做為使用上面兩個(gè)特性的例子,可以有一個(gè)通用函數(shù)把兩個(gè)字段名稱作為參數(shù):把當(dāng)前用戶作為一個(gè)參數(shù)而把當(dāng)前時(shí)標(biāo)做為另一個(gè)參數(shù).這樣就允許我們?cè)凇NSERT 事件上寫(xiě)一個(gè)觸發(fā)器來(lái)自動(dòng)跟蹤一個(gè)事務(wù)表里的記錄的創(chuàng)建.如果用于一個(gè) UPDATE 事件,同樣我們可以當(dāng) "最后更新"(last updated)函數(shù)來(lái)用.
觸發(fā)器函數(shù)返回 HeapTuple 給調(diào)用它的執(zhí)行器.這個(gè)返回在那些在 INSERT,DELETE 或 UPDATE 操作之后執(zhí)行的觸發(fā)器上被忽略,但它允許那些 BEFORE 觸發(fā)器用來(lái):
返回 NULL 以忽略對(duì)當(dāng)前記錄的操作(這樣該記錄就將不會(huì)被插入/更新/刪除).
返回一個(gè)指向另一個(gè)記錄的指針(只用于 INSERT 和 UPDATE ),該指針?biāo)赣涗泴⒋嬖加涗洷徊迦耄ɑ蛘咦鳛樵凇PDATE 中記錄的新版本).
注意,CREATE TRIGGER 句柄將不進(jìn)行任何初始化工作.這一點(diǎn)將在以后進(jìn)行修改.同樣,如果多于一個(gè)觸發(fā)器為同樣的事件定義在同樣的關(guān)系上,觸發(fā)器觸發(fā)的順序?qū)⒉豢深A(yù)料.這一點(diǎn)以后也會(huì)修改.
如果一個(gè)觸發(fā)器函數(shù)執(zhí)行 SQL-查詢(使用 SPI)那么這些查詢可能再次觸發(fā)觸發(fā)器.這就是所謂的嵌套觸發(fā)器.對(duì)嵌套觸發(fā)器的嵌套深度沒(méi)有顯式的限制.
如果一個(gè)觸發(fā)器是被 INSERT 觸發(fā)并且插入一個(gè)新行到同一關(guān)系中,然后該觸發(fā)器將被再次觸發(fā).目前對(duì)這種情況沒(méi)有提供任何同步(等)的措施,這一點(diǎn)也可能會(huì)修改.目前,回歸測(cè)試?yán)镉幸粋€(gè)函數(shù) funny_dup17() 使用了一些技巧避免對(duì)自身的遞歸(嵌套)調(diào)用...
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
與觸發(fā)器管理器交互
如我們前面所說(shuō),當(dāng)觸發(fā)器管理器調(diào)用函數(shù)時(shí),結(jié)構(gòu) TriggerData *CurrentTriggerData 是 NOT NULL?。ǚ强眨┑牟⑶页跏蓟^(guò)的.所以最好檢查 CurrentTriggerData 結(jié)構(gòu)以防止在開(kāi)始時(shí)就是 NULL?。眨┑牟⑶以讷@取信息之后把它清空以避免從非觸發(fā)器管理器來(lái)的觸發(fā)器函數(shù).
結(jié)構(gòu)(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
描述調(diào)用函數(shù)的事件.你可以使用下面的宏來(lái)檢驗(yàn) tg_event:
TRIGGER_FIRED_BEFORE(tg_event)
returns TRUE if trigger fired BEFORE.(觸發(fā)器由 BEFORE 觸發(fā)返回 TRUE)
TRIGGER_FIRED_AFTER(tg_event)
Returns TRUE if trigger fired AFTER.(觸發(fā)器由 AFTER 觸發(fā)返回 TRUE)
TRIGGER_FIRED_FOR_ROW(event)
Returns TRUE if trigger fired for a ROW-level event.(觸發(fā)器行級(jí)(ROW-level)觸發(fā)返回 TRUE)
TRIGGER_FIRED_FOR_STATEMENT(event)
Returns TRUE if trigger fired for STATEMENT-level event.(觸發(fā)器語(yǔ)句級(jí)(ROW-level)觸發(fā)返回 TRUE)
TRIGGER_FIRED_BY_INSERT(event)
Returns TRUE if trigger fired by INSERT.(觸發(fā)器由 INSERT 觸發(fā)返回 TRUE)
TRIGGER_FIRED_BY_DELETE(event)
Returns TRUE if trigger fired by DELETE.(觸發(fā)器由 DELETE 觸發(fā)返回 TRUE)
TRIGGER_FIRED_BY_UPDATE(event)
Returns TRUE if trigger fired by UPDATE.(觸發(fā)器由 UPDATE 觸發(fā)返回 TRUE)
tg_relation
是一個(gè)指向描述被觸發(fā)的關(guān)系的結(jié)構(gòu)的指針.請(qǐng)參考src/include/utils/rel.h 獲取關(guān)于此結(jié)構(gòu)的詳細(xì)信息.最讓人感興趣的事情是 tg_relation->rd_att?。P(guān)系記錄的描述) 和 tg_relation->rd_rel->relname?。P(guān)系名.這個(gè)變量的類型不是 char*,而是 NameData.用 SPI_getrelname(tg_relation) 獲取 char* ,如果你需要一份名字的拷貝的話).
tg_trigtuple
是一個(gè)指向觸發(fā)觸發(fā)器的記錄的指針.這是一個(gè)正在被 插入(INSERT),刪除(DELETE)或更新(UPDATE)的記錄.如果是 INSERT/DELETE ,那么這就是你將返回給執(zhí)行器的東西--如果你不想用另一條記錄覆蓋此記錄(INSERT)或忽略操作.
tg_newtuple
如果是 UPDATE,這是一個(gè)指向新版本的記錄的指針,如果是 INSERT 或 DELETE,就是 NULL這就是你將返回給執(zhí)行器的東西-- 如果你是 UPDATE 并且你不想用另一條記錄替換這條記錄或忽略操作.
tg_trigger
是一個(gè)指向結(jié)構(gòu) Trigger 的指針,該結(jié)構(gòu)在 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 是觸發(fā)器的名稱,tgnargs 是在 tgargs 里參數(shù)的數(shù)量,tgargs 是一個(gè)指針數(shù)組,數(shù)組里每個(gè)指針指向在 CREATE TRIGGER 語(yǔ)句里聲明的參數(shù).其他成員只在內(nèi)部使用.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
數(shù)據(jù)改變的可視性
Postgres 數(shù)據(jù)修改的可視性規(guī)則:在查詢執(zhí)行過(guò)程中,由查詢本身造成的數(shù)據(jù)修改(通過(guò) SQL-函數(shù),SPI-函數(shù),觸發(fā)器)對(duì)查詢掃描而言是不可見(jiàn)的.例如,在查詢
INSERT INTO a SELECT * FROM a
里,插入的記錄對(duì) SELECT 的掃描是不可見(jiàn)的.實(shí)際上,這么做在數(shù)據(jù)庫(kù)內(nèi)部形成非遞歸的數(shù)據(jù)庫(kù)表的復(fù)制(當(dāng)然是要受到唯一索引規(guī)則的制約的)
但是請(qǐng)記住在 SPI 文擋里關(guān)于可視性的注釋:
由查詢 Q 造成的改變可以為查詢 Q 以后運(yùn)行的查詢可見(jiàn),不管這些查詢
是在查詢 Q 內(nèi)部開(kāi)始運(yùn)行(在 Q 運(yùn)行期間)的還是Q運(yùn)行完畢后開(kāi)始運(yùn)行的
這些對(duì)觸發(fā)器而言也是正確的,盡管被插入的記錄?。╰g_trigtuple)對(duì) BEFORE 觸發(fā)器是不可見(jiàn)的,這個(gè)剛被插入的記錄卻可以被一個(gè) AFTER 觸發(fā)器看到,并且對(duì)所有這個(gè)(觸發(fā)器)以后的所有 BEFORE/AFTER 觸發(fā)器均可見(jiàn)!
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
例子
在 src/test/regress/regress.c 和 contrib/spi 里有更復(fù)雜的例子.
這里是一個(gè)非常簡(jiǎn)單的觸發(fā)器使用的例子.函數(shù) trigf 報(bào)告在被觸發(fā)的關(guān)系 ttest 中記錄數(shù)量,并且如果查詢?cè)噲D把 NULL 插入到 x 里(例如?。鰹橐粋€(gè) NOT NULL 約束但不退出事務(wù)的約束)時(shí)略過(guò)操作.
#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);
}
然后,編譯和創(chuàng)建表 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)
--------------------------------------------------------------------------------