CodeGym /Cursos /SQL SELF /Tratamento de erros e transações em triggers: EXCEPTION, ...

Tratamento de erros e transações em triggers: EXCEPTION, RAISE

SQL SELF
Nível 58 , Lição 3
Disponível

Tratamento de erros e transações em triggers: EXCEPTION, RAISE

Às vezes, triggers se comportam de um jeito meio estranho, e isso pode rolar por causa de:

  • Erro lógico no código da função que está ligada ao trigger.
  • Violação de restrições do banco de dados (tipo violar unique ou usar tipo de dado errado).
  • Problemas em transações, quando o trigger faz um rollback por causa de algum erro.
  • Recursão, se o trigger chama ele mesmo (muitas vezes sem querer).

Pra evitar esses perrengues, o PostgreSQL deixa a gente tratar erros dentro dos triggers e das funções deles. As ferramentas pra isso são os blocos EXCEPTION e o comando RAISE, que a gente vai ver hoje com exemplos.

Tratando erros com o bloco EXCEPTION

O bloco EXCEPTION deixa a gente capturar erros e rodar algum código pra tratar eles. É tipo usar try-catch em linguagens como Python ou Java.

O bloco EXCEPTION é usado em funções PL/pgSQL desse jeito:

BEGIN
    -- Código principal da função
EXCEPTION
    WHEN <tipo_de_erro> THEN
        -- Código pra tratar o erro
END;

Onde <tipo_de_erro> é o erro específico ou grupo de erros que você quer tratar (tipo unique_violation, division_by_zero e por aí vai).

Exemplo: registrando erros em triggers

Imagina que a gente tem uma tabela logs onde queremos guardar os erros que rolam quando tentam inserir dados na tabela students. Olha só:

Criando a tabela de logs

CREATE TABLE logs (
    id SERIAL PRIMARY KEY,
    error_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    error_message TEXT
);

Criando uma função com tratamento de erro

CREATE OR REPLACE FUNCTION track_insert_errors()
RETURNS TRIGGER AS $$
BEGIN
    -- Tentando rodar o código principal
    BEGIN
        -- Exemplo de ação "errada": divisão por 0
        PERFORM 1 / (NEW.some_value - NEW.some_value);
    EXCEPTION
        WHEN division_by_zero THEN
            -- Se rolou divisão por zero, salva no log
            INSERT INTO logs (error_message) VALUES ('Erro de divisão por zero ao inserir em students');
    END;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Criando o trigger

CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION track_insert_errors();

Agora, se der erro de divisão por zero ao inserir na tabela students, ele vai ser tratado e a info vai ser salva na tabela logs.

Usando RAISE pra diagnosticar e debugar

O comando RAISE deixa você mostrar mensagens de aviso, erro ou info de debug. Isso é super útil quando você quer entender como seu trigger tá funcionando (ou não tá funcionando!).

Tipos de mensagem do RAISE:

  1. DEBUG — mensagem de debug.
  2. NOTICE — mensagem informativa normal.
  3. WARNING — aviso.
  4. EXCEPTION — mensagem de erro que para a função.

Sintaxe do RAISE:

RAISE <tipo_de_mensagem> 'Mensagem';

Você também pode passar valores de variáveis:

RAISE NOTICE 'Valor de NEW.id = %', NEW.id;

Exemplo: debugando valores no trigger

Imagina que tá rolando erro ao atualizar a tabela students e você quer saber quais valores de NEW e OLD tão causando o problema. Bora usar RAISE:

CREATE OR REPLACE FUNCTION debug_student_update()
RETURNS TRIGGER AS $$
BEGIN
    RAISE NOTICE 'OLD.id = %, NEW.id = %', OLD.id, NEW.id;

    -- Exemplo de condição que gera erro:
    IF NEW.some_field IS NULL THEN
        RAISE EXCEPTION 'Campo some_field não pode ser NULL';
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER after_update_students
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION debug_student_update();

Agora, toda vez que atualizar um registro, você vai ver os valores de OLD e NEW, e ainda recebe uma mensagem de erro clara se rolar algum problema.

Transações em triggers

Triggers rodam dentro do contexto de uma transação. Isso quer dizer que se der erro em qualquer lugar do trigger ou da função dele, toda a transação faz rollback. Isso protege o banco de dados de mudanças parciais.

Mas esse comportamento às vezes complica as coisas:

  • Se o erro dentro do trigger for por causa de dado errado, seria bom fazer rollback só de parte das ações.
  • Tem que lembrar que o rollback da transação inclui não só o trigger, mas toda a operação que chamou esse trigger.

Exemplo: usando transações no trigger

Pra ilustrar, imagina que queremos rodar uma lógica de negócio que faz duas coisas: atualizar a tabela students e registrar um log em logs. Se uma dessas falhar, a transação toda faz rollback.

CREATE OR REPLACE FUNCTION transactional_student_update()
RETURNS TRIGGER AS $$
BEGIN
    -- Registrando tentativa de update
    INSERT INTO logs (error_message) VALUES ('Tentativa de atualizar estudante com id ' || NEW.id);

    -- Checando regras de negócio
    IF NEW.some_value IS NULL THEN
        RAISE EXCEPTION 'Campo some_value não pode ser NULL';
    END IF;

    -- Se deu tudo certo, retorna NEW
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER before_update_students
BEFORE UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION transactional_student_update();

Erros comuns ao trabalhar com triggers e como evitar

Erros que a galera comete bastante:

Triggers recursivos. Isso rola quando o trigger faz uma alteração que chama ele mesmo de novo. Pra resolver: usa condição WHEN ou coloca um flag pra evitar chamadas repetidas.

Rollback da transação toda por causa de erro. Muitas vezes isso não é o que você quer, principalmente se o trigger não mexe direto nos dados principais. Pra resolver: usa blocos EXCEPTION do jeito certo.

Info de debug demais. Isso enche o log e dificulta analisar. Pra resolver: usa RAISE só durante desenvolvimento e teste.

Performance ruim. Triggers complicados podem deixar INSERT, UPDATE ou DELETE bem mais lentos. Pra resolver: deixa a lógica do trigger o mais simples possível e evita consultas pesadas.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION