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

  1. 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.

  1. 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…

  1. 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;

  1. 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));

  1. 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;



  1. 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 :

  1. Les variables sont des paramètres OUT (pas de const) : prenom, nom, suppression, dateCreation, dateModification

  2. Le SELECT INTO remplit ces variables dans l'ordre correspondant aux colonnes

  3. La fonction retourne un booléen (ind) :

    1. *ON (true) si un enregistrement a été trouvé (sqlcode = 0)

    2. *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;

  1. 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');


It’s a kind of magic ! 

Notre fonction utilitaire Sql_GetStatus() :

  1. Lit automatiquement sqlcode et sqlstate (variables globales SQL)

  2. Analyse le code :

    • 0 = succès

    • 100 = aucune ligne affectée

    • -803 = doublon (violation clé unique)

    • etc.

  3. 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;



Critère

Ancienne méthode

Nouvelle méthode

Lisibilité

if sqlcode = -803... partout

if not status.succes

Messages

Codés en dur 50 fois

Centralisés dans Sql_GetStatus

Maintenance

Modifier 50 endroits

Modifier 1 fonction

Logging

Oublié 3 fois sur 4

Automatique et cohérent

Tests

Difficile à mocker

Structure = facile à tester


Commentaires

Articles les plus consultés