Introduction
L’IBM i est le meilleur système d’information, et cela n’est plus à démontrer. Mais la volonté de continuer à prendre en charge toutes les techniques des machines qui l’ont précédé l’oblige à supporter des ressources qui, si à une certaine époque étaient très utiles, sont aujourd’hui devenues des bizarreries.
La bibliothèque QTEMP, héritée de l’AS400 et du S/38, en est un exemple. Je ne vais pas en détailler son fonctionnement ; sachez seulement qu’elle est créée systématiquement au démarrage de tout job et supprimée lorsque celui-ci s’arrête. QTEMP appartient en exclusivité au job qui l’a créée** et à lui seul, et elle ne peut être accessible que par ce qui s’exécute à l’intérieur de celui-ci.
Si les choses sont claires dans une utilisation AS400 de l’IBM i par les sous-systèmes QINTER et QBATCH, cela se complique quand l’utilisation des ressources de l’IBM i se fait via le sous-système QUSRWRK par les jobs de type QZDASOINIT et QZRCSRVS. Je vais expliquer cela au moyen d’un exemple en C#.
Exemple de Code
La séquence de code C# ci-dessous fournit exactement le même résultat : la duplication d’une table dans QTEMP, avec la création de TARTICL1 par une commande CPYF puis TARTICL2 par un ordre SQL CREATE TABLE (ce code utilise bien sûr NTi Data Provider).
Try
{
cx.ExecuteClCommand("CPYF FROMFILE(AIRELLES/TARTICL) TOFILE(QTEMP/TARTICL1) MBROPT(*REPLACE) CRTFILE(*YES)");
cx.Execute("CREATE TABLE QTEMP.TARTICL2 AS (SELECT * FROM AIRELLES.TARTICL) WITH DATA");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
💡 Un point d’arrêt a été placé sur le dernier bracket.
À l’exécution, lorsque le processus atteint le point d’arrêt, nous constatons sur l’IBM i la présence de deux jobs dans QUSRWRK grâce à la commande WRKACTJOB SBS(QUSRWRK) : QZDASOINIT et QZRCSRVS.
QZDASOINIT est dédié aux opérations sur la base de données via SQL.
QZRCSRVS prend en charge toutes les opérations typiquement AS400, telles que les commandes CL, les programmes, etc.
Examen de la structure de ces deux jobs
Ci-dessous la liste de bibliothèques de QZDASOINIT:
Et celle de QZRCSRVS:
Dans les deux cas, nous constatons la présence d’une bibliothèque QTEMP.
Examinons le contenu de ces bibliothèques par l’option 5 de WRKACTJOB
La bibliothèque QTEMP de QZDASOINIT contient le fichier TARTICL2:
QTEMP de QZRCSRVS contient le fichier TARTICL1 :
Conséquences
C’est là que tous se complique.
- Si j’exécute la requête SQL:
select * from qtemp.tarticl1
⚠️ J’obtiens le message « fichier TARTICL1 non trouvé dans la bibliothèque QTEMP ».
- Si j’exécute la commande CL
CPYF FROMFILE(QTEMP/TARTICL2) TOFILE(QGPL/TARTICL2) MBROPT(*REPLACE) CRTFILE(*YES)**
⚠️ J’obtiens le message « Fichier TARTICL2 non trouvé dans la bibliothèque QTEMP ».
- Si j’exécute la requête SQL :
select * from qtemp.tarticl2
✅ Tout se termine normalement.
- Si j’exécute la commande CL :
CPYF FROMFILE(QTEMP/TARTICL1) TOFILE(QGPL/TARTICL2) MBROPT(*REPLACE) CRTFILE(*YES)
✅ Il n’y a pas d’erreur.
Le problème provient du fait qu’une requête SQL s'adresse à la bibliothèque QTEMP associée à QZDASOINIT, tandis que les programmes, commandes et autres ressources purement AS400 utilisent la QTEMP du job QZRCSRVS.
Comme l’utilisation de la bibliothèque QTEMP est un peu passée de mode, ce sont bien souvent des applications anciennes qui y ont recours (avant la généralisation de SQL). Les données utiles se retrouvent presque toujours dans la QTEMP de QZRCSRVS, donc inaccessibles par une requête SQL.
Vous l’avez compris, si vous voulez moderniser en réutilisant des programmes RPG, COBOL, CLP existants dans .NET, vous rencontrerez des cas où cela ne fonctionnera pas. Vous pouvez vous en sortir en utilisant un programme CLP qui ira lire le fichier dans la bonne QTEMP et vous remontera les données dans un programme C#. Mais, juste pour ce problème, vous devrez apprendre à votre développeur .NET comment écrire du code CL.
Une solution
La fonction C# suivante reçoit en paramètres la définition de la connexion à l’IBM i préalablement établie (cx), le fichier de QTEMP à exploiter (QtempFile), et retourne les données dans une DataTable. On peut utiliser d'autres moyens pour réceptionner les données.
Pour simplifier la rédaction des commentaires, je vais utiliser les notations suivantes pour exprimer à quelle bibliothèque QTEMP s’adresse l’exécution d’une instruction :
- QZDASOINIT/QTEMP
- QZRCSRVS/QTEMP
Cette fonction va déplacer le fichier souhaité de QZRCSRVS/QTEMP vers QZDASOINIT/QTEMP afin qu’il devienne accessible via une requête SQL.
public static DataTable QtempDt (NTiConnection cx, string QtempFile)
{
var adapter = new NTiDataAdapter(); // Définition d’un DataAdapter
DataTable dt = new DataTable(); // Définition de la DataTable résultat
var cmd = cx.CreateCommand(); // Définition de commande dans la connexion NTi
cmd.CommandText = "SELECT * FROM QTEMP." + QtempFile; // Composition de la requête SQL avec le nom du fichier
adapter.SelectCommand = cmd; // Association de la commande au DataAdapter
// ** La récupération des données se fait au moyen d’une requête SQL donc le fichier QtempFile sera recherché dans la bibliothèque QTEMP associée au job QZDASOINIT.
try
{
// Si le fichier recherché a été créé sur l’IBM i par une requête SQL,l’instruction ci-dessous s’exécute avec succès (le fichier QtempFile est présent dans QZDASOINIT/QTEMP).
// Les données sont intégrées à la DataTable
adapter.Fill(dt);
}
catch
{
// Sinon il faut copier le fichier dans QZRCSRVS/QTEMP.
// Pour cela, on a besoin d’une bibliothèque intermédiaire.
// Ici, pour éviter les doublons, je crée une bibliothèque dont le nom est composé par la concaténation des caractères « LB » et de l’horaire courant (HHMMSS).
// On peut faire autrement avec des fonctions random
string BibTemporaire = "LB" +
DateTime.Now.ToString().Substring(11).Replace(":", "");
cx.ExecuteClCommand("CRTLIB LIB(" + BibTemporaire + ")");
// Utilise QZRCSRVS/QTEMP : le fichier est copié dans la bib temporaire
cx.ExecuteClCommand("CPYF FROMFILE(QTEMP/" + QtempFile + ") TOFILE(" +
BibTemporaire + "/" + QtempFile + ") MBROPT(*REPLACE) CRTFILE(*YES)");
// Le fichier est créé avec copie des données dans QZDASOINIT/QTEMP. Cette QTEMP est accessible par une requête SQL
cx.Execute("CREATE TABLE QTEMP." + QtempFile + " AS (SELECT * FROM " +
BibTemporaire + "." + QtempFile + ") WITH DATA");
// La bibliothèque temporaire est supprimée
cx.ExecuteClCommand("DLTLIB LIB(" + BibTemporaire + ")");
// Les données sont intégrées à la DataTable
adapter.Fill(dt);
// Le fichier QtempFile est supprimé dans
cx.Execute("Drop table qtemp." + QtempFile);
}
// La DataTable est retournée
return dt;
}
Ce programme utilise bien sûr le connecteur NTi Data Provider.
Sa rapidité d’exécution permet d’insérer cette succession d’instructions sans pénaliser les temps de réponse.
Bonus
Tout au long de cet article, nous avons évoqué les jobs QZDASOINIT et QZRCSRVS. Ces jobs sont normalement ce que l’on appelle des travaux à démarrage anticipé. C’est-à-dire qu’une fois démarrés, ils restent à l’état « WAIT », disponibles pour accueillir une nouvelle requête sur l’IBM i. Ces travaux sont sollicités par ODBC, OleDb, ACS, et beaucoup d’autres, comme un certain « NTi Data Provider ». Cet état « WAIT » est très utile pour compenser la lenteur des outils externes à l’IBM i : le job est prêt dans l’IBM i, il suffit de le réveiller par une demande à partir d’un produit tiers.
La capture d’écran ci-dessous montre les deux jobs ayant servi à une requête à partir d’un programme en C# qui utilise OleDb :
Les deux jobs sont à l’état « TIMW », prêts à accueillir une nouvelle demande. Le problème est que la bibliothèque QTEMP de la demande précédente est toujours présente… Je vous laisse déduire par vous-mêmes les incohérences qui risquent d’en découler.
Normalement, au bout d’un certain temps sans être utilisés, ces jobs disparaissent. Mais parfois, ces travaux restent indéfiniment à l’état « TIMW » sans être réutilisés ; il y a alors un empilement de lignes dans QUSRWRK dont on ne sait quoi faire : les arrêter, les conserver… La consultation de sites sur internet n’éclaircit rien.
Ce risque n’existe pas avec NTi Data Provider, car les travaux QZDASOINIT et QZRCSRVS démarrés par NTi ne passent jamais à l’état « TIMW » ou « PSRW » lorsque la session C# est terminée. Ils sont systématiquement détruits, même lors d’un arrêt brutal du programme en cours (et donc redémarrés et réinitialisés à chaque fois). Il n’y a pas de risque de réutilisation ultérieure.
Et ce redémarrage n’a aucun impact sur la performance de .NET avec NTi.
Laurent Rouillot