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
#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
et RETURN QUERY
#RETURN NEXTexpression
; RETURN QUERYrequete
; RETURN QUERY EXECUTEcommand-string
[ USINGexpression
[, ...] ];
Quand une fonction PL/pgSQL déclare renvoyer
SETOF
, 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
un_certain_type
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
quand il y a un seul paramètre en sortie, et de type
un_certain_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.
Une procédure n'a pas de valeur de retour. De ce fait, une procédure peut
se terminer sans instruction RETURN
. Si vous souhaitez
utiliser l'instruction RETURN
pour quitter le code en
avance, écrivez juste RETURN
sans expression.
Si une procédure a des paramètres en sortie, les valeurs finales des paramètres en sortie seront renvoyées à l'appelant.
Une fonction, une procédure et un bloc DO
en
PL/pgSQL peut appeler une procédure en
appelant CALL
. Les paramètres en sortie sont gérées
différemment de la façon dont CALL
fonctionne en SQL.
Chaque paramètre OUT
ou INOUT
de la procédure doit
correspondre à une variable dans l'instruction CALL
et
le retour de la procédure est affecté à cette variable au retour. Par
exemple :
CREATE PROCEDURE triple(INOUT x int) LANGUAGE plpgsql AS $$ BEGIN x := x * 3; END; $$; DO $$ DECLARE myvar int := 5; BEGIN CALL triple(myvar); RAISE NOTICE 'myvar = %', myvar; -- prints 15 END $$;
La variable correspondant à un paramètre en sortie peut être une variable ou un champ simple d'une variable de type composite. Actuellement, cela ne peut pas être un élément d'un tableau.
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 ... END IF
IF ... THEN ... ELSE ... END IF
IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF
et deux formes de CASE
:
CASE ... WHEN ... THEN ... ELSE ... END CASE
CASE WHEN ... THEN ... ELSE ... END CASE
IF-THEN
#IFexpression-booleenne
THENinstructions
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-THEN-ELSE
#IFexpression-booleenne
THENinstructions
ELSEinstructions
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-THEN-ELSIF
#IFexpression-booleenne
THENinstructions
[ ELSIFexpression-booleenne
THENinstructions
[ ELSIFexpression-booleenne
THENinstructions
... ] ] [ ELSEinstructions
] 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
simple #CASEexpression_recherche
WHENexpression
[,expression
[ ... ]] THENinstructions
[ WHENexpression
[,expression
[ ... ]] THENinstructions
... ] [ ELSEinstructions
] 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
recherché #CASE WHENexpression_booléenne
THENinstructions
[ WHENexpression_booléenne
THENinstructions
... ] [ ELSEinstructions
] END CASE;
La forme recherchée 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.
LOOP
#[<<label
>>] LOOPinstructions
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
#EXIT [label
] [ WHENexpression-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
#CONTINUE [label
] [ WHENexpression-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;
WHILE
#[<<label
>>] WHILEexpression-booléenne
LOOPinstructions
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;
FOR
(variante avec entier) #[<<label
>>] FORnom
IN [ REVERSE ]expression
..expression
[ BYexpression
] LOOPinstruction
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
>>] FORcible
INrequête
LOOPinstructions
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 rafraichir_vuemat() RETURNS integer AS $$ DECLARE vues_mat RECORD; BEGIN RAISE NOTICE 'Rafraichissement de toutes les vues matérialisées...'; FOR mviews IN SELECT n.nspname AS mv_schema, c.relname AS mv_name, pg_catalog.pg_get_userbyid(c.relowner) AS owner FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) WHERE c.relkind = 'm' ORDER BY 1 LOOP -- Maintenant "mviews" contient un enregistrement avec les informations sur la vue matérialisée RAISE NOTICE 'Rafraichissement de la vue matérialisée %.% (propriétaire : %)...', quote_ident(mviews.mv_schema), quote_ident(mviews.mv_name), quote_ident(mviews.owner); EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name); 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
,
DELETE
ou MERGE
avec une clause RETURNING
.
Certaines commandes comme EXPLAIN
fonctionnent aussi.
Les variables PL/pgSQL sont remplacées par les paramètres 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 41.11.1 et dans la Section 41.11.2.
L'instruction FOR-IN-EXECUTE
est un moyen d'itérer sur des
lignes :
[<<label
>>] FORtarget
IN EXECUTEtext_expression
[ USINGexpression
[, ...] ] LOOPinstructions
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 41.7.4.
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
>> ] FOREACHtarget
[ SLICEnombre
] IN ARRAYexpression
LOOPinstructions
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
>> ] [ DECLAREdeclarations
] BEGINinstructions
EXCEPTION WHENcondition
[ ORcondition
... ] THENinstructions_gestion_erreurs
[ WHENcondition
[ ORcondition
... ] THENinstructions_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. 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
et ASSERT_FAILURE
(il
est possible, mais pas recommandé, de
récupérer ces deux types d'erreur par leur 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 41.2. Exceptions avec UPDATE
/INSERT
Cet exemple utilise un gestionnaire d'exceptions pour réaliser soit un
UPDATE
soit un INSERT
, comme approprié.
Il est recommandé d'utiliser la commande INSERT
avec la clause ON CONFLICT DO UPDATE
plutôt
que cette logique. Cet exemple ne sert qu'à illustrer l'usage des
structures de contrôle de PL/pgSQL :
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 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 DIAGNOSTICSvariable
{ = | := }é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 41.2.
Tableau 41.2. 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 au moment de l'exception (voir Section 41.6.9) |
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;
La commande GET DIAGNOSTICS
, précédemment décrite dans
Section 41.5.5, récupère des informations
sur l'état d'exécution courant (alors que la commande GET STACKED
DIAGNOSTICS
discutée ci-dessus rapporte des informations sur
l'état d'exécution de l'erreur précédente). Son élément de statut
PG_CONTEXT
est utile pour vérifier l'emplacement
d'exécution courant. PG_CONTEXT
renvoie une chaîne de
texte dont les lignes correspondent à la pile d'appels. La première ligne
fait référence à la fonction en cours et qui exécute GET
DIAGNOSTICS
. La seconde ligne et toutes les lignes suivantes font
référence aux fonctions appelantes dans la pile d'appel. Par exemple :
CREATE OR REPLACE FUNCTION fonction_externe() RETURNS integer AS $$BEGIN RETURN fonction_interne(); END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION fonction_interne() RETURNS integer AS $$ DECLARE stack text; BEGIN GET DIAGNOSTICS stack = PG_CONTEXT; RAISE NOTICE E'--- Pile d''appel ---\n%', stack; RETURN 1; END; $$ LANGUAGE plpgsql; SELECT fonction_externe(); NOTICE: --- Call Stack --- PL/pgSQL function fonction_interne() line 5 at GET DIAGNOSTICS PL/pgSQL function fonction_externe() line 3 at RETURN CONTEXT: PL/pgSQL function fonction_externe() line 3 at RETURN fonction_externe ----------------- 1 (1 row)
GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT
renvoie le même type de pile d'appels, mais en décrivant l'emplacement où
l'erreur a été détectée, plutôt que l'emplacement actuel.
La clause SCROLL
ne peut pas être utilisée quand la
requête du curseur utilise FOR UPDATE/SHARE
. De plus,
il est préférable d'utiliser NO SCROLL
avec une
requête qui implique des fonctions volatiles. L'implémentation de
SCROLL
suppose que relire la sortie de la requête
donnera des résultats cohérents, ce qu'une fonction volatile pourrait ne
pas faire.