prism

jueves, 26 de mayo de 2016

Construir un programa en C y asociarlo a una función SQL en DB2 para sistemas IBM i

Desarrollé este pequeño apunte para recordar como se construye, depura y asocia una función en lenguaje C al motor de base de datos DB2 en sistemas IBM i 6.1. Esto, aunque ligeramente complejo, permite solucionar problemas que con las funciones nativas del motor, quizás no sería posible.

Una vez publicadas, estas funciones pueden utilizarse de forma transparente, sin ninguna consideración adicional desde otros procedimiento almacenado o funciones, ya que para el desarrollador que las utiliza, no presentan diferencia algunas con las demás.

El ejemplo a utilizar corresponde a una función que se denominó EsNumerico, la cual recibe una hilera de caracteres y verifica si todos los caracteres son números. De ser así, retorna 1, de lo contrario 0.

Por lo tanto, al invocar EsNumerico('ABC012') el resultado será 0, mientras que EsNumerico('123456') retornará 1.

Entrando en materia, lo primero será definir nuestra función SQL, buscando determinar los parámetros que va recibir y el tipo de dato de retorno. Hasta que este paso no esté completado, no es buena idea iniciar el programa en C.

El SQL de la función va ser el siguiente:



 CREATE FUNCTION SQLUTIL.EsNumerico(VARCHAR(50)) 
 RETURNS INTEGER
 EXTERNAL NAME 'SQLUTIL/ESNUMERICO(EsNumerico)'
 LANGUAGE C    
 DETERMINISTIC
 NO SQL
 PARAMETER STYLE SQL
 NO EXTERNAL ACTION




La función se puede definir mediante la consola de comandos SQL (STRSQL), o como en mi caso, mediante la herramienta Open Source SQuirreL. En la consola TN5250, el separador entre la biblioteca y el nombre de la función será / , mientras que en SQuirreL será como en la imagen. La cláusula EXTERNAL NAME tendrá el mismo separador sin importar donde se ejecute.

Algunos puntos importantes sobre esta declaración:

  • Los parámetros no presentan nombre, solo tipo.
  • La cláusula EXTERNAL NAME indica cual es el programa externo que se va a ejecutar. Se conforma de lo siguiente: 'ESQUEMA/PROGRAMA(NOMBRE_FUNCION). En el ejemplo en cuestión, la función se denomina EsNumerico, pertenece a un programa desarrollado en lenguaje C (podría ser cualquier otro lenguaje soportado por la plataforma) y compilado bajo el nombre ESNUMERICO en la librería SQLUTIL
  • La cláusula LANGUAGE especifica en que lenguaje fue desarrollado en programa externo. Algunos posibles valores son: C, C++, CL, COBOL, RPG, JAVA, entre otros. 
  • DETERMINISTIC le indica al motor de base de datos, que esta función siempre retornará el mismo resultado para los mismos parámetros de entrada. Es decir, si se invoca EsNumerico('123') 1000 veces, el resultado siempre va ser 1. Esta información la usa db2 para optimización.
  • PARAMETER STYLE corresponde a la cantidad y tipo de los parámetros que el motor de base de datos transferirá al programa externo. Para programas desarrollado en C se puede utilizar el valor SQL (como este caso) o GENERAL, sin embargo, este último no lo he probado. Al utilizar el valor SQL, se debe preparar el programa externo para que reciba una serie de parámetros adicionales a los definidos en la declaración de la función. Esto se explicará más adelante.

Ahora que ya están definidos los parámetros y el tipo de dato de retorno, es momento de construir la función en C.

Para definir una función en este lenguaje en un sistema IBM i, se crea un miembro de tipo C mediante la herramienta PDM (WRKMBRPDM) y la opción F6 (Crear). Esto muestra la siguiente pantalla:

Crear mienbro de tipo C mediante el PDM


Esto iniciará el editor incorporado. El código C escogido para implementar la función es el siguiente:

#include                
#include                  
                                   
//---------------------------------
                                   
void EsNumerico(char  *entrada,     
                int   *salida,      
                short *input_ind,  
                short *output_ind, 
                char  sqlstate[6], 
                char  fname[140],  
                char  finst[129],  
                char  msgtext[71]) 
{                                  
    while(*entrada)                
    {                              
         if (!isdigit(*entrada))
         {                      
              *salida=0;        
              return;           
         }                      
         //--                   
         entrada++;             
    }                           
    *salida=1;                  
}


