Gestion des erreurs applicatives Oracle sous Delphi et DOA

Laurent Dardenne

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

- Titre : Gestion des erreurs applicatives Oracle sous Delphi et DOA

- Auteur : Laurent Dardenne

- Parution : 26 janvier 2005

- Mise à jour : 26 janvier 2005

- Licence : 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.

Sommaire

- I. Public concerné

- II. Introduction

- III. Gestion des erreurs sous Oracle

- III-A. Table d'erreurs MESSAGES_APPLICATIFS

- III-B. Table de trace des erreurs

- III-C. Contenu du package PK_EXCEP

- III-C-1. Fonction F_GET_MSG_ERR

- III-C-2. Fonction F_GET_ERR_APPLICATIV

- III-C-3. Procédure P_DECLENCHE_ERREUR

- III-C-4. Déclaration d'une exception

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

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

- IV. Gestion des erreurs sous Delphi

- V. Distinction des différentes erreurs

- V-A. erreurs applicatives Oracle

- V-B. Erreurs Oracle

- V-C. Erreurs Delphi

- VI. Mise en œuvre

- VII. Utilisation

- VII-A. Exemple de déclaration dans un script PL/SQL

- VII-B. Exemple d'utilisation sous PL/SQL

- VII-C. Le bloc de traitement des exceptions

- VII-D. Utilisation de la procédure P_Declenche_Erreur

- VII-E. Client de test Delphi

I - Public concerné

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.

II - 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é 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 œuvre de ce type de solution au sein d'une équipe ne supporte pas vraiment l'approximation.

III - 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.

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.

III-A - 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'à trois 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 :

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.

III-B - 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.

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;

À 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.

III-C - Contenu du package PK_EXCEP

III-C-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.

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…

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 remplaçant 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 œuvre si vous souhaitez connaître le type des paramètres afin d'effectuer quelques traitements particuliers.

III-C-2 - Fonction F_GET_ERR_APPLICATIV

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

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 :

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;

III-C-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

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;

III-C-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 :

III-C-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 :

c_eseq_modification_detail CONSTANT     PLS_INTEGER := -20993;

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

/* 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 ) :

PRAGMA     EXCEPTION_INIT(e_seq_modification_detail, -20993);

4 - Ensuite renseignez la table des erreurs applicatives.

III-C-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.

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);

IV - Gestion des erreurs sous Delphi

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

V - Distinction des différentes erreurs

Nous distinguerons trois types d'erreurs :

- une erreur applicative Oracle ;

- une erreur Oracle ;

- une erreur Delphi.

V-A - erreurs applicatives Oracle

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

V-B - 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.

V-C - Erreurs Delphi

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

VI - Mise en œuvre

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.

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é.
À 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.

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).

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.

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;

VII - Utilisation

VII-A - Exemple de déclaration dans un script PL/SQL

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 :

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

VII-B - 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.

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

VII-C - Le bloc de traitement des exceptions

Dans le bloc d'exception suivant, on traite deux 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.

/* 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;

VII-D - Utilisation de la procédure P_Declenche_Erreur

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;

VII-E - Client de test Delphi

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

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 trois 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és JclDebug et JclHookExcept de la JCL), voire celle d'Oracle en utilisant la procédure DBMS_UTILITY.format_call_stack.