Introducción
El IBM i es el mejor sistema de información, y esto ya no se discute. Pero el deseo de seguir soportando todas las técnicas de las máquinas que lo precedieron hace que tenga que soportar recursos que, aunque en su momento fueron muy útiles, ahora se han convertido en rarezas.
La biblioteca QTEMP, heredada del AS400 y del S/38, es un ejemplo de ello. No entraré en detalles sobre su funcionamiento; basta con saber que se crea sistemáticamente al inicio de cualquier trabajo y se borra cuando éste se detiene. QTEMP pertenece exclusivamente al trabajo que lo creó** y sólo a él, y sólo puede acceder a él lo que se está ejecutando en su interior.
Si las cosas están claras en un AS400 que utiliza el IBM i a través de los subsistemas QINTER y QBATCH, las cosas se complican cuando los recursos del IBM i son utilizados a través del subsistema QUSRWRK por trabajos de tipo QZDASOINIT y QZRCSRVS. Explicaré esto usando un ejemplo en C#.
Ejemplo de código
La secuencia de código C# que sigue proporciona exactamente el mismo resultado: la duplicación de una tabla en QTEMP, con la creación de TARTICL1 mediante un comando CPYF y luego TARTICL2 mediante un comando SQL CREATE TABLE (este código utiliza 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());
}
💡Se ha establecido un punto de interrupción en el último corchete.
En tiempo de ejecución, cuando el proceso alcanza el punto de ruptura, notamos en el IBM i la presencia de dos trabajos en QUSRWRK gracias al comando WRKACTJOB SBS(QUSRWRK): QZDASOINIT y QZRCSRVS.
QZDASOINIT se dedica a las operaciones de base de datos vía SQL.
QZRCSRVS maneja todas las operaciones típicas de AS400, como comandos CL, programas, etc.
Examinando la estructura de estos dos trabajos
A continuación se muestra la lista de librerías QZDASOINIT:
Y la de QZRCSRVS:
En ambos casos, vemos la presencia de una librería QTEMP.
Examinemos el contenido de estas librerías utilizando la opción 5 de WRKACTJOB.
La librería QTEMP de QZDASOINIT contiene el fichero TARTICL2:
El QTEMP de QZRCSRVS contiene el fichero TARTICL1 :
Consecuencias
Aquí es donde las cosas se complican.
- Si ejecuto la consulta SQL
select * from qtemp.tarticl1
⚠️ Me aparece el mensaje « Archivo TARTICL1 no encontrado en la biblioteca QTEMP ».
- Si ejecuto el comando CL
CPYF FROMFILE(QTEMP/TARTICL2) TOFILE(QGPL/TARTICL2) MBROPT(*REPLACE) CRTFILE(*YES)**
⚠️ Me aparece el mensaje « Archivo TARTICL2 no encontrado en la biblioteca QTEMP ».
- Si ejecuto la consulta SQL :
select * from qtemp.tarticl2
✅ Todo termina normalmente.
- Si ejecuto el comando CL :
CPYF FROMFILE(QTEMP/TARTICL1) TOFILE(QGPL/TARTICL2) MBROPT(*REPLACE) CRTFILE(*YES)
✅ No hay ningún error.
El problema proviene de que una consulta SQL se dirige a la librería QTEMP asociada a QZDASOINIT, mientras que los programas, comandos y otros recursos puramente AS400 utilizan la QTEMP del trabajo QZRCSRVS.
Como el uso de la librería QTEMP ha pasado de moda, muy a menudo son aplicaciones antiguas las que la utilizan (antes de que SQL se generalizara). Los datos útiles se encuentran casi siempre en el QTEMP de QZRCSRVS, y por tanto son inaccesibles mediante una consulta SQL.
Como puedes ver, si quieres modernizar reutilizando programas existentes RPG, COBOL, CLP en .NET, te encontrarás con casos en los que esto no funcionará. Puedes salirte con la tuya usando un programa CLP que leerá el archivo en el QTEMP correcto y te dará los datos en un programa C#. Pero, sólo para este problema, tendrás que enseñar a tu desarrollador .NET a escribir código CL.
Una solución
La siguiente función C# recibe como parámetros la definición de la conexión al IBM i previamente establecida (cx), el fichero QTEMP a utilizar (QtempFile), y devuelve los datos en una TablaDatos. Se pueden utilizar otros medios para recibir los datos.
Para simplificar los comentarios, utilizaré las siguientes notaciones para expresar a qué biblioteca QTEMP se dirige la ejecución de una instrucción:
- QZDASOINIT/QTEMP
- QZRCSRVS/QTEMP
Esta función mueve el archivo deseado de QZRCSRVS/QTEMP a QZDASOINIT/QTEMP para que se pueda acceder a él mediante una consulta SQL.
public static DataTable QtempDt (NTiConnection cx, string QtempFile)
{
var adapter = new NTiDataAdapter(); // Definición de un DataAdapter
DataTable dt = new DataTable(); // Definición del resultado DataTable
var cmd = cx.CreateCommand(); // Definición de comandos en la conexión NTi
cmd.CommandText = "SELECT * FROM QTEMP." + QtempFile; // Composición de la consulta SQL con el nombre del archivo
adapter.SelectCommand = cmd; // Asociar el comando con el DataAdapter
// Los datos se recuperan mediante una consulta SQL, por lo que el QtempFile se buscará en la biblioteca QTEMP asociada al trabajo QZDASOINIT.
try
{
// Si el archivo que busca se creó en el IBM i mediante una consulta SQL, la instrucción que se indica a continuación se ejecuta correctamente (el archivo QtempFile está presente en QZDASOINIT/QTEMP).
// Los datos se integran en la DataTable *
adapter.Fill(dt);
}
catch
{
// Si no es así, copie el archivo en QZRCSRVS/QTEMP.
// Para ello, necesitamos una biblioteca intermedia.
// Aquí, para evitar la duplicación, estoy creando una biblioteca cuyo nombre está formado por la concatenación de los caracteres «LB» y el horario actual (HHMMSS).
// Podemos hacer las cosas de otra manera con funciones aleatorias.
string BibTemporaire = "LB" +
DateTime.Now.ToString().Substring(11).Replace(":", "");
cx.ExecuteClCommand("CRTLIB LIB(" + BibTemporaire + ")");
// Utiliza QZRCSRVS/QTEMP: el archivo se copia en la biblioteca temporal
cx.ExecuteClCommand("CPYF FROMFILE(QTEMP/" + QtempFile + ") TOFILE(" +
BibTemporaire + "/" + QtempFile + ") MBROPT(*REPLACE) CRTFILE(*YES)");
// El archivo se crea con una copia de los datos en QZDASOINIT/QTEMP. Se puede acceder a este QTEMP mediante una consulta SQL
cx.Execute("CREATE TABLE QTEMP." + QtempFile + " AS (SELECT * FROM " +
BibTemporaire + "." + QtempFile + ") WITH DATA");
// Se elimina la biblioteca temporal
cx.ExecuteClCommand("DLTLIB LIB(" + BibTemporaire + ")");
// Los datos se integran en la DataTable
adapter.Fill(dt);
// el QtempFile se elimina en :
cx.Execute("Drop table qtemp." + QtempFile);
}
// Se devuelve la DataTable
return dt;
}
Este programa utiliza, por supuesto, el conector NTi Data Provider.
Su velocidad de ejecución permite insertar esta sucesión de instrucciones sin penalizar los tiempos de respuesta.
Bonificación
A lo largo de este artículo hemos hablado de las tareas QZDASOINIT y QZRCSRVS. Estos trabajos son normalmente lo que se conoce como trabajos de inicio temprano. En otras palabras, una vez iniciados, permanecen en estado «WAIT», disponibles para recibir una nueva petición en el IBM i. Estos trabajos son solicitados por ODBC, OleDb, ACS, y muchos otros, como un cierto « NTi Data Provider ». Este estado « WAIT » es muy útil para compensar la lentitud de herramientas externas al IBM i: el trabajo está listo en el IBM i, todo lo que hay que hacer es despertarlo con una petición de un producto de terceros.
La captura de pantalla siguiente muestra los dos trabajos utilizados para una solicitud de un programa C# que utiliza OleDb :
Ambos trabajos están en estado « TIMW », listos para recibir una nueva petición. El problema es que la librería QTEMP de la petición anterior sigue presente... Dejaré que vosotros mismos resolváis las inconsistencias que probablemente se produzcan.
Normalmente, después de un cierto tiempo sin ser utilizados, estos trabajos desaparecen. Pero a veces, estos trabajos permanecen indefinidamente en el estado « TIMW » sin ser reutilizados; hay entonces un montón de líneas en QUSRWRK con las que no sabes qué hacer: detenerlas, mantenerlas... Consultar sitios en internet no ayuda.
Este riesgo no existe con NTi Data Provider, porque los trabajos QZDASOINIT y QZRCSRVS iniciados por NTi nunca pasan al estado « TIMW » o « PSRW » cuando se termina la sesión C#. Se destruyen sistemáticamente, incluso cuando se detiene bruscamente el programa en curso (y, por tanto, se reinician y reinicializan cada vez). No hay riesgo de reutilizarlos posteriormente.
Y este reinicio no repercute en el rendimiento de .NET con NTi.
Laurent Rouillot