Les structures de contr�le sont probablement la partie la plus utile (et importante) de PL/pgSQL. Gr�ce aux structures de contr�le de PL/pgSQL, vous pouvez manipuler les données PostgreSQL™ de fa�on tr�s flexible et puissante.
Il y a deux commandes disponibles qui vous permettent de renvoyer des données d'une fonction : RETURN et RETURN NEXT.
RETURN expression;
RETURN accompagné d'une expression termine la fonction et renvoie le valeur de l'expression � l'appelant. Cette forme doit �tre utilisée avec des fonctions PL/pgSQL qui ne renvoient pas d'ensemble de valeurs.
Dans une fonction qui renvoie un type scalaire, le résultat de l'expression sera automatiquement convertie dans le type que la fonction renvoie. Mais pour renvoyer une valeur composite (ligne), vous devez écrire une expression renvoyant exactement l'ensemble de colonnes souhaité. Ceci peut demander l'utilisation de conversion explicite.
Si vous déclarez la fonction avec des param�tres en sortie, écrivez seulement RETURN sans expression. Les valeurs courantes des param�tres en sortie seront renvoyées.
Si vous déclarez que la fonction renvoie void, une instruction RETURN peut �tre utilisée pour quitter rapidement la fonction ; mais n'écrivez pas d'expression apr�s RETURN.
La valeur de retour d'une fonction ne peut pas �tre laissée indéfinie. Si le contr�le atteint la fin du bloc de haut niveau de la fonction, sans parvenir � une instruction RETURN, une erreur d'exécution survient. Néanmoins, cette restriction ne s'applique pas aux fonctions sans param�tre de sortie et aux fonctions renvoyant void. Dans ces cas, une instruction RETURN est automatiquement exécutée si le bloc de haut niveau est terminé.
Quelques exemples :
-- fonctions renvoyant un type scalaire RETURN 1 + 2; RETURN scalar_var; -- fonctions renvoyant un type composite RETURN composite_type_var; RETURN (1, 2, 'three'::text); -- must cast columns to correct types
RETURN NEXT expression;
RETURN QUERY requete;
RETURN QUERY EXECUTE command-string [ USING expression [, ...] ];
Quand une fonction PL/pgSQL déclare renvoyer SETOF un_certain_type, la procédure � suivre est un peu différente. Dans ce cas, les éléments individuels � renvoyer sont spécifiés par une séquence de commandes RETURN NEXT ou RETURN QUERY, suivies de la commande finale RETURN sans argument qui est utilisée pour indiquer la fin de l'exécution de la fonction. RETURN NEXT peut �tre utilisé avec des types de données scalaires comme composites ; avec un type de résultat composite, une � table � enti�re de résultats sera renvoyée. RETURN QUERY ajoute les résultats de l'exécution d'une requ�te � l'ensemble des résultats de la fonction. RETURN NEXT et RETURN QUERY peuvent �tre utilisés dans la m�me fonction, auquel cas leurs résultats seront concaténées.
RETURN NEXT et RETURN QUERY ne quittent pas réellement la fonction -- elles ajoutent simplement zéro ou plusieurs lignes � l'ensemble de résultats de la fonction. L'exécution continue ensuite avec l'instruction suivante de la fonction PL/pgSQL. Quand plusieurs commandes RETURN NEXT et/ou RETURN QUERY successives sont exécutées, l'ensemble de résultats augmente. Un RETURN, sans argument, permet de quitter la fonction mais vous pouvez aussi continuer jusqu'� la fin de la fonction.
RETURN QUERY dispose d'une variante RETURN QUERY EXECUTE, qui spécifie la requ�te � exécuter dynamiquement. Les expressions de param�tres peuvent �tre insérées dans la cha�ne calculée via USING, de la m�me fa�on que le fait la commande EXECUTE.
Si vous déclarez la fonction avec des param�tres en sortie, écrivez RETURN NEXT sans expression. � chaque exécution, les valeurs actuelles des variables param�tres en sortie seront sauvegardées pour un renvoi éventuel en tant que résultat en sortie. Notez que vous devez déclarer la fonction en tant que SETOF record quand il y a plusieurs param�tres en sortie, ou SETOF un_certain_type quand il y a un seul param�tre en sortie, et de type un_certain_type, pour créer une fonction SRF avec des param�tres en sortie.
Voici un exemple d'une fonction utilisant RETURN NEXT :
CREATE TABLE truc (id_truc INT, sousid_truc INT, nom_truc TEXT); INSERT INTO truc VALUES (1, 2, 'trois'); INSERT INTO truc VALUES (4, 5, 'six'); CREATE OR REPLACE FUNCTION obtenir_tous_les_trucs() RETURNS SETOF foo AS $BODY$ DECLARE r truc%rowtype; BEGIN FOR r IN SELECT * FROM truc WHERE id_truc > 0 LOOP -- quelques traitements RETURN NEXT r; -- renvoie la ligne courante du SELECT END LOOP; RETURN; END $BODY$ LANGUAGE 'plpgsql' ; SELECT * FROM obtenir_tous_les_trucs();
Voici un exemple de fonction utilisant RETURN QUERY :
CREATE FUNCTION obtient_idvol_disponibles(date) RETURNS SETOF integer AS $BODY$ BEGIN RETURN QUERY SELECT idvol FROM vol WHERE datevol >= $1 AND datevol < ($1 + 1); -- Comme l'exécution n'est pas terminée, nous vérifions si les lignes -- ont été renvoyées et levons une exception dans le cas contraire. IF NOT FOUND THEN RAISE EXCEPTION 'Aucun vol � %.', $1; END IF; RETURN; END $BODY$ LANGUAGE plpgsql; -- Renvoie les vols disponibles ou l�ve une exception si aucun vol -- n'est disponible. SELECT * FROM obtient_idvol_disponibles(CURRENT_DATE);
L'implémentation actuelle de RETURN NEXT et de RETURN QUERY pour PL/pgSQL récup�re la totalité de l'ensemble des résultats avant d'effectuer le retour de la fonction, comme vu plus haut. Cela signifie que si une fonction PL/pgSQL produit une structure résultat tr�s grande, les performances peuvent �tre faibles : les données seront écrites sur le disque pour éviter un épuisement de la mémoire mais la fonction en elle-m�me ne renverra rien jusqu'� ce que l'ensemble complet des résultats soit généré. Une version future de PL/pgSQL permettra aux utilisateurs de définir des fonctions renvoyant des ensembles qui n'auront pas cette limitation. Actuellement, le point auquel les données commencent � �tre écrites sur le disque est contr�lé par la variable de configuration work_mem. Les administrateurs ayant une mémoire suffisante pour enregistrer des ensembles de résultats plus importants en mémoire doivent envisager l'augmentation de ce param�tre.
Les instructions IF et CASE vous permettent d'exécuter des commandes basées sur certaines conditions. PL/pgSQL a trois formes de IF :
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSIF ... THEN ... ELSE
et deux formes de CASE :
CASE ... WHEN ... THEN ... ELSE ... END CASE
CASE WHEN ... THEN ... ELSE ... END CASE
IF expression-booleenne THEN instructions END IF;
Les instructions IF-THEN sont la forme la plus simple de IF. Les instructions entre THEN et END IF seront exécutées si la condition est vraie. Autrement, elles seront ignorées.
Exemple :
IF v_id_utilisateur <> 0 THEN UPDATE utilisateurs SET email = v_email WHERE id_utilisateur = v_id_utilisateur; END IF;
IF expression-booleenne THEN instructions ELSE instructions END IF;
Les instructions IF-THEN-ELSE s'ajoutent au IF-THEN en vous permettant de spécifier un autre ensemble d'instructions � exécuter si la condition n'est pas vraie (notez que ceci inclut le cas o� la condition s'évalue � NULL.).
Exemples :
IF id_parent IS NULL OR id_parent = '' THEN RETURN nom_complet; ELSE RETURN hp_true_filename(id_parent) || '/' || nom_complet; END IF;
IF v_nombre > 0 THEN INSERT INTO nombre_utilisateurs (nombre) VALUES (v_nombre); RETURN 't'; ELSE RETURN 'f'; END IF;
IF expression-booleenne THEN instructions [ ELSIF expression-booleenne THEN instructions [ ELSIF expression-booleenne THEN instructions ... ] ] [ ELSE instructions ] END IF;
Quelques fois, il existe plus de deux alternatives. IF-THEN-ELSIF fournit une méthode agréable pour vérifier différentes alternatives. Les conditions IF sont testées successivement jusqu'� trouver la bonne. Alors les instructions associées sont exécutées, puis le contr�le est passé � la prochaine instruction apr�s END IF. (Toute autre condition IF n'est pas testée.) Si aucune des conditions IF n'est vraie, alors le bloc ELSE (s'il y en a un) est exécuté.
Voici un exemple :
IF nombre = 0 THEN resultat := 'zero'; ELSIF nombre > 0 THEN resultat := 'positif'; ELSIF nombre < 0 THEN resultat := 'negatif'; ELSE -- hmm, la seule possibilité est que le nombre soit NULL resultat := 'NULL'; END IF;
Le mot clé ELSIF peut aussi s'écrire ELSEIF.
Une fa�on alternative d'accomplir la m�me t�che est d'intégrer les instructions IF-THEN-ELSE, comme dans l'exemple suivant :
IF demo_row.sex = 'm' THEN pretty_sex := 'man'; ELSE IF demo_row.sex = 'f' THEN pretty_sex := 'woman'; END IF; END IF;
Néanmoins, cette méthode requiert d'écrire un END IF pour chaque IF, donc c'est un peu plus compliqué que d'utiliser ELSIF quand il y a beaucoup d'autres alternatives.
CASE expression_recherche WHEN expression [, expression [ ... ]] THEN instructions [ WHEN expression [, expression [ ... ]] THEN instructions ... ] [ ELSE instructions ] END CASE;
La forme simple de CASE fournit une exécution conditionnelle basée sur l'égalité des opérandes. L'expression-recherche est évaluée (une fois) puis comparée successivement � chaque expression dans les clauses WHEN. Si une correspondance est trouvée, alors les instructions correspondantes sont exécutées, puis le contr�le est passé � la prochaine instruction apr�s END CASE. (Les autres expressions WHEN ne sont pas testées.) Si aucune correspondance n'est trouvée, les instructions du bloc ELSE sont exécutées ; s'il n'y a pas de bloc ELSE, une exception CASE_NOT_FOUND est levée.
Voici un exemple simple :
CASE x WHEN 1, 2 THEN msg := 'un ou deux'; ELSE msg := 'autre valeur que un ou deux'; END CASE;
CASE WHEN expression_booléenne THEN instructions [ WHEN expression_booléenne THEN instructions ... ] [ ELSE instructions ] END CASE;
La forme recherché de CASE fournit une exécution conditionnelle basée sur la vérification d'expressions booléennes. Chaque expression-booléenne de la clause WHEN est évaluée � son tour jusqu'� en trouver une qui est validée (true). Les instructions correspondantes sont exécutées, puis le contr�le est passé � la prochaine instruction apr�s END CASE. (Les expressions WHEN suivantes ne sont pas testées.) Si aucun résultat vrai n'est trouvé, les instructions du bloc ELSE sont exécutées. Si aucun bloc ELSE n'est présent, une exception CASE_NOT_FOUND est levée.
Voici un exemple :
CASE WHEN x BETWEEN 0 AND 10 THEN msg := 'valeur entre zéro et dix'; WHEN x BETWEEN 11 AND 20 THEN msg := 'valeur entre onze et vingt'; END CASE;
Cette forme de CASE est enti�rement équivalente � IF-THEN-ELSIF, sauf pour la r�gle qui dit qu'atteindre une clause ELSE omise résulte dans une erreur plut�t que ne rien faire.
Gr�ce aux instructions LOOP, EXIT, CONTINUE, WHILE FOR et FOREACH, vous pouvez faire en sorte que vos fonctions PL/pgSQL rép�tent une série de commandes.
[<<label>>] LOOP instructions END LOOP [ label ];
LOOP définit une boucle inconditionnelle répétée indéfiniment jusqu'� ce qu'elle soit terminée par une instruction EXIT ou RETURN. Le label optionnel peut �tre utilisé par les instructions EXIT et CONTINUE dans le cas de boucles imbriquées pour définir la boucle impliquée.
EXIT [ label ] [ WHEN expression-booléenne ];
Si aucun label n'est donné, la boucle la plus imbriquée se termine et l'instruction suivant END LOOP est exécutée. Si un label est donné, ce doit �tre le label de la boucle, du bloc courant ou d'un niveau moins imbriqué. La boucle ou le bloc nommé se termine alors et le contr�le continue avec l'instruction située apr�s le END de la boucle ou du bloc correspondant.
Si WHEN est spécifié, la sortie de boucle ne s'effectue que si expression-booléenne est vraie. Sinon, le contr�le passe � l'instruction suivant le EXIT.
EXIT peut �tre utilisé pour tous les types de boucles ; il n'est pas limité aux boucles non conditionnelles.
Lorsqu'il est utilisé avec un bloc BEGIN, EXIT passe le contr�le � la prochaine instruction apr�s la fin du bloc. Notez qu'un label doit �tre utilisé pour cela ; un EXIT sans label n'est jamais pris en compte pour correspondre � un bloc BEGIN. (Ceci est un changement de la version 8.4 de PostgreSQL™. Auparavant, il était permis de faire correspondre un EXIT sans label avec un bloc BEGIN.)
Exemples :
LOOP -- quelques traitements IF nombre > 0 THEN EXIT; -- sortie de boucle END IF; END LOOP; LOOP -- quelques traitements EXIT WHEN nombre > 0; END LOOP; <<un_bloc>> BEGIN -- quelques traitements IF stocks > 100000 THEN EXIT un_bloc; -- cause la sortie (EXIT) du bloc BEGIN END IF; -- les traitements ici seront ignorés quand stocks > 100000 END;
CONTINUE [ label ] [ WHEN expression-booléenne ];
Si aucun label n'est donné, la prochaine itération de la boucle interne est commencée. C'est-�-dire que toutes les instructions restantes dans le corps de la boucle sont ignorées et le contr�le revient � l'expression de contr�le de la boucle pour déterminer si une autre itération de boucle est nécessaire. Si le label est présent, il spécifie le label de la boucle dont l'exécution va �tre continuée.
Si WHEN est spécifié, la prochaine itération de la boucle est commencée seulement si l'expression-booléenne est vraie. Sinon, le contr�le est passé � l'instruction apr�s CONTINUE.
CONTINUE peut �tre utilisé avec tous les types de boucles ; il n'est pas limité � l'utilisation des boucles inconditionnelles.
Exemples :
LOOP -- quelques traitements EXIT WHEN nombre > 100; CONTINUE WHEN nombre < 50; -- quelques traitements pour nombre IN [50 .. 100] END LOOP;
[<<label>>] WHILE expression-booléenne LOOP instructions END LOOP [ label ];
L'instruction WHILE rép�te une séquence d'instructions aussi longtemps que expression-booléenne est évaluée � vrai. L'expression est vérifiée juste avant chaque entrée dans le corps de la boucle.
Par exemple :
WHILE montant_possede > 0 AND balance_cadeau > 0 LOOP -- quelques traitements ici END LOOP; WHILE NOT termine LOOP -- quelques traitements ici END LOOP;
[<<label>>] FOR nom IN [ REVERSE ] expression .. expression [ BY expression ] LOOP instruction END LOOP [ label ];
Cette forme de FOR crée une boucle qui effectue une itération sur une plage de valeurs enti�res. La variable nom est automatiquement définie comme un type integer et n'existe que dans la boucle (toute définition de la variable est ignorée � l'intérieur de la boucle). Les deux expressions donnant les limites inférieures et supérieures de la plage sont évaluées une fois en entrant dans la boucle. Si la clause BY n'est pas spécifiée, l'étape d'itération est de 1, sinon elle est de la valeur spécifiée dans la clause BY, qui est évaluée encore une fois � l'entrée de la boucle. Si REVERSE est indiquée, alors la valeur de l'étape est soustraite, plut�t qu'ajoutée, apr�s chaque itération.
Quelques exemples de boucles FOR avec entiers :
FOR i IN 1..10 LOOP -- prend les valeurs 1,2,3,4,5,6,7,8,9,10 dans la boucle END LOOP; FOR i IN REVERSE 10..1 LOOP -- prend les valeurs 10,9,8,7,6,5,4,3,2,1 dans la boucle END LOOP; FOR i IN REVERSE 10..1 BY 2 LOOP -- prend les valeurs 10,8,6,4,2 dans la boucle END LOOP;
Si la limite basse est plus grande que la limite haute (ou moins grande dans le cas du REVERSE), le corps de la boucle n'est pas exécuté du tout. Aucune erreur n'est renvoyée.
Si un label est attaché � la boucle FOR, alors la variable enti�re de boucle peut �tre référencée avec un nom qualifié en utilisant ce label.
En utilisant un type de FOR différent, vous pouvez itérer au travers des résultats d'une requ�te et par l�-m�me manipuler ces données. La syntaxe est la suivante :
[<<label>>] FOR cible IN requ�te LOOP instructions END LOOP [ label ];
La cible est une variable de type record, row ou une liste de variables scalaires séparées par une virgule. La cible est affectée successivement � chaque ligne résultant de la requ�te et le corps de la boucle est exécuté pour chaque ligne. Voici un exemple :
CREATE FUNCTION cs_rafraichir_vuemat() RETURNS integer AS $$ DECLARE vues_mat RECORD; BEGIN RAISE NOTICE 'Rafraichissement des vues matérialisées...'; FOR vues_mat IN SELECT * FROM cs_vues_materialisees ORDER BY cle_tri LOOP -- � présent vues_mat contient un enregistrement de cs_vues_materialisees RAISE NOTICE 'Rafraichissement de la vue matérialisée %s ...', quote_ident(mviews.mv_name); EXECUTE 'TRUNCATE TABLE ' || quote_ident(vues_mat.vm_nom); EXECUTE 'INSERT INTO ' || quote_ident(vues_mat.vm_nom) || ' ' || vues_mat.vm_requete; END LOOP; RAISE NOTICE 'Fin du rafraichissement des vues matérialisées.'; RETURN 1; END; $$ LANGUAGE plpgsql;
Si la boucle est terminée par une instruction EXIT, la derni�re valeur ligne affectée est toujours accessible apr�s la boucle.
La requ�te utilisée dans ce type d'instruction FOR peut �tre toute commande SQL qui renvoie des lignes � l'appelant : SELECT est le cas le plus commun mais vous pouvez aussi utiliser INSERT, UPDATE ou DELETE avec une clause RETURNING. Certaines commandes comme EXPLAIN fonctionnent aussi.
Les variables PL/pgSQL sont substituées dans le texte de la requ�te et le plan de requ�te est mis en cache pour une réutilisation possible. C'est couvert en détail dans la Section 40.10.1, � Substitution de variables � et dans la Section 40.10.2, � Mise en cache du plan �.
L'instruction FOR-IN-EXECUTE est un moyen d'itérer sur des lignes :
[<<label>>] FOR target IN EXECUTE text_expression [ USING expression [, ...] ] LOOP instructions END LOOP [ label ];
Ceci est identique � la forme précédente, � ceci pr�s que l'expression de la requ�te source est spécifiée comme une expression cha�ne, évaluée et replanifiée � chaque entrée dans la boucle FOR. Ceci permet au développeur de choisir entre la vitesse d'une requ�te préplanifiée et la flexibilité d'une requ�te dynamique, uniquement avec l'instruction EXECUTE. Comme avec EXECUTE, les valeurs de param�tres peuvent �tre insérées dans la commande dynamique via USING.
Une autre fa�on de spécifier la requ�te dont les résultats devront �tre itérés est de la déclarer comme un curseur. Ceci est décrit dans Section 40.7.4, � Boucler dans les résultats d'un curseur �.
La boucle FOREACH ressemble beaucoup � une boucle FOR mais, au lieu d'itérer sur les lignes renvoyées par une requ�tes SQL, elle it�re sur les éléments d'une valeur de type tableau. (En général, FOREACH est fait pour boucler sur les composants d'une expression composite ; les variantes pour boucler sur des composites en plus des tableaux pourraient �tre ajoutées dans le futur.) L'instruction FOREACH pour boucler sur un tableau est :
[ <<label>> ] FOREACH target [ SLICE nombre ] IN ARRAY expression LOOP instructions END LOOP [ label ];
Sans SLICE ou si SLICE 0 est indiqué, la boucle it�re au niveau des éléments individuels du tableau produit par l'évaluation de l'expression. La variable cible se voit affectée chaque valeur d'élément en séquence, et le corps de la boucle est exécuté pour chaque élément. Voici un exemple de boucle sur les éléments d'un tableau d'entiers :
CREATE FUNCTION somme(int[]) RETURNS int8 AS $$ DECLARE s int8 := 0; x int; BEGIN FOREACH x IN ARRAY $1 LOOP s := s + x; END LOOP; RETURN s; END; $$ LANGUAGE plpgsql;
Les éléments sont parcourus dans l'ordre de leur stockage, quelque soit le nombre de dimensions du tableau. Bien que la cible est habituellement une simple variable, elle peut �tre une liste de variables lors d'une boucle dans un tableau de valeurs composites (des enregistrements). Dans ce cas, pour chaque élément du tableau, les variables se voient affectées les colonnes de la valeur composite.
Avec une valeur SLICE positive, FOREACH it�re au travers des morceaux du tableau plut�t que des éléments seuls. La valeur de SLICE doit �tre un entier constant, moins large que le nombre de dimensions du tableau. La variable cible doit �tre un tableau et elle re�oit les morceaux successifs de la valeur du tableau, o� chaque morceau est le nombre de dimensions indiquées par SLICE. Voici un exemple d'itération sur des morceaux � une dimension :
CREATE FUNCTION parcourt_lignes(int[]) RETURNS void AS $$ DECLARE x int[]; BEGIN FOREACH x SLICE 1 IN ARRAY $1 LOOP RAISE NOTICE 'ligne = %', x; END LOOP; END; $$ LANGUAGE plpgsql; SELECT parcourt_lignes(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]); NOTICE: ligne = {1,2,3} NOTICE: ligne = {4,5,6} NOTICE: ligne = {7,8,9} NOTICE: ligne = {10,11,12}
Par défaut, toute erreur survenant dans une fonction PL/pgSQL annule l'exécution de la fonction mais aussi de la transaction qui l'entoure. Vous pouvez récupérer les erreurs en utilisant un bloc BEGIN avec une clause EXCEPTION. La syntaxe est une extension de la syntaxe habituelle pour un bloc BEGIN :
[ <<label>> ] [ DECLARE declarations ] BEGIN instructions EXCEPTION WHEN condition [ OR condition ... ] THEN instructions_gestion_erreurs [ WHEN condition [ OR condition ... ] THEN instructions_gestion_erreurs ... ] END;
Si aucune erreur ne survient, cette forme de bloc exécute simplement toutes les instructions puis passe le contr�le � l'instruction suivant END. Mais si une erreur survient � l'intérieur des instructions, le traitement en cours des instructions est abandonné et le contr�le est passé � la liste d'EXCEPTION. Une recherche est effectuée sur la liste pour la premi�re condition correspondant � l'erreur survenue. Si une correspondance est trouvée, les instructions_gestion_erreurs correspondantes sont exécutées puis le contr�le est passé � l'instruction suivant le END. Si aucune correspondance n'est trouvée, l'erreur se propage comme si la clause EXCEPTION n'existait pas du tout : l'erreur peut �tre récupérée par un bloc l'enfermant avec EXCEPTION ou, s'il n'existe pas, elle annule le traitement de la fonction.
Les noms des condition sont indiquées dans l'Annexe A, Codes d'erreurs de PostgreSQL™. Un nom de catégorie correspond � toute erreur contenue dans cette catégorie. Le nom de condition spéciale OTHERS correspond � tout type d'erreur sauf QUERY_CANCELED (il est possible, mais pas recommandé, de récupérer QUERY_CANCELED par son nom). Les noms des conditions ne sont pas sensibles � la casse. De plus, une condition d'erreur peut �tre indiquée par un code SQLSTATE ; par exemple, ces deux cas sont équivalents :
WHEN division_by_zero THEN ... WHEN SQLSTATE '22012' THEN ...
Si une nouvelle erreur survient � l'intérieur des instructions_gestion_erreurs sélectionnées, elle ne peut pas �tre récupérée par cette clause EXCEPTION mais est propagée en dehors. Une clause EXCEPTION l'englobant pourrait la récupérer.
Quand une erreur est récupérée par une clause EXCEPTION, les variables locales de la fonction PL/pgSQL restent dans le m�me état qu'au moment o� l'erreur est survenue mais toutes les modifications � l'état persistant de la base de données � l'intérieur du bloc sont annulées. Comme exemple, considérez ce fragment :
INSERT INTO mon_tableau(prenom, nom) VALUES('Tom', 'Jones'); BEGIN UPDATE mon_tableau SET prenom = 'Joe' WHERE nom = 'Jones'; x := x + 1; y := x / 0; EXCEPTION WHEN division_by_zero THEN RAISE NOTICE 'récupération de l''erreur division_by_zero'; RETURN x; END;
Quand le contr�le parvient � l'affectation de y, il échouera avec une erreur division_by_zero. Elle sera récupérée par la clause EXCEPTION. La valeur renvoyée par l'instruction RETURN sera la valeur incrémentée de x mais les effets de la commande UPDATE auront été annulés. La commande INSERT précédant le bloc ne sera pas annulée, du coup le résultat final est que la base de données contient Tom Jones et non pas Joe Jones.
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.
Exemple 40.2. Exceptions avec UPDATE/INSERT
Cet exemple utilise un gestionnaire d'exceptions pour réaliser soit un UPDATE soit un INSERT, comme approprié :
CREATE TABLE base (a INT PRIMARY KEY, b TEXT); CREATE FUNCTION fusionne_base(cle INT, donnee TEXT) RETURNS VOID AS $$ BEGIN LOOP -- commen�ons par tenter la mise � jour de la clé UPDATE base SET b = donnee WHERE a = cle; IF found THEN RETURN; END IF; -- si elle n'est pas dispo, tentons l'insertion de la clé -- si quelqu'un essaie d'insérer la m�me clé en m�me temps, -- il y aura une erreur pour violation de clé unique BEGIN INSERT INTO base(a,b) VALUES (cle, donnee); RETURN; EXCEPTION WHEN unique_violation THEN -- ne rien faire, et tente de nouveau la mise � jour END; END LOOP; END; $$ LANGUAGE plpgsql; SELECT fusionne_base(1, 'david'); SELECT fusionne_base(1, 'dennis');
Ce code suppose que l'erreur unique_violation est causée par la commande INSERT, et pas par un INSERT dans une fonction trigger sur la table. Cela pourrait avoir un mauvais comportement s'il y a plus d'un index unique sur la table car il ré-essaiera l'opération quelque soit l'index qui a causé l'erreur. On pourrait avoir plus de sécurité en utilisant la fonctionnalité discuté ci-apr�s pour vérifier que l'erreur récupérée était celle attendue.
Les gestionnaires d'exception ont fréquemment besoin d'identifier l'erreur spécifique qui est survenue. Il existe deux fa�ons d'obtenir l'information sur l'exception en cours dans PL/pgSQL : des variables spéciales et la commande GET STACKED DIAGNOSTICS.
Avec un gestionnaire d'exceptions, la variable spéciale SQLSTATE contient le code d'erreur qui correspond � l'exception qui a été levée (voir Tableau A.1, � Codes d'erreur de PostgreSQL™ � pour la liste de codes d'erreur possibles). La variable spéciale SQLERRM contient le message d'erreur associé � l'exception. Ces variables ne sont pas définies en dehors des gestionnaires d'exception.
Dans le gestionnaire d'exceptions, il est possible de récupérer des informations sur l'exception en cours en utilisant la commande GET STACKED DIAGNOSTICS qui a la forme :
GET STACKED DIAGNOSTICS variable = élément [ , ... ];
Chaque élément est un mot clé identifiant une valeur de statut � assigner � la variable spécifiée (qui doit �tre du bon type de données). Les éléments de statut actuellement disponibles sont indiqués dans Tableau 40.1, � Diagnostiques et erreurs �.
Tableau 40.1. Diagnostiques et erreurs
Nom | Type | Description |
---|---|---|
RETURNED_SQLSTATE | text | le code d'erreur SQLSTATE de l'exception |
COLUMN_NAME | text | le nom de la colonne en relation avec l'exception |
CONSTRAINT_NAME | text | le nom de la contrainte en relation avec l'exception |
PG_DATATYPE_NAME | text | le nom du type de données en relation avec l'exception |
MESSAGE_TEXT | text | le texte du message principal de l'exception |
TABLE_NAME | text | le nom de la table en relation avec l'exception |
SCHEMA_NAME | text | le nom du schéma en relation avec l'exception |
PG_EXCEPTION_DETAIL | text | le texte du message détaillée de l'exception, si disponible |
PG_EXCEPTION_HINT | text | le texte du message d'astuce de l'exception, si disponible |
PG_EXCEPTION_CONTEXT | text | ligne(s) de texte décrivant la pile d'appel |
Si l'exception n'a pas configuré une valeur pour un élément, une cha�ne vide sera renvoyée.
Voici un exemple :
DECLARE text_var1 text; text_var2 text; text_var3 text; BEGIN -- un traitement qui cause une exception ... EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT, text_var2 = PG_EXCEPTION_DETAIL, text_var3 = PG_EXCEPTION_HINT; END;