PostgreSQL7.0手冊-程序員手冊 -47. 觸發器
2019-09-08 23:34:18
供稿:網友
第四十七章. 觸發器
內容
創建觸發器
與觸發器管理器交互
數據改變的可視性
例子
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)
--------------------------------------------------------------------------------