Inter­cep­ter les erreurs SQL en RPG ile

Inter­cep­ter les erreurs SQL en RPG ile

ven 15 novembre 2019 0 Par Ibmiiste

SQL c’est bien MAIS !

En RPG ile, quand une requête SQL se passe mal, le SQLCODE est néga­tif tout le monde le sait. Cepen­dant dans le cas du code SQL embar­qué dans du RPG ile, si le code SQL est infé­rieur à zéro, le RPG ne peux pas l’in­ter­cep­ter avec un moni­tor par exemple.

La solu­tion est d’é­crire une pro­cé­dure qui va pro­vo­quer une erreur inter­pré­table par les opé­ra­tions d’in­ter­cep­tion (Moni­tor, *PSSR, Exception/error system…).

**FREE
ctl-opt nomain;
ctl-opt datedit(*ymd) decedit('0,');
ctl-opt option(*nodebugio:*srcstmt:*nounref) CCSIDCVT(*LIST);

ctl-opt bnddir('QC2LE');

// API d'information des JOBs 
Dcl-pr rtvJobInf EXTPGM('QUSRJOBI');
  Receiver char(100) OPTIONS(*VARSIZE);     // Receiver variable
  RcvLen  int(10:0) CONST;                  // Length of receiver
  FmtName char(8) CONST;                    // Format name
  ObjName char(20) CONST;                   // Object and library
  ObjType char(10) CONST;                   // Object type
  //  Optional 1
  ErrorCode char(32767) OPTIONS(*VARSIZE:*NOPASS); // Error Code
end-pr;            

// Job information
dcl-ds jobi0100 LikeDS(JOBI0100_t);

//----------------------------------------------------------------
// QMHSNDPM  - send program messages
//----------------------------------------------------------------
dcl-pr QMHSNDPM extpgm('QMHSNDPM');
  *n char(7) const; // MessageId
  *n char(20) const; // MessageFile
  *n char(512) const options(*varsize); // MessageData
  *n bindec(9) const; // MessageDataL
  *n char(10) const; // MessageType
  *n char(128) const options(*varsize); // CallStkEntry
  *n bindec(9) const; // CallStkCount
  *n char(4) const; // MessageKey
  *n char(120) options(*varsize); // ErrApi
end-pr;

// send program message
dcl-ds sndpgmmsg;
  msgid char(7) inz('CPF9898');
  msgfile char(20) inz('QCPFMSG   QSYS      ');
  msgdataL bindec(9) inz(512);
  msgtype char(10) inz('*COMP     ');
  msgmsgq char(11) inz('*       ');
  msgstack bindec(9) inz(1);
  msgkey char(4);
end-ds;

dcl-s msgdata char(512);

//----------------------------------------------------------------
// sql communication area
//----------------------------------------------------------------
dcl-ds SQLCA;
  SQLCAID char(8);
  SQLCABC bindec(9);
  SQLCODE bindec(9);
  SQLERRML bindec(4);
  SQLERRMC char(70);
  SQLERRP char(8);
  SQLERRD char(24);
  SQLER1 bindec(9) overlay(sqlerrd:1);
  SQLER2 bindec(9) overlay(sqlerrd:5);
  SQLER3 bindec(9) overlay(sqlerrd:9);
  SQLER4 bindec(9) overlay(sqlerrd:13);
  SQLER5 bindec(9) overlay(sqlerrd:17);
  SQLER6 char(4) overlay(sqlerrd:21);
  SQLWARN char(11);
  SQLSTATE char(5);
  P_SQLCA char(136) pos(1);
end-ds;

dcl-pr Sleep int(10) extproc('sleep');
  *n uns(10) value; // Seconds
end-pr;

dcl-s rc int(10);

