37.1. Visão geral do comportamento de gatilho #

Um gatilho especifica que o banco de dados deve executar automaticamente uma determinada função sempre que um determinado tipo de operação for executado. Os gatilhos podem estar ligados a tabelas (particionadas ou não), visões e tabelas estrangeiras

Nas tabelas e tabelas estrangeiras, os gatilhos podem ser definidos de modo a serem executados antes ou após qualquer operação de INSERT, UPDATE ou DELETE, uma vez por linha modificada, ou uma vez por-instrução SQL. Além disso, os gatilhos de UPDATE podem ser configurados para disparar apenas se certas colunas mencionadas na cláusula SET da instrução UPDATE forem modificadas. Os gatilhos também podem disparar para instruções TRUNCATE. Se ocorrer um evento de gatilho, a função de gatilho será chamada no momento apropriado para lidar com o evento.

Nas visões, os gatilhos podem ser definidos para executar em vez das operações INSERT, UPDATE, ou DELETE. Estes gatilhos INSTEAD OF são disparados uma vez para cada linha na visão que precisa ser modificada. É responsabilidade da função do gatilho realizar as alterações necessárias na(s) tabela(s) base subjacente(s) da visão e, quando apropriado, retornar a linha modificada conforme aparecerá na visão. Os gatilhos em visões também podem ser definidos para serem executados uma vez por-instrução SQL, e antes ou depois das operações INSERT, UPDATE ou DELETE. Entretanto, estes gatilhos são disparados apenas se houver também um gatilho INSTEAD OF na visão. Caso contrário, qualquer instrução direcionada à visão deve ser reescrita em uma instrução que afete sua(s) tabela(s) base subjacente(s) e, em seguida, os gatilhos que serão disparados são aqueles anexados à(s) tabela(s) base.

A função de gatilho deve ser definida antes que o próprio gatilho possa ser criado. A função de gatilho deve ser declarada como uma função sem argumentos, e retornando o tipo trigger. (A função de gatilho recebe sua entrada por meio da estrutura TriggerData passada especialmente, e não na forma de argumentos de função comuns.)

Uma vez criada a função de gatilho adequada, o gatilho é estabelecido com o comando CREATE TRIGGER. A mesma função de gatilho pode ser usada para vários gatilhos.

O PostgreSQL oferece os gatilhos por-linha e por-instrução. Com um gatilho por-linha, a função de gatilho é chamada uma vez para cada linha afetada pela instrução que disparou o gatilho. Por outro lado, um gatilho por-instrução é chamado apenas uma vez quando uma instrução apropriada é executada, independentemente do número de linhas afetadas por esta instrução. Em particular, uma instrução que afeta zero linhas ainda resultará na execução de quaisquer gatilhos por-instrução aplicáveis. Estes dois tipos de gatilhos às vezes são chamados de gatilhos no nível de linha, e gatilhos no nível de instrução, respectivamente. Os gatilhos para TRUNCATE só podem ser definidos no nível de instrução, e não por linha.

Os gatilhos também são classificados conforme o disparo aconteça antes, após, ou em vez da operação, sendo referidos como gatilhos BEFORE, gatilhos AFTER e gatilhos INSTEAD OF, respectivamente. Os gatilhos BEFORE no nível de instrução disparam naturalmente antes que a instrução comece a fazer qualquer coisa, enquanto os gatilhos AFTER no nível da instrução disparam bem no final da instrução. Estes tipos de gatilhos podem ser definidos em tabelas, visões, ou tabelas estrangeiras. Gatilhos BEFORE no nível de linha disparam imediatamente antes de uma determinada linha ser operada, enquanto gatilhos AFTER no nível de linha disparam no final da instrução (mas antes de qualquer gatilho no nível de instrução AFTER). Estes tipos de gatilhos só podem ser definidos em tabelas e tabelas estrangeiras, e não em visões. Os gatilhos INSTEAD OF só podem ser definidos em visões, e apenas no nível de linha; eles disparam imediatamente quando cada linha na visão é identificada como precisando ser operada.

