La p√°gina de DriverOp

Archivos en RM/COBOL.


En este art√≠culo tratar√© de ense√Īarc√≥mo se trabaja con archivos de datos COBOL usando el compilador RM/COBOL85.

Nota: los archivos con extensión .rm corresponden a la aplicación RealOne/Real Player y no tienen nada que ver con COBOL.

Consideraciones preliminares.

Hace tiempo que vengo rumeando la idea de poner por escrito lo que he aprendidoacerca de archivos de datos en RM/COBOL 85 en mi larga relaci√≥n con estecompilador el cual me ha reportado grandes satisfacciones. Es verdad que el compilador que he estado usando todos estos a√Īos est√° definitivamenteobsoleto a la fecha de escribir esto, sin embargo gracias a este mi sitio weby a las listas de correo en las que participo, me llega cada tanto alg√ļnmensaje de alg√ļn/a desesperado/a programador novel y no tan novel pregunt√°ndomesobre este tema en particular. Debido a esto he llegado a la conclusi√≥nde que apesar de los a√Īos de antig√ľedad de esta herramienta de programaci√≥n, a√ļn hay inter√©s en √©l. Aunque m√°s no sea por razoneshist√≥ricas, pues aqu√≠ vamos.

Debo dejar bien claro que este art√≠culo se refiere √ļnicamentepara el compilador RM/COBOL 85 versi√≥n 5.36, que es la √ļltimaversi√≥n para MS-DOS de la casa Liant (antes conocida como Ryan McFarland)ya que es el compilador que mejor conozco (con todo y sus bugs). No es mi intenci√≥ndescribir ni entrar en los detalles sobre las diferencias con otros compiladoresdel mismo lenguaje y plataforma. Advierto al lector que si est√° usandouna versi√≥n diferente o un compilador completamente diferente al queme refiero aqu√≠ tendr√° que buscar por s√≠ mismo c√≥mosubsanar las diferencias que pudiera haber con lo que aqu√≠ escribo.

Asumo que el lector (o lectora) interesando en este artículo cuentacon conocimientos básicos de COBOL. Esto no es un tutorial de programaciónen COBOL y no explicaré las cosas más básicas de este lenguaje, cosa que ya he hecho, en parte, en otro lugarde mi sitio.

Exportar datos a otros lenguajes.

La mitad de las consultas que recibo a mi correo personal vienen de programadoresde otros lenguajes que se ven en la necesidad de portar una aplicaci√≥n COBOL a otro lenguaje usando las bases de datos de ese otro lenguaje DBM (sea√©ste DBASE, Clipper o Clarion) o a otro DBE (Data Base Engine, tal como SQL Server, BDE, Interbase/FireBird o MySQL) y se encuentran con la falta deherramientas para hacer esa exportaci√≥n de datos. Considero que los programadoresm√°s j√≥venes est√°n acostumbrados a los formatos de datos"abiertos" que esos lenguajes y DBMs tienen mientras que √©ste compilador (como as√≠ otros compiladores de COBOL de la misma √©poca)se basan en un formato de tipo "cerrado" donde es el propio programadorquien establece el formato interno de los archivos que va a usar. Lejos de entraren la disputa sobre qu√© m√©todo es mejor, mi respuesta a todosellos es esta: si no cuentan con la definici√≥n de registros (FD), eltipo de encabezado del archivo (SELECT) y la versi√≥n con que fue creado el programa COBOL que genera dichos archivos me temo que tendr√°s que "hackear" de alguna forma la aplicaci√≥n COBOL que ya est√°andando para poder extraer al menos una parte de los campos de los mismos. Claro que existe la posibilidad de comprar a Liant una herramienta de volcado de datosaunque este ser√≠a el √ļltimo recurso.

A pesar de lo dicho anteriormente sí existen algunas herramientas que sin ser 100% efectiva posibilitan hacer un volcado de los datos a un formato abierto.

Espec√≠ficamente, si hablamos de una aplicaci√≥n RM/COBOL existen tres formatos b√°sicos sobre los que un programador puede armar su base de datos, numeradas de la 1 a la 3. La versi√≥n 1 la usan las versionesde la 1 a la 4 del RM/COBOL 85. La versi√≥n 2 la usan las versiones dela 4 a la 5.36. Mientras que la √ļltima es usada por los compiladores orientados a objetos y visuales de COBOL hechos por la casa Liant. Hasta dondes√©, todas las versiones del RM/COBOL mantienen compatibilidad hacia atr√°spero no hacia adelante. Esto quiere decir que si contamos con un compilador que usa la versi√≥n 2 de los archivos de datos COBOL podemosleerlos de la versi√≥n 1, pero el caso inverso no se da.

Cuando he dicho "hackear" la aplicaci√≥n RM/COBOL 85 quiero decir hacer cosas como ejecutar una salida por impresora, si es que la aplicaci√≥n cuenta con ella, y redireccionar la salida de datos hacia un archivo de texto, cosa facil de hacer en MS-DOS y m√°s facil a√ļn ejecutando la aplicaci√≥n sobre Windows 95/98, aunque luego requiera reformatear el archivo resultante, eso es mejor que nada (esta aplicaci√≥n podr√≠a resultar √ļtil en casos como el mencionado).

Tipos de organizaciones.

En COBOL existen tres tipos de organizaciones b√°sicas para sus archivos de datos. Estos son:

  • Secuencial.
  • Relativa.
  • Secuencial indexada.

El orden mencionado es también el orden de complejidad de los archivos donde "complejo" en este caso quiere decir complejidad estructural del archivo y no en sus métodos de acceso, como veremos más adelante.

Secuencial.

Es el tipo m√°s sencillo y es en la pr√°ctica, equivalente a un archivo de texto donde cada l√≠nea ser√≠a un registro. La forma de acceso (lectura, escritura), como su nombre indica es secuencial. La diferenciacon un archivo de texto normal est√° dada en el tama√Īo del registro. Mientras que en un archivo de texto cada l√≠nea est√° separada por un retorno de carro/avance de l√≠nea (dependiendo del S.O.)y √©stos pueden caer en cualquier parte del archivo haciendo que el tama√Īode las l√≠neas sean variables, en COBOL el tama√Īo de las l√≠neases fijo y √©sta est√° dada por la suma de los tama√Īos delos campos que componen dicho registro. Esto permite que se puedanleerarchivos secuenciales hacia atr√°s o insertar registros enmedio de otros dos yaexistentes. La desventaja es que no contamos con √≠ndices para reordenarlas vistas de los datos. En cada lectura del archivo los datos aparecen en elorden en que se encuentran ordenados f√≠sicamente en el archivo.

Otra forma de referirse a este tipo de organización es archivos de acceso directo en contraposición a archivos de acceso aleatorio, ya que, parallegar a un registro en particular es necesario pasar por todos los anteriorestanto hacia adelante como hacia atrás.

Relativa.

Esto es un avance respecto de la organización anterior ya que contamos con un índice para efectuar las lecturas o escrituras del archivo aunque este índice está dado exclusivamente por la posición relativadel registro. Cada registro cuenta con un índice implícito dado, como mencioné, por su posición en relación al inicio del archivo. Este tipo de organización es familiar para los programadores en Pascal y otros lenguajes de propósito general.

Aqu√≠ ya no es necesario pasar por todos los registros anteriores alque nos interesaleero re escribir, basta con indicar su posici√≥n relativa. Por ejemplo, si quisi√©ramos acceder al d√©cimo registro del archivo el puntero avanzar√≠a nueve posiciones y leer√≠a la d√©cima.De igual manera, si qusi√©ramosleerel quinto solamente hace falta restar cuatro posiciones. Cu√°nto espacio hay que sumar y restar para accedera un registro en particular est√° dado por el tama√Īo declarado del registro que no es m√°s que la suma de los tama√Īos de los campos que lo componen.

Secuencial indexada.

√Čste tipo de organizaci√≥n es lo que el com√ļn de los programadores entienden como un verdadero archivo de bases de datos. Aqu√≠ ya contamos con uno o varios √≠ndices que mantienen un orden l√≥gico de los datos contenidos independientemente de su posici√≥n relativa dentro del archivo. Lo que sorprender√° a algunos programadores aqu√≠ es que RM/COBOL 85 mantiene esos √≠ndices dentro del propio archivo de datos y no como uno o varios archivos externos. Sobre c√≥mo consigue esto el compilador y su motor de bases de datos internos escapa a los prop√≥sitos de este art√≠culo, solo baste decir que para lograrlo usa una t√©cnicasimilar a una estructura de √°rbol din√°mica.

La ventaja de tener los √≠ndices f√≠sicamente en el mismo archivo que los datos recide en que no se corre el riesgo de perder esa indexaci√≥n, de esta forma no se necesita hacer una reindexaci√≥n previa al uso del archivo (programadores de Clipper ¬Ņles suena esto de alguna parte?).

El o los índices de un archivo con organización secuencial indexada puede ser cualquiera de los campos declarados en el registro aunque con la salvedad de que una vez establecido uno o más de esos campos estos permanecen fijos, es decir que posteriormente no se pueden usar otros campos además de los declarados. Esta declaración se hace en tiempo de edición haciendo uso de las cláusulas que COBOL proporciona para este propósito, como veremos más adelante.

Esto √ļltimo representa una desventaja respecto a otros mecanismos deindexaci√≥n. Es obvio que acceder a un conjunto de registros ordenados por un campo √≠ndice en particular (lo que en la jerga de base de datos relacionales se llama "vista") es mucho m√°s r√°pido que hacerlo secuencialmente o relativamente, pero como esos campos √≠ndices se establecen al momento de dise√Īar la base de datos y una vez creadoel o los archivos f√≠sicos ya no se pueden agregar m√°s o modificarlos ya existentes un programador COBOL debe tener muy en claro qu√© tipo de vistas necesita extraer de esa base de datos de acuerdo a la aplicaci√≥nque est√° programado. Una aplicaci√≥n COBOL con los √≠ndicesmal elegidos puede resultar en una aplicaci√≥n lenta o directamente in√ļtil.

Modos de accesos.

En la secci√≥n anterior vimos los tipos de organizaci√≥n que pueden establecerse con los archivos COBOL. Hemos visto adem√°s que esa organizaci√≥nqueda establecida al momento del dise√Īo y permanece as√≠ durantetoda la vida del archivo. Ahora voy a dar revista a los modos en que esos archivos pueden accederse seg√ļn su organizaci√≥n.

Estos son:

Acceso secuencial.

Como mencion√© anteriormente este tipo de acceso se refiere a cuando se va accediendo a los registros sucesivamente uno detr√°s del otro. Estemodo de acceso es el √ļnico permitido para archivos con organizaci√≥n secuencial.

Acceso directo o aleatorio.

Aquí el modo ya no es secuencial sino que paraleero escribir en un registro particular basta con indicar la posición relativa o indexadade ese registro dentro del archivo sin necesidad de pasar por todos los anteriores. Este modo de acceso está permitido para archivos con organización relativa y organización secuencial indexada.

Para el caso de archivos con organización relativa hay que especifivarqué variable almacenará el puntero del archivo. Esta variableno puede ser un campo del propio archivo. Si el modo de acceso es secuencialse trata el archivo como si fuera de organización secuencial.

Mientras que para el caso de los archivos con organización secuencial indexada se debe usar cualquiera de los campos índices declarados.

Implementación.

En este art√≠culo implementar√© en RM/COBOL 85 la organizaci√≥n secuencial indexada con acceso directo. Pasar√© de largo la organizaci√≥n relativa ya que queda obsoleta frente a este otro modo, mientras que hacia elfinal comentar√© brevemente la implementaci√≥n de la organizaci√≥n secuencial que a pesar de su humildad resulta √ļtil cuando no se necesitala sofisticaci√≥n de un acceso basado en √≠ndices.

Declaración del encabezado (SELECT).

