Définition de paramètres en RPG Free Form (et considérations SQL, aussi, un peu...)
Définition de paramètres en RPG Free Form
Des paramètres en Entrée et en Sortie
Voici une fonction qui a :
en entrée : le PRIX
en entrée : la QUANTITE
en sortie : le RESULTAT qui sera égale à PRIX * QUANTITE
Hé ben, ça s’écrit comme ça :
**FREE
dcl-proc CalculerMontant export;
dcl-pi *n packed(15:2);
prix packed(11:2) const;
quantite packed(7:0) const;
end-pi;
dcl-s resultat packed(15:2);
resultat = prix * quantite;
return resultat;
end-proc;
Explications des mots-clés :
const : Le paramètre est passé par valeur en lecture seule (protection + optimisation). Idéal pour les paramètres d'entrée.
value : Passage par valeur (copie). Utilisé quand tu veux modifier localement sans affecter l'original.
options(*nopass) : Paramètre optionnel (doit vérifier avec %parms() dans la procédure).
Pas de mot-clé : Passage par référence (le défaut). Modifications visibles par l'appelant - c'est leparamètre de sortie.
Pleins de paramètres en sortie
Comme le RPG ne peut avoir qu'une seule valeur de retour via return, il faut ruser un peu…
Paramètres par référence (la méthode classique)
dcl-proc CalculerMontantEtTVA export;
dcl-pi *n;
prix packed(11:2) const;
quantite packed(7:0) const;
montantHT packed(15:2); // OUT
montantTTC packed(15:2); // OUT
end-pi;
montantHT = prix * quantite;
montantTTC = montantHT * 1.20;
end-proc;
Structure de retour, avec une DS (un peu plus moderne)
dcl-ds ResultatCalcul qualified template;
montantHT packed(15:2);
montantTTC packed(15:2);
tauxTVA packed(5:2);
end-ds;
dcl-proc CalculerMontantComplet export;
dcl-pi *n likeds(ResultatCalcul);
prix packed(11:2) const;
quantite packed(7:0) const;
end-pi;
dcl-ds resultat likeds(ResultatCalcul);
resultat.montantHT = prix * quantite;
resultat.tauxTVA = 20.00;
resultat.montantTTC = resultat.montantHT * (1 + resultat.tauxTVA/100);
return resultat;
end-proc;
On s’en sert comme ça :
dcl-ds monResultat likeds(ResultatCalcul);
monResultat = CalculerMontantComplet(100.00 : 5);
dsply ('HT: ' + %char(monResultat.montantHT));
Retourner un indicateur + paramètres en sortie
… bien pratique pour gérer des erreurs…
dcl-proc CalculerAvecValidation export;
dcl-pi *n ind; // *ON = succès, *OFF = erreur
prix packed(11:2) const;
quantite packed(7:0) const;
resultat packed(15:2);
msgErreur varchar(100);
end-pi;
if prix <= 0 or quantite <= 0;
msgErreur = 'Prix ou quantité invalide';
return *off;
endif;
resultat = prix * quantite;
return *on;
end-proc;
Avec du SQeuL simple 🏴☠️
dcl-proc Client_Read export;
dcl-pi *n ind;
code int(10) const;
prenom varchar(30);
nom varchar(30);
suppression char(1);
dateCreation date;
dateModification date;
end-pi;
exec sql
SELECT PRENOM, NOM, SUPPRESSION, DATE_CREATION, DATE_MODIFICATION
INTO :prenom, :nom, :suppression, :dateCreation, :dateModification
FROM CLIENT
WHERE CODE = :code;
return (sqlcode = 0);
end-proc;
Décortiquons ce qui se passe :
Les variables sont des paramètres OUT (pas de const) : prenom, nom, suppression, dateCreation, dateModification
Le SELECT INTO remplit ces variables dans l'ordre correspondant aux colonnes
La fonction retourne un booléen (ind) :
*ON (true) si un enregistrement a été trouvé (sqlcode = 0)
*OFF (false) si aucun enregistrement ou erreur SQL
Utilisation du bidule :
dcl-s monPrenom varchar(30);
dcl-s monNom varchar(30);
dcl-s estSupprime char(1);
dcl-s dtCreation date;
dcl-s dtModif date;
if Client_Read(12345 : monPrenom : monNom :
estSupprime : dtCreation : dtModif);
// Client trouvé, les variables sont remplies
dsply ('Client: ' + monPrenom + ' ' + monNom);
else;
// Pas trouvé ou erreur SQL
dsply 'Client introuvable';
endif;
Code retour SQL DANS la structure (pour du code simple)
dcl-ds ClientData qualified template;
trouve ind; // ou "succes"
sqlcode int(10);
prenom varchar(30);
nom varchar(30);
suppression char(1);
dateCreation date;
dateModification date;
end-ds;
dcl-proc Client_Read export;
dcl-pi *n likeds(ClientData);
code int(10) const;
end-pi;
dcl-ds resultat likeds(ClientData);
exec sql
SELECT PRENOM, NOM, SUPPRESSION, DATE_CREATION, DATE_MODIFICATION
INTO :resultat.prenom, :resultat.nom, :resultat.suppression,
:resultat.dateCreation, :resultat.dateModification
FROM CLIENT
WHERE CODE = :code;
resultat.sqlcode = sqlcode;
resultat.trouve = (sqlcode = 0);
return resultat;
end-proc;
Utilisation du machin
dcl-ds client likeds(ClientData);
client = Client_Read(12345);
if client.trouve;
dsply client.prenom;
else;
dsply ('Erreur SQL: ' + %char(client.sqlcode));
endif;
Structure séparée + indicateur de retour (séparation données/statut)
dcl-ds ClientInfo qualified template;
prenom varchar(30);
nom varchar(30);
suppression char(1);
dateCreation date;
dateModification date;
end-ds;
dcl-proc Client_Read export;
dcl-pi *n ind;
code int(10) const;
client likeds(ClientInfo);
erreur int(10); // sqlcode OUT
end-pi;
exec sql
SELECT PRENOM, NOM, SUPPRESSION, DATE_CREATION, DATE_MODIFICATION
INTO :client.prenom, :client.nom, :client.suppression,
:client.dateCreation, :client.dateModification
FROM CLIENT
WHERE CODE = :code;
erreur = sqlcode;
return (sqlcode = 0);
end-proc;
Elégant : Structure avec sous-structure de statut pour API/DAO
dcl-ds StatusInfo qualified template;
succes ind;
sqlcode int(10);
message varchar(100);
end-ds;
dcl-ds ClientData qualified template;
status likeds(StatusInfo);
prenom varchar(30);
nom varchar(30);
suppression char(1);
dateCreation date;
dateModification date;
end-ds;
dcl-proc Client_Read export;
dcl-pi *n likeds(ClientData);
code int(10) const;
end-pi;
dcl-ds resultat likeds(ClientData);
exec sql
SELECT PRENOM, NOM, SUPPRESSION, DATE_CREATION, DATE_MODIFICATION
INTO :resultat.prenom, :resultat.nom, :resultat.suppression,
:resultat.dateCreation, :resultat.dateModification
FROM CLIENT WHERE CODE = :code;
resultat.status.sqlcode = sqlcode;
select;
when sqlcode = 0;
resultat.status.succes = *on;
resultat.status.message = 'Client trouvé';
when sqlcode = 100;
resultat.status.succes = *off;
resultat.status.message = 'Client inexistant';
other;
resultat.status.succes = *off;
resultat.status.message = 'Erreur SQL: ' + %char(sqlcode);
endsl;
return resultat;
end-proc;
L’usage est très simple :
dcl-ds client likeds(ClientData);
client = Client_Read(12345);
if client.status.succes;
dsply (client.prenom + ' ' + client.nom);
else;
dsply client.status.message;
endif;
Optimisation du SQL
Structure de Statut SQL réutilisable
**FREE
// Structure de statut SQL standardisée
dcl-ds SqlStatus qualified template;
succes ind;
sqlcode int(10);
sqlstate char(5);
message varchar(100);
end-ds;
Fonction utilitaire de gestion d’erreur
dcl-proc Sql_GetStatus export;
dcl-pi *n likeds(SqlStatus);
operation varchar(50) const options(*nopass); // Contexte optionnel
end-pi;
dcl-ds status likeds(SqlStatus);
dcl-s contexte varchar(50);
// Récupérer le contexte si fourni
if %parms() >= 1;
contexte = operation;
else;
contexte = '';
endif;
// Capturer les infos SQL
status.sqlcode = sqlcode;
status.sqlstate = sqlstate;
// Analyser le code retour
select;
when sqlcode = 0;
status.succes = *on;
status.message = 'Succès';
when sqlcode = 100;
status.succes = *off;
status.message = 'Aucune donnée trouvée';
when sqlcode > 0;
status.succes = *off;
status.message = 'Warning SQL ' + %char(sqlcode);
when sqlcode = -803;
status.succes = *off;
status.message = 'Doublon détecté (clé en double)';
when sqlcode = -204;
status.succes = *off;
status.message = 'Table/vue introuvable';
when sqlcode = -530;
status.succes = *off;
status.message = 'Violation contrainte d''intégrité';
other;
status.succes = *off;
status.message = 'Erreur SQL ' + %char(sqlcode)
+ ' (State: ' + sqlstate + ')';
endsl;
// Ajouter le contexte si fourni
if contexte <> '';
status.message = contexte + ': ' + status.message;
endif;
return status;
end-proc;
Utilisation dans une fonction DAO (version simple)
dcl-ds ClientData qualified template;
status likeds(SqlStatus);
prenom varchar(30);
nom varchar(30);
suppression char(1);
dateCreation date;
dateModification date;
end-ds;
dcl-proc Client_Read export;
dcl-pi *n likeds(ClientData);
code int(10) const;
end-pi;
dcl-ds resultat likeds(ClientData);
exec sql
SELECT PRENOM, NOM, SUPPRESSION, DATE_CREATION, DATE_MODIFICATION
INTO :resultat.prenom, :resultat.nom, :resultat.suppression,
:resultat.dateCreation, :resultat.dateModification
FROM CLIENT
WHERE CODE = :code;
// Une seule ligne pour gérer l'erreur !
resultat.status = Sql_GetStatus('Lecture client ' + %char(code));
return resultat;
end-proc;
Avec gestion détaillée
dcl-proc Client_Insert export;
dcl-pi *n likeds(SqlStatus);
client likeds(ClientInfo) const;
end-pi;
dcl-ds status likeds(SqlStatus);
exec sql
INSERT INTO CLIENT (PRENOM, NOM, DATE_CREATION)
VALUES (:client.prenom, :client.nom, CURRENT_DATE);
status = Sql_GetStatus('Insertion client');
// Logging centralisé si erreur
if not status.succes;
Logger_Write(status.message);
endif;
return status;
end-proc;
Détaillons z’un peu…
1. Signature de la procédure
dcl-proc Client_Insert export;
dcl-pi *n likeds(SqlStatus);
client likeds(ClientInfo) const;
end-pi;
export : La procédure est exportable (utilisable depuis d'autres modules)
dcl-pi *n likeds(SqlStatus) :
*n = nom de l'interface = nom de la procédure
Retourne une structure SqlStatus (pas les données client, juste le statut)
client likeds(ClientInfo) const :
Paramètre d'entrée structuré (contient prenom, nom, etc.)
const = lecture seule, passage optimisé
Pourquoi retourner SEULEMENT le statut ici ? Parce qu'un INSERT ne retourne pas de données métier, juste "ça a marché" ou "ça a planté". Pas besoin d'une grosse structure avec des données vides !
2. Variable locale
dcl-s status likeds(SqlStatus);
Déclaration d'une variable locale pour stocker le statut SQL
On va la remplir puis la retourner
3. Exécution SQL
exec sql
INSERT INTO CLIENT (PRENOM, NOM, DATE_CREATION)
VALUES (:client.prenom, :client.nom, CURRENT_DATE);
Variables hôtes précédées de : → :client.prenom prend la valeur de la structure
CURRENT_DATE : Fonction SQL pour la date du jour (pas besoin de variable RPG)
Après l'exécution, sqlcode et sqlstate sont automatiquement renseignés par DB2
4. Récupération du statut (LE CŒUR !)
status = Sql_GetStatus('Insertion client');
Notre fonction utilitaire Sql_GetStatus() :
Lit automatiquement sqlcode et sqlstate (variables globales SQL)
Analyse le code :
0 = succès
100 = aucune ligne affectée
-803 = doublon (violation clé unique)
etc.
Remplit la structure SqlStatus avec :
succes = *on ou *off
sqlcode = code brut
sqlstate = état SQL
message = "Insertion client: Succès" ou "Insertion client: Doublon détecté"
Le paramètre 'Insertion client' = contexte métier pour des messages explicites.
5. Logging conditionnel
// Logging centralisé si erreur
if not status.succes;
Logger_Write(status.message);
endif;
Gestion centralisée des erreurs :
Si status.succes = *off → on log l'erreur
Pas besoin de dupliquer ce code partout
Logger_Write() = ta fonction de log (fichier, table DB, journal système...)
Avantage :
Production : tous les INSERT ratés sont tracés automatiquement
Debug : on voit exactement "Insertion client 12345: Doublon détecté"
6. Retour du statut
return status;
On retourne la structure complète au code appelant.
Utilisation complète
// Structure des infos client (à définir ailleurs)
dcl-ds ClientInfo qualified template;
prenom varchar(30);
nom varchar(30);
end-ds;
// Code appelant
dcl-ds nouveauClient likeds(ClientInfo);
dcl-ds resultat likeds(SqlStatus);
nouveauClient.prenom = 'Pierre';
nouveauClient.nom = 'Dupont';
resultat = Client_Insert(nouveauClient);
if resultat.succes;
dsply 'Client inséré avec succès';
else;
// Message déjà formaté et logué !
dsply resultat.message;
// Ex: "Insertion client: Doublon détecté (clé en double)"
// Gestion spécifique selon le code
select;
when resultat.sqlcode = -803;
// Proposer de mettre à jour au lieu d'insérer
Client_Update(nouveauClient);
when resultat.sqlcode = -530;
// Contrainte d'intégrité : peut-être créer l'entité parente d'abord
dsply 'Créez d''abord le compte parent';
other;
// Erreur générique
dsply 'Erreur inattendue';
endsl;
endif;
Avantages 🎯
✅ Code DRY : gestion d'erreur en un seul endroit
✅ Messages cohérents : toutes les erreurs SQL formatées pareil
✅ Maintenabilité : modification des messages en un seul fichier
✅ Traçabilité : facile d'ajouter du logging centralisé
✅ Évolutivité : ajout facile de nouveaux codes erreur
Pro tip : On peut même créer un fichier de config externe (table DB) pour mapper sqlcode → messages personnalisés par application ! 🚀
Comparaison : AVANT et APRES…
❌ Avant (code dupliqué partout)
exec sql INSERT INTO CLIENT...;
if sqlcode <> 0;
if sqlcode = -803;
msg = 'Doublon';
else;
msg = 'Erreur ' + %char(sqlcode);
endif;
// Copier-coller ce bloc dans 50 procédures...
endif;
✅ Après (centralisé et réutilisable)
exec sql INSERT INTO CLIENT...;
status = Sql_GetStatus('Insertion client');
if not status.succes;
Logger_Write(status.message);
endif;
return status;


Commentaires
Enregistrer un commentaire