A execução de um gatilho AFTER pode ser adiada para o final da transação, em vez do final da instrução, se for definido como um gatilho de restrição. Em todos os casos, o gatilho é executado como parte da mesma transação que a instrução que o disparou, portanto, se a instrução ou o gatilho causar um erro, os efeitos dos dois serão desfeitos. Além disso, o gatilho sempre será executado como a função de banco de dados (role) que enfileirou o evento do gatilho, a menos que a função do gatilho esteja marcada como SECURITY DEFINER, caso em que será executada como o dono da função.

Se o comando INSERT tiver uma cláusula ON CONFLICT DO UPDATE, será possível que gatilhos no nível de linha BEFORE INSERT e, em seguida, BEFORE UPDATE sejam executados nas linhas envolvidas. Estas interações podem ser complexas se os gatilhos não forem idempotentes [119]), porque as alterações feitas pelos gatilhos BEFORE INSERT serão vistas pelos gatilhos BEFORE UPDATE, incluindo alterações em colunas EXCLUDED.

Note que os gatilhos UPDATE no nível de instrução são executados quando ON CONFLICT DO UPDATE é especificado, independentemente de quaisquer linhas terem sido afetadas ou não pelo UPDATE (e independentemente de ter sido seguido um caminho alternativo de UPDATE). Um comando INSERT com a cláusula ON CONFLICT DO UPDATE executará primeiro os gatilhos BEFORE INSERT no nível de instrução, em seguida, os gatilhos BEFORE UPDATE no nível de instrução, seguido pelos gatilhos AFTER UPDATE no nível de instrução e, finalmente, os gatilhos AFTER INSERT no nível de instrução.

Uma instrução que tem como alvo uma tabela mãe em uma hierarquia de herança ou particionamento não faz com que os gatilhos em nível de instrução das tabelas filhas afetadas sejam acionados; somente os gatilhos no nível de instrução da tabela mãe são disparados. Entretanto, os gatilhos no nível de linha de quaisquer tabelas filhas afetadas serão disparados.

Se um UPDATE em uma tabela particionada fizer com que a linha seja movida para outra partição, isto será executado como um DELETE na partição original, seguido por um INSERT na nova partição. Neste caso, todos os gatilhos BEFORE UPDATE no nível de linha, e todos os gatilhos BEFORE DELETE no nível de linha, são disparados na partição original. Em seguida, todos os gatilhos BEFORE INSERT no nível de linha serão disparados na partição de destino. A possibilidade de resultados surpreendentes deve ser considerada quando todos estes gatilhos afetam a linha que está sendo movida. No que diz respeito aos gatilhos AFTER ROW, os gatilhos AFTER DELETE e AFTER INSERT são aplicados; mas os gatilhos AFTER UPDATE não são aplicados, porque o UPDATE foi convertido em um DELETE e um INSERT. No que diz respeito aos gatilhos no nível de instrução, nenhum dos gatilhos de DELETE ou INSERT é disparado, mesmo que ocorra movimentação de linha; somente os gatilhos para UPDATE definidos na tabela de destino usada na instrução UPDATE serão disparados.

Não são definidos gatilhos em separado para MERGE. Em vez disso, os gatilhos para UPDATE, DELETE e INSERT, no nível de instrução ou de linha, são disparados dependendo (para os gatilhos no nível de instrução) das ações especificadas na consulta MERGE e (para os gatilhos no nível de linha) das ações executadas.

Ao executar um comando MERGE, os gatilhos BEFORE e AFTER no nível de instrução são disparados para eventos especificados nas ações do comando MERGE, independentemente da ação ser ou não ser executada. É semelhante a uma instrução UPDATE que não atualiza nenhuma linha, mas mesmo assim os gatilhos no nível de instrução são disparados Os gatilhos no nível de linha são disparados somente quando uma linha é efetivamente atualizada, inserida ou excluída. Portanto, é perfeitamente legal que, embora gatilhos no nível de instrução sejam disparados para certos tipos de ação, nenhum gatilho no nível de linha seja disparado para o mesmo tipo de ação.