Para comenzar hay que declarar el tipo de organización y modo de acceso. Esto se realiza en la ENVIRONMENT DIVISION, sección INPUT-OUTPUT SECTION, párrafo FILE-CONTROL usando la sentencia SELECT la cual todo su párrafodebe estar identado en la columa 12. La sintaxis general de la sentencia SELECTes como sigue:

Sintaxis SELECT
SELECT [OPTIONAL] nombre-del-archivo-lógico ASSIGN TO nombre-del-archivo-físico
ORGANIZATION IS tipo-de-organización
ACCESS MODE modo-de-acceso
RECORD KEY campo-indice-primario
[ALTERNATE RECORD KEY campo-indice-secuntario [WITH DUPLICATES]]

Las cl√°usulas encerradas entre corchetes rectos indican cl√°usulasopcionales. La cl√°usula OPTIONAL le indica al compilador que en casode que el archivo nombre-del-archivo-f√≠sico no exista al momentode intentar abrirlo, lo cre e. La cl√°usula ALTERNATE RECORD KEY indicala clave secundaria que ser√° usada en el archivo, √©sta cl√°usulase puede repetir m√°s de una vez siempre que en cada caso se indique uncampo del archivo diferente o combinaci√≥n no ambig√ľa de ellos. Asu vez esta cl√°usula pose e otra cl√°usula tambi√©n opcional WITH DUPLICATES que indica los valores para esos campos pueden estar repetidos en el archivos. El √≠ndice primario del archivo no puede contener valores duplicados en el archivo.

El literal nombre-del-archivo-l√≥gico es un identificador arbitrario que se usar√° para referirse l√≥gicamente al archivo durante la PROCEDURE DIVISION, mientras que nombre-de-archivo-f√≠sico es el nombre aceptado por el sistema operativo para ese archivo. Hay que tener en cuenta que RM/COBOL 85 ver. 5.36 fue dise√Īado para MS-DOS y el nombre f√≠sico del archivo debe ser compatible con este sistema operativo.

La cláusula ORGANIZATION declara el tipo de organización del archivo, el cual puede ser:

  • SEQUENTIAL
  • RELATIVE
  • INDEXED

Cada una se corresponde con lo ya explicado. M√°s adelante en este art√≠culo veremos una peque√Īa modificaci√≥n al tipo SEQUENTIAL.

La cl√°usula ACCESS MODE puede ser uno de estos:

  • SEQUENTIAL
  • RANDOM
  • DYNAMIC

Si el archivo tiene organización INDEXED y luego se declara modo de acceso SEQUENTIAL el archivo se trata como si tuviera organización SEQUENTIAL. Si el modo de acceso es RANDOM cada vez que se haga una lectura o escritura en el archivo primero hay que proporcionar un valor de la clave primaria obligatoriamente pues de esa forma el compilador sabe qué registroleery escribir. Finalmente si el modo de acceso es DYNAMIC basta con proporcionar un valor aproximadode la clave primaria (o secundaria en el caso de que se delcaren cláusulasALTERNATE RECORD KEY). Por lo que el acceso DYNAMIC es el más versatil de los tres.

Debo hacer notar un hecho importante. El tipo de organización queda plasmado físicamente en el archivo, como se explicó anteriormente, no así el modo de acceso. Esto significa que un archivo declarado conorganización INDEXED puede ser accedido por cualquiera de los tres modos de acceso. Para el caso de un archivo con organización SEQUENTIAL noes necesario especificar el modo de acceso ya que solo permite uno: el secuencial.

El campo que se especifica en la cláusula RECORD KEY debe existir como identificador en la declaración del registro del archivo, el cual veremosmás adelante, como así tambien el o los campos de las cláusulas optativas ALTERNATE RECORD KEY.

Veamos un ejemplo real de declaración de archivo:

Ejemplo de SELECT
            SELECT OPTIONAL CLIENTES ASSIGN TO "CLIENTES.DAT"
            ORGANIZATION IS INDEXED

ACCESS MODE DYNAMIC

RECORD KEY CODIGO-CLIENTE ALTERNATE RECORD KEY APELLIDO-NOMBRE WITH DUPLICATES.

Aquí he declarado un archivo con nombre lógico CLIENTES y nombre físico "CLIENTES.DAT", organización secuencial indexada. Usaré el archivo con modo de acceso DYNAMIC, el índice principaldel archivo será el campo CODIGO-CLIENTE y he declarado ademásuna clave secundaria formada por el campo APELLIDO-NOMBRE el cual puede contener valores duplicados.

Declaración del registro (FD).

Pasemos ahora a la declaración del registro del archivo. Esta se hace en la división DATA DIVISION, sección FILE SECTION, párrafo FD ("FD" viene por "File Definition" o "Definición de Archivo").

La sintaxis general de la cl√°usula FD es como sigue:

Sintaxis de FD
        FD nombre-del-archivo-lógico
        [RECORD tama√Īo-del-registro CHARACTERS]
            [BLOCK tama√Īo-del-bloque CHARACTERS/RECORDS]
            [LABEL RECORD tipo-de-etiqueta]
            [DATA RECORD identificador].

Como se puede ver no todas las cl√°usulas son obligatorias, la √ļnica obligatoria es la propia definici√≥n FD (que designa un p√°rrafoen s√≠ mismo) a la cual se le debe seguir el nombre del archivo l√≥gico (tal como se especific√≥ en el p√°rrafo SELECT) para el cual estamos definiendo su registro.

RECORD es opcional pero √ļtil, le indica al compilador cu√°ntos bytes ocupa un registro del archivo. Si esta cl√°usula se omite el compilador calcula la suma de los tama√Īos de los campos del registro y ese ser√°el tama√Īo final del registro f√≠sico. En cambio si se proporcionaun tama√Īo, el cual debe ser un entero positivo, √©ste no puede ser menor a esa suma. Si el tama√Īo indicado expl√≠citamente esmayor a la suma de los tama√Īos de los campos, el compilador llenar√°el espacio sobrante con espacios en blanco. Aqu√≠ est√° la utilidad de especificar un tama√Īo f√≠sico para el registro pues permite que en el futuro se le puedan ir agregando m√°s campos no indicados inicialmente. Incluso, con una peque√Īa variaci√≥n en la declaraci√≥n de esta cl√°usula es posible declarar un tama√Īo variable para el registro de la siguiente manera:

