在 Postgres 触发函数中调用异常之前执行操作
Posted
技术标签:
【中文标题】在 Postgres 触发函数中调用异常之前执行操作【英文标题】:Perform action before exception call in Postgres trigger function 【发布时间】:2014-09-03 17:36:49 【问题描述】:这里是 Postgres 8.4。想象一下this code snippet from Postgres doc:
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
如果我们想做一些事情,比如登录自定义表,这些异常:
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
INSERT INTO my_log_table ('User didn't supplied empname')
RAISE EXCEPTION 'empname cannot be null';
END IF;
它不会起作用,因为我们在 RAISE EXCEPTION
调用之前放置的任何内容都被 RAISE EXCEPTION
回滚所撤消,即我们创建的 my_log_table 行将在调用 RAISE EXCEPTION
时立即被删除。
完成这样的事情的最佳方法是什么?也许捕捉到我们的自定义异常?
关闭回滚@TRIGGER 不是一个选项,我需要它。
【问题讨论】:
你真正想要的是一个子事务(大致相当于 Oracle 的pragma autonomous
。不幸的是,这仍然只是一个提议,还没有实现(Postgres 9.3)。你可以检查不过,这个post 详细说明了一个常见的解决方法。
这可能行得通,虽然我感觉在这种特殊情况下就像用大锤敲碎坚果:)
当您从任何来源添加长引用时,请同时添加指向该来源的链接。我添加了 Postgres 8.4 手册的链接。
【参考方案1】:
你可以trap errors/捕获异常。
在EXCEPTION
块中,您可以执行任何其他操作,例如插入到另一个表中。之后您可以重新引发异常以传播出去,但这会将包括 INSERT 在内的整个事务回滚到日志表(除非异常被包装并在外部函数中捕获)。
你可以:
使用诸如 dblink 调用之类的技巧来模拟自治事务,当包装事务回滚时,该事务不会撤消。相关:
How to use (install) dblink in PostgreSQL? dblink can't update a table on the same database in an after UPDATE trigger Does Postgres support nested or autonomous transactions?RAISE
NOTICE
或 WARNING
另外,ROOLBACK
也无法撤消。
RAISE
一个不同的EXCEPTION
与您自己的文字。
或者,您可以只取消触发触发函数的行,不引发异常。交易中的其他一切都正常进行。
假设这是一个触发器ON UPDATE
,并且您有另一个具有相同结构的表可以将失败的 INSERT 写入:
CREATE OR REPLACE FUNCTION emp_stamp()
RETURNS trigger AS
$func$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF ...
RETURN NEW; -- regular end
EXCEPTION WHEN others THEN -- or be more specific
INSERT INTO log_tbl VALUES (NEW.*); -- identical table structure
RETURN NULL; -- cancel row
END
$func$ LANGUAGE plpgsql;
请注意,NEW
包含异常发生前行的状态,包括同一函数中先前成功的语句。
触发器:
CREATE TRIGGER emp_stamp
BEFORE INSERT OR UPDATE ON tbl
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
【讨论】:
我最终会使用这样的东西,我丢失了在应用程序级别捕获并显示的异常,但至少触发触发器的 plpgsql 函数可以向应用程序返回错误值(因为它检测到没有更新的行-FOUND-) 我发现其他更复杂的解决方案不值得麻烦【参考方案2】:实际上你有两个选择。
-
您可以通过在 raise 上使用某个级别来登录到 postgresql 日志检查manual
在您的应用程序中记录错误,而不是在 Db 事务中。例如,不应该像这样在触发器函数中检查负工资。或者应该在插入调用时处理异常。
【讨论】:
1.实际上,我要保留的不是 log_add(这只是一个示例),而是不同的东西(插入另一个表)。 2.我知道这一点,但是在应用程序级别执行此操作会更慢,因为我必须使用更多数据库查询来检查条件。我只是想知道 postgresql 是否有办法在 TRIGGERS 中做到这一点以上是关于在 Postgres 触发函数中调用异常之前执行操作的主要内容,如果未能解决你的问题,请参考以下文章