As funções de gatilho chamadas por gatilhos por-instrução devem sempre retornar NULL. As funções de gatilho chamadas por gatilhos por-linha podem retornar uma linha de tabela (um valor do tipo de dados HeapTuple) para o executor da chamada, se desejarem. Um gatilho no nível de linha disparado antes de uma operação tem as seguintes opções:

Um gatilho BEFORE no nível de linha, que não pretenda causar nenhum desses comportamentos, deve ter o cuidado de retornar como resultado a mesma linha que foi passada (ou seja, a linha NEW nos gatilhos para INSERT e UPDATE, ou a linha OLD nos gatilhos para DELETE).

Um gatilho INSTEAD OF no nível de linha deve retornar NULL para indicar que não modificou nenhum dado das tabelas-base subjacentes da visão, ou deve retornar a linha da visão que foi passada (a linha NEW para operações de INSERT e UPDATE, ou a linha OLD para as operações de DELETE). Um valor não nulo retornado é usado para sinalizar que o gatilho executou as alterações de dados necessárias na visão. Isto fará com que a contagem do número de linhas afetadas pelo comando seja incrementada. Somente para operações de INSERT e UPDATE, o gatilho pode modificar a linha NEW antes de retorná-la. Isto muda os dados retornados por INSERT RETURNING ou UPDATE RETURNING, sendo útil quando a visão não mostra os mesmos dados fornecidos.

O valor retornado é ignorado para gatilhos no nível de linha disparados após uma operação, portanto, podem retornar NULL.

Algumas considerações se aplicam às colunas geradas. As colunas geradas armazenadas são calculadas após os gatilhos BEFORE, e antes dos gatilhos AFTER. Portanto, o valor gerado pode ser inspecionado nos gatilhos AFTER. Nos gatilhos BEFORE, a linha OLD contém o antigo valor gerado, como seria de esperar, mas a linha NEW ainda não contém o novo valor gerado, não devendo ser acessado. Na interface da linguagem C, o conteúdo da coluna é indefinido neste ponto; uma linguagem de programação de nível mais alto, deve impedir o acesso a uma coluna gerada armazenada na linha NEW em um gatilho BEFORE. Mudanças no valor de uma coluna gerada em um gatilho BEFORE são ignoradas e serão sobrescritas. As colunas virtuais geradas nunca são calculadas quando os gatilhos são disparados. Na interface da linguagem C, seu conteúdo é indefinido em uma função de gatilho. Linguagens de programação de alto nível devem impedir o acesso a colunas virtuais geradas em gatilhos.

Se for definido mais de um gatilho para o mesmo evento na mesma relação, os gatilhos serão disparados em ordem alfabética pelo nome do gatilho. No caso dos gatilhos BEFORE e INSTEAD OF, a linha possivelmente modificada retornada por cada gatilho torna-se a entrada para o próximo gatilho. Se qualquer gatilho BEFORE ou INSTEAD OF retornar NULL, a operação será abandonada para esta linha, e os gatilhos subsequentes não serão disparados (para esta linha).

Uma definição de gatilho também pode especificar uma condição booleana WHEN, que será testada para ver se o gatilho deve ser disparado. Em gatilhos no nível de linha, a condição WHEN pode examinar os valores antigos e/ou novos das colunas da linha. (Gatilhos no nível de instrução também podem ter condições WHEN, embora o recurso não seja tão útil para eles.) Em um gatilho BEFORE, a condição WHEN é avaliada logo antes da função ser executada, então usar WHEN não é materialmente diferente de testar a mesma condição no início da função de gatilho. Entretanto, em um gatilho AFTER, a condição WHEN é avaliada logo após a ocorrência da atualização da linha, e determina se um evento é colocado na fila para disparar o gatilho no final da instrução. Portanto, quando a condição WHEN de um gatilho AFTER não retorna verdade, não é necessário colocar um evento na fila, nem buscar novamente a linha no final da instrução. Isto pode resultar em acelerações significativas em instruções que modificam muitas linhas, se o gatilho precisar ser disparado apenas para algumas das linhas. Os gatilhos INSTEAD OF não dão suporte a condições WHEN.

