Event Triggers no PostgreSQL *ative triggers quando uma tabela é criada, atualizada ou apagada*

Event triggers são triggers ativadas nos casos de: criação/atualização/remoção de tabelas, funções (user defined functions), tipos (user defined types), etc. Em outras palavras, são triggers ativadas por comandos DDL (Data Definition Language), em outras palavras, comandos CREATE, ALTER e DROP.

Com as Event Triggers você pode rodar um script ou SQL quando uma tabela nova for criada ou apagada.

As Event Triggers são ativadas a nível de Banco de Dados, ou seja, qualquer operação DDL que ocorrer dentro do banco de dados chamará a trigger.

Exemplo de Event Trigger

A Event Trigger tem mais ou menos essa cara (documentação aqui):

CREATE EVENT TRIGGER event_trigger_teste ON ddl_command_start    EXECUTE FUNCTION function_name();/* Ou segundo a documentação oficial */CREATE EVENT TRIGGER name ON event    [ WHEN filter_variable IN (filter_value [, ... ]) [ AND ... ] ]    EXECUTE { FUNCTION | PROCEDURE } function_name();

E para que a Even Trigger funcione, você precisa ter uma função previamente criada, note o RETURNS, é do tipo event_trigger:

CREATE FUNCTION function_name()RETURNS event_trigger ASBEGIN -- logicEND;LANGUAGE language_name;

As event triggers podem ser ativadas em 4 tipos de eventos:

  • ddl_command_start
  • ddl_command_end
  • table_rewrite
  • sql_drop

Explicarei abaixo quais são esses 4 eventos, mas se quiser conferir a documentação oficial, fique à vontade.

E as limitações das Event Triggers?

As event triggers não podem ser disparadas em eventos envolvendo databases, roles, tablespaces ou as próprias event triggers (esse grupo de eventos são chamados "objetos compartilhados", "shared objects").

Em que ordem as triggers são chamadas?

As event triggers para um mesmo evento são disparadas em ordem alfabética.

Evento ddl_command_start

É chamado logo após os comandos:
CREATE, SELECT INTO, ALTER, DROP, SECURITY LABEL, COMMENT, GRANT ou REVOKE.

É possível definir um WHEN, ativando a trigger apenas após algum comando específico.

Evento ddl_command_end

É chamado num momento antes do final da transação, após os mesmos comandos do ddl_command_start serem executados (as tabelas do system_catalog já foram alteradas nesse momento).

Os comandos executados podem ser vistos através da função pg_event_trigger_ddl_commands()

SELECT *FROM pg_event_trigger_ddl_commands();

Retorna:

NameTypeDescription
classidoidOID of catalog the object belongs in
objidoidOID of the object itself
objsubidintegerSub-object ID (e.g. attribute number for a column)
command_tagtextCommand tag
object_typetextType of the object
schema_nametextName of the schema the object belongs in, if any; otherwise NULL. No quoting is applied.
object_identitytextText rendering of the object identity, schema-qualified. Each identifier included in the identity is quoted if necessary.
in_extensionboolTrue if the command is part of an extension script
commandpg_ddl_commandA complete representation of the command, in internal format. This cannot be output directly, but it can be passed to other functions to obtain different pieces of information about the command. Ao tentar usar esse comando normalmente ele irá retornar a exceção:
ERROR: cannot output a value of type pg_ddl_command

Abaixo vai um exemplo completo de criação de event trigger.

Criando a função:

CREATE OR REPLACE FUNCTION funcao_mostrar_alteracoes()        RETURNS event_trigger LANGUAGE plpgsql AS $$DECLARE    obj record;BEGIN    FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()    LOOP        RAISE NOTICE 'Event trigger ddl_command_end detected!Operation: % Class id: % Object id: % Object Sub id: % Command tag: % Object type: % Schema: % Object identity: % In extension: %',                     tg_tag,                     obj.classid,                     obj.objid,                     obj.objsubid,                     obj.command_tag,                     obj.object_type,                     obj.schema_name,                     obj.object_identity,                     obj.in_extension        ;    END LOOP;END$$;

Criando a event trigger:

CREATE EVENT TRIGGER trigger_atualizar_views ON ddl_command_end    EXECUTE FUNCTION funcao_mostrar_alteracoes();

Ao rodar um ALTER TABLE ADD COLUMN recebemos:

NOTICE:  Event trigger ddl_command_end detected!Operation: ALTER TABLE Class id: 1259 Object id: 33737 Object Sub id: 0 Command tag: ALTER TABLE Object type: table Schema: public Object identity: public."unit-monitor-1" In extension: f

Evento sql_drop

É chamado por qualquer operação que apague objetos do banco de dados, antes do evento ddl_command_end.

Para visualizar quais objetos foram apagados, chamara a função do catálogo:

SELECT *FROM pg_event_trigger_dropped_objects();

Retorna:

NameTypeDescription
classidoidOID of catalog the object belonged in
objidoidOID of the object itself
objsubidintegerSub-object ID (e.g. attribute number for a column)
originalboolTrue if this was one of the root object(s) of the deletion
normalboolTrue if there was a normal dependency relationship in the dependency graph leading to this object
is_temporaryboolTrue if this was a temporary object
object_typetextType of the object
schema_nametextName of the schema the object belonged in, if any; otherwise NULL. No quoting is applied.
object_nametextName of the object, if the combination of schema and name can be used as a unique identifier for the object; otherwise NULL. No quoting is applied, and name is never schema-qualified.
object_identitytextText rendering of the object identity, schema-qualified. Each identifier included in the identity is quoted if necessary.
address_namestext[]An array that, together with object_type and address_args, can be used by the pg_get_object_address() function to recreate the object address in a remote server containing an identically named object of the same kind
address_argstext[]Complement for address_names

