DB2 for i


Réutilisation de l'existant dans projet de modernisation

BoTTom

Si vous souhaitez réutiliser tout ou partie de vos programmes GAP existant lors de l'utilisation d'odbc/jdbc, vous avez trois grandes solutions

  1. Procédures cataloguées
    Appel sous SQL, à des programmes enregistrés, avec retour éventuel d'informations sous forme d'un jeux d'enregistrements (RecordSet)

    ex CALL GESPROD.CALLPRIX   ('A1') ;

  2. Fonctions SQL
    Enrichir le langage SQL avec l'appel implicite à des programmes RPG retournant une valeur

    ex SELECT CODART,   FAMILLE,   gesprod.prix(CODART)   FROM articles ;

  3. Fonctions TABLES
    Utiliser sous SQL, un programme en tant que SOURCE, dans la clause FROM

    ex SELECT CODART,   FAMILLE   FROM TABLE(  af4srct/tblibl() ) as LIB ;

  4. Alternative : les services web




| Procédures cataloguées



   Procédures cataloguées [STORED PROCEDURES] :
  -----------------------------------------------

 SQL permet maintenant de demander l'exécution d'un programme sur un serveur
  distant (normalement devant réaliser une action sur la base de ce serveur)

 Cette fonction n'est pas encore normalisée (mais largement répandue).

  - certains SGBD n'autorisent que des procédures SQL.

  - DB2/400 autorise l'appel à n'importe quel programme.

    + avec passage de paramètres.

    + ces paramètres pouvant être modifiés par le pgm distant.



 Syntaxe  CALL nom-procédure----------------------------------------------
                                 |                       |
                                 |-(param1,[param2,...])-|


|


 nom-procédure = nom du pgm à appeler ou procédure telle que définie par
                  DECLARE PROCEDUREou CREATE PROCEDURE (voir ci-dessous)

                 la procédure est recherchée dans la collection en cours

                  pour qualifier :  BIB/PGM avec la convention système
                                    BIB.PGM avec la convention SQL

                                      voir: STRSQL    paramètre NAMING
                                            CRTSQLxxx paramètre OPTION

 paramx        = paramètre envoyé (ou reçu) qui peut être

                 + une constante
                 + une variable du pgm (:var)
                   (par défaut :var est en entrée/sortie avec SQL statique
                                    en entrée (envoyée) avec SQL dynamique)
                 + NULL
                 + une valeur spéciale (CURRENT DATE/TIME/TIMEZONE , SERVER)
                                       (USER)


|


Il est possible de déclarer une procédure afin de préciser certaines options

