【发布时间】:2020-04-01 11:37:19
【问题描述】:
我有两个存储函数。 delete_item 删除一项,在actionlog 表中记录它的成功或失败,它在错误时返回1,在成功运行时返回0。
其次,我有另一个函数remove_expired,它可以找到要删除的内容并循环调用delete_item。
所有这一切都旨在使用简单的 bash 脚本调用(操作的硬性要求,因此不讨论这样的调用),并且当事情不适用于他们的报告工具时,它必须给出错误代码.
我们希望所有可能的删除都成功(我们不期望错误,但人仍然是人,错误确实会发生),所以如果我们要删除 10 个项目并且 1 个失败,我们仍然希望其他 9 个被删除。
其次,我们真的希望日志在成功和错误情况下都在表actionlog 中。即,我们希望该日志是完整的。
由于 plpgsql 函数不允许手动事务管理,这似乎不是一种选择(除非我错过了规避此问题的方法?)。
到目前为止,我发现实现这一点的唯一方法是将脚本包装在 plpgsql 之外,但我们非常希望这可以在纯 plpgsql 中实现,因此我们可以给操作一个 pssql -C ... 命令,然后他们不应该关心其他任何事情。
重现问题的SQL:
DROP FUNCTION IF EXISTS remove_expired(timestamp with time zone);
DROP FUNCTION IF EXISTS delete_item(integer);
DROP TABLE IF EXISTS actionlog;
DROP TABLE IF EXISTS evil;
DROP TABLE IF EXISTS test;
CREATE TABLE test (
id serial primary key not null,
t timestamp with time zone not null
);
CREATE TABLE evil (
test_id integer not null references test(id)
);
CREATE TABLE actionlog (
eventTime timestamp with time zone not null default now(),
message text not null
);
INSERT INTO test (actualTime, t)
VALUES ('2020-04-01T10:00:00+0200'),
('2020-04-01T10:15:00+0200'), -- Will not be deleable due to foreign key
('2020-04-01T10:30:00+0200')
;
INSERT INTO evil (test_id) SELECT id FROM test WHERE id = 2;
CREATE OR REPLACE FUNCTION remove_expired(timestamp with time zone)
RETURNS void
AS
$$
DECLARE
test_id int;
failure_count int = 0;
BEGIN
FOR test_id IN
SELECT id FROM test WHERE t < $1
LOOP
failure_count := delete_item(test_id) + failure_count;
END LOOP;
IF failure_count > 0 THEN
-- I want this to cause 'psql ... -c "SELECT * FROM remove_expred...' to exit with exit code != 0
RAISE 'There was one or more errors deleting. See the log for details';
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION delete_item(integer)
RETURNS integer
AS
$$
BEGIN
DELETE FROM test WHERE id = $1;
INSERT INTO actionlog (message)
VALUES ('Deleted with ID: ' || $1);
RETURN 0;
EXCEPTION WHEN OTHERS THEN
INSERT INTO actionlog (message)
VALUES ('Error deleting ID: ' || $1 || '. The error was: ' || SQLERRM);
RETURN 1;
END
$$ LANGUAGE plpgsql;
提前感谢您提供任何有用的意见
【问题讨论】:
-
如果你有 Postgres 版本>=11,你应该考虑让 delete_item 成为一个过程而不是函数。它们允许您提交和回滚,从而将错误提交到数据库。 0/1 可以返回 in/inout 参数。函数总是会在出错时回滚所有内容。个人在程序方面不是很老练,但我很确定这是要走的路。 postgresql.org/docs/11/sql-createprocedure.html
标签: postgresql plpgsql