The sql_drop event occurs just before the ddl_command_end event trigger for any operation that drops database objects. To list the objects that have been dropped, use the set-returning function pg_event_trigger_dropped_objects() from the sql_drop event trigger code (see Section 9.28). Note that the trigger is executed after the objects have been deleted from the system catalogs, so it's not possible to look them up anymore.

Evento table_rewrite

É chamado antes de uma tabela ser reescrita por um comando ALTER TABLE e ALTER TYPE. Não é chamada pelos comandos CLUSTER e VACUUM.

Em um table_rewrite, é possível chamar:

NameReturn TypeDescription
pg_event_trigger_table_rewrite_oid()OidThe OID of the table about to be rewritten.
pg_event_trigger_table_rewrite_reason()intThe reason code(s) explaining the reason for rewriting. The exact meaning of the codes is release dependent.

Referências

Definição de Event Trigger
https://www.postgresql.org/docs/current/event-trigger-definition.html

Como ativar trigger após uma tabela ser criada?
https://stackoverflow.com/questions/39833466/get-table-name-in-event-trigger

Documentação Oficial - Event Triggers
https://www.postgresql.org/docs/current/event-triggers.html

Documentação Oficial - Event Trigger Functions
https://www.postgresql.org/docs/current/functions-event-triggers.html#PG-EVENT-TRIGGER-DDL-COMMAND-END-FUNCTIONS

Exemplos

Exemplo de Event Trigger para:
Dado que ocorreu um CREATE TABLE ou ALTER TABLE em uma tabela com determinado padrão em seu nome (regex):

  • Criar ou atualizar uma VIEW com base na tabela criada.
  • Fazer INSERT em em uma determinada tabela com o nome das colunas não presentes na tabela

CREATE OR REPLACE FUNCTION unit_monitor.update_unit_monitor_column_definitions()        RETURNS event_trigger LANGUAGE plpgsql AS $$DECLARE    obj RECORD;    tb_name TEXT;    BEGIN    FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()    LOOP        IF (obj.command_tag = 'CREATE TABLE' OR obj.command_tag = 'ALTER TABLE') AND obj.object_identity ~ '"?unit_monitor"?\."?unit_monitor(_[0-9]+)+"?$' THEN            tb_name := (regexp_match(obj.object_identity, '"?unit_monitor"?\."?(unit_monitor(_[0-9]+)+)"?$'))[1];            RAISE INFO 'CREATE TABLE or ALTER TABLE detected on  %.%.', obj.schema_name, tb_name;        -- Deletar View        EXECUTE format('DROP VIEW IF EXISTS unit_monitor.%I ;', tb_name||'_columns');        -- Criar View        EXECUTE format('CREATE OR REPLACE VIEW unit_monitor.%I AS SELECT tb.table_schema AS schema,    tb.table_name AS "table",    tb.column_name AS "column",    tb.data_type,    COALESCE(cd."pt-BR", tb.column_name::text) AS "pt-BR",    COALESCE(cd."en-US", tb.column_name::text) AS "en-US",    COALESCE(cd.is_categorical, false) AS is_categorical,    COALESCE(cd.is_main_timestamp, false) AS is_main_timestamp,    COALESCE(cd.is_visible_at_birth_certificate, false) AS is_visible_at_birth_certificate,        CASE            WHEN tco.constraint_type::text = ''PRIMARY KEY''::text THEN true            ELSE false        END AS is_primary_key,    kcu.ordinal_position,    tco.constraint_type   FROM information_schema.columns tb     LEFT JOIN unit_monitor.unit_monitor_column_definition cd ON tb.table_schema::text = cd.schema AND tb.table_name::text = cd."table" AND tb.column_name::text = cd."column"     LEFT JOIN information_schema.key_column_usage kcu ON kcu.table_schema::text = cd.schema AND kcu.table_name::text = cd."table" AND kcu.column_name::text = cd."column"     LEFT JOIN information_schema.table_constraints tco ON kcu.constraint_name::text = tco.constraint_name::text AND kcu.constraint_schema::text = tco.constraint_schema::text AND tco.constraint_type::text = ''PRIMARY KEY''::text  WHERE tb.table_name::text = %L::text;',tb_name||'_columns',tb_name);        INSERT INTO unit_monitor.unit_monitor_column_definition        SELECT         tb.table_schema::TEXT AS schema,        tb.table_name::TEXT AS "table",        tb.column_name::TEXT AS "column"        FROM information_schema.columns tb        LEFT JOIN unit_monitor.unit_monitor_column_definition umc             ON umc.schema = tb.table_schema AND umc.table = tb.table_name AND umc.column = tb.column_name        WHERE         tb.table_schema = obj.schema_name AND tb.table_name = tb_name        AND umc.column IS NULL;        END IF;            END LOOP;END$$;CREATE EVENT TRIGGER update_unit_monitor_column_definitions ON DDL_COMMAND_END    EXECUTE PROCEDURE unit_monitor.update_unit_monitor_column_definitions();

You should also read:

PostgreSQL

# Instalando o wget para fazer download da chave do repositório # Instalando o wget, capaz de fazer downloads de documentos via HTTP…