Este puede tomarse tal cual y pegarse en la consola TN5250 y debería compilar sin errores.

En el ejemplo se observa que el programa tiene un único método denominado EsNumerico, el cual recorre la hilera recibida en el parámetro entrada y va comparando cada elemento mediante la función isdigit.

Como se explicó previamente, al utilizar el valor SQL en la cláusula PARAMETER STYLE del CREATE FUNCTION, se debe incluir a la función en C una serie de parámetros adicionales requeridos por el motor de base de datos.  Estos contienen información de control y de invocación. A continuación se detalla la lógica que rige la definición de estos parámetros:


  • Los primeros N parámetros de la función C, corresponden a los N parámetros de la función SQL. En este caso, como la función definida en el CREATE FUNCTION solo tiene un parámetro de tipo VARCHAR, entonces este será recibido en el primer parámetro de la función C que será de tipo *char. 
  • Seguido a los parámetros de entrada, está el parámetro que contendrá el valor de salida (int *salida). Dado que la función SQL se definió como de tipo entera (RETURNS INTEGER), entonces el parámetro en C será de tipo int 
  • Después del parámetro de salida, se deben definir N parámetros de tipo short, donde N está representada por la cantidad de parámetros de la función SQL. Estos parámetros contienen indicadores de entrada.
  • Seguido a estos indicadores de entrada, se deben definir un solo parámetro de tipo short que corresponde a un indicador de salida.
  • Por último, se deben definir las variables sqlstate[6], fname[140],  finst[129] y msgtext[71]. sqlstate sirve para retornar códigos de excepción, fname contiene el nombre de la función SQL que está haciendo el llamado, finst contiene un nombre específico y msgtext permite retornar mensajes de excepción.
Teniendo esto claro, podemos continuar con la compilación de la función.

En los sistemas IBM i, la compilación se puede efectuar con opción 14 para crear un objeto de tipo *PGM u opción 15 para crear uno de tipo *MODULE. La opción a seleccionar va ser la 15, dado que el programa final utilizado por el SQL debe ser de tipo *SRVPGM y estos solo pueden crearse a partir de objetos de tipo *MODULE. Entonces, compilamos el miembro mediante la opción 15, tal y como se muestra en la siguiente imagen:

Creación de un módulo a partir de un miembro en lenguaje C

Si el proceso de compilación resulta exitoso, tendremos en nuestra biblioteca un objeto denominado ESNUMERICO de tipo *MODULE. Como se muestra a continuación:

Objeto de tipo *MODULE resultante de la compilación

Una vez ha sido creado el objeto de tipo *MODULE en la biblioteca, se debe generar a partir de esto un objeto de tipo *SRVPGM. Para esto utilizaremos el comando CRTSRVPGM, tal y como se muestra en la siguiente imagen:

Opciones del comando CRTSRVPGM

Analicemos un poco los parámetros que espera este comando:


  • Programa de servicio: Corresponde al nombre del objeto de salida. Este sera el objeto de tipo *SRVPGM utilizado por el SQL.
    • Biblioteca: Biblioteca donde será creado el objeto.
  • Modulo: Nombre del objeto de tipo *MODULE creado en el paso anterior.
    • Biblioteca: Biblioteca donde se encuentra el objeto de tipo *MODULE
  • Exportar: Especifica que datos y programas serán exportados al objeto *SRVPGM. Colocar la opción *ALL
Los demás parámetros quedan con su valor por defecto.

Al ejecutar el comando se debe generar el siguiente mensaje de salida:

Programa de servicio ESNUMERICO creado en biblioteca SQLUTIL.

Si todos los pasos se han seguido, en este punto la función SQL ya debería funcionar. Para verificarlo, se puede iniciar la consola de comandos SQL (STRSQL) y ejecutar la siguiente sentencia:

SELECT ESNUMERICO('123') FROM SYSIBM/SYSDUMMY1

Antes de esto, hay que cerciorarse que la biblioteca donde se haya definido la función SQL se encuentre en nuestra lista de bibliotecas. Si no lo está, agregarla mediante el comando ADDLIBLE

El resultado debería ser igual al mostrado en la siguiente imagen:

Resultado de la función en la consola SQL 

Hasta aquí esta nota. Pronto un nuevo artículo sobre como depurar el programa C construido.