DECLARE -nom-proc-PROCEDURE------------------------------------------------>
                             |        |-IN----|                         |
                             |-(param---OUT-----type de données(lg)-----|
                                      |-INOUT-|
   |-EXTERNAL------------------|
 >------------------------------LANGUAGE--langage---------------------------
   |-EXTERNAL NAME-nom-externe-|                   |-SIMPLE CALL-----------|
                                                   |-SIMPLE CALL WITH NULL-|

 définition des paramètres :

    IN  le paramètre est uniquement reçu par la procédure
    OUT le paramètre est renvoyé par la procédure
    INOUT le paramètre est utilisé dans les deux sens

    type de donnée(lg) : idem CREATE TABLE    CHAR(xx)
                                              INT(lg, dec)
                                              etc ...


|
 EXTERNAL NAME permet de définir le nom réel du pgm sur le serveur, si cette
  clause n'est pas renseignée c'est le nom de procédure qui est utilisé.

 LANGUAGE permet de définir le langage d'origine du pgm à exécuter

          C, CL, COBOL, COBOLLE, RPG, RPGLE, REXX, ...

          ceci est obligatoire avec une procédure REXX

          si cette clause n'est pas utilisé le server recherche l'attribut
           du pgm et par défaut le considère comme un pgm C.



 SIMPLE CALL WITH NULL signifie que le pgm doit recevoir les indicateurs
                        indiquant les valeurs nulles (254 param maxi)

 SIMPLE CALL           le pgm ne reçoit pas ces indicateurs (255 param maxi)



 l'instruction DECLARE PROCEDURE doit être utilisée pour appeler des
  procédures REXX, et doit être écrite AVANT l'instruction CALL.


|




  Particularité de SQL : il ne reconnait pas les membres.

     vous pouvez quand même outrepasser en utilisant les procédures :

     EXEC SQL
      CALL QSYS.QCMDEXC ('OVRDBF FIC MBR(xxx) OVRSCOPE(*JOB)',
                               0000000035,00000)
     END-EXEC.



     Attention

     le point (dans QSYS.CMDEXC) doit être le qualifiant en cours
     la virgule (dans 0000000035,00000) doit être la marque décimale.

  Cette solution peut-être utilisée aussi avec ODBC/JDBC.








|


 Vous pouvez même renvoyer un groupe d'enregistrements en une seule fois :

  Dans la procédure appelée, ajoutez :



       SET RESULT SETS -----------------------------------------------------
                        !                                       !
                        !-----CURSOR nomcurseur-----------------!
                        !                                       !
                        !                                       !
                        !--ARRAY nomdetableau -----FOR x ROWS----



  Le curseur indiqué (il doit être ouvert) ou l'ensemble des enregistrements
   placés dans le tableau (ou la table en COBOL) représentent les valeurs
   en retour retournées au micro.



  L'application cliente (VB, WINDEV, PHP, Java/JDBC, exécute la requête (contenant CALL)
et recoit un paquet d'enregistrements, comme si elle avait lancé un SELECT (movefirst puis movenext, etc...)





|


 Vous pouvez enfin préparer tout cela à l'avance en déclarant une fois pour
  toute
 la procédure et ses particularités ( ce qui est conseillé )





  CREATE PROCEDURE   -nom-proc--(même syntaxe que DECLARE --)---->

                     ----------------------------------------------.
                       !                                        !
                       !---DYNAMIC RESULT SETS -----n------------
                    
                       
n indique le nombre "result sets" retournés


   La définition est enregistrée dans les fichiers SYSPROCS et SYSPARMS
    du catalogue.
|
V5R30 :

    pour un curseur, il faut indiquer en plus

                         --CLIENT--
     ----WITH RETURN TO            --------
     !                   --CALLER--       !    
  >- !                                    !--- 
     !--WITHOUT RETURN--------------------!    



     WITH RETURN TO CLIENT, le curseur ouvert est retourné à l'application
      client, sinon au pgm appelant (cas d'appels en cascade)

    si le curseur est SCROLL (dynamique), seuls les lignes non lues par
      FETCH, sont retournées.



              LA VALEUR PAR DÉFAUT EST RETURN TO CALLER 

|


  l'ordre SET RESULT SETS (dans les pgms) possède lui aussi la clause :

                     --CLIENT--
    FOR RETURN TO --            ---  .
                     --CALLER--

 qui indique si le curseur ou le tableau (ARRAY) doit etre
  retourné à l'application cliente ou au programme appelant.

 la différence est importante quand on appelle un CL, qui lui meme appelle
  un RPG (par exemple) retournant une DS à occurrences.

 la valeur par défaut est RETURN TO CALLER, ce qui fait qu'un programme
  qui fonctionnait en V5R2 ne fonctionne plus en V5R3 sauf à etre recompilé
  avec :

 C/EXEC SQL
 C+ SET RESULT SETS ARRAY :DS1 FOR :X ROWS FOR RETURN TO CLIENT
 C/END-EXEC


Vous pouvez faire cette déclaration depuis operation navigator

Détail de création d'une procédure cataloguée externe (cliquez pour agrandir l'image)

Nom de la procédure et nom du programme "externe"

Définition des paramètres

Options



Vous pourrez ensuite générer le source avec cette option :
|


Exemple (retourne *LIBL)



1/ enregistrement dans les catalogues

CREATE PROCEDURE AF4TOOL/DSPLIBL LANGUAGE RPGLE NOT DETERMINISTIC
CONTAINS SQL EXTERNAL NAME AF4TOOL/DSPLIBL PARAMETER STYLE GENERAL ;



2/ le pgm (ici le RPG principal)

     DDSlibl           ds                  occurs(265)
     D   unebib                      10
     DUSRlibl          ds
     d tblibl                        11    dim(265)

     D i               S              5I 0
     D R               S              5I 0

     Ddspliblc         PR                  EXTPGM('DSPLIBLC')
     D                             2750

|


      /free
       dspliblc(USRLIBL); //équivalent à CALL

       for i=1 to 265;
         if tblibl(i) = *blanks and i > 15; // on est dans usrlibl ==> fin pgm
            leave;
         else;
            if tblibl(i) <> *blanks; // certains noms à blanc dans syslibl
                R = R + 1;
                %occur(DSlibl) = R;
                unebib = tblibl(i);
            endif;
         endif;
        endfor;
      /end-free

     C/EXEC SQL
     C+ SET RESULT SETS  ARRAY :DSLIBL FOR :R ROWS
     C/END-EXEC
     
 /free

        return;
      /end-free

|


 et enfin le CL appellé (DSPLIBLC) , utilisant RTVJOBA 

        PGM        PARM(&LIBL)
         DCL        VAR(&LIBL) TYPE(*CHAR) LEN(2915)
         DCL        VAR(&USRLIBL) TYPE(*CHAR) LEN(2750)
         DCL        VAR(&SYSLIBL) TYPE(*CHAR) LEN(165)

             RTVJOBA    USRLIBL(&USRLIBL) SYSLIBL(&SYSLIBL)
             CHGVAR     VAR(&LIBL) VALUE(&SYSLIBL *CAT &USRLIBL)

        ENDPGM



 pour utiliser cette procédure, lancez comme ordre SQL :



   "CALL DSPLIBL"



  et traitez (suivant le langage) comme si vous aviez demandé un SELECT

Le gestionnaire de script d'operation navigator vous permet de tester cette procédure et vous affiche le résultat.
      
Notez le nom de la colonne, qui est le nom de la zone dans la DS

|

 La V7 offre la possibilité de récupérer dans un RPG, le « result set » produit par une procédure.
il faut déclarer un RESULT_SET_LOCATOR
D unebib s 10
D RS1 S SQLTYPE(RESULT_SET_LOCATOR)
/free
exec sql
CALL DSPLIBL;
if SQLCODE = +466;
exec sql ASSOCIATE LOCATORS (:RS1) WITH PROCEDURE DSPLIBL;
exec sql ALLOCATE C1 CURSOR FOR RESULT SET :RS1;
exec sql fetch c1 into :unebib;
dow sqlcode = 0;
// traitement des variables lues...
exec sql fetch c1 into :unebib;
ENDDO;
exec sql close C1;
ENDIF;
*inlr = *on;
/end-free


|



 Enfin, Il est possible de créer des procédures cataloguées SQL en SQL PSM

    (même langage que les bases Oracle avec PL/SQL)

 ATTENTION :

    cela va créer un programme OS/400 qui sera référencé dans les
         catalogues SQL (SYSPROCS et SYSPARMS dans QSYS2)

    pour créer ce programme, SQL va générer un source C contenant
         du SQL (dans QTEMP) et appeler le compilateur.


  Un compilateur C est intégré à DB2 depuis la V5R10.
depuis la V7R1, vous pouvez indiquer : - PROGRAM TYPE MAIN , cela créé un programme - PROGRAM TYPE SUB , cela créé un programme de service.




|


Exemple général :

  CREATE PROCEDURE xxxxxxx/P1
         (in  nouveau_module CHAR(10) ,
          out probleme       integer  )
         LANGUAGE SQL

         BEGIN ATOMIC
              DECLARE UNDO HANDLER FOR SQLEXCEPTION
                          SET probleme = -1;

              FOR v1 as C1 cursor for SELECT module, nomcours
                                       FROM COURS
                  DO
                  IF nomcours <> ' '
                     THEN
                     UPDATE COURS SET module = nouveau_module
                     WHERE CURRENT OF C1 ;
                  END IF ;
              END FOR;
         END

|


 I/ syntaxe générale 

          --ATOMIC------
 BEGIN ---              ------------------>
          --NOT ATOMIC--

     déclaratives
       .../...



     instructions SQL
       .../...
       .../...

 END

 ATOMIC ==>   l'ensemble de la procédure doit être réalisé comme un tout,
               vous ne pouvez pas placer d'ordres COMMIT/ROLLBACK

 NOT ATOMIC   pas de validation/invalidation automatique, vous définissez
               vos transactions par COMMIT/ROLLBACK.

|


 II/ déclaratives :

 on peut déclarer des variables (sauf les paramètres qui sont déja déclarés)

                                          ---DEFAULT NULL-----
    DECLARE ---nom-variable----type-sql---                    -----
                                          ---DEFAULT val/dft--





 on peut assigner des noms aux codes erreurs SQL :

    DECLARE ---nom-condition--- CONDITION FOR -----code-erreur



     code-erreur:       '01234'       = code 1234
                        SQLEXCEPTION  = erreur <0
                        SQLWARNING    = erreur >0 (sauf 100)
                        NOT FOUND     = erreur 100




|


 déclaration de condition d'erreur (sorte de MONMSG ..)

   on déclare 1/ l'erreur (code ou nom-condition)

              2/ le traitement

              3/ la reprise après traitement.
                                                             (1)

    DECLARE   ------------------------  HANDLER FOR  code-erreur--------->
                !              !
             (3)!--CONTINUE----!
                !              !
                !--EXIT--------!
                !              !
                !--UNDO--------!



   >-------instruction SQL ;
                 (2)


|


  la reprise :

     dans tous les cas on exécute le traitement (2) avant de gérer
      la reprise (3)





     CONTINUE =  on continue le code en séquence.
                 (si l'erreur a lieu dans un IF, CASE, WHILE, on sort
                  et on se débranche à l'instruction qui suit le IF ...)



     EXIT     =  on va au END



     UNDO     =  on annule tous les changements (ROLLBACK) puis EXIT,

                  ce qui implique  BEGIN ATOMIC !






|


 III/ Instructions :

  toutes les instructions SQL sont valides, aussi bien les ordres de gestion
   de la base (CREATE, GRANT, ...) que les ordres de manipulation.

 plus, en assignation :

  SET nom-variable = expression (même possibilités que SELECT)

 et en structuration  :

  IF  --test----THEN--instruction;---------->

  >----------------------------------------->
    !                                 !
    !-ELSEIF test2 THEN instruction2;-!

  >--------------------------END IF ;------->
    !                    !
    !-ELSE instruction3;-!


|


CASE

1ere forme

  CASE--nom-variable----------------------->

  >---WHEN valeur1 THEN instruction1;------>

   >----------------------------------------->
     !                                !
     !-WHEN valeur2 THEN instruction2;!

    >------------------------END CASE;
     !                    !
     !-ELSE instruction3;-!





  chaque test se fait par égalité et sur la même variable.




|


CASE

2eme forme

  CASE------------------------------------->

  >---WHEN test1---THEN instruction1;------>

   >----------------------------------------->
     !                              !
     !-WHEN test2 THEN instruction2;!

    >------------------------END CASE;
     !                    !
     !-ELSE instruction3;-!

 chaque test peut utiliser :

             - un autre opérateur que l'égalité.
             - une variable différente.


|


LOOP (boucle)

    -----------------LOOP---------->
     !-label:--!

     série d'instructions SQL;----->

    ----END LOOP;

le seul moyen de sortir de la boucle est LEAVE

WHILE (boucle conditionnée, tant que)

    -----------------WHILE--condition pour boucler--- DO ---->
     !-label:--!

     série d'instructions SQL;----->

    ----END WHILE;

 la condition est testée AVANT de faire.

|




REPEAT (boucle conditionnée, jusqu'à)

    -----------------REPEAT ---->
     !-label:--!

     série d'instructions SQL;----->

    --UNTIL (condition d'arret)

    ----END REPEAT;

 la condition est testée en fin de boucle (après).



  LEAVE

  ----LEAVE label-------->

      sortie de la boucle qui s'appelle "label"
  ITERATE
      saut d'un tour de boucle

|


FOR  (exécution d'une série d'instructions pour chaque ligne d'un curseur)

     -------------FOR nomf -------------------------------------->
      !-label:--!           !                                 !
                            !--AS nomcurseur CURSOR FOR-------!

    ----------clause SELECT------------- DO -------------->

     série d'instructions SQL;----->

    END FOR;

si vous utilisez un nom de curseur il doit être unique
   (non déclaré par DECLARE CURSOR)
sinon, il sera déclaré implicitement.

toutes les variables retournées par le SELECT doivent être uniques (jointure)
si vous utilisez AS [nocli AS codeclient] c'est "codeclient" que vous devez
    manipuler dans la boucle.

et enfin "nomf" n'est pas utilisable dans la série d'instructions.

|


  l'ordre FOR remplace la série suivante :

 ..............................................................
 :   DECLARE ... pour chaque variable du SELECT               :
 :                                                            :
 :   DECLARE C1 CURSOR FOR (votre SELECT)                     :
 :                                                            :
 :   OPEN C1;                                                 :
 :                                                            :
 :   FETCH C1 INTO (les variables déclarées plus haut);       :
 :                                                            :
 :   WHILE (fin de fichier non rencontrée)                    :
 :                                                            :
 :     ---vos instructions SQL;---                            :
 :                                                            :
 :     FETCH C1 INTO (les variables déclarées plus haut);     :
 :                                                            :
 :   END WHILE;                                               :
 :                                                            :
 :   CLOSE C1 ;                                               :
 :............................................................:

|
 Quelques exemples :


 FOR :

 BEGIN
 DECLARE nomlong  CHAR(40);
          FOR vl AS
              c1 CURSOR FOR
            SELECT prenom , init , nom  FROM personp1
             DO
               SET nomlong  =
                  nom  concat ', ' concat prenom concat
                       ' (' concat init concat ')';
               INSERT INTO TNOMS VALUE ( nomlong );
          END FOR;
 END

|


  avec des curseurs :

  ins_loop:
            LOOP
               CALL RETURN_serviveINFO (SERVICE, SERVNOM, SERVADM);
               IF SERVICE IS NULL THEN
                 LEAVE ins_loop;
               ELSE
                 INSERT INTO DEPARTMENT (SERVICE , SERVNOM, SERVADM) ;
            END LOOP;

  fetch_loop:
            LOOP
               FETCH c1 INTO
                   v_prenom, v_init,
                   v_nom
               IF SQLCODE <> 0 THEN
                  LEAVE fetch_loop;
            END LOOP fetch_loop;
            CLOSE c1;


|


 BEGIN
 DECLARE n_salaire  DECIMAL(9,2);
 DECLARE v_nopers CHAR(6) DEFAULT '123456';

             SELECT salaire
                  INTO n_salaire
                  FROM personp1
                  WHERE nopers = v_nopers;
               IF code  = 1
                THEN SET n_salaire =
                             n_salaire + (n_salaire * 0,10);
                ELSEIF code  = 2
                THEN SET n_salaire =
                             n_salaire + (n_salaire * 0,05);
               END IF;

               UPDATE personp1
                  SET salaire = n_salaire
                  WHERE nopers = v_nopers;


 END

|
Et enfin, :


CREATE PROCEDURE PROC1 () LANGUAGE SQL
 BEGIN
 DECLARE v_prenom VARCHAR(12);
 DECLARE v_init CHAR(1);
 DECLARE v_nom VARCHAR(15);
 DECLARE v_coef SMALLINT;
 DECLARE v_salaire DECIMAL(9,2);
 DECLARE wEOF INT DEFAULT 0;

  DECLARE not_found
                   CONDITION FOR '02000';
  DECLARE c1 CURSOR FOR
              SELECT prenom, init, nom,
                                coef, salaire
              FROM personp1;
  DECLARE CONTINUE HANDLER FOR not_found
                        SET wEOF = 1;


|
            OPEN c1;

            FETCH c1 INTO v_prenom, v_init,
                        v_nom, v_coef, v_salaire;
             WHILE wEOF = 0 DO
                (traitement de la ligne lue)
               FETCH c1 INTO
                             v_prenom, v_init,
                             v_nom, v_coef, v_salaire;
             END WHILE;
            CLOSE c1;
 END




| Fonctions SQL


   Fonctions Définies par l'utilisateur (V4R40) :
  ------------------------------------------------

une fonction est un programme ou une procédure dans un programme de service
 enregistré(e) dans les catalogues SQL par CREATE FUNCTION.

 par exemple :

    CREATE FUNCTION AF4TEST/DT8_CHAR8 (DEC(8 , 0) )
                              RETURNS  CHAR(8)
           EXTERNAL NAME 'AF4TEST/DT_SQL(DT8_CHAR8)'    (1)
           PARAMETER STYLE GENERAL                      (2)
           RETURNS NULL ON NULL INPUT  ;                (3)

(1) fait référence à DT8_CHAR8 dans DT_SQL

(2) le passage de paramètres se fait sans gestion de la val. nulle (uniquement *SRVPGM)

(3) la fonction retourne nul si un des argument est nul
     (il n'y aura pas d'appel)


|


 voici le source RPG de la procédure associée à cette fonction :

 H nomain
  * prototype  =========================================================
 D dt8_char8       pr             8
 D                                8  0
  * fonction  ==========================================================
 Pdt8_char8        b                   export
 d                 pi             8
 d dt8_recue                      8  0

 d date            s               D   datfmt(*dmy/)
 c     *iso          test(d e)               dt8_recue

 c                   if        %error
 c                   return    '01/01/40'
 c                   else
 c     *iso          move      dt8_recue     date
 c                   return    %char(date)
 c                   endif
 p                 e

|


la création se réalise par :

   CRTRPGMOD MODULE(DT8_CHAR8)
 et
   CRTSRVPGM SRVPGM(DT_SQL) MODULE(DT8_CHAR8) EXPORT(*ALL)
___________________________________________________________________________

 cela permet maintenant de passer des ordres SQL comme :

     [soit un fichier client avec DATCRT DEC(8 , 0) ]

 SELECT * FROM  CLIENTS WHERE DT8_CHAR8(DATCRT) <> '01/01/40'

 SELECT * FROM CLIENTS WHERE DATE( DT8_CHAR8(DATCRT) ) = current date

  ..................................................................
  :                                                                :
  : la fonction SQL date attend une chaîne de caractères au format :
  :   du JOB, soit en interactif 'JJ/MM/AA'.                       :
  :                                                                :
  :................................................................:

|


 Deuxième exemple retournant une date au format caractère en ISO

d dt8_char10      pr            10
d                                8  0

Pdt8_char10       b                   export
d                 pi            10
d dt8_recue                      8  0

d date            s               D   datfmt(*iso)
c     *iso          test(d e)               dt8_recue

c                   if        %error
c                   return    '0001-01-01'
c                   else
c     *iso          move      dt8_recue     date
c                   return    %char(date)
c                   endif
c
P                 e


|

déclaration :

 CREATE FUNCTION AF4TEST/DT8_CHAR10 (DEC(8 , 0) )
                           RETURNS  CHAR(10)
        EXTERNAL NAME 'AF4TEST/DT_SQL(DT8_CHAR10)'
        PARAMETER STYLE GENERAL
        RETURNS NULL ON NULL INPUT  ;

 lors de la déclaration de la fonction vous pouvez convertir (caster) la valeur retour :

 CREATE FUNCTION AF4TEST/DT8_DATE (DEC(8 , 0) )
                           RETURNS  DATE CAST FROM CHAR(10)
        EXTERNAL NAME 'AF4TEST/DT_SQL(DT8_CHAR10)'
        PARAMETER STYLE GENERAL
        RETURNS NULL ON NULL INPUT  ;

 ce qui permet directement :

  SELECT * FROM CLIENTS WHERE DT8_DATE(DATCRT) = current date

|


autre exemple :

 CREATE FUNCTION AF4TEST/DOUBLECAR (CHAR(1) )
                           RETURNS  CHAR(2)
        EXTERNAL NAME 'AF4TEST/DOUBLECAR'
        PARAMETER STYLE SQL

 ici, la fonction va lancer un programme (*PGM, mais avec des paramètres particuliers) :
*=========================================================== * paramètre(s) en entrée et réponse
Dcar S 1
Dcar2 S 2
* variables indicateur (indication de nullité)
Dcarindic S 5I 0
Dcar2indic S 5I 0
* SQLSTATE , '00000' si tout va bien
dSQLSTATE S 5
* nom de la fonction qualifié
DfonctionQ S 517 VARYING
* nom (simple) dela fonction
Dfonction S 128 VARYING
* message d'erreur, s'il y a lieu
Dmessage S 1000 VARYING
C *entry plist
C parm car
C parm car2
C parm carindic
C parm car2indic C parm SQLSTATE
C parm fonctionQ
C parm fonction
C parm message
* traitement
C eval car2 = car + car
C return

EN FREE **free
dcl-pi *n; car CHAR(1)
car2 CHAR(2)
carindic INT(5)
car2indic INT(5)
SQLSTATE CHAR(5)
fonctionQ VARCHAR(517)
fonction VARCHAR(128)
message VARCAHR(1000)
end-pi; // traitement
car2 = car + car ;
return ;

|


Il peut exister plusieurs versions d'une même fonction

par exemple CALECHEANCE( date) et CALECHEANCE( dec(8,0) ),
dans le fichier SYSFUNCS, cela s'appele une signature (colonne PARM_SIGNATURE)

c'est le type qui fait référence, pas la longueur (par exemple transmettre dec(8,0) ou dec(6,0) c'est pareil)


Donc si vous essayez > values doublecar('a')
DOUBLECAR de type *N dans *LIBL non trouvé. (SQL0204) SQL n'a pas trouvé la version de DOUBLECAR attendant du VARCHAR

(en effet les constantes sont toujours traitées par SQL comme du VARCHAR)


le plus simple semble être de créer une deuxième version de la fonction, par :
CREATE FUNCTION AF4TEST/DOUBLECAR (p1 VARCHAR(1) )
RETURNS CHAR(2)
LANGUAGE SQL
RETURN DOUBLECAR( CAST(P1 as CHAR(1) ) )

|

depuis la version  7.2, 

• les paramètres peuvent avoir des valeur par défaut 

• on peut nommer le paramètre (=>) lors de l'appel
create function calecheance   
    SPECIFIC CALECH1
(ladate DATE,
nbjour int default 60)
returns date
language SQL
BEGIN
declare wdate date;
wdate = ladate + nbjours days;
return wdate;
END


Mais quel est le comportement de DB2 si nous créons la fonction suivante
create function calecheance    
SPECIFIC CALECH2
(ladate DATE,
nbjour int default 60,
findemois char(1) default 'o')
returns date
language SQL
BEGIN
declare wdate date;
set wdate = ladate + nbjour days ;
if (findemois = 'o') then
set wdate = last_day(wdate);
end if;
return wdate;
END

Et bien la règle concernant les signatures, évolue en 7.2 et devient la suivante :
  • recherche des versions de la fonction, compatibles avec les paramètres utilisés :
    • seuls les types comptent (CHAR, FLOAT, ...) pas les différences de décimales ou de CCSID
    • sur cette version 7.2 (c'est une nouveauté)
      • CHAR, VARCHAR , GRAPHIC sont considérés comme compatibles
      • DATE et TIMESTAMP
      • DEC et FLOAT
      • INTEGER et tous les formats numériques
    • même si un type strictement identique sera privilégié.

  • prise en compte du nombre de paramètres

  • si le choix est multiple, prise en compte du PATH, sélection du schéma le premier rencontré (le plus à gauche comme *LIBL)

  • s'il y a plusieurs choix dans le même schéma,aucun avec des paramètres exacts, mais tous compatibles, SQL retourne une erreur.


    Dans notre exemple

    affiche

    il faut utiliser cette écriture pour utiliser la 2eme version

    ->

 

|


 Vous pouvez aussi créer des fonctions SQL à l'aide du PL/SQL

 create function af4test/euro_francs (VALEUR decimal(9, 2))
                 returns decimal(9, 2)
 LANGUAGE SQL RETURNS NULL ON NULL INPUT

 BEGIN (1)
(3) DECLARE VALFRANCS DECIMAL ( 9 , 2 ) ;(2)
(4) SET VALFRANCS= VALEUR * 6.55957;
(5) RETURN VALFRANCS ;
 END;

(1)  le corps de la fonction est encadré de BEGIN  - END;
(2) chaque ligne se termine par ;
(3) les déclarations se font par DECLARE (même types que CREATE TABLE)
(4) les manipulations se font par SET ou VALUES
(5) RETURN termine la fonction en indiquant la valeur retournée.

|


L'utilisation de fonctions a un coût car on ne peux pas utiliser directement d'index.


les fonctions écrites en RPG, particulièrement dans des *SRVPGM, sont plus rapides.

Celles écrites en Java (voir ci dessous), probablement les plus longues à l'exécution.



 Pour améliorer les performances, ajoutez :

    • NOT FENCED

       sans ce paramètre la fonction est lancée dans un second JOB de type BCI
       (ce qui d'ailleurs rend compliqué le Debug)


    • DETERMINISTIC

      indiquant que cette fonction avec les mêmes paramètres fournie toujours les même résultats 

• NO EXTERNAL ACTION

      indiquant que cette fonction ne lance pas d'autres traitements que DB2 (Data area, IFS, etc...) 

Ces deux derniers paramètres vont permettre à DB2 de faire du cache
 et de ne jamais appeler deux fois la fonction avec des valeurs identiques

|


    Exemple


 

 


Quelques Exemples de fonctions SQL

|


-- retourne le libellé d'une date

CREATE FUNCTION MABIB.LIBDATE ( DATE_IN DATE )
RETURNS CHAR(27)
LANGUAGE SQL SPECIFIC MABIB.LIBDATE
NOT DETERMINISTIC READS SQL DATA
RETURNS NULL ON NULL INPUT

BEGIN
DECLARE WDATE CHAR ( 27 ) ;
DECLARE MOIS CHAR ( 9 ) ;
DECLARE JOUR CHAR ( 8 ) ;

SET JOUR = CASE DAYOFWEEK( DATE_IN )
WHEN 1 THEN 'DIMANCHE'
WHEN 2 THEN 'LUNDI'
WHEN 3 THEN 'MARDI'
WHEN 4 THEN 'MERCREDI'
WHEN 5 THEN 'JEUDI'
WHEN 6 THEN 'VENDREDI'
WHEN 7 THEN 'SAMEDI'
END;

SET MOIS = CASE SUBSTR ( CHAR ( DATE_IN ) , 6 , 2 )
WHEN '01' THEN 'JANVIER'
WHEN '02' THEN 'FÉVRIER'
WHEN '03' THEN 'MARS'
WHEN '04' THEN 'AVRIL'
WHEN '05' THEN 'MAI'
WHEN '06' THEN 'JUIN'
WHEN '07' THEN 'JUILLET'
WHEN '08' THEN 'AOUT'
WHEN '09' THEN 'SEPTEMBRE'
WHEN '10' THEN 'OCTOBRE'
WHEN '11' THEN 'NOVEMBRE'
WHEN '12' THEN 'DÉCEMBRE'
END ;

SET WDATE = TRIM(JOUR) CONCAT ', '
CONCAT SUBSTR ( CHAR ( DATE_IN ) , 9 , 2 )
CONCAT ' ' CONCAT TRIM(MOIS)
CONCAT ' ' CONCAT SUBSTR ( CHAR ( DATE_IN ) , 1 , 4 ) ;

RETURN WDATE ;
END ;

|


-- retourne une date de jj/mm/aa vers aaaa-mm-jj


CREATE FUNCTION MABIB.INVDATE ( CHAR_IN CHAR(10) )
RETURNS CHAR(10)
LANGUAGE SQL SPECIFIC MABIB.INVDATE
NOT DETERMINISTIC READS SQL DATA
RETURNS NULL ON NULL INPUT

BEGIN
DECLARE WDATE CHAR(10) ;
SET WDATE = CASE
WHEN LENGTH(TRIM(CHAR_IN)) > 8
THEN SUBSTR(CHAR_IN, 7 ,4)
CONCAT '-' CONCAT SUBSTR(CHAR_IN, 4 ,2)
CONCAT '-' CONCAT SUBSTR(CHAR_IN, 1, 2)
WHEN LENGTH(TRIM(CHAR_IN)) > 6
THEN '20' CONCAT SUBSTR(CHAR_IN, 7 ,2)
CONCAT '-' CONCAT SUBSTR(CHAR_IN, 4 ,2)
CONCAT '-' CONCAT SUBSTR(CHAR_IN, 1, 2)
ELSE
'20' CONCAT SUBSTR(CHAR_IN, 5 ,2)
CONCAT '-' CONCAT SUBSTR(CHAR_IN, 3 ,2)
CONCAT '-' CONCAT SUBSTR(CHAR_IN, 1, 2)
END;

RETURN WDATE ;
END ;

|
Enfin, les procédures et les fonctions peuvent être écrites en Java (c'est ce qu'a fait IBM pour les fonctions HTTPGETBLOB et suivantes)


Quelques remarques :

par exemple string sous SQL / String sous Java, mais String[] pour un paramètre OUT d'une procédure (ne renseigner que l'élément [0] )

Exemple

import java.sql.*; 
public class ProcJava
{
 public static void UpdateSalary (String empNum, int rating )
 throws SQLException
 {
   int errorCode = 0;
   boolean bFlag;
   String sql;
   PreparedStatement stmt = null;
   
   try 
    {
     Connection con = DriverManager.getConnection("jdbc:default:connection");
  
     if (rating == 1) {
        sql = "UPDATE db2sample.employee "
        + "SET salary = salary * 1.10, "
        + "bonus = 1500 "
        + "WHERE empno = '" + empNum + "'";
     }
     else {
        sql = "UPDATE db2sample.employee "
        + "SET salary = salary * 1.05, "
        + "bonus = 1000 "
        + "WHERE empno = '" + empNum + "'";
     }
     stmt = con.prepareStatement( sql );
     bFlag = stmt.execute(); 
    } 
    catch ( SQLException sqle)
    {
     errorCode = sqle.getErrorCode();
     throw new SQLException ( "Erreur : " + errorCode );
   }
  }
}

Déclaration

CREATE PROCEDURE QGPL.UPDATE_SALARY
 ( IN empNum CHAR(6), IN rating Int )
 SPECIFIC QGPL.UPDATE_SALARY
 DYNAMIC RESULT SETS 0
 NOT DETERMINISTIC
 LANGUAGE JAVA
 PARAMETER STYLE JAVA
 EXTERNAL NAME 'ProcJava.UpdateSalary'
 FENCED ;


Exemple de déclaration utilisant un fichier .jar

CREATE FUNCTION SYSTOOLS.HTTPGETBLOB ( 
URL VARCHAR(2048) CCSID 1208 ,
HTTPHEADER CLOB(10240) CCSID 1208 )
RETURNS BLOB(2147483647)
LANGUAGE JAVA
SPECIFIC SYSTOOLS.HTTPGETBLOBNONXML
NOT DETERMINISTIC
NO SQL
CALLED ON NULL INPUT
EXTERNAL NAME 'SYSTOOLS.DB2RESTUDF:com.ibm.db2.rest.DB2UDFWrapper.httpGetBlob'
PARAMETER STYLE JAVA ;


Pour plus de détails http://pic.dhe.ibm.com/infocenter/iseries/v7r1m0/topic/rzaha/jsqlrout.htm

|



Vous pourrez créer vos fonctions Depuis ACS

Création d'une fonction (ici Externe, c'est à dire existant déjà en tant qu'objet *PGM ou *SRVPGM))


Dont vous pourrez modifier les caractèristiques ensuite



Puis retrouver le script SQL (ici une fonction SQL)





| V5R20, Fonctions TABLES


Comme nous venons de le voir, SQL propose de créer vos propres fonctions -> UDF ou User Defined Fonction.

ces fonctions ne retournent qu'une valeur sous la forme date2 = findemois(date1)

 

Pour retourner plusieurs valeurs vous pouvez écrire des fonctions tables, qui retourne des lignes (donc une série de colonnes) comme une table (ou un fichier)

 

1/ en PL SQL

cela consiste á placer un SELECT sur la clause RETURN.

 

IBM fourni un exemple avec la fonction USERS() dont voici le source

CREATE FUNCTION SYSIBM.USERS ( ) 
 RETURNS TABLE ( 
   ODOBNM CHAR(10), 
   ODOBTX CHAR(50) ) 
 LANGUAGE SQL 
 READS SQL DATA 
 
BEGIN
 DECLARE CMD CHAR ( 300 ) DEFAULT ' ' ;
 DECLARE WARN CONDITION FOR '01HII' ;
 DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SIGNAL WARN SET MESSAGE_TEXT = 'SOME USERS NOT AVAILABLE' ;
 SET CMD = 'QSYS/DSPOBJD qsys/*ALL *USRPRF OUTPUT(*OUTFILE) ' CONCAT ' OUTFILE(qtemp/q_users) ' ;
 CALL QSYS . QCMDEXC ( CMD , 0000000300.00000 ) ;
 RETURN SELECT ODOBNM , ODOBTX FROM QTEMP.Q_USERS ; 
END  ; 
    

 

Utilisation : SELECT * from TABLE( udtf() ) as alias :

Mais vous pouvez utiliser TOUTE la puissance de la syntaxe SQL

 

Dans ce cadre nous vous proposons un exemple basé sur DSPPGMREF

CREATE FUNCTION FORMATION0.PGMREF ( 
PGMPARM VARCHAR(10),
LIBPARM VARCHAR(10) )
RETURNS TABLE (
PGM CHAR(10),
OBJRFF CHAR(11),
LIBREF CHAR(11),
OBJTYPE CHAR(10) )
LANGUAGE SQL
READS SQL DATA

BEGIN
DECLARE CMD CHAR ( 300 ) DEFAULT ' ' ;
DECLARE WARN CONDITION FOR '01HII' ;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SIGNAL WARN SET MESSAGE_TEXT = 'Liste en erreur' ;
SET CMD = 'QSYS/DSPPGMREF PGM(' CONCAT trim(LIBPARM) concat '/' CONCAT trim(pgmparm) CONCAT ') OUTPUT(*OUTFILE) OUTFILE(qtemp/dsppgm) ' ;
CALL QSYS . QCMDEXC ( CMD , 0000000300.00000 ) ;
RETURN SELECT WHPNAM, WHFNAM, WHLNAM, WHOTYP FROM qtemp.dsppgm ;
END ;




par exemple, IBM fournit en V6R1 avec la PTF SI39822, la fonction table DISPLAY_JOURNAL

Select * From TABLE (Display_Journal(
   -- bib et journal
   'BDVIN1', 'QSQJRN',
   -- bib et récepteur
   ' ', ' ',
   -- timestampt de début ou null
   now() - 7 days ,
   -- séquence de début ou null
   CAST(null as DECIMAL(21 , 0)),
   -- code journal
   ' ',
   -- type d'entrée
   ' ',
   -- bib, objet, type, membre
   ' ' , ' ', ' ' , ' ',
   -- profil utilisateur
   'QPGMR',
   -- job
   ' ',
   -- pgm
   ' ' )
 ) AS jrn

La colonne contenant les données du poste (ENTRY_DATA) est retournée sous forme de BLOB,
 castez par CAST(ENTRY_DATA AS CHAR(2000)) pour la voir en clair.

 

ET en V7R1 (SF99701 level 3) une fonction table OBJECT_STATISTIC, retournant une liste d'objets.

   select * from table (qsys2.OBJECT_STATISTICS('BDVIN1','JRN') ) as x donne la liste des journaux de BDVIN1

   select * from table (qsys2.OBJECT_STATISTICS('BDVIN1','JRN JRNRCV') ) as x donne la liste des journaux et des récepteurs de BDVIN1

2/ Fonction EXTERNAL


(ici en RPG utilisant des API)

 

Dans cet exemple, nous utiliserons l'API QUSLMBR qui permet d'obtenir la liste des membres dans un User Space

-> la fonction est définie de la manière suivante

 CREATE FUNCTION AF4TOOL/LISTMBR (
BIB CHAR(10),
FILE char(10) ,
MBR char(10) )
RETURNS TABLE (NOM CHAR(10) , TYPE CHAR(10), DATCRT DATE ,
DATCHG DATE, texte char(50)
)
EXTERNAL NAME 'AF4TOOL/LISTMBR'
PARAMETER STYLE DB2SQL
DISALLOW PARALLEL

Paramètres en entrée :

 * Interface de procédure sur le pgm en cours  = paramètres en entrée
 * on peut se passer du prototype uniquement depuis la V7
dLISTMBR PI EXTPGM('LISTMBR') *paramètres définis dans CREATE FUNCTION (INPUT) d INbib 10 d INfile 10 d INmbr 10 *paramètres définis dans CREATE FUNCTION (OUTPUT) d OUTnom 10 d OUTtype 10 d OUTdatcrt D d OUTdatchg D d OUTtexte 50 * indicateurs valeur nulle (IN) d INbib_i 5I 0 d INfile_i 5I 0 d INmbr_i 5I 0 * indicateurs valeur nulle (OUT) d OUTnom_i 5I 0 d OUTtype_i 5I 0 d OUTdatcrt_i 5I 0 d OUTdatchg_i 5I 0 d OUTtexte_i 5I 0 * paramètres standards (STYLE SQL) DSQLSTATE 5 Dfonction_qual 517 VARYING Dfonction_name 128 VARYING Dmessage_diag 80 VARYING Dcall_type 10I 0

le pgm est appellé

Autres déclarations (variables + prototypes des Api)

 * prototype pour API création/destruction User Space
dQUSCRTUS         PR                  EXTPGM('QUSCRTUS')
d  space                        20    CONST
d  USattribut                   50    CONST
d  UStaille                     10I 0 CONST
d  UScontenu                     1    CONST
d  USdroits                     10    CONST
d  UStexte                      50    CONST
d  USreplace                    10    CONST
d  USerrcode                          likeds(errcodeDS)
dQUSDLTUS         PR                  EXTPGM('QUSDLTUS')
d  space                        20    CONST
d  USerrcode                          likeds(errcodeDS)
 * prototype pour API qui retrouve pointeur de début
dQUSPTRUS         PR                  EXTPGM('QUSPTRUS')
d  space                        20    CONST
d  ptr                            *
Dusrspc           s             20    inz('LISTMBR   QTEMP')
 * API litse des membres d'un fichier
dQUSLMBR          PR                  EXTPGM('QUSLMBR')
d  space                        20    CONST
d  format                        8    CONST
d  ficlib                       20    CONST
d  membre                       10    CONST
d  OVRDBF                         N   CONST
     
 * va contenir l'adresse de début du User Space
Dpointeur         s               *
DI                s             10i 0
 * l'entête
Dptrinfos         s               *
DRTVINF           ds                  based(ptrinfos)
D  offset                       10i 0
D  taille                       10i 0
D  nbpostes                     10i 0
D  lgposte                      10i 0
 * la liste
dptrliste         s               *
Dmembre           ds                  based(ptrliste) qualified
d  nom                          10
d  type                         10
d  DatCrt                        7
d  HeurCrt                       6
d  DatChg                        7
d  HeurChg                       6
d  texte                        50
DerrcodeDS        ds                  qualified
d  tailleDS                     10i 0 inz(%size(errcodeDS))
d  taille                       10i 0
d  msgID                         7
d  reserve                       1
d  errdta                       50
      

le code

  /free
     if call_type < 0 ;
       // premier appel, constitution de la liste
       SQLSTATE = '00000'  ;
       QUSCRTUS(usrspc: *Blanks: 1024: x'00': '*USE':
                'Liste des membres': '*YES' : errcodeDS);
       QUSLMBR(usrspc: 'MBRL0200': INfile + INbib
              : INmbr: *OFF);
       // recpération pointeur de début
         QUSPTRUS(usrspc : pointeur);
         ptrinfos = pointeur + 124;
       // position sur 1er poste
         ptrliste = pointeur + offset;
         return;
     elseif    call_type = 0 ;
         i+=1;
         if i<=nbpostes;
           // appel "normal", retour d'un membre
           OUTnom = membre.nom;
           OUTtype = membre.type;
           Monitor;
OUTdatcrt = %date(membre.datcrt : *CYMD0);
on-error *all;
OUTdatcrt_i = -1;
Endmon;
Monitor;
OUTdatchg = %date(membre.datchg : *CYMD0);
on-error *all;
OUTdatchg_i = -1;
Endmon; OUTtexte = membre.texte; if i<nbpostes; //déplacement poste suivant ptrliste = ptrliste + lgposte; endif; else; // fin de liste SQLSTATE = '02000'; *INLR = *on; endif; return; else; // appel final, faire le ménage QUSDLTUS(usrspc: errcodeDS) ; *inlr = *on; endif; /end-free

Utilisation :

Rappel : il peut exister plusieurs versions d'une même fonction avec des paramètres en entrée différents.
Ce qui identifie une fonction est donc le nombre et le type de chaque paramètre. Cette identification est appelée signature.


En SQL toute constante est réputée de type VARCHAR. La fonction ayant été créée avec des paramètres CHAR, dans notre exemple, il faut caster


(ce n'est plus vrai en 7.2, SQL cherche une signature compatible)

résultat

 

là aussi la fonction aurait pu être utilisée de la manière suivante :

 

EN 7.2/TR1 (et aussi 7.1/TR9) l'instruction PIPE offre une alternative pour les fonctions TABLE SQL

PIPE

nouvelle instruction du SQL PSM offrant une alternative aux fonctions tables pour retourner une ligne à la fois

Contrairement aux fonctions tables écrites en RPG qui retournent une ligne à chaque appel,
  les fonctions tables écrites en PL/SQL devaient retourner le résultat d'un Select SQL



Désormais vous pouvez ajouter de la logique à cela :


Résultat

Alternative : services web

Aujourd'hui, pour une application WEB, l'architecture logique doit être :


Voyez le nouveau serveur d'applications intégré LWI

Ici un programme W_RECAP de BDVIN0, attendant une zone PR_CODE, et retournant une DS nommée INFOCENTRE,
    ce pgm a été compilé avec PGMINFO(*PCML : *MODULE)

Indiquez le nom public du service et un texte explicatif

et précisez le sens d'utilisation des paramètres (automatiquement découverts par l'assistant grave à PGMINFO)

Indiquez le profil utilisateur qui lancera le programme

indiquez aussi, la liste des bibliothèques à utiliser

et voilà, une fenêtre récapitulative est affichée et le service déployé

Puis, vous basculez automatiquement sur la gestion du serveur d'application de type web services V1.3, cette fois.

Vous pouvez, ici, revenir gérer les servies déployés, ou bien, en déployer de nouveaux.


(properties, permet de changer le profil)

 

mais surtout, tester votre service web, par le bouton "Test Service"

Les paramètres sont reconnus grâce à PCML, indiquez le sens d'utilisation


Ensuite précisez

 


Top

 

Copyright © 2017 VOLUBIS