Триггеры событий
- Обзор поведения триггера событий
- Матрица запуска событий
- Написание функций запуска событий на C
- Полный пример запуска события
- Пример запуска события перезаписи таблицы
В дополнение к механизму триггера, описанному в главе 7, QHB. также предоставляет триггеры событий. В отличие от обычных триггеров, которые присоединяются к одной таблице и захватывают только события DML, триггеры событий являются глобальными для конкретной базы данных и способны захватывать события DDL.
Как и обычные триггеры, триггеры событий могут быть написаны на любом процедурном языке, который включает поддержку триггеров событий, на C или Rust, но не на простом SQL.
Обзор поведения триггера событий
Триггер события срабатывает всякий раз, когда событие, с которым оно связано, происходит в базе данных. В настоящее время поддерживаются только события ddl_command_start, ddl_command_end, table_rewrite и sql_drop. Поддержка дополнительных событий может быть добавлена в будущих выпусках.
Событие ddl_command_start наступает непосредственно перед выполнением ddl_command_start CREATE, ALTER, DROP, SECURITY LABEL, COMMENT, GRANT или REVOKE. Перед срабатыванием триггера события не проверяется, существует ли затронутый объект или нет. Однако это событие не возникает для команд DDL, нацеленных на общие объекты - базы данных, роли и табличные пространства - или для команд, нацеленных на сами триггеры событий. Механизм запуска событий не поддерживает эти типы объектов. ddl_command_start также происходит непосредственно перед выполнением команды SELECT INTO, поскольку это эквивалентно CREATE TABLE AS.
Событие ddl_command_end наступает сразу после выполнения этого же набора команд. Чтобы получить более подробную информацию о выполненных операциях DDL, используйте функцию возврата pg_event_trigger_ddl_commands() из ddl_command_end триггера события ddl_command_end (см. Раздел 9.28). Обратите внимание, что триггер срабатывает после выполнения действий (но до фиксации транзакции), и, таким образом, системные каталоги могут быть прочитаны как уже измененные.
Событие sql_drop происходит непосредственно перед ddl_command_end для любой операции, которая удаляет объекты базы данных. Функция pg_event_trigger_dropped_objects() возвращает список объектов, которые были удалены, используйте функцию возврата из триггера события sql_drop (см. Функции запуска событий). Обратите внимание, что триггер выполняется после того, как объекты были удалены из системных каталогов, поэтому их больше невозможно увидеть.
Событие table_rewrite наступает непосредственно перед перезаписью таблицы некоторыми действиями команд ALTER TABLE и ALTER TYPE. В то время как другие операторы управления доступны для перезаписи таблицы, такие как CLUSTER и VACUUM, событие table_rewrite не инициируют.
Триггеры событий (как и другие функции) не могут быть выполнены в прерванной транзакции. Таким образом, если команда DDL завершается с ошибкой, любые связанные триггеры ddl_command_end не будут выполнены. И наоборот, если триггер ddl_command_start завершился с ошибкой, дальнейшие триггеры событий ddl_command_start не будут предпринимать попыток выполнить саму команду. Точно так же, если триггер ddl_command_end завершается с ошибкой, эффекты оператора DDL будут откатываться, как и в любом другом случае, когда содержащая транзакция прерывается.
Полный список команд, поддерживаемых механизмом запуска событий, см. в главе Денежные Типы.
Триггеры событий создаются с помощью команды CREATE EVENT TRIGGER. Для того чтобы создать триггер события, вы должны сначала создать функцию со специальным типом возврата event_trigger. Эта функция не должна (и не может) возвращать значение; возвращаемый тип служит просто сигналом, что функция должна быть вызвана как триггер события.
Если для определенного события определено более одного триггера, они будут срабатывать в алфавитном порядке по имени триггера.
Определение триггера также может указывать условие WHEN так что, например, триггер ddl_command_start может быть запущен только для определенных команд, которые пользователь хочет перехватить. Обычно такие триггеры используются для ограничения диапазона операций DDL, которые могут выполнять пользователи.
Матрица запуска событий
В таблице 8.1 перечислены все команды, для которых поддерживаются триггеры событий.
Таблица 8.1. Поддержка триггеров событий по командам
Командный тег | ddl_command_start | ddl_command_end | sql_drop | table_rewrite | Примечания |
---|---|---|---|---|---|
ALTER AGGREGATE | X | X | - | - | |
ALTER COLLATION | X | X | - | - | |
ALTER CONVERSION | X | X | - | - | |
ALTER DOMAIN | X | X | - | - | |
ALTER DEFAULT PRIVILEGES | X | X | - | - | |
ALTER EXTENSION | X | X | - | - | |
ALTER FOREIGN DATA WRAPPER | X | X | - | - | |
ALTER FOREIGN TABLE | X | X | X | - | |
ALTER FUNCTION | X | X | - | - | |
ALTER LANGUAGE | X | X | - | - | |
ALTER LARGE OBJECT | X | X | - | - | |
ALTER MATERIALIZED VIEW | X | X | - | - | |
ALTER OPERATOR | X | X | - | - | |
ALTER OPERATOR CLASS | X | X | - | - | |
ALTER OPERATOR FAMILY | X | X | - | - | |
ALTER POLICY | X | X | - | - | |
ALTER PROCEDURE | X | X | - | - | |
ALTER PUBLICATION | X | X | - | - | |
ALTER SCHEMA | X | X | - | - | |
ALTER SEQUENCE | X | X | - | - | |
ALTER SERVER | X | X | - | - | |
ALTER STATISTICS | X | X | - | - | |
ALTER SUBSCRIPTION | X | X | - | - | |
ALTER TABLE | X | X | X | X | |
ALTER TEXT SEARCH CONFIGURATION | X | X | - | - | |
ALTER TEXT SEARCH DICTIONARY | X | X | - | - | |
ALTER TEXT SEARCH PARSER | X | X | - | - | |
ALTER TEXT SEARCH TEMPLATE | X | X | - | - | |
ALTER TRIGGER | X | X | - | - | |
ALTER TYPE | X | X | - | X | |
ALTER USER MAPPING | X | X | - | - | |
ALTER VIEW | X | X | - | - | |
COMMENT | X | X | - | - | Только для локальных объектов |
CREATE ACCESS METHOD | X | X | - | - | |
CREATE AGGREGATE | X | X | - | - | |
CREATE CAST | X | X | - | - | |
CREATE COLLATION | X | X | - | - | |
CREATE CONVERSION | X | X | - | - | |
CREATE DOMAIN | X | X | - | - | |
CREATE EXTENSION | X | X | - | - | |
CREATE FOREIGN DATA WRAPPER | X | X | - | - | |
CREATE FOREIGN TABLE | X | X | - | - | |
CREATE FUNCTION | X | X | - | - | |
CREATE INDEX | X | X | - | - | |
CREATE LANGUAGE | X | X | - | - | |
CREATE MATERIALIZED VIEW | X | X | - | - | |
CREATE OPERATOR | X | X | - | - | |
CREATE OPERATOR CLASS | X | X | - | - | |
CREATE OPERATOR FAMILY | X | X | - | - | |
CREATE POLICY | X | X | - | - | |
CREATE PROCEDURE | X | X | - | - | |
CREATE PUBLICATION | X | X | - | - | |
CREATE RULE | X | X | - | - | |
CREATE SCHEMA | X | X | - | - | |
CREATE SEQUENCE | X | X | - | - | |
CREATE SERVER | X | X | - | - | |
CREATE STATISTICS | X | X | - | - | |
CREATE SUBSCRIPTION | X | X | - | - | |
CREATE TABLE | X | X | - | - | |
CREATE TABLE AS | X | X | - | - | |
CREATE TEXT SEARCH CONFIGURATION | X | X | - | - | |
CREATE TEXT SEARCH DICTIONARY | X | X | - | - | |
CREATE TEXT SEARCH PARSER | X | X | - | - | |
CREATE TEXT SEARCH TEMPLATE | X | X | - | - | |
CREATE TRIGGER | X | X | - | - | |
CREATE TYPE | X | X | - | - | |
CREATE USER MAPPING | X | X | - | - | |
CREATE VIEW | X | X | - | - | |
DROP ACCESS METHOD | X | X | X | - | |
DROP AGGREGATE | X | X | X | - | |
DROP CAST | X | X | X | - | |
DROP COLLATION | X | X | X | - | |
DROP CONVERSION | X | X | X | - | |
DROP DOMAIN | X | X | X | - | |
DROP EXTENSION | X | X | X | - | |
DROP FOREIGN DATA WRAPPER | X | X | X | - | |
DROP FOREIGN TABLE | X | X | X | - | |
DROP FUNCTION | X | X | X | - | |
DROP INDEX | X | X | X | - | |
DROP LANGUAGE | X | X | X | - | |
DROP MATERIALIZED VIEW | X | X | X | - | |
DROP OPERATOR | X | X | X | - | |
DROP OPERATOR CLASS | X | X | X | - | |
DROP OPERATOR FAMILY | X | X | X | - | |
DROP OWNED | X | X | X | - | |
DROP POLICY | X | X | X | - | |
DROP PROCEDURE | X | X | X | - | |
DROP PUBLICATION | X | X | X | - | |
DROP RULE | X | X | X | - | |
DROP SCHEMA | X | X | X | - | |
DROP SEQUENCE | X | X | X | - | |
DROP SERVER | X | X | X | - | |
DROP STATISTICS | X | X | X | - | |
DROP SUBSCRIPTION | X | X | X | - | |
DROP TABLE | X | X | X | - | |
DROP TEXT SEARCH CONFIGURATION | X | X | X | - | |
DROP TEXT SEARCH DICTIONARY | X | X | X | - | |
DROP TEXT SEARCH PARSER | X | X | X | - | |
DROP TEXT SEARCH TEMPLATE | X | X | X | - | |
DROP TRIGGER | X | X | X | - | |
DROP TYPE | X | X | X | - | |
DROP USER MAPPING | X | X | X | - | |
DROP VIEW | X | X | X | - | |
GRANT | X | X | - | - | Только для локальных объектов |
IMPORT FOREIGN SCHEMA | X | X | - | - | |
REFRESH MATERIALIZED VIEW | X | X | - | - | |
REVOKE | X | X | - | - | Только для локальных объектов |
SECURITY LABEL | X | X | - | - | Только для локальных объектов |
SELECT INTO | X | X | - | - |
Написание функций запуска событий на C
В этом разделе описываются подробности низкоуровневого интерфейса для функции запуска события. Эта информация необходима только при написании функций триггера событий на C. Если вы используете язык более высокого уровня, то эти детали обрабатываются автоматически. В большинстве случаев вам следует подумать об использовании процедурного языка, прежде чем писать свои триггеры событий на C. В документации каждого процедурного языка объясняется, как написать триггер событий на этом языке.
Функции триггера событий должны использовать интерфейс диспетчера «version 1».
Когда функция вызывается менеджером триггера события, ей не передаются никакие обычные аргументы кроме указателя «context», указывающий на структуру EventTriggerData. Функции на C могут проверить, были ли они вызваны из менеджера триггеров событий или нет, выполнив макрос:
CALLED_AS_EVENT_TRIGGER(fcinfo)
который распахивается в
((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))
Если это возвращает true, тогда безопасно привести fcinfo->context к типу EventTriggerData * и использовать структуру EventTriggerData. Функция не должна изменять структуру EventTriggerData или любые данные, на которые она указывает.
Структура EventTriggerData определена в commands/event_trigger.h:
typedef struct EventTriggerData
{
NodeTag type;
const char *event; /* event name */
Node *parsetree; /* parse tree */
const char *tag; /* command tag */
} EventTriggerData;
где члены определены следующим образом:
-
type - всегда T_EventTriggerData.
-
event описывает событие, для которого вызывается функция, одно из ddl_command_start, ddl_command_end, sql_drop, table_rewrite (см. Раздел 8.1 о значении этих событий).
-
parsetree - указатель на дерево разбора команды. Проверьте исходный код QHB для уточнения деталей. Структура дерева разбора может быть изменена без предварительного уведомления.
-
tag - тег команды, связанный с событием, для которого запускается триггер события, например "CREATE FUNCTION".
Внимание! Функция триггера события должна возвращать указатель NULL (но НЕ нулевое значение SQL, т.е. не устанавливать isNull как true).
Полный пример запуска события
Вот очень простой пример функции триггера события, написанной на C. (Примеры триггеров, написанных на процедурных языках, можно найти в документации по процедурным языкам.)
Функция noddl вызывает ошибку каждый раз, когда она вызывается. Определение триггера события связало функцию с событием ddl_command_start. В результате все команды DDL (с исключениями, упомянутыми в разделе 8.1) не могут быть запущены.
Это исходный код функции триггера:
#include "postgres.h"
#include "commands/event_trigger.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(noddl);
Datum
noddl(PG_FUNCTION_ARGS)
{
EventTriggerData *trigdata;
if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */
elog(ERROR, "not fired by event trigger manager");
trigdata = (EventTriggerData *) fcinfo->context;
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("command \"%s\" denied", trigdata->tag)));
PG_RETURN_NULL();
}
После того, как вы скомпилировали исходный код (см. Раздел 37.10.5), объявите функцию и триггеры:
CREATE FUNCTION noddl() RETURNS event_trigger
AS 'noddl' LANGUAGE C;
CREATE EVENT TRIGGER noddl ON ddl_command_start
EXECUTE FUNCTION noddl();
Теперь вы можете проверить работу триггера:
=# \dy
List of event triggers
Name | Event | Owner | Enabled | Function | Tags
-------+-------------------+-------+---------+----------+------
noddl | ddl_command_start | dim | enabled | noddl |
(1 row)
=# CREATE TABLE foo(id serial);
ERROR: command "CREATE TABLE" denied
В этой ситуации, чтобы иметь возможность запускать некоторые команды DDL, когда вам нужно это сделать, вы должны либо сбросить триггер события, либо отключить его. Также может быть удобно отключить триггер только на время транзакции:
BEGIN;
ALTER EVENT TRIGGER noddl DISABLE;
CREATE TABLE foo (id serial);
ALTER EVENT TRIGGER noddl ENABLE;
COMMIT;
(Напомним, что на команды DDL самих триггеров событий не влияют триггеры событий.)
Пример запуска события перезаписи таблицы
Благодаря событию table_rewrite можно реализовать политику перезаписи таблицы, разрешив перезапись только в определенное время обслуживания.
Вот пример реализации такой политики.
CREATE OR REPLACE FUNCTION no_rewrite()
RETURNS event_trigger
LANGUAGE plpgsql AS
$$
---
--- Implement local Table Rewriting policy:
--- public.foo is not allowed rewriting, ever
--- other tables are only allowed rewriting between 1am and 6am
--- unless they have more than 100 blocks
---
DECLARE
table_oid oid := pg_event_trigger_table_rewrite_oid();
current_hour integer := extract('hour' from current_time);
pages integer;
max_pages integer := 100;
BEGIN
IF pg_event_trigger_table_rewrite_oid() = 'public.foo'::regclass
THEN
RAISE EXCEPTION 'you''re not allowed to rewrite the table %',
table_oid::regclass;
END IF;
SELECT INTO pages relpages FROM pg_class WHERE oid = table_oid;
IF pages > max_pages
THEN
RAISE EXCEPTION 'rewrites only allowed for table with less than % pages',
max_pages;
END IF;
IF current_hour NOT BETWEEN 1 AND 6
THEN
RAISE EXCEPTION 'rewrites only allowed between 1am and 6am';
END IF;
END;
$$;
CREATE EVENT TRIGGER no_rewrite_allowed
ON table_rewrite
EXECUTE FUNCTION no_rewrite();