...
           RECORD tama√Īo-m√≠nimo TO tama√Īo-m√°ximo CHARACTERS
...

Siendo tama√Īo-m√≠nimo y tama√Īo-m√°ximoenteros positivos.

La cl√°usula BLOCK es tambi√©n opcional. Su utilidad consiste en especificar cu√°ntos bytes (CHARACTERS) o registros f√≠sicos (RECORD) ser√°n tratados en bloque por el compilador por cada acceso al archivo. Esto permite una optimizaci√≥n en la eficiencia en el accesoa disco y est√° relacionado con el tama√Īo de los sectores de disco que maneja el sistema operativo. Suponiendo que estamos trabajando sobre un disco formateado con el sistema de archivos FAT cuyos sectores tienen un tama√Īode 2048 bytes y suponiendo que la suma de los tama√Īos de los campos delregistro de nuestro archivo es de 512 bytes podemos ver que por cada sector del disco caben 4 registros. Como el sistema operativo lee del disco un sector completo, si especificamos (o para el caso omitimos) un BLOCK de igual tama√Īoque el tama√Īo del registro del archivo expresado en bytes, cada vez quese haga una lectura secuencial (es decir, registros f√≠sicamente contiguos) del archivo, el sistema operativo har√° cuatro lecturas del mismo sector para entregarle al programa los cuatro registros. BLOCK hace que el compiladorreserve espacio de memoria para esas lecturas, de modo tal que si en vez de especificar un bloque igual al tama√Īo del registro, especificamos un bloque de tama√Īo igual al del sector f√≠sico del disco, el sistemaoperativo entregar√° cuatro registros de una sola vez, acelerando enormemente el proceso ya que para la pr√≥xima lectura secuencial de un registro ese registro estar√° en el espacio de memoria reservado por el compilador.

Jugar con los tama√Īos de las cl√°usulas RECORD y BLOCK puede impactar enormemente en el rendimiento de la base de datos.

La cl√°usula BLOCK puede especificarse tanto en bytes como en cantidad de registros, en este √ļltimo caso el tama√Īo final en bytes ser√°el producto del tama√Īo del registro multiplicado por la cantidad de registros especificados. Sabiendo esto resultar√° evidente que si especificamos un bloque demasiado grande podemos agotar la memoria de la computadora.

La cl√°usula LABEL RECORD, tambi√©n opcional, le indica al compilador que al momento de crear el archivo f√≠sico agregue unas etiquetas especiales al principio y al final del archivo para poder identificar correctamente el tipo de archivo del que se trata. Esto es solo relevante para archivos que sealmacenan en disco (y a decir verdad se trata de un anacronismo, de la √©pocaen que se usaban cintas magn√©ticas las cuales necesitan de registros especiales llamadas "de inicio y parada"). Esta cl√°usula puede tomar dos valores. OMITTED hace que no se creen estas etiquetas, y STANDARD hace que se cre en las etiquetas seg√ļn el tipo de almacenamiento encontrado.

Por √ļltimo, la cl√°usula DATA RECORD indica cu√°les el nombre l√≥gico del registro para ese archivo. Si esta cl√°usulase omite, puesto que es opcional, el registro l√≥gico debe hallarse declarado inmediatamente despu√©s del p√°rrafo FD. Si se especifica nos dala posibilidad de referenciar m√°s de un registro l√≥gico para el archivo (separando los identificadores con espacio). Si este es el caso entoncesel valor de la cl√°usula RECORD debe ser la suma de todos los registrosl√≥gicos especificados en √©sta cl√°usula (y tambi√©npara la cl√°usula BLOCK).

Continuando con el ejemplo del archivo CLIENTES, esta sería una declaración FD típica:

Ejemplo de FD
       FD CLIENTES
           RECORD 128 CHARACTERS
           BLOCK 512 CHARACTERS
           DATA RECORD REG-CLIE.
       01 REG-CLIE.
         02 CODIGO-CLIENTE PIC 9(5).
         02 APELLIDO-NOMBRE.
           03 APELLIDO PIC X(20).
           03 NOMBRE PIC X(20).

He declarado un registro con tama√Īo 128 bytes, bloque de lectura de 512 bytes (es decir 4 registros), el registro l√≥gico ser√° REG-CLIE el cual declaro inmediatamente a continuaci√≥n.

Puede verse que la suma de los campos del registro lógico suman 45 que está bien por debajo de los 128 declarados en la cláusula RECORD. Notar además que los identificadores de los campos del registros soncorrespondientes a los usados en la delaración SELECT más arriba.

He decidido separar el campo APELLIDO-NOMBRE en dos campos dependientes para facilitar la ordenación de la clave alternativa. Hablando del ejemploconcreto, al haber declarado el índice alternativo con la cláusulaWITH DUPLICATES me permite tener dos clientes con el mismo nombre y apellido, esto es válido en la vida real puesto que es posible que dos personascompartan el mismo nombre. Para nuestro hipotético sistema quedarían almacenados como dos clientes distintos, cada uno con su código de cliente.

Sentencias para la manipulación de Archivos.

Antes de comenzar a tratar el tema de la manipulaci√≥n de archivos, quisiera completar el ejemplo que he estado armando para este art√≠culo poniendo a continuaci√≥n el c√≥digo incompleto a√ļn, pero v√°lido(es decir, el compilador lo acepta), que me servir√° para explicar mejor el uso de las sentencias de manipulaci√≥n de archivos:

