PL/pgSQL : gestion des erreurs

Lors de l’écriture d’une fonction PL/pgSQL, il y a régulièrement des erreurs qui apparaissent. Par défaut, dans ce cas, la fonction PL/pgSQL s’arrête, et provoque un rollback complet de la transaction (et donc des fonctions appelantes).

Exemple d’erreur non traitée

Reprenons notre table users, et vidons là :

Maintenant, créons une fonction qui va automatiquement passer en erreur :

L’erreur est évidente : on initialise i à 5, et on décroit progressivement cette valeur. A chaque boucle,on affecte à j la valeur de (10/i). Forcément, quand i va arriver à 0…

Testons ça tout de suite :

Pas de surprise. On voit bien un gentil message d’erreur, assez explicite (ERROR: division by zero), qui nous indique dans quelle fonction et à quelle ligne (CONTEXT: PL/pgSQL function “test_erreur” line 8) et qui en plus à la gentillesse de nous dire dans quelle instruction ( at assignment).

Allez, on va aller encore plus loin dans l’horreur, on va créer une fonction qui va appeler la fonction test_erreur :

Sans surprise, ça plante aussi, aucun insert n’est effectué. On voit que Postgres nous indique en détail où se trouve l’erreur.

Bon, c’est bien joli tout ça, mais là, je viens de vous apprendre que quand on code mal, ça plante (… non, ne me remerciez pas … 😀 ). C’est pas vraiment ce qu’on appelle la gestion des erreurs non ?

Gérer les erreurs

Nous avons comment le corps d’une fonction PL/pgSQL se décomposait en deux parties : le DECLARE (optionnel), et le BEGIN (obligatoire). Nous allons maintenant rajouter une troisième partie, le EXCEPTION. Modifions notre première fonction :

A noter : dans ce cas, on ne traite que l’exception division_by_zero. Si il y a une autre erreur, comme elle n’est pas prévue dans le bloc exception, elle sera traitée comme une erreur normale. La liste des erreurs disponibles se trouve ici : http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
Si on veut traiter plus d’exceptions, il faut enchainer les when :

Et maintenant, testons là :

La fonction s’est terminée, a priori sans retourner d’erreur. Que s’est-il passé ?

  • les variables ont gardé leur dernier état avant l’exception
  • les modifications à la base de données sont annulées (rollback)

Et alors ? Au final, on a exactement la même chose que quand on n’avait pas le bloc exception, quel est donc l’intérêt ? Le premier est déjà de pouvoir fournir une valeur de retour, ce qui n’était pas le cas avant.

Le second intérêt va être visible si maintenant on appelle notre seconde fonction :

Et là surprise ! Il y a bien un enregistrement. En effet, la fonction test_erreur() ne sort plus en erreur, donc la fonction test_appel_erreur() ne finit pas en erreur elle non plus. Seules les instructions de test_erreur passent en rollback (c’est quand même elle qui provoque une division par 0), mais appel_test_erreur se déroule bien, donc elle passe en commit.

Extension de cette gestion d’erreurs

Nous avons vu dans le chapitre précédent comment provoquer manuellement une erreur. Il est donc possible dans une fonction de provoquer une erreur, pour pouvoir la traiter dans une exception dans une fonction appelante. Oui, je sais, c’est tordu… mais possible 🙂

Limitation de la gestion manuelle des erreurs

Il y a deux choses auxquelles il faut faire vraiment très attention quand on gère les blocs exception :

La cohérence des données : les deux fonctions ci dessus sont un exemple parfait. Lorsque la seconde fonction est lancée, on pourrait croire qu’on a aussi fait tous les insert de la première fonction, ce qui n’est pas le cas. On n’a fait que les insert de la fonction appelante. Si on ne fait pas attention, on peut vite se retrouver avec des données inconsistantes.
Les performances : Un bloc exception est très consommateur en ressources, beaucoup plus que des tests unitaires sur les valeurs à traiter. D’ailleurs, la documentation officielle de PostgreSQL est très claire : Un bloc contenant une clause EXCEPTION est significativement plus coûteuse en entrée et en sortie qu’un bloc sans. Du coup, n’utilisez pas EXCEPTION sans besoin.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.