Normalmente, os gatilhos BEFORE no nível de linha são usados para verificar ou modificar os dados que serão inseridos ou atualizados. Por exemplo, um gatilho BEFORE pode ser usado para inserir a hora corrente em uma coluna timestamp, ou para verificar se dois elementos da linha são consistentes. Os gatilhos AFTER no nível de linha são usados de maneira mais prática para propagar as atualizações para outras tabelas, ou fazer verificações de consistência em outras tabelas. A razão para esta divisão de trabalho, é que um gatilho AFTER pode ter certeza de que está vendo o valor final da linha, enquanto um gatilho BEFORE não pode; pode haver outros gatilhos BEFORE disparando depois dele. Caso não haja um motivo específico para usar um gatilho BEFORE ou AFTER, a opção BEFORE é mais eficiente, porque as informações sobre a operação não precisam ser salvas até o final da instrução.

Se uma função de gatilho executar comandos SQL, estes comandos poderão disparar gatilhos novamente. Isto é conhecido como gatilhos em cascata. Não há um limite direto no número de níveis da cascata. É possível que cascatas causem uma chamada recursiva do mesmo gatilho; por exemplo, um gatilho INSERT pode executar um comando que insere uma linha adicional na mesma tabela, fazendo com que o gatilho INSERT seja disparado novamente. É responsabilidade do programador do gatilho evitar a recursão infinita nesses cenários.

Se uma restrição de chave estrangeira especificar ações referenciais (isto é, atualizações ou exclusões em cascata), estas ações serão executadas por meio de comandos SQL UPDATE ou DELETE comuns na tabela de referência. Em particular, quaisquer gatilhos existentes na tabela de referência serão acionados para estas alterações. Se um gatilho desse tipo modificar ou bloquear o efeito de um desses comandos, o resultado final poderá ser a quebra da integridade referencial. É responsabilidade do programador do gatilho evitar isto.

Quando um gatilho está sendo definido, podem ser especificados argumentos para ele. O objetivo de incluir argumentos na definição do gatilho, é permitir que diferentes gatilhos com requisitos semelhantes chamem a mesma função. Por exemplo, pode haver uma função de gatilho geral que usa como argumentos dois nomes de colunas, e coloca o usuário corrente em uma coluna, e o carimbo de data/hora corrente na outra. Escrito corretamente, esta função de gatilho seria independente da tabela específica em que está disparando. Portanto, a mesma função pode ser usada para eventos INSERT em qualquer tabela com colunas adequadas, para rastrear automaticamente a criação de registros em uma tabela de transações, por exemplo. Também pode ser usada para rastrear eventos da última atualização, se definida como um gatilho para UPDATE.

Cada linguagem de programação que oferece suporte a gatilhos tem seu próprio método para disponibilizar os dados de entrada do gatilho para a função de gatilho. Estes dados de entrada incluem o tipo de evento do gatilho (por exemplo, INSERT ou UPDATE), bem como quaisquer argumentos listados em CREATE TRIGGER. Para um gatilho no nível de linha, os dados de entrada também incluem a linha NEW para os gatilhos INSERT e UPDATE, e/ou OLD para os gatilhos UPDATE e DELETE.

Por padrão, os gatilhos no nível de instrução não têm como examinar a(s) linha(s) individual(is) modificada(s) pela instrução. Mas um gatilho AFTER STATEMENT pode requisitar que sejam criadas tabelas de transição para disponibilizar os conjuntos de linhas afetadas pelo gatilho. Os gatilhos AFTER ROW também podem solicitar tabelas de transição, para poderem ver o total de alterações na tabela, bem como a alteração na linha individual para a qual estão sendo disparados no momento. O método para examinar novamente as tabelas de transição depende da linguagem de programação que está sendo usada, mas a abordagem típica é fazer com que as tabelas de transição atuem como tabelas temporárias, somente para leitura, que podem ser acessadas por comandos SQL emitidos na função de gatilho.



[119] idempotente: que tem a propriedade de poder ser aplicado mais do que uma vez sem que o resultado se altere. in Dicionário Priberam da Língua Portuguesa. (N. T.)