Ejemplo funcional (incompleto)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. EJEMPLO.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT OPTIONAL CLIENTES ASSIGN TO "CLIENTES.DAT"
           ORGANIZATION IS INDEXED
           ACCESS MODE DYNAMIC
           RECORD KEY CODIGO-CLIENTE
           ALTERNATE RECORD KEY APELLIDO-NOMBRE WITH DUPLICATES.
       DATA DIVISION.
       FILE SECTION.
       FD CLIENTES
           RECORD 128 CHARACTERS
           BLOCK 512 CHARACTERS
           DATA RECORD REG-CLIE.
       01 REG-CLIE.
         02 CODIGO-CLIENTE PIC 9(5).
         02 APELLIDO-NOMBRE.
           03 APELLIDO PIC X(20).
           03 NOMBRE PIC X(20).
       WORKING-STORAGE SECTION.
       77 ESPERA PIC X.
       77 FLAG PIC X.
       PROCEDURE DIVISION.
       COMIENZO.
           ...

He agregado lo que faltaba para hacer un programa funcional y he declarado un par de variables de trabajo.

OPEN.

Antes de manipular un archivo hay que abrirlo. De esto se encarga la sentencia OPEN. El cual requiere un modo de apertura seg√ļn el tratamiento que se le va a dar al archivo en cuesti√≥n. Los modos pueden ser:

INPUT: el archivo ser√° usado para leer registros √ļnicamente.

OUTPUT: el archivo será escrito con registros pero esta cláusula de la sentencia OPEN si se usa en un archivo que ya existe físicamente causa que todo el contenido previo desaparezca, es decir, el archivo queda concero registros luego de la apertura.

I-O: el archivo ser√° usado tanto para lectura como escritura.

Excepto por la cláusula OUTPUT, si se intenta abrir un archivo en modo INPUT o I-O y éste no existe físicamente se elevará una excepción. Esto puede ser evitado agregando la cláusula OPTIONAL en la declaración SELECT, tal como vimos anteriormente.

A continuación de esta cláusula debe especificarse el nombre lógico del archivo que se intenta abrir.

Entonces la sintaxis general de la sentencia OPEN es:

Sintaxis de OPEN
OPEN INPUT/OUTPUT/I-O nombre-logico-del-archivo.

Y el ejemplo concreto para nuestro archivo CLIENTES sería:

Ejemplo de OPEN
           OPEN I-O CLIENTES.

En este caso vamos a trabajar tanto paraleercomo para escribir en el archivo.

CLOSE.

Luego de trabajar con un archivo hay que cerrarlo. La utilidad de esta sentencia está relacionada con las actualizaciones necesarias que debe efectuar el compilador sobre el archivo en cuestión además de liberar todoslos buffers y apuntadores que creó para poder manipular el archivo. Si bien el compilador (mejor dicho, el runtime) lleva la cuenta de los archivos abiertos por el programa y los cierra automáticamente luego que se abandonala ejecución del mismo, no hay que confiar esta tarea al compilador.

De hecho mi recomendaci√≥n personal es que nunca se mantengan archivos abiertos que ya no se van a usar y a su vez no abrir archivos que se sabe no van a ser usados. Esto se debe a por el modo en que COBOL mantiene los √≠ndices de los archivos de datos explicado m√°s arriba, si se tiene un archivo (o muchos) abierto y ocurre un imprevisto, como un corte de electricidad, el arbol de √≠ndices de los archivos abiertos podr√≠a da√Īarse, a veces irreparablemente (RM/COBOL tiene una utilidad para reparar √≠ndices pero no es 100% efectiva).

Por otro lado el propio sistema operativo puede tener un límite en la cantidad de archivos abiertos que un programa cualquiera puede mantener (enMS-DOS esto se especifica en el CONFIG.SYS mediante la directiva FILES).

La sintaxis de la sentencia CLOSE es sumamente sencilla:

Sintaxis de CLOSE
CLOSE nombre-lógico-del-archivo.

Y en nuestro ejemplo sería:

Ejemplo de CLOSE
           CLOSE CLIENTES.

Hay cuatro operaciones básicas que se pueden realizar sobre un archivo. Leer. Escribir. Sobre escribir. Y borrar. Todas referidas a registros individuales. El comportamiento de cada una de estas operaciones en COBOL está ligadoal tipo de organización del archivo.

READ.

La sentencia READ sirve para leer un registro del archivo siempre que el archivo no haya sido abierto en modo OUTPUT. √Čsta viene en dos "sabores"dependiendo del modo de acceso, ACCESS MODE, que fue declarado en el encabezado.

Si el ACCESS MODE es SEQUENTIAL o bien DYNAMIC, la sintaxis es:

Sintaxis de READ con ACCESS MODE SEQUENTIAL o DYNAMIC
READ nombre-lógico-del-archivo [NEXT/PREVIOUS [AT END sentencia]].

Como siempre, lo que est√° entre corchetes rectos es opcional. Si se omite NEXT y PREVIOUS se asume NEXT que significa que por cada lectura el puntero se avanza al siguiente registro, seg√ļn el orden del √≠ndice empleado para hacer la lectura en el caso de que el ACCESS MODE sea DYNAMIC o al siguiente registro f√≠sico en el caso de que el ACCESS MODE sea SEQUENTIAL. Mientras que PREVIOUS es lo opuesto a NEXT, es decir, sirve para hacer lecturas en reversa. La cl√°usula AT END sirve para indicar qu√© debe hacerse cuando se encuentra el final del archivo (oel principio para lecturas PREVIOUS) por lo tanto debe ser seguida de una sentencia(¬ŅCLOSE tal vez?).

Ahora bien, la pregunta l√≥gica que el lector se debe estar haciendoes ¬Ņc√≥mo sabe READ qu√© √≠ndice debe seguir para hacerlas lecturas en el caso de que el archivo tenga declarado m√°s de un √≠ndice?. O peor a√ļn, ¬Ņc√≥mo sabe cu√°l es el √≠ndice que debe usar para comenzar aleery desde d√≥nde comenzar a leer?. Estas preguntas las responder√© cuando veamos la sentencia START. Por ahora baste decir que si el archivo est√° declarado con ACCESS MODE DYNAMIC el valor de la clave que se usa para hacer las lecturas (y las otras operacionesque veremos a continuaci√≥n) deben tener un valor concreto. M√°s sobre este punto m√°s adelante.

Ahora veamos el segundo "sabor" de READ el cual se usa cuando el ACCESS MODE del archivo es RANDOM:

Sintaxis de READ con ACCESS MODE RANDOM
READ nombre-lógico-del-archivo [[NOT] INVALID KEY sentencia].

Como comentaba cuando expuse el tema de los modos de acceso, cuando el ACCESS MODE es RANDOM solo se puede usar el √≠ndice primario del archivo, esdecir el campo que est√° a continuaci√≥n de la declaraci√≥n RECORD KEY en la sentencia SELECT. Ese campo debe tener un valor concreto previo a la lectura del archivo. La cl√°usula INVALID KEY indica qu√© debe hacerse en caso de que no exista ning√ļn registro cuyo campo clave contenga el valor asignado previamente para ese campo clave. Esta cl√°usula puede ser negada anteponiendo la palabra NOT, en ese caso la sentencia se ejecutar√°si se encontr√≥ un registro que contiene el valor asignado a la clave primaria.

Si no se especifica la cl√°usula INVALID KEY y se encuentra un registroque s√≠ contiene el valor asignado a la clave primaria, se continua la ejecuci√≥n con la sentencia siguiente a READ. Pero, si no se encuentraun registro concordante el programa aborta con una excepci√≥n de entrada-salida (concretamente la n√ļmero 23).

Usando nuestro ejemplo de CLIENTES.

Ejemplo de READ
           OPEN INPUT CLIENTES.
           MOVE 1 TO CODIGO-CLIENTE.
           READ CLIENTES.
           CLOSE CLIENTES.

En este primer ejemplo comenzamos abriendo el archivo para lectura. Asignamos un valor para el campo índice primario, intentamos leer el archivo que buscará un registro cuyo campo contenga el valor asignado. Como no especificamosla cláusula INVALID KEY en el READ podría causar una excepciónde entrada-salida. Luego cerramos el archivo.

Ahora veamos un ejemplo m√°s funcional.

Ejemplo de READ con INVALID KEY
           OPEN INPUT CLIENTES.
           MOVE 1 TO CODIGO-CLIENTE.
           READ CLIENTES INVALID KEY DISPLAY "Registro no encontrado".
           CLOSE CLIENTES.

Aqu√≠ a continuaci√≥n de la cl√°usula INVALID KEY mostramos un mensaje en caso de que el registro 1 no haya sido encontrado. En cierta manera el READ funciona como una pregunta. Le pregunta al archivo ¬Ņexiste un registro cuya clave primaria es igual a 1?. Si la respuesta es afirmativa el registro es transferido al registro l√≥gico del archivo, que para nuestro ejemplo es REG-CLIE, caso contrario se ejecuta la sentencia especificada a continuaci√≥nde INVALID KEY.

Ahora consideremos el siguiente ejemplo asumiendo que el archivo CLIENTES contieneunos cuantos registros:

       COMIENZO.
           OPEN INPUT CLIENTES.
           MOVE 1 TO CODIGO-CLIENTE.
       LEER.
           READ CLIENTES INVALID KEY GO CERRAR.
           DISPLAY CODIGO-CLIENTE.
           DISPLAY APELLIDO.
           DISPLAY NOMBRE.
           Ad d 1 TO CODIGO-CLIENTE.
           GO LEER.
       CERRAR.
           CLOSE CLIENTES.
       SALIR.
           EXIT PROGRAM.
           STOP RUN.

Comenzamos abriendo el archivo. Establecemos el valor de la clave primaria. Intentamos una lectura. Que si fue exitosa mostramos los valores del resto delos campos leídos. Sumamos uno a la clave primaria y volvemos a leer.

Si el archivo contiene registros cuya clave primara tiene valores sucesivos tal como 1, 2, 3 y 4 habremos le√≠do exit√≥samente de principio a fin todo el archivo, ¬Ņcierto?, cierto. Pero qu√© suceder√≠aen caso de que el archivo tenga registros cuya clave primaria es 1, 2, 3 y 5. Cuando intentemos la lectura del registro 4 saltar√° una excepci√≥n que har√° que el flujo de ejecuci√≥n continue en el p√°rrafo CERRAR, cerrando el archivo y culminando la ejecuci√≥n. Y el registro n√ļmero 5 nunca se leer√°.

De esto podemos deducir que usar READ en modo RANDOM sirve en aquellos casos en los que estamos seguros de encontrar el registro que queremos leer, o bien cuando queremos verificar que un determinado registro exista en el archivo. Por ejemplo si el usuario quisiera saber el nombre del cliente n√ļmero 5 basta con pedirle al usuario que ingrese ese n√ļmero en el campo CODIGO-CLIENTE, procedemos a leer el archivo y en caso de que exista mostrar sus datos, casocontrario (cl√°usula INVALID KEY) mostrar un mensaje acorde.

Bien pues, pero qué tal si quisiéramos leer un grupo de registros cuya clave primara no sabemos con anticipación. En ese caso debemos usar el primer "sabor" de la sentencia READ en conjunción con la sentencia START.

START.

Esta sentencia permite iniciar el proceso de acceso al archivo cuya organización sea INDEXED (o relativa) por cualquiera de sus índices (no necesariamente la primaria, como veremos a continuación) y sin necesidad de que el valor inicial del índice elejido exista en el archivo; y además el ACCESSMODE sea DYNAMIC.

La sintaxis general de START es:

Sintaxis de START
START nombre-lógico-del-archivo [KEY [NOT] condicional nombre-índice 
[[NOT] INVALID KEY sentencia]]]

Si no se especifica ninguna de las cláusulas opcionales, START no tiene efecto sobre el archivo (excepto si el archivo está vacío, enese caso causará una excepción). START funciona de esta manera:se evalua condicional contra el valor actual de nombre-índice, el cual debe ser cualquiera de los campos declarados como índices en la SELECT, sea primario o cualquiera de los secundarios; y el puntero del archivo se posiciona en el primer registro que satisface la condición. Condiciónes un operador de comparación tal como los símbolos = (iguala) > (mayor que) o < (menor que) o combinación de ellos, excepto <> (distinto que), en re emplazo de éste se usa lanegación indicada por la palabra NOT que antecese a la condición(o sea para indicar "distinto que" se usa "NOT =").

