Trigger : Pgm associé à une action base de données (insert, update, delete) cde OS/400: Ajouter déclencheur fich physique ( ADDPFTRG ) ........................................................................... : : : Fichier physique . . . . . . . . > CLIENTP Nom : : Bibliothèque . . . . . . . . . *LIBL Nom, *LIBL, *CURLIB : : Moment de déclenchement . . . . > *BEFORE *BEFORE, *AFTER : : Evénement de déclenchement . . . > *UPDATE *INSERT, *DELETE, *UPD : : Programme . . . . . . . . . . . > TRIGGER1 Nom : : Bibliothèque . . . . . . . . . *LIBL Nom, *LIBL, *CURLIB : :.........................................................................: Le trigger, s'il est écrit en L3G (RPG ou COBOL) recoit deux paramètres: 1/ une structure contenant a/ des infos système b/ les données manipulées 2/ la longueur de 1/ Il peut enfin, signaler son désaccord, en envoyant un message d'erreur |
le buffer change en V5R10, il faut IMPERATIVEMENT utiliser les décalages (offsets) indiqués en 48 et 64 (faire +1 pour avoir la position). Dépl Type Champ 0 Char(10) Nom du fichier physique 10 Char(10) Nom de la librairie 20 Char(10) Nom du membre 30 Char(1) Evénement déclencheur 1=INSERT,2=DELETE,3=UPDATE,4... 31 Char(1) Moment du déclenchement 1=AFTER,2=BEFORE 32 Char(1) Niveau de verrouillage de la définition de validation (0=*NONE,1=*CHG,2=*CS,3=*ALL) 36 Binary(4) CCSID des données (297 ?) 40 Binary(4) N° de rang de l'enregistrement 48 Binary(4) Image avant : Déplacement de l'enregistrement 52 Binary(4) Longueur de l'enregistrement 56 Binary(4) Déplacement de la description des champs 60 Binary(4) Longueur de la description des champs 64 Binary(4) Image après : Déplacement de l'enregistrement 68 Binary(4) Longueur de l'enregistrement 72 Binary(4) Déplacement de la description des champs 76 Binary(4) Longueur de la description des champs ?? Char(*) Début de la zone variable |
cette partie variable contient : - enregistrement version avant. - indicateurs de nullité (avant), 1/zone - enregistrement version après. - indicateurs de nullité (après), 1/zone soit un fichier article avec le buffer suivant | codearticle | libellé | datecrt | | | | | le trigger recoit un paramètre 4 6 infos 9 5 | code |libellé |date | null | code |libellé |date | | | Dep1 | Dep2 | |../.. | | ! ! ^ ^ !-----!----! ! ! ! !---------------------------------! infos représente toutes les informations systèmes dont nous venons de voir le détail |
dep1 indique le déplacement pour aller à la version avant du buffer dep2 " " " " " " après du buffer Vous devez absolument utiliser ces deux déplacements (dep1 et dep2) pour accèder aux data, la position exacte pouvant changer d'une version à l'autre. La position de dep1 et dep2 étant elle même immuable, bien sûr. null contient dans notre cas trois octets contenant '0' ou '1' le premier octet contient '0' si code article est significatif '1' si code article contient null etc pour les deux autres champs. la fonction %NULLIND() du RPG étant inutilisables (réservées au zones venant de fichiers, et dans notre cas il n'y a pas de spécif F) |
la gestion des triggers est elle même modifiée en V5.10 : -un trigger peut être suspendu -on peut associer 300 triggers à un fichier -un trigger possède un nom long et une bibliothèque -on peut associer un trigger à la lecture (peut-elle avoir lieu ?) -l'écriture des triggers peut se faire en SQL PSM (L4G) dans ce dernier cas, - le trigger peut-être associé à la modification d'un champ. - vous pouvez indiquer des conditions au lancement du trigger. - il peut-être lancé à chaque ligne modifée ou bien une fois par instruction (UPDATE par ex.). |
un trigger s'identifie maintenant par un nom long et une bibliothèque de trigger (ce qui n'est pas la bibliothèque du programme) voyons la commande ADDPFTRG : Ajouter déclencheur fich phys (ADDPFTRG) Fichier physique . . . . . . . . Nom Bibliothèque . . . . . . . . . *LIBL Nom, *LIBL, *CURLIB Moment de déclenchement . . . . *BEFORE, *AFTER Evénement de déclenchement . . . *INSERT, *DELETE, *UPDATE. Programme . . . . . . . . . . . Nom Bibliothèque . . . . . . . . . *LIBL Nom, *LIBL, *CURLIB Remplacer déclencheur . . . . . *NO *NO, *YES Déclencheur . . . . . . . . . . *GEN Bibliothèque du déclencheur . . *FILE Nom, *FILE, *CURLIB |
le paramètre Déclencheur (TRG) permet d'indiquer un nom de trigger, ce dernier ne pouvant plus être identifié par le couple moment/évenement. le nom ne doit pas dépasser 128 c. (ou 258 entre guillemets) les triggers existants en V4 sont nommés QSYS_TRIG_nombib_nomfichier_00001 les noms de bibliothèque et de fichier étant complémentés à 10 par "_" le paramètre Bibliothèque (TRGLIB) indique la bibliothèque du trigger (et non la bibliothèque du pgm lui même qui est fournie dans PGM) la valeur *FILE attribue le nom de la bibliothèque du fichier, *CURLIB, la bibliothèque en cours. c'est le couple TRGLIB/TRG qui identifie un trigger de manière unique. (ATTENTION, l'unicité se faisant sur tout le système) |
désactivation d'un trigger : CHGPFTRG FILE(QGPL/TEST) TRG(TRG_TEST_READ) TRGLIB(*FILE) STATE(*ENABLED | *DISABLED) à l'état *DISABLED le trigger n'est plus appellé lors des E/S. la commande DSPFD indique l'état actuel ............................................................................ :Description de déclencheur : : : : Nom de déclencheur. . . . . . . . . . . . . : TRG QSYS_TRIG_BDVIN_: : ____MA_CAVE____000001 : : Bibliothèque de déclencheur . . . . . . . : BDVIN9 : : Etat du déclencheur . . . . . . . . . . . : STATE *ENABLED : : Etat du déclencheur . . . . . . . . . . . : *OPERATIVE : :..........................................................................: *operative indique un trigger associé à un fichier avec au moins un membre. |
un trigger peut-être associé à l'ordre de lecture : (trigger système [non SQL] uniquement.) ADDPFTRG ... TRGTIME(*AFTER) TRGEVENT(*READ) ALWREPCHG(*NO) on intercepte la lecture après, uniquement. mais c'est le buffer avant qu'il faut traiter (???) les évenements sont codés : 1 = insert 2 = delete 3 = update 4 = read ........................................................................ : IL FAUT IMPERATIVEMENT : : : - utiliser ces codes, les déplacements sont TOUJOURS renseignés : : - utiliser les déplacements (offsets), la position des données : : a été modifiée sur cette version. : :......................................................................: on ne peut pas sustituer des données dans le buffer [ALWREPCHG(*NO).] on peut empecher la lecture, par l'envoi d'un message d'erreur. |
et enfin la crétation de triggers en SQL : --- BEFORE---- CREATE TRIGGER --nom-trigger----------------------! !------> ---AFTER------ ---INSERT----------------------------------- ! ! >-----!--DELETE----------------------------------!-----REFERENCING---> ! ! !--UPDATE----------------------------------! ! ! !-- OF colonne --------! ----OLD ROW -- AS ---nom-correlation------- ! ! !---NEW ROW -- AS ---nom correlation------! -FOR EACH STATEMENT- >----! !---! !-> !---OLD TABLE -- AS --identifiant---------! --- FOR EACH ROW---- ! ! !---NEW TABLE -- AS --identifiant---------! |
--MODE DB2ROW--- >---- SET OPTION (option de compilation)--! !--------> --MODE DB2SQL--- >-----WHEN (condition d'exécution du trigger)----------------> >-BEGIN ... (corps du trigger ) ... END quelques explications : + BEFORE ne peut pas contenir des ordres INSERT, UPDATE, DELETE mais peut modifier les variables de la table par SET. AFTER peut contenir des ordres INSERT, UPDATE, DELETE sur d'autres tables |
+ INSERT | DELETE | UPDATE représentent l'événement associé(READ non admis) UPDATE OF "colonne" permet de ne déclencher le trigger que si "colonne" a été modifiée. + REFERENCING OLD ROW | NEW ROW OLD n'est valide qu'avec UPDATE, DELETE NEW " " " UPDATE, INSERT exemple : REFERENCING OLD ROW AS avant NEW ROW AS apres puis dans le code : IF apres.prix > avant.prix .... OLD ROW, NEW ROW ne peuvent pas être utilisés avec l'option FOR EACH STATEMENT. |
exemple, lors d'une modification si le prix actuel est < au prix d'achat il doit prendre au moins la valeur du nouveau prix d'achat : ........................................................................ : : : CREATE TRIGGER MAJ_PRIX BEFORE UPDATE OF CAV_PRXACTUEL : : ON BDVIN/MA_CAVE : : REFERENCING NEW ROW AS NOUVEAU : : FOR EACH ROW MODE DB2ROW : : : : BEGIN : : : : IF NOUVEAU.CAV_PRXACTUEL < NOUVEAU.CAV_PRIX THEN : : SET NOUVEAU.CAV_PRXACTUEL = NOUVEAU.CAV_PRIX ; : : END IF; : : : : END : : : :......................................................................: |
+ REFERENCING OLD TABLE | NEW TABLE OLD n'est valide qu'avec UPDATE, DELETE NEW " " " UPDATE, INSERT OLD table représente une table temporaire contenant l'ensemble des enregistrements modifiés ou supprimés. (les valeurs sont celles avant l'instruction d'origine) NEW table représente une table temporaire contenant l'ensemble des enregistrements insérés ou supprimés. (les valeurs sont celles après l'instruction d'origine et après l'appel du(des) trigger(s) BEFORE) ces deux options ne peuvent être utilisées qu'avec : le mode DB2SQL, et le moment AFTER. (FOR EACH ROW ou FOR EACH STATEMENT) |
exemple, on trace cumul des prix modifiés : ....................................................................... : CREATE TRIGGER VIN_CUMUL AFTER UPDATE OF CAV_PRIX : : ON BDVIN9/MA_CAVE : : REFERENCING OLD TABLE AS OCAVE : : NEW TABLE AS NCAVE : : FOR EACH STATEMENT MODE DB2SQL : : : : BEGIN : : DECLARE AV_CUMUL DEC(11, 2); : : DECLARE AP_CUMUL DEC(11, 2); : : : : SELECT SUM(CAV_PRIX) INTO AV_CUMUL FROM OCAVE ; : : SELECT SUM(CAV_PRIX) INTO AP_CUMUL FROM NCAVE ; : : : : INSERT INTO AF4TEST/CUMUL VALUES(NOW() , AV_CUMUL, AP_CUMUL) ; : : END : :.....................................................................: suite à un ordre : update bdvin9/ma_cave set cav_prix = cav_prix * 1,5 QUAND AVANT_CUMUL APRES_CUMUL 2001-08-23-14.25.20.181728 205,95 308,92 |
+ FOR EACH ROW | EACH STATEMENT FOR EACH ROW le trigger est appellé une fois par ligne cas des contrôles, des mise à jour de champs, des traces ... FOR EACH STATEMENT le trigger est appellé une fois par instruction. cas de recalcul automatique de cumuls, etc... ATTENTION : le trigger est appellé même si aucune ligne n'est impactée + MODE DB2ROW , DB2SQL MODE DB2ROW, l'appel a lieu à chaque ligne modifiée, BEFORE ET AFTER sont valides, FOR EACH STATEMENT et l'utilisation de OLD TABLE/NEW TABLE ne sont PAS admis. |
MODE DB2SQL, l'appel a lieu à la fin de l'instruction, seul AFTER est valide, FOR EACH STATEMENT et l'utilisation de OLD TABLE/NEW TABLE SONT admis. les valeurs reçues sont bien celles utilisée lors de la transaction, la grande différence est que le trigger n'est appellé que si l'ordre SQL va jusqu'au bout. exemple : mise à jour d'un fichier dont la dernière ligne est verrouillée par un autre JOB, le trigger écrit dans une table afin de conserver une trace. avec MODE DB2ROW le trigger est appellé (n - 1 ) fois, n étant le nombre d'enregistrements à modifier. avec MODE DB2SQL le trigger n'est pas activé (fichier trace vide). |
+ SET OPTIONS options du compilateur (C), particulièrement DBGVIEW = *SOURCE + WHEN (condition) condition quant à l'appel du trigger exemple : ........................................................................ : : : CREATE TRIGGER MAJ_PRIX BEFORE UPDATE OF CAV_PRXACTUEL : : ON BDVIN/MA_CAVE : : REFERENCING NEW ROW AS NOUVEAU : : FOR EACH ROW MODE DB2ROW : : WHEN (NOUVEAU.CAV_PRXACTUEL < NOUVEAU.CAV_PRIX) : : : : BEGIN : : SET NOUVEAU.CAV_PRXACTUEL = NOUVEAU.CAV_PRIX ; : : END : :......................................................................: |
corps du triggers, (voir les procédures cataloguées) BEGIN (ATOMIC | NON ATOMIC) déclaration de variables ; déclaration de curseurs ; déclaration de handler (gestion des exceptions) ; instructions SQL (INSERT, UPDATE, DELETE, FETCH) ; instructions de structuration (IF, WHILE, LOOP) ; assignation de variables (SET ou VALUES) ; génération d'une exception (SIGNAL , RESIGNAL) ; END |
Quelques nouveautés en ce qui conserne la gestion des exceptions : rappel, une erreur se prévoit par DECLARE nom_condition CONDITION FOR SQLSTATE 'n°' et/ou --SQLSTATE 'n°'-- ---CONTINUE------- !- nom-condition--! DECLARE !------------EXIT--!-- HANDLER FOR -------!- SQLEXCEPTION---!-> -- UNDO----------- !- SQL WARNING--- ! - NOT FOUND ----- >-- instruction(s) SQL à exécuter en cas d'erreur-- ; avec les triggers : UNDO n'est pas valide, utilisez ATOMIC (trigger complet ou rien) ou bien gérer le COMMIT/ROLLBACK |
GET DIAGNOSTIC variable ROW COUNT : retourne le nombre de lignes affectées " " " RETURN STATUS " " dernier code status pour signaler un code retour, terminer la procédure par RETURN n° RETURN 0 ou RETURN -200 pour signaler une erreur, utilisez SIGNAL (ordre de la V4) SIGNAL SQLSTATE 'n°'---------------------------------------- - !--- SET MESSAGE_TEXT 'votre-texte'--! RESIGNAL doit être placé dans un condition handler (sans paramètres, il génère l'erreur qu'il a lui-même recu.) |
SQLSTATE est ammené à remplacer SQLCOD/SQLCODE (orientation ISO) SQLSTATE est composé de CINQ caractères (chiffres ou lettres) les deux premiers caractères représentent la classe, les codes commencant par 0 à 6 et A à H sont réservés 00 : Success (terminé normalement) 01 : Warning (message d'attention), donne SQLCOD = +438) 02 : No DATA (l'équivalent de SQLCOD = 100) 03 à ZZ : erreur, donne SQLCOD = -438. (38 signale la fin anormale, d'une fonction par exemple) le troisième caractère représente la sous classe. les sous classes de '0' à 'H' sont réservés, I à Z est libre. les codes commencant par 7,8,9 et I à Z sont libres,les sous classes aussi. ce qui fait que vous devez utiliser : - '00000' quand tout va bien. - '01Hxx' pour signaler un WARNING. - '38Ixx' à '38Zxx' pour signaler vos erreurs dans vos fonctions/triggers. - un code disponible dans les autres cas. |
Sinon, voici un rappel succinct des ordres de structuration du SQL PSM : IF test THEN instruction ELSEIF instruction ELSE instruction END IF; CASE WHEN ....WHEN ... ELSE ... END CASE; (comme dans le SELECT) LEAVE label; sortie de la boucle qui s'appelle "label" GOTO label; aller à l'instruction qui s'appelle "label" label: LOOP instructions END LOOP; label: WHILE (condition) instructions END WHILE; label: REPEAT instructions UNTIL (condition) END REPEAT; FOR nom AS C1 CURSOR FOR select .. from .. where .. instructions END FOR; |