35.7. Structures de contr�le

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.

35.7.1. Retour d'une fonction

Il y a deux commandes disponibles qui vous permettent de renvoyer des donn�es d'une fonction : RETURN et RETURN NEXT.

35.7.1.1. RETURN

RETURN expression;

RETURN accompagn� d'une expression termine la fonction et renvoie le valeur de l'expression � l'appelant. Cette forme est � utiliser avec des fonctions PL/pgSQL qui ne renvoient pas d'ensemble de valeurs.

Lorsqu'elle renvoie un type scalaire, n'importe quelle expression peut �tre utilis�e. Le r�sultat de l'expression sera automatiquement transtyp� vers le type de retour de la fonction, comme d�crit pour les assignations. Pour renvoyer une valeur composite (ligne), vous devez �crire une variable record ou ligne comme expression.

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 premier niveau sans avoir rencontr� d'instruction RETURN une erreur d'ex�cution sera lanc�e.

Notez que si vous avez d�clar� la fonction comme renvoyant void, une instruction RETURN doit �tre quand m�me fournie ; l'expression suivant la commande RETURN est cependant optionnelle et sera ignor�e dans tous les cas.

35.7.1.2. RETURN NEXT

RETURN NEXT expression;

Lorsqu'une fonction PL/pgSQL est d�clar�e renvoyer SETOF type quelconque, la proc�dure � suivre est l�g�rement diff�rente. Dans ce cas, les �l�ments individuels � renvoyer sont sp�cifi�s dans les commandes RETURN NEXT, et ensuite une commande RETURN finale sans arguments est utilis�e pour indiquer que la fonction a termin� son ex�cution. RETURN NEXT peut �tre utilis� avec des types scalaires et des types composites de donn�es; dans ce dernier cas, une <<�table�>> enti�re de r�sultats sera renvoy�e.

Les fonctions qui utilisent RETURN NEXT devraient �tre appel�es d'apr�s le mod�le suivant :

SELECT * FROM une_fonction();

En fait, la fonction doit �tre utilis�e comme une table source dans une clause FROM.

RETURN NEXT n'effectue pas vraiment de renvoi; il sauvegarde simplement les valeurs des expressions. L'ex�cution continue alors avec la prochaine instruction dans la fonction PL/pgSQL. Lorsque des commandes RETURN NEXT successives sont renvoy�es, l'ensemble des r�sultats est �labor�. Un RETURN final, qui ne devrait pas avoir d'argument, provoque la sortie du contr�le de la fonction.

Note�: L'impl�mentation actuelle de RETURN NEXT pour PL/pgSQL emmagasine 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 des r�sultats entier soit g�n�r�. Une version future de PL/pgSQL pourra permettre 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 devraient envisager l'augmentation de ce param�tre.

35.7.2. Contr�les conditionnels

Les instructions IF vous permettent d'ex�cuter des commandes bas�es sur certaines conditions. PL/pgSQL a cinq formes de IF :

35.7.2.1. IF-THEN

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 n�glig�es.

Exemple :

IF v_id_utilisateur <> 0 THEN
    UPDATE utilisateurs SET email = v_email WHERE id_utilisateur = v_id_utilisateur;
END IF;

35.7.2.2. IF-THEN-ELSE

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 ensemble d'instructions alternatif � ex�cuter si la condition est �valu�e � false.

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;

35.7.2.3. IF-THEN-ELSE IF

Les instructions IF peuvent �tre imbriqu�es, comme dans l'exemple suivant :

IF demo_ligne.sexe = 'm' THEN
    joli_sexe := ''man'';
ELSE
    IF demo_ligne.sexe = 'f' THEN
        joli_sexe := 'woman';
    END IF;
END IF;

Lorsque vous utilisez cette forme, vous imbriquez une instruction IF dans la partie ELSE d'une instruction IF ext�rieure. Ainsi, vous avez besoin d'une instruction END IF pour chaque IF imbriqu� et une pour le IF-ELSE parent. Ceci fonctionne mais devient fastidieux quand il y a de nombreuses alternatives � traiter. Consid�rez alors la forme suivante.

35.7.2.4. IF-THEN-ELSIF-ELSE

IF expression-booleenne THEN
    instructions
[ ELSIF expression-booleenne THEN
    instructions
[ ELSIF expression-booleenne THEN
    instructions
    ...]]
[ ELSE
    instructions ]
END IF;

IF-THEN-ELSIF-ELSE fournit une m�thode plus pratique pour v�rifier de nombreuses alternatives en une instruction. Elle est �quivalente formellement aux commandes IF-THEN-ELSE-IF-THEN imbriqu�es, mais un seul END IF est n�cessaire.

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;

35.7.2.5. IF-THEN-ELSEIF-ELSE

ELSEIF est un alias pour ELSIF.

35.7.3. Boucles simples

Gr�ce aux instructions LOOP, EXIT, WHILE et FOR vous pouvez faire en sorte que vos fonctions PL/pgSQL r�p�tent une s�rie de commandes.

35.7.3.1. LOOP

[<<label>>]
LOOP
    instructions
END LOOP;

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 dans le cas de boucles imbriqu�es pour d�finir quel niveau d'imbrication doit s'achever.

35.7.3.2. EXIT

EXIT [ label ] [ WHEN expression ];

