Gestion des erreurs applicatives Oracle sous Delphi et DOA

Cet article décrit le mécanisme utilisé pour propager les erreurs applicatives Oracle vers un client Delphi utilisant les composants DOA.

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1. Public concerné

Image non disponible

Testé avec Delphi 6 sous Windows 2000, Delphi 7 sous Xp pro et Oracle 9i.

version 1.0

Les fichiers sources

Je tiens à remercier Pomalaix pour ses corrections orthographiques.

2. Introduction

J'ai pu voir au cours de ma dernière mission que de très bons programmeurs Delphi n'utilisaient pas les exceptions Oracle pour gérer les erreurs applicatives mais utilisaient à la place un paramètre supplémentaire dans l'appel de procédures stockées. Je peux donc supposer que cet article offrira une solution, pour les novices sous Oracle, en expliquant simplement l'utilisation des mécanismes mis à disposition par Oracle pour propager une erreur Oracle vers un client Delphi ou autre.

Dans un premier temps cet article est limité aux composants DOA pour Delphi ( ou BCB ) qui offrent, à mon avis, une des meilleures solutions sur le marché en utilisant au mieux les OCI. Ces composants proposent notamment l'utilisation d'objets Oracle (UDT) et d'Advanced queuing pour la dernière version.

Le portage de cette solution peut se faire aisément avec tous les modes d'accès SGBD de Delphi à partir du moment où vous savez récupérer le code d'erreur en provenance du serveur Oracle et non pas un code interprété comme par exemple avec DBexpress ( plus précisément je ne sais pas comment le récupérer ).

Le cas de figure que nous allons étudier permettra de

  • centraliser, dans une table, les codes d'erreur et les messages littéraux associés
  • tracer, également dans une table, les erreurs non traitées qui peuvent survenir pendant le cycle de développement ou en production. Offrant ainsi au support une aide précieuse.
  • déterminer simplement du côté client de quel type d'erreur il s'agit
    - est-ce une erreur applicative (règle de gestion),
    - une erreur Oracle mais jugée comme une erreur applicative (par exemple insertion de doublon),
    - une erreur du moteur Oracle
    - une erreur réseau
    - ou encore une erreur Delphi ?

Rien de bien complexe techniquement si ce n'est d'avoir une démarche structurée, car la centralisation des messages d'erreurs implique une méthodologie simple mais rigoureuse. Sinon cette manière de faire devient très vite une source de problèmes au lieu d'être une solution. Vous apprendrez à vos dépens que la mise en oeuvre de ce type de solution au sein d'une équipe ne supporte pas vraiment l'approximation.

3. Gestion des erreurs sous Oracle

Article sur les exceptions PL/SQL.

côté serveur on utilise l'instruction PL/SQL Raise_Application_Error qui permet de déclencher une erreur dans la session Oracle de l'utilisateur puis de l'acheminer sur le poste client via NET8.

 
Sélectionnez

RAISE_APPLICATION_ERROR(-20001,'Erreur applicative n°1');

Nous utiliserons un package nommé PK_EXCEP qui regroupera les déclarations des fonctions de gestion d'erreurs utilisées dans les différents scripts PL/SQL liés à une application.
Si ce package contient des déclarations d'exceptions cela entraîne des problèmes de dépendances, i.e la livraison de ce package impliquera une invalidation et une recompilation des packages dépendants. Pour les équipes qui livrent directement dans l'environnement de production cela peut être "gênant", je vous recommande d'éviter cette approche directe.

Dans ce cas il est du ressort du développeur de définir le numéro et le message d'erreur. On centralise ces messages d'erreurs en créant une table, de préférence dans le schéma de l'application, nommée par exemple MESSAGES_APPLICATIFS.
Cette table recensera tous les messages d'erreurs associés à un code d'erreur.

3-1. Table d'erreurs MESSAGES_APPLICATIFS

Cette table contient les champs suivants :

  • CODE_ERREUR
    contient le numéro d'erreur Oracle ( ex : -00054)
  • NOM_ERREUR
    contient le nom de l'exception attribuée, pour ce numéro d'erreur et cette application, cf. package PK_EXCEPT.
  • APPLICATION
    contient le nom de l'application concernée ( ex : MonAppli)
  • MESSAGE
    contient la chaîne de caractères que l'on souhaite afficher sur le client lors du déclenchement d'une erreur applicative.
    On peut y insérer jusqu'à 3 champs paramétrables qui sont signalés par %1, %2 et %3.
    Exemple : ('%1 que vous tentez de %2 est en cours de modification.')
  • COMMENTAIRE
    Contient un commentaire sur le contexte d'utilisation de l'exception.
    Permet de documenter l'erreur, par exemple pour commenter la déclaration d'exception dans le code PL/SQL.

