Introdução
Neste laboratório, você explorará técnicas de tratamento de erros no PostgreSQL usando sua linguagem procedural, PL/pgSQL. Você aprenderá a criar funções que podem lidar com erros potenciais de forma elegante, em vez de falhar. O laboratório abrange o envio de mensagens informativas, a captura de exceções específicas como "divisão por zero" e o registro de detalhes de erros em uma tabela de banco de dados para revisão posterior. Ao final, você entenderá como construir funções de banco de dados mais robustas e confiáveis.
Emitindo Avisos (Notices) e Alertas (Warnings)
Nesta etapa, você aprenderá a usar a instrução RAISE em uma função PL/pgSQL. A instrução RAISE é útil para fornecer mensagens informativas, avisos ou saída de depuração sem interromper a execução da função.
Primeiro, conecte-se ao terminal interativo do PostgreSQL, psql, como o usuário postgres. Todos os comandos SQL subsequentes neste laboratório serão executados dentro deste terminal.
sudo -u postgres psql
Agora você verá o prompt do psql, que se parece com postgres=#.
Vamos criar uma função chamada greet que recebe um nome como entrada e emite um NOTICE. Notices são mensagens de baixa prioridade.
Execute o seguinte comando SQL no shell psql:
CREATE OR REPLACE FUNCTION greet(name TEXT)
RETURNS TEXT AS $$
BEGIN
RAISE NOTICE 'Greeting function called with name: %', name;
RETURN 'Hello, ' || name;
END;
$$ LANGUAGE plpgsql;
Esta função recebe um argumento name. A linha RAISE NOTICE '...', name; gera uma mensagem de aviso (notice). O % é um placeholder que é substituído pelo valor da variável name.
Agora, chame a função para vê-la em ação:
SELECT greet('LabEx');
A saída mostra tanto a mensagem de aviso (notice) quanto o valor de retorno da função:
NOTICE: Greeting function called with name: LabEx
greet
-----------
Hello, LabEx
(1 row)
Você também pode emitir mensagens com um nível de severidade mais alto, como WARNING. Vamos modificar a função para emitir um aviso se o nome de entrada estiver vazio.
CREATE OR REPLACE FUNCTION greet(name TEXT)
RETURNS TEXT AS $$
BEGIN
IF name IS NULL OR name = '' THEN
RAISE WARNING 'Input name is empty or NULL.';
RETURN 'Hello, stranger';
ELSE
RAISE NOTICE 'Greeting function called with name: %', name;
RETURN 'Hello, ' || name;
END IF;
END;
$$ LANGUAGE plpgsql;
Agora, chame a função com uma string vazia para acionar o aviso:
SELECT greet('');
Desta vez, você verá uma mensagem WARNING em vez de um NOTICE:
WARNING: Input name is empty or NULL.
greet
------------------
Hello, stranger
(1 row)
Você usou com sucesso RAISE para gerar diferentes tipos de mensagens de dentro de uma função.
Disparando e Capturando uma Exceção
Nesta etapa, você aprenderá a lidar com erros de tempo de execução, conhecidos como exceções. Uma exceção não tratada encerrará imediatamente sua função. Primeiro, você criará uma função que falha e, em seguida, a modificará para capturar a exceção de forma elegante.
Vamos criar uma função simple_divide que realiza divisão inteira. Esta função tem uma falha potencial: ela travará se você tentar dividir por zero.
CREATE OR REPLACE FUNCTION simple_divide(numerator INTEGER, denominator INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN numerator / denominator;
END;
$$ LANGUAGE plpgsql;
Primeiro, chame a função com entradas válidas para confirmar que ela funciona corretamente:
SELECT simple_divide(10, 2);
A saída será o resultado da divisão:
simple_divide
---------------
5
(1 row)
Agora, chame a função com um denominador zero para acionar uma exceção:
SELECT simple_divide(10, 0);
Este comando falhará e retornará uma mensagem de erro. A execução da função é abortada.
ERROR: division by zero
CONTEXT: PL/pgSQL function simple_divide(integer,integer) line 3 at RETURN
Esta é uma exceção não tratada. Para evitar que a função trave, você pode usar um bloco EXCEPTION. Vamos criar uma nova função safe_divide que inclua tratamento de erros.
CREATE OR REPLACE FUNCTION safe_divide(numerator INTEGER, denominator INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN numerator / denominator;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Error: Cannot divide by zero.';
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Veja como o bloco EXCEPTION funciona:
BEGIN...END;: Isso define um bloco de código.EXCEPTION: Esta palavra-chave inicia a seção de tratamento de exceções.WHEN division_by_zero THEN: Isso especifica que o código a seguir deve ser executado apenas quando ocorrer um errodivision_by_zero.- O código dentro do manipulador emite um aviso (notice) e retorna
NULLem vez de travar.
Agora, chame a nova função safe_divide com um denominador zero:
SELECT safe_divide(10, 0);
Desta vez, a função não trava. Ela captura a exceção, exibe seu aviso personalizado e retorna NULL.
NOTICE: Error: Cannot divide by zero.
safe_divide
-------------
(1 row)
Você capturou com sucesso uma exceção específica e controlou a saída da função.
Registrando Erros em uma Tabela
Capturar exceções é bom, mas para auditoria e depuração, geralmente é melhor registrar erros em um local persistente, como uma tabela. Nesta etapa, você modificará a função safe_divide para gravar detalhes do erro na tabela error_log que foi criada durante a configuração do laboratório.
Primeiro, vamos examinar a estrutura da tabela error_log usando o comando \d no psql.
\d error_log
Você verá as colunas da tabela, que incluem um id auto-incrementável, um timestamp, a message de erro e o function_name.
Table "public.error_log"
Column | Type | Collation | Nullable | Default
---------------+-----------------------------+-----------+----------+----------------------------------------------------
id | integer | | not null | nextval('error_log_id_seq'::regclass)
timestamp | timestamp without time zone | | | (now() AT TIME ZONE 'utc'::text)
message | text | | |
function_name | text | | |
Indexes:
"error_log_pkey" PRIMARY KEY, btree (id)
Agora, vamos modificar a função safe_divide. No bloco EXCEPTION, você adicionará uma instrução INSERT para registrar o erro antes de retornar.
CREATE OR REPLACE FUNCTION safe_divide(numerator INTEGER, denominator INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN numerator / denominator;
EXCEPTION
WHEN division_by_zero THEN
INSERT INTO error_log (message, function_name)
VALUES ('Division by zero occurred!', 'safe_divide');
RAISE NOTICE 'Error: Cannot divide by zero. Details logged.';
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Agora, chame a função novamente para acionar o erro e a nova lógica de registro:
SELECT safe_divide(10, 0);
A função retornará NULL como antes, mas também realizou uma escrita no banco de dados. Para verificar isso, consulte a tabela error_log para ver a nova entrada.
SELECT function_name, message FROM error_log;
A saída deve mostrar o erro registrado:
function_name | message
---------------+----------------------------
safe_divide | Division by zero occurred!
(1 row)
Isso confirma que sua lógica de tratamento de erros agora é capaz de persistir informações de erro para análise posterior.
Tratando Exceções Gerais com OTHERS
Até agora, você lidou com um erro muito específico: division_by_zero. Mas e quanto a outros erros inesperados? PL/pgSQL fornece um manipulador de exceção de propósito geral OTHERS para capturar qualquer erro que não tenha sido capturado por uma cláusula WHEN mais específica.
Nesta etapa, você aprimorará a função safe_divide para lidar com qualquer exceção possível. Você também usará duas variáveis especiais, SQLSTATE (o código de erro SQL) e SQLERRM (a mensagem de erro SQL), para registrar informações mais detalhadas.
Vamos modificar a função safe_divide mais uma vez.
CREATE OR REPLACE FUNCTION safe_divide(numerator INTEGER, denominator INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN numerator / denominator;
EXCEPTION
WHEN division_by_zero THEN
INSERT INTO error_log (message, function_name)
VALUES ('Division by zero occurred!', 'safe_divide');
RAISE NOTICE 'Error: Cannot divide by zero. Details logged.';
RETURN NULL;
WHEN OTHERS THEN
INSERT INTO error_log (message, function_name)
VALUES ('An unexpected error occurred: ' || SQLSTATE || ' - ' || SQLERRM, 'safe_divide');
RAISE NOTICE 'An unexpected error occurred. Details logged.';
RETURN -1;
END;
$$ LANGUAGE plpgsql;
O novo bloco WHEN OTHERS capturará qualquer outro erro. Ele constrói uma mensagem detalhada usando as variáveis SQLSTATE e SQLERRM e a insere no log. Em seguida, retorna -1 para sinalizar que ocorreu um erro genérico.
Embora nossa função atual só possa acionar um erro division_by_zero, essa estrutura a torna muito mais robusta contra modificações futuras ou entradas inesperadas. Por exemplo, se os tipos de entrada fossem alterados para um tipo que pudesse causar overflow, o bloco OTHERS capturaria o erro numeric_value_out_of_range.
Vamos testar o caminho division_by_zero novamente para garantir que ele ainda funcione como esperado. Primeiro, limpe a tabela de log para um teste limpo.
TRUNCATE error_log;
Agora, chame a função:
SELECT safe_divide(10, 0);
Finalmente, verifique o log. Ele deve conter a mensagem específica "Division by zero", confirmando que o primeiro bloco WHEN foi escolhido corretamente.
SELECT message FROM error_log;
message
----------------------------
Division by zero occurred!
(1 row)
Você agora construiu uma função resiliente que lida tanto com erros específicos e antecipados quanto com erros gerais e inesperados.
Resumo
Neste laboratório, você aprendeu os fundamentos do tratamento de erros em PL/pgSQL do PostgreSQL. Você começou usando a instrução RAISE para emitir mensagens NOTICE e WARNING para fins de depuração e informativos. Em seguida, progrediu para o tratamento de exceções em tempo de execução, criando uma função que poderia falhar e, em seguida, implementando um bloco BEGIN...EXCEPTION...END para capturar um erro específico de division_by_zero. Com base nisso, você aprimorou a função para registrar detalhes do erro em uma tabela de banco de dados, fornecendo um registro persistente para auditoria. Finalmente, você tornou a função mais robusta adicionando um manipulador geral WHEN OTHERS para capturar quaisquer erros inesperados, garantindo que sua função possa falhar graciosamente sob várias condições.


