prism

viernes, 5 de agosto de 2016

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

Como bien lo dice el título de la nota, el objetivo aquí es explicar los pasos necesarios para construir un programa de COBOL y asociarlo a una función de DB2. Esto permite que el programa pueda ser utilizado como cualquier otra función propia del motor de base de datos y por lo tanto se pueda invocar por ejemplo desde un cliente jdbc, una vista, un procedimiento almacenado u otra función.

Hace un tiempo publiqué una entrada muy similar a esta, pero en lugar de utilizar COBOL, se utilizó el lenguaje C. A fin de cuentas el procedimiento es muy similar (casi es una copia), sin embargo hay un par de diferencias muy importantes y poco documentadas que justifican esta nota.

Para no complicar mucho el asunto, el ejemplo va ser el mismo de la nota en lenguaje C, es decir, se va crear una función llamada EsNumerico, que determinará si una hilera de caracteres contiene solamente números. De ser así, retornará 1, de lo contrario 0. Por ejemplo, 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
  LANGUAGE COBOLLE
  EXTERNAL NAME 'SQLUTIL/CBLESNUM01'
  DETERMINISTIC
  NO SQL
  PARAMETER STYLE SQL
  NO EXTERNAL ACTION
La función se puede definir mediante la consola de comandos SQL (STRSQL), o tambien se puede usar la herramienta Open Source SQuirreL o el System i navigator. En la consola TN5250, el separador entre la biblioteca y el nombre de la función será / , mientras que en SQuirreL y en el System i navigator será un punto. La cláusula EXTERNAL NAME tendrá el mismo separador sin importar desde que herramienta sea ejecutado.

Algunos puntos importantes sobre esta declaración:
  • Los parámetros no presentan nombre, solo tipo.
  • LANGUAGE COBOLLE especifica que el lenguaje es COBOL ILE. 
  • La cláusula EXTERNAL NAME indica cual es el programa externo (objeto) que se va a ejecutar. El valor se conforma de la siguiente forma: BIBLIOTECA/PROGRAMA. 
  • La cláusula LANGUAGE especifica en que lenguaje está 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 COBOL 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.
Entendido lo anterior, ejecutamos el SQL para crear la función. En la herramienta STRSQL el resultado se muestra en la siguiente imagen:

Creación de la función mediante el STRSQL

Ahora que ya está creada la función y se conoce por lo tanto la cantidad de parámetros, su tipo de dato y el tipo de dato de retorno, es momento de construir la función en COBOL.

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

Crear miembro de tipo CBLLE mediante STRSEU
Al aceptar el comando, se muestra el editor de código fuente. El código de nuestro programa será el siguiente:


 IDENTIFICATION DIVISION.                           
 PROGRAM-ID. CBLESNUM01.                            
 AUTHOR. OLMAN.                                     
*                                                   
 DATA DIVISION.                                     
  LINKAGE SECTION.                                  
   01 HILERAVALIDAR.                                
     49 TAMANIO              PIC S9(4) USAGE BINARY.
     49 CONTENIDO            PIC X(50).             
   01 RESULTADO              PIC S9(9) COMP-4.      
   01 IN-IND                 PIC S9(4) COMP-4.      
   01 OUT-IND                PIC S9(4) COMP-4.      
   01 SQLSTATE               PIC X(5).              
   01 FNAME                  PIC X(517).            
   01 ESPENAME               PIC X(128).            
   01 MSGTEXT                PIC X(1000).           
*                                                           
 PROCEDURE DIVISION USING HILERAVALIDAR,                    
                          RESULTADO,                        
                          IN-IND,                           
                          OUT-IND,                          
                          SQLSTATE,                         
                          FNAME,                            
                          ESPENAME,                         
                          MSGTEXT.                          
                                                                                             
     IF TAMANIO > 0 AND CONTENIDO(1:TAMANIO) IS NUMERIC THEN
        MOVE 1 TO RESULTADO                                 
     ELSE                                                   
        MOVE 0 TO RESULTADO                                 
     END-IF.                                                



Este código puede tomarse tal cual y pegarse en la consola TN5250 y debería compilar sin errores.
En el ejemplo se observa que el programa recibe la hilera, valida que tenga al menos un carácter y que estos sean numéricos. Esto último lo efectúa mediante la función nativa "IS NUMERIC".