Les champs CODE_ERREUR et APPLICATION constituent la clé primaire. On permet ainsi d'avoir plusieurs occurrences d'un même message d'erreur mais pour des applications différentes.

Le champ MESSAGE est obligatoire.

Cette table doit contenir au moins la ligne suivante :

 
Sélectionnez

INSERT INTO MESSAGES_APPLICATIFS ( CODE_ERREUR, NOM_ERREUR, APPLICATION, MESSAGE,
COMMENTAIRE ) VALUES ( 
-20999, 'E_CodeErreur_inexistant', 'ALL', 'Le numéro d''erreur (%1) ou le nom de l''application (%2)
 n''est pas référencé dans la table des messages applicatifs.'
, 'Code erreur inexistant dans la table des messages d''erreurs.'); 

Par convention, pour cette gestion des erreurs applicatives, le code -20999 est destiné aux erreurs inconnues.

3-2. Table de trace des erreurs

Vous devez prévoir une purge régulière de cette table d'erreurs.

On utilisera la procédure suivante pour écrire dans cette table d'erreurs. L'utilisation de la PRAGMA AUTONOMOUS_TRANSACTION permet de réaliser une transaction autonome, c'est à dire une transaction indépendante de la transaction en cours qui a appelé cette procédure. On peut donc effectuer un roll back dans le script appelant sans annuler la trace de l'erreur courante.

 
Sélectionnez

PROCEDURE P_TRACE_ERROR
 (P_DEBUG_ON IN BOOLEAN
 ,P_NOM_PROC IN VARCHAR2
 ,P_SQL_CODE IN BINARY_INTEGER
 ,P_SQL_ERROR IN VARCHAR2
 )
 IS
--|====================================================
--| P_TRACE_ERROR :
--|====================================================
--| Objet : Trace des erreurs survenues dans un script PL/SQL
--|
--| Entrée:
--|	   P_DEBUG_ON		Si TRUE trace l'erreur dans la table si FALSE ne trace pas l'erreur
--|	   P_NOM_PROC		Nom de la procédure déclenchant l'erreur
--|	   P_SQL_CODE		Code d'erreur
--|	   P_SQL_ERROR 		Message d'erreur
--|
--| Sortie:
--|====================================================
--| 30/12/04 -> L. Dardenne : original
--|====================================================

v_UserCourant	 	  VARCHAR2(30); -- Attention en cas d'évolution LDAP

PRAGMA AUTONOMOUS_TRANSACTION; -- Transaction autonome indépendante de la transaction en cours qui appelle ce script.
BEGIN
   IF NOT P_DEBUG_ON THEN
     RETURN;
   END IF;

   v_UserCurrent := USER; -- Attention en cas d'évolution LDAP
   INSERT INTO ERREURS_SCRIPT VALUES(P_SQL_CODE,P_NOM_PROC||P_SQL_ERROR,v_UserCourant,SYSDATE);
   COMMIT; -- On s'assure de tracer l'erreur même si la procédure appelante exécute un ROLLBACK
END;

A noter que le composant TOracleSession de DOA propose au travers de la propriété MessageTable ce type de gestion des messages d'erreurs mais dans ce cas elle se fait côté client.

3-3. Contenu du package PK_EXCEP

3-3-1. Fonction F_GET_MSG_ERR

Cette fonction remplace les champs paramétrables contenus dans le corps du message d'erreur extrait de la table MESSAGES_APPLICATIFS.

 
Sélectionnez

FUNCTION F_GET_MSG_ERR
 (P_CODE_ERREUR IN MESSAGES_APPLICATIFS.CODE_ERREUR%TYPE
 ,P_APPLICATION IN MESSAGES_APPLICATIFS.APPLICATION%TYPE
 ,P_SPECIFICATEUR1 IN VARCHAR2 := 'NULL'
 ,P_SPECIFICATEUR2 IN VARCHAR2 := 'NULL'
 ,P_SPECIFICATEUR3 IN VARCHAR2 := 'NULL'
 )
 RETURN VARCHAR2
 IS
