DB2 for i
Réutilisation de
l'existant dans une architecture client serveur ou n
tiers
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
- 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') ;
- 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 ;
- 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
;
- 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

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
|
| 
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) ) )
|
| 
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 |
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 :
- les classes Java doivent être copiées dans /QIBM/UserData/OS400/SQLLib/Function
- les fichiers jar doivent être en enregistrés par la procédure SQLJ.INSTALL_JAR
- la connexion peut se faire avec les paramètres jdbc:default:connection pour se connecter à la base locale
- l'accès aux données se fait en Unicode, donc pas de CCSID à 65535 !
- la méthode doit être public static (void pour une procédure, un type retour compatible SQL, pour une fonction)
- les types des paramètres doivent être compatibles entre Java et SQL (voir la correspondance ici)
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
|
Dans tous les cas, vous pourrez créer votre fonction
depuis operation navigator, puis retrouver le source de la
déclaration ensuite

Ici, une fonction en SQL PSM

ici, une fonction externe en Java
|
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 :
- autant que de paramètres déclarés (ici 3)
- + autant que colonnes retour (ici 5)
- des variables binaires (8) pour signaler la nullité(-1) ou pas (0) des paramètres
- puis des paramètres standard SQL
- SQLSTATE
- nom qualifié de la fonction
- nom simple de la fonction
- une zone message (taille variable)
- call_type : type d'appel
* 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é
- une première fois pour réaliser un pseudo "Open", call_type est alors égal à -1
- n fois avec call_type à 0, il doit retourner UNE ligne ou SQLSTATE à '02000' pour signaler la fin de fichier
- une dernière fois représentant un pseudo "Close" avec call_type à 1
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
:
- 3 tiers
- la couche présentation (html, wap, pda, ...)
- la logique applicative
- un serveur Web (Apache par ex.)
- un serveur d'application rendant possible l'exécution de
servlets et de pages JSP.
- une base de données (avec eventuellement du traitement :
procédures cataloguées)
un ensemble de normes autour de Java (origine SUN) , représente
l'illustration parfaite de ce modèle : J2EE
- N tiers
- la couche présentation (html, wap, pda, ...)
- la logique applicative
- un serveur Web (Apache par ex.)
- un serveur d'application rendant possible l'exécution de
servlets et de pages JSP (programmes JAVA tournant
derrière le serveur Web)
- les servlets allant consommer un(des) service(s)
sur n serveurs.
ce service pouvant être :
- un calcul effectué par un serveur spécialisé.
- un information retournée :
- carte sous forme d'image pour aller d'un point à
un autre (voir MapPoint de
Microsoft)
- un prix retourné par chaque agence de voyage pour
une recherche de billet d'avion
- etc...
- un appel à un programme (utilisant ou non une base de
données), sur un serveur central.
- cette philosophie offrant le grand avantage de :
- réutiliser au maximum (particulièrement les applications
lourdes et historiques),
- d'utiliser chaque système pour ce qu'il sait faire au mieux
(calcul intensif, stockage BD, gestion d'images)
- s'intégrer parfaitement à la notion de portail
> un service rendu, sur les protocoles standards
de l'internet (plus quelques petits nouveaux)
est appelé Web Service (au pluriel, Web Services
ou servicizzze avec l'accent).
- Un web-service :
c'est un logiciel qui interagit avec d'autres au moyen de protocoles
universels (http, xml...)
Il existe deux types de services WEB
- Les services REST, utilisant uniquement des méthodes standard du WWW
- Accès par URI (comme une URL), facile à composer, facile à lire
-> par exemple (pour une gestion d'articles, n° d'article 24 ) /article/24
- HTTP comme méthode d'appel (GET, POST , PUT et DELETE)
par exemple :
-> GET /articles/ : retourne tous les articles, GET /article/123 retourne l'article 123
-> POST
/article/123: créé l'article 123
-> PUT /article/123 : met à jour l'article 123
-> DELETE /article/123 : supprime l'article 123
- Retour de la donnée dans un format normalisé comme HTML, XML, JSON, ...
- il sont dit stateless, c'est à dire qu'ils fonctionnent comme le web classique, ils ne se souviennent de rien, tout paramètre doit être retransmis.
- Les services dits WS, utilisant de nouveaux protocoles :
- Il existe deux formes de services-web WS : SOAP et XML-RPC. SOAP est
orienté objet et gère les états tandis que XML-RPC
est procédural et sans gestion des états.
- Les services web présentent les 2 caractéristiques
suivantes :
- enregistrement facultatif auprès d'un service de
recherche (UDDI)
- interface publique avec laquelle le client invoque le service
(WSDL)
- UDDI : Universal Desciption, Discovery and Integration peut
être vu comme les pages blanches (ou jaunes) des services-web.
C'est un annuaire permettant à des fournisseurs de
présenter leurs services à des 'clients'.
- WSDL : Web
Service Description Language est un langage reposant sur XML dont
on se sert pour décrire les services-web. Il est indispensable
à UDDI pour permettre aux clients de trouver les méthodes
leur permettant d'invoquer les services web. Beaucoup d'outils comme
JBuilder, Delphi ou Office se servent de WSDL pour découvrir et
générer les mécanismes d'invocation des
services-web.
- SOAP : Simple
Object Access Protocol est un protocole basé sur XML et qui
définit les mécanismes d'échanges d'information
entre les clients et les fournisseurs de service-web. Les messages SOAP
sont susceptibles d'être transportés en HTTP, SMTP,
FTP...(souvent HTTP)
- XML-RPC :
protocole RPC (Remote Procedure Call) basé sur XML.
Permet donc l'invocation de procédure distante sur internet.
-
Ces standards sont très bien intégrés à des langages comme Java, .Net ou PHP
Et sur nos systèmes IBMi
?
IBM i et services Web
Une des particularités des versions For SOA de RDP/RDI,
est la création directe de services WEB
IBM proposait déjà des mécanismes d'appel de programmes (RPG
ou COBOL) depuis une application Java : PCML : langage de description du programme
<pcml version="1.0"> <!-- Create a Data Structure
--> <struct name ="custinfo" > <
data name ="Number" type="char"
length= " 7"
usage="inputoutput" init="0014400"> </data>
< data name ="Name"
type="char" length= " 40"
usage="inputoutput" init=" "> </data> </struct>
<!-- Program getcust --> <program name="getcust"
path="/ QSYS.lib/MABIB.lib /GETCUST.pgm" > <
data name ="gotback" type=" struct"
usage="inputoutput" struct="custinfo">
</data> </program > </pcml> |
public static void main(String[] argv)
{ AS400 as400System = new AS400();
ProgramCallDocument pcml = null;
String msgId, msgText;
Object value = null; try {
System.out.
println ("Creating ProgramCallDocument for GetCust pgm.");
pcml = new ProgramCallDocument(as400System, "GETCUST");
boolean ok = pcml.callProgram("getcust");
System.out.println("
rc is---> " + rc);
if (!ok)
{ /* gerer l'erreur et afficher qqchose */ }
else
{
value = pcml.getValue("getcust.gotback.Name");
System.out.println("Customer name: " + value);
} } catch (PcmlException exc)
{ System.out.println("***
impossible de lancer le pgm. ***");
Sy ste m. exit (0); }
System.exit(0 );
} // end main method |
La version 6.1 de l'OS permet de réaliser ce type d'opération
directement sur le serveur
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
- il doit s'agir d'un profil avec un mot de passe (*NONE provoquera une erreur),
qui a des droits sur le programme.
- si vous utilisez l'option server's user ID, vous devez attribuer
un mot passe à QWSERVICE
- Si vous modifiez le profil ensuite (c'est possible) il faudra redémarrer
le serveur.

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"

- Avec les correctifs SF99713 level 5 et SF99368 level 3, le serveur (maintenant basé sur liberty) permet de créer des web services REST

Choisissez le pgm ou le pgm de service (comme avant)

Indiquez l'URL permettant de le reconnaître
- /chemin/{variable} dans le cas d'une transmission de paramètre dans le PATH (PATH_PARAM)
(vous pouvez alors précisez : et une expression régulière devant être vraie, ici que des chiffres)
- /chemin, dans tous les autres cas

ici l'URL sera /web/services/W_RECAP/recap/prod/1 pour le producteur 1
Les paramètres sont reconnus grâce à PCML, indiquez le sens d'utilisation

Ensuite précisez
Copyright © 2015 VOLUBIS