Si aucun label n'est donn�, la boucle la plus imbriqu�e se termine et l'instruction suivant END LOOP est ex�cut�e ensuite. 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 pr�sent, la sortie de boucle ne s'effectue que si les conditions sp�cifi�es sont true, autrement le contr�le passe � l'instruction suivant le EXIT.

EXIT peut �tre utilis� pour causer un d�part rapide de tout type de boucles ; il n'est pas limit� en utilisation aux boucles sans condition.

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;

BEGIN
    -- quelques traitements
    IF stocks > 100000 THEN
        EXIT;  -- cause la sortie (EXIT) du bloc BEGIN
    END IF;
END;

35.7.3.3. WHILE

[<<label>>]
WHILE expression LOOP
    instructions
END LOOP;

L'instruction WHILE r�p�te une s�quence d'instructions aussi longtemps que l'expression conditionnelle est �valu�e � vrai. La condition 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 expression_booleenne LOOP
    -- quelques traitements ici
END LOOP;

35.7.3.4. FOR (variante avec entier)

[<<label>>]
FOR nom IN [ REVERSE ] expression .. expression LOOP
    instruction
END LOOP;

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. Les deux expressions donnant les limites inf�rieures et sup�rieures de la plage sont �valu�es une fois en entrant dans la boucle. Le pas de l'it�ration est normalement de 1 mais vaut -1 quand REVERSE est sp�cifi�.

Quelques exemples de boucles FOR avec entiers :

FOR i IN 1..10 LOOP
    -- quelques calculs ici
    RAISE NOTICE 'i is %', i;
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- quelques calculs ici
END LOOP;

Si la limite basse est plus grande que la limite haute (ou moins grande que, dans le cas du REVERSE case), le corps de la boucle n'est pas ex�cut� du tout. Aucune erreur n'est renvoy�e.

35.7.4. Boucler dans les r�sultats de requ�tes

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 record_ou_ligne IN requ�te LOOP
    instructions
END LOOP;

La variable record ou ligne est successivement assign�e � chaque ligne r�sultant de la requ�te (qui doit �tre une commande SELECT) et le corps de la boucle est ex�cut� pour chaque ligne. Voici un exemple :

CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$
DECLARE
    mviews RECORD;
BEGIN
    PERFORM cs_log('Refreshing materialized views...');

    FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP

        -- � pr�sent "mviews" contient un enregistrement de cs_materialized_views

        PERFORM cs_log('Refreshing materialized view ' || quote_ident(mviews.mv_name) || '...');
        EXECUTE 'TRUNCATE TABLE  ' || quote_ident(mviews.mv_name);
        EXECUTE 'INSERT INTO ' || quote_ident(mviews.mv_name) || ' ' || mviews.mv_query;
    END LOOP;

    PERFORM cs_log('Done refreshing materialized views.');
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

Si la boucle est termin�e par une instruction EXIT, la derni�re valeur ligne assign�e est toujours accessible apr�s la boucle.

L'instruction FOR-IN-EXECUTE est un moyen d'it�rer sur des lignes :

[<<label>>]
FOR record_ou_ligne IN EXECUTE expression_texte LOOP 
    instructions
END LOOP;

Ceci est identique � la forme pr�c�dente, � ceci pr�s que l'expression SELECT 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 programmeur de choisir la vitesse d'une requ�te pr�planifi�e, ou la flexibilit� d'une requ�te dynamique, uniquement avec la simple instruction EXECUTE.

Note�: L'analyseur PL/pgSQL distingue actuellement deux types de boucles FOR (entier ou r�sultat d'une requ�te) en v�rifiant si .. appara�t � l'ext�rieur des parenth�ses entre IN et LOOP. Si .. n'est pas trouv�, la boucle est suppos�e �tre une boucle entre des lignes. Une mauvaise saisie de .. am�nera donc une plainte du type <<�loop variable of loop over rows must be a record or row variable�>> (NdT : une variable de boucle d'une boucle sur des enregistrements doit �tre un enregistrement ou une variable de type ligne) plut�t qu'une simple erreur de syntaxe comme vous pourriez vous y attendre.

35.7.5. R�cup�rer les erreurs

Par d�faut, toute erreur survenant dans une fonction PL/pgSQL annule l'ex�cution de la fonction et, en fait, aussi de la transaction qui l'entoure. Vous pouvez r�cup�rer les erreurs et les surpasser 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_gestionnaire
  [ WHEN condition [ OR condition ... ] THEN
    instructions_gestionnaire
    ... ]
  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_gestionnaire 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 peuvent �tre n'importe laquelle parmi celles list�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 (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.

Si une nouvelle erreur survient � l'int�rieur des instructions_gestionnaire 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 reste comme elles �taient 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 'caught 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.

Astuce�: 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 35-1. Exceptions avec UPDATE/INSERT

Cet exemple utilise la gestion des exceptions pour r�aliser soit un UPDATE soit un INSERT suivant le cas.

CREATE TABLE base (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION fusionne_base(cle INT, donnee TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        UPDATE base SET b = donnee WHERE a = cle;
        IF found THEN
            RETURN;
        END IF;

        BEGIN
            INSERT INTO base(a,b) VALUES (cle, donnee);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- ne fait rien
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT fusionne_base(1, 'david');
SELECT fusionne_base(1, 'dennis');