--|==================================================
--| F_GET_MSG_ERR :
--|==================================================
--| Objet : Renvoie un numéro d'erreur en remplaçant les spécificateurs présents dans le message associé
--|         Permet d'utiliser des messages en base du type : 
--|         'Erreur %1 dans le fichier %2 de la table %3' 
--|
--| Entrée: P_Code_Erreur    Numéro d'erreur à retrouver
--|         P_Application    Nom de l'application concernée
--|         P_Specificateur1 Chaîne en correspondance avec %1
--|         P_Specificateur2 Chaîne en correspondance avec %2 
--|         P_Specificateur3 Chaîne en correspondance avec %3
--|
--| Sortie: N/A
--|==================================================
--| 30/12/04 -> L.DARDENNE    : original
--|==================================================
 
 v_message MESSAGES_APPLICATIFS.MESSAGE%TYPE;
BEGIN
 v_message:=F_GET_ERR_APPLICATIV(P_Code_Erreur,P_Application);
   -- Récupére dans la table des messages d'erreurs, le message associé au numéro d'erreur
  RETURN ( F_FORMAT_MSG_ERR( v_message,P_Specificateur1,P_Specificateur2,P_Specificateur3)) ;
END;

Les messages d'erreurs pouvant contenir des paramètres, on doit remplacer les occurrences %1,%2,...

 
Sélectionnez

FUNCTION F_FORMAT_MSG_ERR
 (P_CHAINE IN OUT VARCHAR2
 ,P_SPECIFICATEUR1 IN VARCHAR2 := 'NULL'
 ,P_SPECIFICATEUR2 IN VARCHAR2 := 'NULL'
 ,P_SPECIFICATEUR3 IN VARCHAR2 := 'NULL'
 )
 RETURN VARCHAR2
 IS
--|==================================================
--| F_FORMAT_MSG_ERR :
--|==================================================
--| Objet : Renvoie une chaîne de caractères en remplacant les spécificateurs %1
--|         Permet d'utiliser des chaînes de message du type : 
--|         'Erreur %1 dans le fichier %2 de la table %3' 
--|
--| Entrée: P_Chaine         Chaîne de caractères contenant des spécificateurs
--|         P_Specificateur1 Chaîne en correspondance avec %1
--|         P_Specificateur2 Chaîne en correspondance avec %2 
--|         P_Specificateur3 Chaîne en correspondance avec %3
--|
--| Sortie: N/A
--|==================================================
--| 12/10/04 -> L.DARDENNE    : original
--|==================================================
BEGIN 
  If P_Specificateur1 IS NOT NULL Then 
    P_Chaine := Replace( P_Chaine, '%1', P_Specificateur1 ) ; 
  End if ; 
  If P_Specificateur2 IS NOT NULL Then 
     P_Chaine := Replace( P_Chaine, '%2', P_Specificateur2 ) ; 
  End if ; 
  If P_Specificateur3 IS NOT NULL Then 
     P_Chaine := Replace( P_Chaine, '%3', P_Specificateur3 ) ; 
  End if ; 
  RETURN (P_Chaine) ; 
END;

Une solution basée sur une collection de variables de type AnyData peut être mise en oeuvre si vous souhaitez connaître le type des paramètres afin d'effectuer quelques traitements particuliers.

3-3-2. Fonction F_GET_ERR_APPLICATIV

Cette fonction renvoie le message d'erreur qui correspond au couple (CODE_ERREUR, APPLICATION).

 
Sélectionnez

FUNCTION F_GET_ERR_APPLICATIV
 (P_NUMERO_ERREUR IN MESSAGES_APPLICATIFS.CODE_ERREUR%TYPE
 ,P_NOM_APPLICATION IN MESSAGES_APPLICATIFS.APPLICATION%TYPE
 )
 RETURN VARCHAR2
 IS
--|==================================================
--| F_GET_ERR_APPLICATIV :
--|==================================================
--| Objet : Renvoie le message d'erreur demandé
--|
--| Entrée: P_NUMERO_ERREUR   Numéro d'erreur applicative concerné ( ex : -20123)
--|         P_NOM_APPLICATION Nom de l'application concernée ( ex : MonAppli )
--|
--| Sortie: N/A
--| Retourne : Le message d'erreur correspond au couple ( numErreur,NomAppli) ou 
--|            le message 'Impossible de récupérer les messages d''erreurs.' si la table est vide !
--|==================================================
--| 30/12/04 -> L.DARDENNE    : original
--|==================================================