Si ning√ļn registro del archivo satisface la condici√≥n se provocauna excepci√≥n de entrada-salida an√°logo a lo ya visto para lasentencia READ de all√≠ que es opcional la cl√°usula INVALID KEY y su negaci√≥n.

Acepto que todo esto es un poco confuso a primera vista así que vamos a ver un ejemplo práctico, de paso resolveremos el problema que dejamos pendiente en la sección anterior.

Ejemplo de START
       COMIENZO.
           OPEN INPUT CLIENTES.
           MOVE 1 TO CODIGO-CLIENTE.
           START CLIENTES KEY NOT < CODIGO-CLIENTE.
       LEER.
           READ CLIENTES NEXT AT END GO CERRAR.
           DISPLAY CODIGO-CLIENTE.
           DISPLAY APELLIDO.
           DISPLAY NOMBRE.
           GO LEER.
       CERRAR.
           CLOSE CLIENTES.
       SALIR.
           ACCEPT ESPERA NO Be eP.
           EXIT PROGRAM.
           STOP RUN.

Aquí comenzamos abriendo el archivo. Asignamos valor 1 para el campoíndice primario. Ejecutamos un START el cual dice que debe buscarse elprimer registro que no sea menor a CODIGO-CLIENTE, o lo que es lo mismo, queno sea menor a 1, o... lo que es lo mismo que sea mayor o igual a 1. En nuestro caso ese registro podría ser cualquiera del archivo excepto el cero, pero el primero que cumple la condición es precisamente el 1. Si no existiera el 1 sería el 2 y así sucesivamente.

En caso de que el archivo est√© vac√≠o ning√ļn registro cumple la condici√≥n, por lo que se provocar√° una excepci√≥n que en nuestro caso no est√° controlada con la cl√°usula INVALID KEY en el START.

Luego procedemos a la lectura del archivo secuencialmente. Esta lectura sehace siguiendo el valor ascendente de la clave primaria puesto que ese campoes el que se especificó en la sentencia START. Así hasta el finaldel archivo.

Si hubiésemos asignado el valor 3 a CODIGO-CLIENTE el registro trescumpliría la condición. En nuestro hipotético caso de queno existiera el registro 4 en el archivo y le asignáramos ese valor aCODIGO-CLIENTE antes del START, el registro 5 cumpliría la condición.

START no solo funciona con el índice primario, también sirvecon cualquiera de los índices secundarios. En nuestor ejemplo ese índice es el campo APELLIDO-NOMBRE que a su vez está permitido que sea duplicado, es decir que puede haber dos registros cuyos campos APELLIDO-NOMBRE tienen el mismo valor.

El siguiente ejemplo sería un ejemplo de listar el archivo de clientes alfabéticamente por APELLIDO-NOMBRE.

Ejemplo START por índice secundario
       COMIENZO.
           OPEN INPUT CLIENTES.
           MOVE " " TO APELLIDO-NOMBRE.
           START CLIENTES KEY NOT < APELLIDO-NOMBRE
           INVALID KEY GO CERRAR.
       LEER.
           READ CLIENTES NEXT AT END GO CERRAR.
           DISPLAY CODIGO-CLIENTE.
           DISPLAY APELLIDO.
           DISPLAY NOMBRE.
           GO LEER.
       CERRAR.
           CLOSE CLIENTES.
       SALIR.
           ACCEPT ESPERA NO Be eP.
           EXIT PROGRAM.
           STOP RUN.

Aquí lo que hacemos es asignar un espacio vacío al campo APELLIDO-NOMBRE y en el START usar ese campo como nombre-índice usando a suvez el mismo condicional ya visto. Lo que provoca que el archivo sea mostradoen pantalla ordenado alfabéticamente por ese campo, ya que el espacioes menor a la letra A. Incluso si se da el caso de que APELLIDO-NOMBRE estévacío en alguno de los registros ese será el primero en ser listado.

Por supuesto, variando la condici√≥n y el valor asignado para el campo√≠ndice se puede listar el archivo de diferente manera. Y tambi√©nse puede crear una condici√≥n que no puede ser satisfecha por ning√ļnregistro. Por ejemplo quitando la palabra NOT en el ejemplo podemos ver queno existe y no puede existir un registro que sea menor a espacio. As√≠mismo he agregado la cl√°usula INVALID KEY para el caso de que el archivo est√© vac√≠o (un archivo sin registros no satisface ninguna condici√≥n en START).

START no solo funciona de esta manera en conjunción con la sentencia READ, otras sentencias de manipulación de archivos también obedencen a START tal como se explicó hasta ahora.

WRITE.

Esta sentencia es opuesta a READ, es decir, escribe un registro en el archivo por lo que no se puede usar en archivos abiertos en modo INPUT:

Sintaxis de WRITE
WRITE nombre-del-registro [[NOT] INVALID KEY sentencia].

Notar que se usa el nombre del registro del archivo y no el nombre lógico del archivo.

Si se especifica la cláusula INVALID KEY ésta funciona de manera inversa, es decir salta una excepción de entrada-salida si el registroque se intenta escribir ya existe en el archivo. Esto sucede cuando el registroque se intenta agregar al archivo lleva en el campo declarado como índice primario un valor igual al de otro registro que ya existe en el archivo (por ejemplo, intentar escribir un nuevo registro con CODIGO-CLIENTE igual a 1 cuando ya existe un registro con ese valor). O bien, intentar escribir un nuevo registroen el que alguno de sus campos declarados como índices secundarios y ese campo no está declarado con WITH DUPLICATES ya existe en el archivo, es decir no se permite la duplicación de índices secundarios (elíndice primario nunca se permite duplicado).

La sintaxis de WRITE explicada aquí es válida para archivos conorganización INDEXED en modos de accesos RANDOM, DYNAMIC y SEQUENTIAL.

Muesto a continuación un ejemplo de uso de WRITE en conjuncióncon READ para cargar un registro a nuestro archivo de CLIENTES.

