У меня есть две сохраненные функции. delete_item
удаляет один элемент, регистрируя его успех или неудачу в таблице actionlog
, возвращает 1
при ошибках и 0
при успешной работе.
Во-вторых, у меня есть другая функция remove_expired
он находит, что нужно удалить, и перебирает его, вызывая delete_item
.
Все это предназначено для вызова с использованием простого сценария bash (жесткое требование к операциям, поэтому такой вызов не обсуждается) и он должен выдавать код ошибки, когда вещи не работают для их инструментов отчетности.
Мы хотим, чтобы все возможные удаления были успешными (мы не ожидаем ошибок, но люди по-прежнему люди, и ошибки действительно происходит), поэтому, если мы хотим удалить 10 элементов, а 1 не удалось, мы по-прежнему хотим удалить остальные 9.
Во-вторых, нам бы очень хотелось, чтобы журналы были в таблице actionlog
в успех и ошибка. То есть, мы хотим, чтобы этот журнал был завершен.
Поскольку функции plpg sql не позволяют ручное управление транзакциями, что, по-видимому, не вариант (если я не пропустил способ обойти это?).
Единственный способ, который я нашел для достижения этой цели, - это обернуть сценарии вокруг него вне plpg sql, но мы бы очень хотели, чтобы это было возможно в чистом plpg sql, поэтому мы можем просто выполнять операции команду 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;
Заранее благодарим за любой полезный ввод