v_message MESSAGES_APPLICATIFS.MESSAGE%TYPE;
BEGIN
 SELECT MESSAGE INTO v_message FROM MESSAGES_APPLICATIFS ma
   WHERE ma.CODE_ERREUR=P_NUMERO_ERREUR AND ma.APPLICATION=P_NOM_APPLICATION;

 RETURN(v_message);   
 
EXCEPTION
  WHEN NO_DATA_FOUND THEN  
   BEGIN
    SELECT MESSAGE INTO v_message FROM MESSAGES_APPLICATIFS ma
     WHERE ma.CODE_ERREUR=c_ECodeErreur_inexistant AND ma.APPLICATION='ALL'; 
     -- Msg d'erreur de l'erreur !
    RETURN(F_FORMAT_MSG_ERR( v_message,TO_CHAR(P_NUMERO_ERREUR),P_NOM_APPLICATION)); 
     
   EXCEPTION
    WHEN NO_DATA_FOUND THEN  
      v_message:='Impossible de récupérer les messages d''erreurs.';
      RETURN(v_message); -- la table ne contient pas de ligne.
   END;
END;

Ici on trace toutes les erreurs, même les erreurs applicatives que l'on a déclenchées dans le script dans la table d'erreurs.
Et surtout on redéclenche dans tous les cas l'exception afin d'avertir le client, car sans l'exécution de cette instruction RAISE l'exception ne serait pas propagée.

La recherche des erreurs non applicatives se faisant sur le test suivant :

 
Sélectionnez

FUNCTION F_ERREUR_APPLICATIVE
 (P_CODE_ERREUR IN NUMBER
 )
 RETURN BOOLEAN
 IS
--|====================================================
--| F_ERREUR_APPLICATIV :
--|====================================================
--| Objet : indique si le code d'erreur est un code d'erreur applicatif ou non.
--|
--| Entrée: P_CODE_ERREUR Numéro d'erreur à tester ( Cf. SQLCODE )
--|
--|====================================================
--| 11/10/2004 -> L. Dardenne : Création
--|====================================================
BEGIN
 IF (P_CODE_ERREUR<=-20000) and (P_CODE_ERREUR>=-20999)
  THEN RETURN(TRUE);
  ELSE RETURN(FALSE);
 END IF;   
END;

3-3-3. Procédure P_DECLENCHE_ERREUR

Cette procédure encapsule la gestion des messages basés, elle appelle l'instruction Raise_Application_Error avec le message d'erreur formaté. Son utilisation allège l'écriture et la relecture du code. Mais rien ne vous empêche d'utiliser l'appel de la fonction Raise_Application_error

 
Sélectionnez

PROCEDURE P_DECLENCHE_ERREUR
 (P_NUMERO_ERREUR IN MESSAGES_APPLICATIFS.CODE_ERREUR%TYPE
 ,P_NOM_APPLICATION MESSAGES_APPLICATIFS.APPLICATION%TYPE
 ,P_SPECIFICATEUR1 IN VARCHAR2 := 'NULL'
 ,P_SPECIFICATEUR2 IN VARCHAR2 := 'NULL'
 ,P_SPECIFICATEUR3 IN VARCHAR2 := 'NULL'
 )
 IS
--|==================================================
--| P_DECLENCHE_ERREUR :
--|==================================================
--|==================================================
--| F_GET_MSG_ERR :
--|==================================================
--| Objet : Déclenche l'instruction Raise_Application_Error
--|         Encapsule la gestion des messages basés.
--|
--| Entrée: P_Numero_Erreur    Numéro d'erreur à retrouver
--|         P_Nom_Application  Nom de l'application concernée
--|         P_Specificateur1   Chaîne en correspondance avec %1
--|         P_Specificateur2   Chaîne en correspondance avec %2 
--|         P_Specificateur3   Chaîne en correspondance avec %3
--|
--| Sortie: N/A
--|==================================================
--| 08/10/04 -> L.DARDENNE    : original
--|==================================================
BEGIN
 RAISE_APPLICATION_ERROR(P_Numero_Erreur, F_GET_MSG_ERR(P_Numero_Erreur,
 						 P_Nom_Application,
						 P_Specificateur1,
						 P_Specificateur2,
						 P_Specificateur3));