Ejemplo de WRITE
       COMIENZO.
           DISPLAY "Ingrese el cĘdigo de cliente: ".
           ACCEPT CODIGO-CLIENTE NO Be eP PROMPT.
           MOVE " " TO FLAG.
           OPEN INPUT CLIENTES.
           READ CLIENTES INVALID KEY MOVE "I" TO FLAG.
           CLOSE CLIENTES.
           IF FLAG = " " THEN
             DISPLAY "Ya existe este cĘdigo."
             GO COMIENZO.
           DISPLAY "Ingrese el Nombre de cliente: ".
           ACCEPT NOMBRE NO Be eP PROMPT.
           DISPLAY "Ingrese el Apellido de cliente: ".
           ACCEPT APELLIDO NO Be eP PROMPT.
           OPEN I-O CLIENTES.
           WRITE REG-CLIE.
           CLOSE CLIENTES.
       SALIR.
           EXIT PROGRAM.
           STOP RUN.

En este ejemplo comienzo pidi√©ndole al usuario que ingrese un c√≥digo de cliente. Pongo una variable bandera vac√≠a. Abro el archivo en modolectura. Intento leer el archivo usando el c√≥digo proporcionado por elusuario. Luego cierro el archivo. Si la lectura fue exitosa la variable bandera permanecer√° vac√≠a que es lo que pregunto a continuaci√≥n, esto significa que ese c√≥digo ya existe, por lo que se lo informo al usuario y vuelvo a pedirle un c√≥digo. Si no existe ning√ļn registro conel c√≥digo que el usuario ingres√≥ (cl√°usula INVALID KEY del READ), entonces muevo "I" a la bandera haci√©ndo falsa la condici√≥n del IF que sigue, por lo que el flujo del programa proceder√°a pedir los datos que faltan antes de escribir el registro. Notar que para escribiren el registro abro el archivo en modo I-O (lectura-escritura).

No hay un equivalente del NEXT/PREVIUOS del READ para WRITE. Pero una variaciónde esta sentencia puede ser usada en archivos con organización SEQUENTIAL, la cual es:

Sintaxis de WRITE para SEQUENTIALs
WRITE nombre-lógico-del-archivo FROM identificador.

La cl√°usula FROM indica que previo a la escritura, se debe mover elvalor contenido en indentificador al registro l√≥gico del archivo. Esta forma de la sentencia WRITE es √ļtil para usarse en archivos de textoo para hacer salidas de impresora.

REWRITE.

Con esta sentencia podemos re escribir un registro ya existente. Le caben las mismas restricciones que a READ en modo de acceso RANDOM, es decir, hay que proveer una clave primaria y ésta debe existir en el archivo, o lo que es lo mismo, el registro a reescribir debe existir ya en el archivo. Y de lamisma manera no puede reescribirse un registro que no existe en el archivo. La sentencia, otra vez es similar a la de READ para archivos en modo de acceso RANDOM:

Sintaxis de REWRITE.
REWRITE nombre-lógico-del-archivo [[NOT] INVALID KEY sentencia].

Si se ha especificado la cláusula INVALID KEY esta se usará cuandoel valor contenido en el campo de índice primario del registro que sedesea re emplazar no existe en el archivo. O bien, cuando el valor del campoque referencia cualquiera de los índices secundarios ya existe en el archivo y no se especificó WITH DUPLICATES.

Esta sentencia se puede usar con todos los modos de accesos siempre que el archivo no haya sido abierto en modo INPUT.

DELETE.

Finalmente tenemos la sentencia que elimina un registro del archivo la cualse puede usar en un archivo abierto en modo I-O o OUTPUT. La sintaxis es similara READ y le caben también las mismas consideraciones que aquella.

Sintaxis de DELETE.
DELETE nombre-lógico-del-archivo [[NOT] INVALID KEY sentencia].

DELETE se usa para otra cosa ademásde borrar un registro. Sirve también para borrar físicamente un archivo, cualquier archivo; la sintaxis para esto es:

Sintaxis de DELETE para borrar archivos.
DELETE FILE nombre-lógico-del-archivo.

Archivos de líneas.

Los archivos que se declaran con organización SEQUENTIAL no tienen modode acceso, al menos no es necesario especificarlo ya que todos son equivalentesal modo de acceso sequencial. No tienen índices tampoco por lo que noes necesario especificar esta cláusula en la sentencia SELECT.

Sintaxis de SELECT para archivos secuenciales.
SELECT [OPTIONAL] nombre-lógico-del-archivo ASSIGN TO nombre-físico-del-archivo

ORGANIZATION [LINE/BINARY] SEQUENTIAL.

La cláusula opcional OPTIONAL es igual a la ya explicada. Mientras quelas cláusulas opcionales LINE y BINARY merecen una explicaciónaparte.

El registro de un archivo secuencial tampoco necesita ser complicado, bastacon declarar cualquier estructura teniendo en cuenta que el largo total de eseregistro será lo que se lea del archivo tal cual está en el archivofísico. De modo que si tenemos una declaración de registro talque tiene un solo campo de tipo PIC X(80) se leerán 80 bytes interpretándosecada uno como caracteres ASCII.

Cuando se declara la cl√°usula LINE entonces la lectura se realiza hasta el primer salto de l√≠nea o retorno de carro (caracteres ASCII 10 y 13respectivamente), esto es ideal para leer archivos de texto. Con la salvedad de que si el registro declarado para la lectura es menor a cualquiera de las l√≠neas le√≠das, la l√≠nea le√≠da se truncar√°al tama√Īo del registro.

Por √ļltimo si se utiliza la cl√°usula BINARY entonces se asumeque los dos primeros bytes del registro le√≠do especifica el largo total del registro (esos dos primeros bytes no son trasladados al registro l√≥gico).

Conclusión.

Espero haber cumplido mi prop√≥sito inicial de ense√Īar lo b√°sico para usar archivos de datos en COBOL.

Referencias.

  • RM/COBOL 85 User Guide - Liant Software.
  • Curso de programaci√≥n RM/COBOL 85 - Fco. Javier Ceballos - 1997 - ISBN 970-15-0334-1

Diego Romero - .