Como se explicó previamente, al utilizar el valor SQL en la cláusula PARAMETER STYLE del CREATE FUNCTION, se debe incluir a el linkage una serie de parámetros adicionales requeridos por el motor de base de datos para poder interactuar con el programa.  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 del linkage section, 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 del programa COBOL (HILERAVALIDAR). Este parámetro requiere especial atención, ya que se conforma de dos partes: TAMANIO y CONTENIDO. ¿Porqué un solo parámetro en la función SQL se convierte en dos parámetros de COBOL? Pues la respuesta se encuentra en el tipo de datos utilizado en el CREATE FUNCTION. Si el tipo de dato es VARCHAR o VARGRAPHICS entonces el parámetro en COBOL deberá definirse con estos dos elementos. El primero (TAMANIO) indica el tamaño real de la hilera de caracteres, esto ya que un parámetro de tipo VARCHAR aunque se define con un tamaño máximo, el tamaño en tiempo de ejecución puede variar desde cero hasta el tamaño máximo definido, por eso la variable TAMANIO tendrá el largo real del parámetro definido. Por ejemplo, si se invoca la función de la siguiente forma: EsNumerico('ABC123'),  entonces el programa COBOL recibirá el valor 6 en la variable TAMANIO mientras que el valor ABC123 será recibido en el parámetro CONTENIDO. Este último como se puede observar en los ejemplos, tiene el mismo tamaño que el parámetro de la función SQL; PIC X(50) / VARCHAR(50). Para más detalle sobre la compatibilidad de tipos de datos entre SQL y COBOL, revisar el siguiente enlace.
  • Seguido a los parámetros de entrada, está el parámetro que contendrá el valor de salida (RESULTADO). Dado que la función SQL se definió como de tipo entera (RETURNS INTEGER), entonces el parámetro en COBOL será de tipo PIC S9(9) COMP-4. Esto según las tabla de compatibilidad de IBM.
  • Después del parámetro de salida, se deben definir N parámetros de tipo PIC S9(9) COMP-4, donde N está representada por la cantidad de parámetros de la función SQL. Estos son indicadores que señalan si en la invocación de la función alguno sus parámetros se envió con valor null. Si el valor de estos indicadores es menor que 0, quiere decir que el parámetro al cual este indicador está asociado viene con valor null. Si el valor del indicador es 0 o mayor, el parámetro tienen un valor distinto de null. Por ejemplo, si la función se invoca de la forma EsNumerico(ValorA) y ValorA es null, entonces el programa COBOL recibirá el parámetro IN-IND con un valor menor a 0. Especial énfasis en la relación entre la cantidad de parámetros y la cantidad de indicadores:  por cada parámetros en el CREATE FUNCTION, se debe incluir en el COBOL un indicador, cuyo tipo debe ser de tipo PIC S9(9) COMP-4.
  • Seguido a estos indicadores de entrada, se deben definir un solo parámetro de tipo PIC S9(9) COMP-4 que corresponde a un indicador de salida. Este indicador sirve para indicarle al DB2 si el resultado de la función es null o no, es decir, este indicador está pensado para ser manipulado en el COBOL; si se le asigna un valor inferior a 0, el DB2 interpretará que la función tuvo como resultado null, si es 0 o mayor, entonces DB2 interpretará que el resultado es distinto de null. Si el indicador no es modificado en el programa (como en el ejemplo que se muestra lineas atrás) entonces su valor siempre será 0 o mayor, por lo tanto el resultado de la función siempre será distinto de null
  • Por último, siempre se deben definir las variables SQLSTATE, FNAME,  ESPENAME y MSGTEXT (Los nombres no importan, lo importante es su tipo de dato). SQLSTATE sirve para retornar códigos de excepción, FNAME contiene el nombre de la función SQL que está haciendo el llamado, ESPENAME contiene un nombre específico y MSGTEXT permite retornar mensajes de excepción. Realmente para casos  tan simples como el de este artículo, son campos poco relevantes, sin embargo siempre deben incluirse en el programa.
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 14, dado que el programa final utilizado por el SQL debe ser de tipo *PGM. Entonces, compilamos el miembro mediante la opción 14, tal y como se muestra en la siguiente imagen:

Creación de un objeto a partir de un miembro en lenguaje cobol ILE

Antes de oprimir "Enter" es necesario establecer una opción de vital importancia para el funcionamiento de la función. Esta opción se denomina Grupo de activación  (ACTGRP) y se edita oprimiendo F4 (siempre con el 14 digitado a la par del miembro). Esto muestra una pantalla adicional, en la cual se debe presionar F10 (mostrar parámetros adicionales) y bajar (Av Pág) hasta encontrar la opción que se muestra en la siguiente imagen:

Editar el grupo de activación antes de compilar
Esta opción por defecto tiene el valor QILE, el cual debe ser cambiado a *CALLER. Este paso es fundamental, ya que si no se efectúa, cualquier invocación a la función, sin importar el cliente que se utiliza, termina con un error tal que cierra la conexión.

Establecida esta opción, presionamos "Enter" para iniciar la compilación. Si todo sale bien, se debe haber creado un objeto de nombre CBLESNUM01 y de tipo *PGM en la biblioteca SQLUTIL, tal y como se muestra en la siguiente imagen:

Objeto creado después de la compilación

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 (en el ejemplo SQLUTIL) se encuentre en nuestra lista de bibliotecas. Si no lo está, agregarla mediante el comando ADDLIBLE NombreBiblioteca

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. Espero se entienda!