END;

3-3-4. Déclaration d'une exception

Nous utilisons ici une convention de nommage arbitraire, libre à vous de la respecter ou pas.
Chaque exception utilisée dans un script PL/SQL doit être déclarée de la manière suivante :

3-3-5. Pour les numéros d'erreur applicative

Il s'agit des numéros contenus dans l'intervalle -20000..-20999.

1 - Déclarez une constante débutant par les caractères c_ de type PLS_INTEGER, ce type de donnée utilisant les registres du processeur au lieu d'une émulation pour le type number il est donc plus efficace :

 
Sélectionnez

 c_eseq_modification_detail CONSTANT 	PLS_INTEGER := -20993;
 

2 - Déclarez une exception débutant par les caractères E_ :

 
Sélectionnez

/* Signale que les détails d'une séquence sont déjà en cours de modification.*/
e_seq_modification_detail 	EXCEPTION;
 

3 - Puis déclarez la pragma suivante, cette instruction permet d'associer un numéro d'erreur à une variable d'exception ( bloc, procédure ou package ) :

 
Sélectionnez

PRAGMA 	EXCEPTION_INIT(e_seq_modification_detail, -20993);

4 - Ensuite renseignez la table des erreurs applicatives.

3-3-6. Pour les numéros d'erreur Oracle

Il s'agit de tous les numéros qui ne sont pas contenus dans l'intervalle -20000..-20999.
Procédez de la même manière que pour les numéros d'erreur applicative.

 
Sélectionnez

 c_eresource_busy CONSTANT	 PLS_INTEGER := -54;

/* Ligne verrouillée, uniquement pour l'instruction : Select for Update NOWAIT */
 e_resource_busy	EXCEPTION;
 
 PRAGMA		EXCEPTION_INIT(E_RESOURCE_BUSY, -54);

4. Gestion des erreurs sous Delphi

Nous utiliserons la gestion des exceptions native de Delphi couplée à l'exception EOracleError des composants DOA.

5. Distinction des différentes erreurs

Nous distinguerons 3 types d'erreur :

  • une erreur applicative Oracle
  • une erreur Oracle
  • une erreur Delphi

5-1. erreurs applicatives Oracle

Elles concernent les erreurs contrôlées de l'application dans les traitements en PL/SQL.

5-2. Erreurs Oracle

Elles concernent les erreurs non contrôlées de l'application dans les traitements en PL/SQL, les problèmes liés au réseau ou des problèmes particuliers sur le serveur Oracle.

Certaines exceptions Oracle au sein d'un traitement PL/SQL, par exemple l'insertion de ligne violant les contraintes d'intégrité référentielle, peuvent être interceptées afin de transformer cette exception en une exception applicative qui permettra d'informer l'utilisateur que l'opération demandée n'est pas possible.

Dans les autres cas l'exception déclenchée sera bien un bug de l'application côté serveur. Dans ce cas l'utilisateur reçoit un message d'erreur générique, seul le support au travers des fichiers de log retrouvera exactement le message Oracle d'origine.

5-3. Erreurs Delphi

Elles concernent les erreurs, contrôlées ou non, de l'application pour les traitements côté client.

6. Mise en oeuvre

La propriété UseMessageTable du composant TOraclaeDataSet.OracleDictionary doit être à false !

Pour distinguer les erreurs Oracle/Delphi des erreurs applicatives déclenchées sous PL/SQL nous utiliserons dans l'unité FError un type de donnée intervalle délimitant les numéros d'erreurs applicatives autorisés.

 
Sélectionnez

Type
  NumeroErreurApplicativeOracle = 20000..20999; 

Attention, la directive $R doit être activée.

La suite de composants DOA propose une exception spécifique nommée EOracleError. Elle contient la propriété, en lecture seule, ErrorCode. On peut donc connaître exactement le type d'erreur Oracle survenue lors d'un traitement basé.
A la différence que sous Oracle les erreurs applicatives sont négatives mais positives sous DOA.

La fonction isErreurApplicativeOracle permet de déterminer si l'erreur Oracle est ou non une erreur applicative.

 
Sélectionnez

