Após verificarmos o escopo das variáveis locais dentro de procedimentos armazenados no servidor de bancos de dados MySQL, na parte 01 deste artigo, entraremos em contato com o tratamento de erros que também são realizados dentro de Stored Routines, recursos estes suportados pelo MySQL, alguns após a versão 4.1 e outros após a versão 5.0.
Para você que ainda não leu nenhum dos artigos que formam a série de artigos nos quais falamos sobre Stored Procedures, é aconselhável ler desde o início e então ir avançando até chegar neste ponto, que trata de recursos mais avançados na escrita de programas que permanecerão armazenados dentro do servidor de bancos de dados.
Os artigos anteriores são:
Stored Procedures – Transações
Stored Procedures e Variáveis do Usuário
Variáveis Locais e Tratamento de Erros em Stored Procedures
Após ler este artigo, você estará apto a:
- Trabalhar com Conditions e Handlers;
- Implementar Stored Routines com tratamento de erro;
Desde muito tempo temos a necessidade de programar também no lado servidor, ou seja, que os servidores de bancos de dados pudessem armazenar alguma lógica para evitar o consumo de banda com o envio de muitos comandos SQL repetidos ao servidor. Com isso, surgiram os procedimentos que nada mais são que programas que persistem armazenados no servidor de bancos de dados, vinculados a um banco de dados obrigatoriamente e executam algum processamento após ser invocados.
Assim como disse um grande estudioso das ciências administrativas, Peter Senge, “os problemas de hoje vêm das soluções de ontem“, somente escrever pequenos programas e armazená-los dentro do MySQL não foi tão somente a solução do problema, pois agora se espera poder tratar os erros nas operações destes programas. Erros como, por exemplo, uma violação de uma chave primária ou índice único, a violação de integridade referencial ou mesmo um WARNING em meio ao processamento do procedimento.
Percebemos então que o tratamento de erros dentro de procedimentos armazenados no MySQL é baseado em condições. Caso uma determinada condição for atendida, um erro que podemos personalizar, baseado nos tipos de condições existentes, será disparado.
Conditions e Handlers
Conforme supracitado, o tratamento de erros do MySQL é baseado em condições, que por sua vez, são nomeadas e preparadas para serem executadas mediante ao atendimento de tais condições. O recurso chamado HANDLER poderá ser declarado da mesma forma como se declara uma variável local, criando uma condição para que uma mensagem seja retornada quando um SQLSTATE for disparado pelo servidor de bancos de dados, podendo parar imediatamente o processamento do programa ou mesmo continuar sem exibir qualquer mensagem.
O SQLSTATE aparecerá sempre que um erro for enviado a um usuário quando este executa uma operação ilegal ou que viole a integridade dos dados, por exemplo. O que acontecerá se você tentar inserir uma informação duplicada em uma coluna que é chave primária de uma tabela?
mysql> CREATE TABLE test.tbl_1 (id tinyint primary key);
Query OK, 0 rows affected (0.02 sec)
mysql> insert into test.tbl_1 set id=1;
Query OK, 1 row affected (0.00 sec)
mysql> insert into test.tbl_1 set id=1;
ERROR 1062 (23000): Duplicate entry '1' for key 1
Perceba que na manipulação anterior, com o intuito de mostrar o erro retornado pelo MySQL ao tentar inserir uma informação duplicada em uma coluna chave primária, criamos uma tabela chamada tbl_1, no banco de dados test (lembre-se de que podemos trabalhar com nomes totalmente qualificados – database_name.table_name), cuja coluna id desta tabela é a nossa PK e na seqüencia tentamos inserir o mesmo dado mais de uma vez. Logo um erro 1062 com o SQLSTATE 23000 é retornado, gerando esta mensagem nada amigável para o usuário que tenta efetivar estas informações.
No seguinte exemplo, podemos declarar um HANDLER para finalizar o processamento do programa, devolvendo uma mensagem personalizada, mais amigável, em relação a uma violação da chave primária.
mysql> DELIMITER //
mysql> CREATE PROCEDURE test.sp_1(IN v_id INT)
-> BEGIN
-> DECLARE EXIT HANDLER FOR SQLSTATE '23000'
-> BEGIN
-> SELECT 'Violação de chave primária!' AS Msg;
-> END;
-> INSERT INTO test.tbl_1 SET id =v_id;
-> END;
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> CALL test.sp_2(1);
-----------------------------
Msg
-----------------------------
Violação de chave primária!
-----------------------------
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Além de tratarmos a violação de chaves primárias, podemos tratar o truncamento de dados. Perceba que, caso uma Stored procedure permita que dados alfanuméricos sejam passados como parâmetro para serem cadastrados em uma coluna do tipo inteiro, no caso abaixo INT, podemos rapidamente tratar o fato:
mysql> mysql> -- criamos o Stored Procedure no banco de dados test
mysql> CREATE PROCEDURE test.sp_2(IN num CHAR(1))
-> -- acima, possibilitamos o recebimento de uma parâmetro do tipo CHAR
-> -- ou seja, podemos enviar ao procedure dados alfanuméricos...
-> BEGIN
-> DECLARE EXIT HANDLER FOR SQLWARNING
-> BEGIN
-> SELECT 'O dado foi truncado!' AS Msg;
-> END;
-> INSERT INTO test.tbl_1 SET id =num;
-> END;
-> //
Query OK, 0 rows affected (0.00 sec)
Antes mesmo da execução do procedimento escrito acima, já podemos perfeitamente visualizar qual será o problema que certamente disparará a mensagem de erro que personalizamos entre o BEGIN ... END aninhado. Propositalmente, enviamos dado do tipo CHAR para ser inserido em uma coluna do tipo INT, o que causará um WARNING e uma mensagem de truncamento dos dados, ou seja, se enviarmos ao Stored Procedure uma letra a, a condição declarada no HANDLER será estabelecida, e como ela é uma condição EXIT, esta encerrará o programa.
mysql> DELIMITER ;
mysql> CALL test.sp_1('a');
----------------------
Msg
----------------------
O dado foi truncado!
----------------------
1 row in set (0.03 sec)
mysql> SELECT * FROM test.tbl_1;
----
id
----
0
1
----
2 rows in set (0.00 sec)
Perceba que houve basicamente um Try/Catch relacionado aos problemas que possam vir a acontecer internamente na execução de um programa. Mesmo sendo o erro levantado no procedimento anterior, o dado truncado será inserido na tabela, o que não deixa de ser danoso. Com facilidade, desde que se esteja trabalhando com tabelas controladas pelo Storage Engine InnoDB ou Falcon, podemos impedir que este dado seja inserido na tabela utilizando COMMIT e ROLLBACK.
E como ficaria o procedimento anterior utilizando COMMIT e ROLLBACK?
mysql>mysql> DELIMITER //
mysql> CREATE PROCEDURE test.sp_2(IN num CHAR(1))
-> -- acima, possibilitamos o recebimento de uma parâmetro do tipo CHAR
-> -- ou seja, podemos enviar ao procedure dados alfanuméricos...
-> BEGIN
-> DECLARE EXIT HANDLER FOR SQLWARNING
-> BEGIN
-> SELECT 'O dado foi truncado!' AS Msg_1;
-> ROLLBACK; -- a transação sofre um ROLLBACK
-> SELECT 'A transação sofreu um ROLLBACK!' AS Msg_2;
-> END;
-> START TRANSACTION; -- aqui, autocommit é setado para 1 automaticamente
-> INSERT INTO tbl_1 SET id =num; -- se o erro acontecer, executa o bloco BEGIN ... END acima
-> COMMIT; -- a transação sofre um commit caso o erro não aconteça
-> END;
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> CALL test.sp_2('a');
----------------------
Msg_1
----------------------
O dado foi truncado!
----------------------
1 row in set (0.00 sec)
---------------------------------
Msg_2
---------------------------------
A transação sofreu um ROLLBACK!
---------------------------------
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM test.tbl_1; -- os mesmos dados de antes!
----
id
----
0
1
----
2 rows in set (0.00 sec)
Outros tipos de condições também podem ser abordadas na criação de um HANDLER ou CONDITION:
- Podemos declarar explicitamente um código de SQLSTATE para tratar o erro;
- Podemos declarar, como já exibido, o SQLWARNING e todos os SQLSTATES iniciados com 01 serão tratados;
- Podemos declarar NOT FOUND, mais comum em Cursors e Stored Functions, para tratamento de SQLSTATES iniciados com 02;
- Podemos declarar o SQLEXCEPTION que tratará erros de SQLWARNING ou NOT FOUND.
Um fato interessante a ser observado na escrita e definição da mensagem personalizada é que, iniciamos a definição do procedimento, abrimos o primeiro BEGIN ... END e depois declaramos o HANDLER ou CONDITION. Abrimos outro bloco BEGIN ... END para que este seja executado sempre que o erro for levantado. Fique bem atento a este detalhe, pois, de outra maneira, não funcionará.
Conclusão
Neste artigo fechamos então a questão do tratamento de erros dentro de programas armazenados no servidor de bancos de dados MySQL. Para isto, vimos que podemos trabalhar com os HANDLERS e CONDITIONS declarados com o auxílio da declaração DECLARE que contará com uma bloco BEGIN ... END para ser executado caso o erro aconteça.
Até a próxima e Happy MySQL’ing!