//----------------------------------------------------------------
dcl-proc SQLErreur export;
  dcl-pi SQLErreur;
    pSqlca char(136) const;
    pStop ind options( *nopass );
  end-pi;
  dcl-s wStop ind inz(*on);

  Jobi0100.Job = '*';
  Jobi0100.IntJobID = *Blanks;
  Jobi0100.Status = 'JOBI0100';

  if %Parms =2;
    wStop = pStop;
  endif;
  P_SQLCA = pSqlca;
  select;
  // Erreur détectée
  when SQLCODE < 0;
    msgtype = '*DIAG';
    // cpf message...
    If SQLER1 > 0;
      msgid = %editw(%dec(SQLER1:7:0):'0      ');
      %subst(msgid:1:3) = 'CPF';
    else;
      // cpd message...
      If SQLER2 > 0;
        msgid = %editw(%dec(SQLER2:7:0):'0      ');
        %subst(msgid:1:3) = 'CPD';
      else;
        msgid = %editw(%dec(SQLCODE*-1:7:0):'0      ');
        %subst(msgid:1:3) = 'SQL';
        %subst(msgfile:1:10) = 'QSQLMSG';
      EndIf;
    EndIf;
  // Avertissement
  when SQLCODE > 0;
    msgid = %editw(%dec(SQLCODE:7:0):'0      ');
    %subst(msgid:1:3) = 'SQL';
    %subst(msgfile:1:10) = 'QSQLMSG';
  // Exécution SQL OK
  other;
    msgid = 'SQL' + SQLER6;
    %subst(msgfile:1:10) = 'QSQLMSG';
  endsl;

  // message text
  If SQLERrml > 0;
    msgdata = SQLERrmc;
    msgdataL = SQLERrml;
  EndIf;
  // Retrouve les attributs du Job en cours
  rtvJobInf (jobi0100:Jobi0100.BytesRet:Jobi0100.Status:
              Jobi0100.Job:Jobi0100.IntJobID:Errapi);
  // Job inter-actif ?
  If Jobi0100.Type = 'I';
    msgtype  = '*STATUS';
    msgmsgq  = '*EXT';
    msgstack = 0;
    QMHSNDPM (msgid:msgfile:msgdata:msgdataL:msgtype:
              msgmsgq:msgstack:msgkey:ErrApi);
    rc = sleep(1);
    msgtype  = '*DIAG';
    msgmsgq  = '*';
  Endif;
  // Message d'échappement
  If SQLCODE < 0 and wStop;
    QMHSNDPM (msgid:msgfile:msgdata:msgdataL:
              '*ESCAPE':'*CTLBDY':1:msgkey:ErrApi);
  EndIf;
end-proc;

Voi­ci ci-des­sous les expli­ca­tions sur la pro­cé­dure utilisée :

  • Lignes 9 à 17, nous décla­rons le pro­to­type de récep­tion de l’A­PI QUSRJOBI.
  • Ligne 20 nous décla­rons la DS du for­mat JOBI100 de l’A­PI QUSRJOBI
  • Lignes 25 à 35, nous décla­rons le pro­to­type de l’A­PI QMHSNDPM qui nous ser­vi­ra en envoyé des mes­sages programme
  • Lignes 38 à 46 la DS sndpgmm­sg va recueillir les infor­ma­tions rela­tives à l’er­reur SQL inter­cep­tée. Elle est ini­tia­li­sé avec le code mes­sage CPF9898, puis selon le code SQL reçu, elle sera mise à jour avec des infor­ma­tions supplémentaires.
  • Ligne 48 est décla­rée la variable msg­da­ta qui reçoit les valeurs qui prennent la place des par­ties variables du mes­sage (SQLERRMC).
  • Lignes 54 à 71, SQLCA est décla­ré car ce source est décla­ré en type RPGILE.
  • Lignes 73 à 75 pro­to­type de la pro­cé­dure sleep qui a pour fonc­tion de faire un délai, comme la com­mande CL DLYJOB.
  • Ligne 80 à 84 décla­ra­tion de l’in­ter­face pro­cé­dure, le 1er para­mètre contient le SQLCA de la requête que l’on sou­haite et qui sera contrô­lée pour extraire une erreur éven­tuelle. Le 2me pare­mètre indique s’il faut ou non envoyé un mes­sage d’é­chap­pe­ment en cas de SQL­code néga­tif, par défaut « oui ».
  • Lignes 95 à 123, contrôles des dif­fé­rents niveaux de SQLERR pour déter­mi­ner quels type de mes­sage (CPF, CPD, SQL) il s’a­git. Les numé­ro de mes­sage sont extrait des variables SQLERRx.
  • Lignes 125 à 129, on extrait le texte du message.
  • Lignes 131 à 143, on affiche un mes­sage en cas de tra­vail interactif.
  • Lignes 145 à 148, si le SQL­code est néga­tif et que l’on sou­haite une inter­cep­tion de mes­sage par le sys­tème, on envoi un mes­sage d’échappement.