Function isErreurApplicativeOracle(ErrorCode: Integer):Boolean;
var ErreurOracle: NumeroErreurApplicativeOracle;
begin
 Result:=True;
 try
   // Cette affectation provoquera une exception
   // si le numéro d'erreur n'est pas une erreur applicative
  ErreurOracle:=ErrorCode;
 Except
  Result:=False;
 end;
end;

La procédure AnalyseException se contente de déclencher, selon le type d'erreur, l'exception Delphi appropriée et renseigne selon le cas :

  • soit le message utilisateur avec la chaîne contenue dans l'exception déclenchée par le serveur Oracle
  • soit le message utilisateur passé en paramètre et le message destiné au support ( pile d'appel + message Oracle).
 
Sélectionnez

Procedure AnalyseException(E: Exception;UserMessage:string);
Begin
try
 FrmErreur.Memo1.Clear;
 if (E is EOracleError) then
    // Il s'agit d'une erreur Oracle DOA
   if isErreurApplicativeOracle((E as EOracleError).ErrorCode ) then
       // Affiche le message de l'erreur applicative
      begin
       FrmErreur.Memo1.Text:=FormatUserMsgError(E.Message);
       exit;
      end;

  // Affiche le message d'erreur d'Oracle ou de Delphi
 FrmErreur.Memo1.Text:=userMessage+#13#10+AnsiReplaceText(E.Message, #10, #13#10);

 Finally
  FrmErreur.Show;
 end;
end;

La fonction FormatUserMsgError supprime du message d'erreur Oracle la partie située en début de chaîne 'ORA-xxxxx : ' et supprime également les informations de trace ajoutées par Oracle.

 
Sélectionnez

function FormatUserMsgError(MsgError : string):string;
{-----------------------------------------------------------------------------
Formate le message d'erreur utilisateur

MsgError : 	Message d'erreur destiné à l'utilisateur
Renvoie 	Le message débarrassé des informations inutiles.
-----------------------------------------------------------------------------}
const Header_msg='ORA-'; // un message d'erreur Oracle débute par 'ORA-'
      lg_Header_msg=11; // suivi de 5 chiffres 'ORA-12345' et enfin des 2 caractères ': ' et ce jusqu'à la version 9iR2
      Stack_msg='ORA-06512';
var
  Pos_Stack_msg:Integer;

begin
 Try
  //Supprime l'entête d'un message d'erreur Oracle
  If Pos(Header_msg, MsgError)=1
   then Delete(MsgError,1,lg_Header_msg);

   // Supprime les informations ajoutées par le Serveur Oracle tels que
   //  ORA-06512: à "LAURENT.P_MAKE_ERREUR", ligne 57
   //  ORA-06512: à ligne 2
   //
   //ORA-06512 at string line string
   // Cause: Backtrace message as the stack is unwound by unhandled exceptions.
  Pos_Stack_msg:=Pos(Stack_msg, MsgError);
  if Pos_Stack_msg<>0
                               // -1 pour le caractère #10 ajouté par Oracle
   then Delete(MsgError,Pos_Stack_msg-1,Length(MsgError));

 finally
  Result:=AnsiReplaceText(MsgError, #10, #13#10);
 end;
end;

7. Utilisation

7-1. Exemple de déclaration dans un script PL/SQL

 
Sélectionnez

c_eseq_modification_detail  CONSTANT	 PLS_INTEGER := -20993;

e_seq_modification_detail	EXCEPTION;

PRAGMA 			EXCEPTION_INIT(e_seq_modification_detail, -20993);

Exemple de ligne dans la table Oracle MESSAGES_APPLICATIFS, pour l'erreur (-20991,'TEST') la ligne est :

 
Sélectionnez

-20991,'TEST',"Vous ne pouvez pas %1 plus de %2 lignes en même temps. "

7-2. Exemple d'utilisation sous PL/SQL

Dans cet exemple, il s'agit d'une règle de gestion, on teste une borne qui détermine si une opération est possible. Si elle ne l'est pas dans ce cas on déclenche une erreur applicative.
Ce type d'exception est traité, dans notre cas, dans le bloc d'exception OTHERS d'une procédure Oracle.

 
Sélectionnez

 -- Plus aucune lignes à copier, on quitte
IF p_tb_ligne.COUNT=0   
 THEN RAISE E_Liste_Vide;
END IF;

7-3. Le bloc de traitement des exceptions

Dans le bloc d'exception suivant on traite 2 exceptions nommées, E_Liste_vide qui est une erreur applicative et la seconde E_Resource_Busy qui est une erreur du moteur Oracle. On peut donc effectuer des traitements avant de propager l'exception vers le client Delphi. Le troisième cas OTHERS prend en charge tous les cas que l'on n'a pas prévus ou ceux prévus mais qui ne nécessitent pas de traitements.

 
Sélectionnez

 /* Bloc d'exception d'une procédure ou fonction PL/SQL */
EXCEPTION
 WHEN E_Liste_vide THEN
  ROLLBACK; -- Libère les verrous précédemment posés
   
 WHEN PK_EXCEP.E_Resource_Busy THEN
   ROLLBACK;
   PK_EXCEP.P_Declenche_Erreur(c_ESEQ_Modification_Detail,'LISA','L''équipier','copier');
   
 WHEN OTHERS THEN
    -- On trace 
   PK_EMISSION.P_TRACE_ERROR(L_DEBUG, L_NOM_PROC, SQLCODE, SUBSTR(SQLERRM,1,150));
    -- et on redéclenche l'exception pour informer le client.
   RAISE;   
END;

7-4. Utilisation de la procédure P_Declenche_Erreur

 
Sélectionnez

PROCEDURE P_DECLENCHE_ERREUR
 (P_NUMERO_ERREUR IN MESSAGES_APPLICATIFS.CODE_ERREUR%TYPE
 ,P_NOM_APPLICATION MESSAGES_APPLICATIFS.APPLICATION%TYPE
 ,P_SPECIFICATEUR1 IN VARCHAR2 := 'NULL'
 ,P_SPECIFICATEUR2 IN VARCHAR2 := 'NULL'
 ,P_SPECIFICATEUR3 IN VARCHAR2 := 'NULL'
 )
 IS
--|==================================================
--| P_DECLENCHE_ERREUR :
--|==================================================
--|==================================================
--| F_GET_MSG_ERR :
--|==================================================
--| Objet : Déclenche l'instruction Raise_Application_Error
--|         Encapsule la gestion des messages basés.
--|
--| Entrée: P_Numero_Erreur    Numéro d'erreur à retrouver
--|         P_Nom_Application  Nom de l'application concernée
--|         P_Specificateur1   Chaîne en correspondance avec %1
--|         P_Specificateur2   Chaîne en correspondance avec %2 
--|         P_Specificateur3   Chaîne en correspondance avec %3
--|
--| Sortie: N/A
--|==================================================
--| 08/01/05 -> L.DARDENNE    : original
--|==================================================
BEGIN
 RAISE_APPLICATION_ERROR(P_Numero_Erreur, F_GET_MSG_ERR(P_Numero_Erreur,
 						 P_Nom_Application,
						 P_Specificateur1,
						 P_Specificateur2,
						 P_Specificateur3));
END;

7-5. Client de test Delphi

Le code de gestion des exceptions lors d'un appel de procédure stockée est le suivant :

 
Sélectionnez

procedure TMainForm.GenereErreur(ANumErreur:Integer);
var Requete:TOracleQuery;

Begin
try
 try
  Requete:=TOracleQuery.Create(Nil);
  with Requete do
    begin
      Session := MainSession;
      SQL.Add('Begin');
      SQL.Add(' P_MAKE_ERREUR(:P_NUM_ERREUR);');
      SQL.Add('End;');
      DeclareVariable('P_NUM_ERREUR', otInteger);
      SetVariable('P_NUM_ERREUR', ANumErreur);
      Execute;
    end;
  Finally
   Requete.Free;
  end;

 Except
  on E:Exception  do
    AnalyseException(E,'Problème lors de l''appel de procédure stockée.');
 end;
end;

Le client de test permet de générer les 3 types d'erreurs et de visualiser l'affichage correspondant.

Il va sans dire que ce client peut être amélioré, par exemple l'ajout d'un flag /Debug sur la ligne de commande permettrait d'afficher le message d'erreur complet ou encore d'afficher la pile d'appel Delphi ( cf. les unité JclDebug et JclHookExcept de la JCL) voire celle d'Oracle en utilisant la procédure DBMS_UTILITY.format_call_stack.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2005 Laurent Dardenne. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.