viernes, septiembre 10, 2021

¿ Quien es "alias" ?

Tengo 20 años programando en Clipper, y mas o menos 15 dando clases de programación y análisis y diseño de sistemas, de los cuales 5 estuve dando clases en la Universidad La Salle de México, como catedrático de la Escuela de Ingeniería en la carrera de Ingeniería en Cibernética y Ciencias de la Computación. 

He dirigido algunos trabajos de tesis sobre bases de datos, 2 de ellos específicamente sobre lenguajes Xbase y hasta una tesis sobre programación orientada al Objeto con FiveWin, sí sí, aunque ustedes no lo crean FiveWin fue en su momento (hace como 8 años) el tema de un trabajo de titulación profesional de 2 de mis alumnos. 

El caso es que en todos estos años pegándole a la tecla con Clipper y ahora con (x)Harbour, me he encontrado con que casi nadie saber manejar los archivos DBF en condiciones. Actualmente la idea es ir mas por el tema del SQL, pero hay muchas personas que todavía manejan archivos DBF y que conociendo un poco mas sobre ellos se pueden hacer cosas muy interesante.  

Pero vamos a ver..... ¿ como que nadie sabe usar los DBFs ?, si cientos de programadores del mundo los han usado durante años..... de acuerdo, pero de todos esos cientos de programadores, que levanten la mano aquellos que consideren que saben manejar los DBF BIEN. 

Si levantaste la mano, continúa leyendo, espero poder enseñarte algo nuevo, si no la levantaste, también continúa leyendo, seguro vas a aprender algo nuevo.  

El mito de las 256 "áreas" de trabajo. 

 Los que venimos del dBase III recordamos que en los libros ponían, dBase puede abrir hasta 256 "areas" de trabajo, entendiendo por "areas de trabjo" un espacio de memoria en donde se "abría" la tabla DBF (está mal llamarla "base de datos"), lo que en el SQL moderno de hoy se llama "cursor".

 Estas "areas" estaban numeradas, de tal forma que cuando queríamos abrir una tabla, todos hacíamos (y algunos siguen haciendo) la forma erronea de abrir la tabla:  

SELECT 1 // para seleccionar el "area" donde vamos a abrir la tabla USE archivo.dbf 

Esto quería decir que la tabla ARCHIVO.DBF estaría abierta en el "área" 1, así pues siempre que quisiéramos hacer referencia a los campos o a alguna operación sobre ARCHIVO.DBF, antes que nada tendríamos que hacer primeramente un salto al "área" donde está abierta dicha tabla, algo como esto:

SELECT 1
GO TOP
DO WHILE ! EOF()
    ? Recno()
 
SKIP
ENDDO 

La cosa comienza a ponerse interesante cuando requieres usar mas tablas simultáneamente, digamos unas 5 tablas, entonces erroneamente haces:  

SELECT 1
USE archivo1.dbf
SELECT 2 USE archivo2.dbf
......

SELECT 5 USE archivo5.dbf 

Mal, mal, mal, esto te lleva a hacer lo que yo he llamado "código ping-pong", es decir selección de área, operación, selección de otra área, otra operación, selección de otra area mas, otra operación mas, y luego vamos de vuelta al área original, lo cual nos lleva a un código lleno de SELECTs por todos sitios, que es complicado de mantener. 

Si a esto aunamos que a 16 bits el número de áreas de trabajo estaba limitado por la memoria (las 256 áreas de trabajo que prometía dBase nunca fueron ciertas), entonces nuestro código se volvía un maremagnum de saltos entre áreas, de tener que cerrar tablas para abrir otras en las mismas "áreas" de tener que acordarte en que número de área quedaba tal o cual tabla DBF de tal o cual proceso, vamos, un rollo. 

Y todo por no utilizar el "alias" de los archivos DBF.  

¿ Y quien o qué es ALIAS ? 

En todos los lenguajes de programación no orientados a datos, como Pascal, "C" o el mismo Basic, cuando se manejan archivos de datos, siempre se les asignan 2 nombres, el nombre "físico" que es el nombre del archivo tal cual se almacena en el disco, y el nombre "lógico", que es el nombre con el cual haremos referencia dentro del código de nuestro programa.

Clipper/(x)Harbour no es una excepción a esta regla, porque en realidad, SIEMPRE estas manejando 2 nombres cuando abres una tabla DBF para trabajar sobre ella, es decir, cuando haces algo como esto : 

USE archivo.dbf 

En realidad estas generando un "ALIAS", es decir, un "apodo" para hacer referencia al archivo abierto durante la ejecución de tu programa. 

Si revisas la sintaxis del comando USE, te encontrarás varias cosas interesantes:  

USE [ ; [INDEX ] ; [ALIAS ] ; [EXCLUSIVE | SHARED] ; [NEW] ; [READONLY] ; [VIA ]  

El comando USE tiene una clausula ALIAS, que si no fuera importante, creeme, no la pondrían como parte de la sintaxis. 

Esta clausula ALIAS se utiliza para darle un "nombre lógico" a tu tabla para que puedas manejarla en memoria, si tu omites este nombre, se asigna automáticamente como "alias" el mismo nombre de la tabla, sin extensión. 

 El ALIAS de una tabla es una pieza muy importante de la programación de datos porque si sabes como utilizarlo, te ahorrarás lineas y líneas de SELECTS inútiles. 

Existe un "operador ALIAS", representado por un "->" signo de menos + signo de mayor que, este operador nos servirá para hacer referencia al área de trabajo que deseamos usar sin necesidad de tener que hacer un SELECT 

Por ejemplo:  

USE archivo1.dbf
archivo1->(dbgotop())

DO WHILE ! archivo1->(EOF())
  
? archivo1->(RECNO())
   archivo1->(DBSKIP())

ENDDO

En este ejemplo asumimos que no hemos usado la cáusula ALIAS, luego entonces el ALIAS asignado por omisión será "archivo1" (sin extensión), luego usaremos el operador "alias" (->) para indicar que operaciones deben realizarse sobre él.  

Nota por favor que todas las operaciones sobre la tabla son funciones NO COMANDOS y que todas las operaciones van encerradas entre paréntesis. 

Otra cláusula importante del comando USE es la claúsula NEW, esta clausula te ahorrará miles de lineas de SELECTS ya que esta clausula abre la tabla DBF en la primer área que se encuentre disponible, es decir, no tienes que hacer un SELECT previo a abrir un DBF, simplemente le pones NEW al momento de abrir, y dejas que Clipper/(x)Harbour se haga cargo del resto. 

 USE archivo1.dbf NEW
USE archivo2.dbf NEW
 

No tienes que hacer ningun SELECT para abrir las tablas, ahora, para hacer referencia a los campos de cada archivo, o bien realizar operaciones sobre las tablas, tampoco hay necesidad de hacer SELECT, el ALIAS nos soluciona la vida: 

USE archivo1.dbf NEW
USE archivo2.dbf NEW
archivo1->(DBGOTOP())
archivo2->(DBGOBOTTOM())
DO WHILE ! archivo2->(BOF())
   ? archivo2->(fieldget(1))

       ? archivo1->(fieldget(5))
       IF ! archivo1->(EOF())
      archivo1->(DBSKIP())

       ENDIF
       archivo2->(DBSKIP(-1))
ENDDO 

El manejo de los campos es muy simple, funciona igual, basta con que pongas el nombre del campo despues del operador alias SIN PARENTESIS:  

? archivo1->nombre
? archivo2->iva
 

Los campos también son asignables usando el operador alias, tanto para leer sus valores, como para actualizar o agregar valores, sustituyendo con esto al comando REPLACE:  

cNombre := archivo1->nombre
archivo2->(DBAPPEND()) // el comando APPEND BLANK se sustituye pro su función

archivo2->nombre := cNombre // no hay REPLACE, el valor se asinga directamente
 

Esto de los alias puede ser tan complejo como quieras, un ejemplo típico es buscar en una segunda tabla el valor de un campo contenido en la primera: 

CON SELECTS:  

SELECT 1
cNombre := field->nombre

SELECT 2 SEEK
cNombre
 

CON ALIAS:  

archivo2->(DBSEEK(archivo1->nombre)) 

Efectivamente, con la línea anterior, basta con que hagas referencia a los alias y en vez de usar 4 líneas de código en una sola solucionas el problema y haces exactamente lo mismo. 

Ahora esto del alias es mas versátil porque tu puedes dar TU PROPIO ALIAS  

USE archivo1.dbf ALIAS MiAlias NEW
MiAlias->(DBGOTOP())
? MiAlias->nombre
 

Usando tu propio ALIAS puedes darle un nombre mas identificativo al manejo de tus tablas DBFs. 

Veamos otro ejemplo mas complejo pero sin duda mucho mas útil:  

USE archivo.dbf ALIAS ArchPant SHARED NEW
USE archivo.dbf ALIAS ArchRep SHARED NEW

USE archivo.dbf ALIAS ArchMovs SHARED NEW
 

NO, No hay ningún error, estoy abriendo LA MISMA TABLA (archivo.dbf) 3 veces AL MISMO TIEMPO..... ¿ que no causa esto un error del tipo ALIAS already in use ?.... pues va a ser que no.... y esto es algo de lo mas alucinante y útil de los archivos DBFs. 

Una de las cosas que casi nadie usa es esta poderosa característica de los DBFs, poder abrir la misma tabla 2, 3, 4, o "n" veces al mismo tiempo, esto se logra CAMBIANDO EL ALIAS y abriendo la tabla como compartida (SHARED) como si fuesemos a programar para red. 

Lo interesante de esta forma de usar los DBFs es que cada "alias" mantiene su puntero independiente, es decir, puedes hacer algo tan loco como esto:  

ArchPant->(DBGOTOP())
ArchMovs->(DBGOTO(5))
ArchRep->(DBGOBOTTOM()) 

El puntero de cada "alias" se moverá a distintas posiciones, sin importar que estemos hablando del mismo archivo DBF (recuerda, una cosa es el nombre físico y otra es el nombre lógico).

Y si tu preguntas los valores de los campos :  

ArchPant->nombre
ArchMovs->nombre
ArchRep->nombre 

Seguramente obtendrás valores distintos en cada oportunidad, esto porque sin importar que sea el mismo archivo, para efectos prácticos en la memoria de la computadora esta "abierto" 3 veces bajo 3 alias distintos y cada alias guarda y mantiene su propio estado. 

Solo como comentario, Xailer a través de los DataSets facilita aun mas el manejo de los alias. 

Pero volviendo al tema, para los que no tienen Xailer o tienen la versión personal, aun así podemos seguir haciendo cosas interesantes a base de usar solo los alias: Si yo hago:  

ArchMovs->(DbAppend())
ArchMovs->nombre := "Juan Perez"
ArchMovs->direccion := "Da igual donde viva, Viva El"
ArchMovs->(DBCommit())
ArchMovs->(DBUnlock()) 

El registro añadido será visible EN TODOS LOS ALIAS QUE APUNTEN AL MISMO ARCHIVO FISICO, esto es maravilloso, porque es como dar de alta al mismo tiempo en 3 tablas distintas. 

La pregunta que puede surgir en este momento es .... ¿ y esto como para que me serviría en la vida real ?.... pues para facilitarte la existencia: 

Supongamos que tu quieres mostrar en pantalla un browse del archivo de clientes, donde tienes además 2 índices, uno por clave y otro por nombre, y que además quieres hacer operaciones de mantenimiento como altas bajas y cambios y para rematar quieres hacer reportes que pueden ir ordenados por clave o por nombre, entonces haríamos algo como esto:  

USE clientes ALIAS CliBrow SHARED NEW
CliBrow->(OrdSetFocus("nombre")) // arrancamos ordenado por nombre del cliente
CliBrow->(DBGOTOP())

USE clientes ALIAS CliMovs SHARED NEW
CliMovs->(OrdSetFocus("clave")) // esta segunda instacia del archivo va ordenado por clave

USE clientes ALIAS CliReps SHARED NEW

CliRep->(OrdSetFocus("clave") // idem anterior. 

Si yo quiero meter la primer instancia del archivo de clientes a un browse, basta con hacer: 

CliBrow->(Browse())  

¡¡¡Así de simple !!!!! 

El usuario podrá moverse sobre el browse y podrá cambiar los criterios de ordenación sin afectar a los otros 2 alias que apuntan a la misma tabla. 

Ahora viene lo bueno, si yo quiero hacer un reporte o hacer un ABM sobre la tabla clientes NO LO HAGO SOBRE EL ALIAS DEL BROWSE, lo hago ya sea sobre el alias CliMovs o sobre el alias CliReps para no molestar al alias del browse. 

Cuando trabajas con un solo alias, y quieres dar un mantenimiento a una tabla mostrada en un browse, entonces tienes que "guardar" el estado del área de trabajo que muestras en el browse, es decir, en que registro estás, sobre que indice estas trabajando, etc. y luego hacer las operaciones de mantenimiento sobre ese mismo alias que pueden vincular, mover el puntero sobre la tabla, agregar un registro nuevo, cambiar el orden del índice actual, realizar búsquedas, etc, y luego de la operación, debes regresar todo a como estaba antes. 

Esta técnica te evita eso precisamente. 

 Por ejemplo, si tu quieres agregar un registro el código sería algo como esto:  

CliMovs->(RLOCK())
CliMovs->(DBGOBOTTOM())
nSiguienteClave := CliMovs->clave+1
CliMovs->(DbAppend())

CliMovs->clave := nSiguienteClave
CliMovs->nombre:= cVariableConElNuevoNombre
CliMovs->(DbCommit())
CliMovs->(DBUnlock()) 

Nota que toda la operación de mantenimiento la hacemos sobre el Alias CliMovs, no sobre el Alias CliBrow, de esta manera no tenemos que guardar posiciones ni estados, porque CliBrow no se va a mover su puntero y su índice se quedan como están, en cambio CliMovs hará toda la labor de mantenimiento y el cambio se verá reflejado automáticamente en CliBrow y en CliRep en cuanto haya terminado la actualización CliMovs, el Browse ni si quiera se va a mover, a menos claro, que la operación de mantenimiento afecte al registro en el cual estaba el puntero del alias CliBrow. 

Lo mismo pasa para los reportes, si yo quiero un reporte, usualmente ese reporte abarca un rango de datos, para no molestar al Browse ni a su alias, pues el reporte lo emitimos sobre el alias CliRep:

CliRep->(OrdSetFocus("nombre"))
CliRep->(OrdScope(0,"A")) // reporte de todos los clientes cuyo nombre empiece con "A"
CliRep->(OrdScope(1,"A"))

CliRep->(DBGOTOP())
DO WHILE ! CliRep->(EOF())
 
... aqui val el codigo del reporte...
    CliRep->(DBSKIP())
ENDDO 

Y el CliBrow ni se va a enterar que hay un alias llamado CliRep con un Scope sobre la tabla moviendo el puntero para generar un informe.
 

Esta técnica de programación es ideal para Windows, sobre todo si estás trabajando en entornos MDI porque puedes asignar a cada ventana hija los alias que vaya a necesitar para trabajar, en los entornos MDI una de las cosas mas complicadas de manejar es precisamente esa, evitar que el puntero en la ventana "A" se mueva cuando en la ventana "B" se está realizando otra operación sobre la misma tabla DBF. 

Puedes incluso abrir todas las tablas al momento de arrancar tu programa, porque en (x)Harbour no existe la limitación de las 256 "areas" de dBase, en (x)Harbour prácticamente no hay limite en la cantidad de archivos que puedas abrir simultáneamente. 

En Xailer Pro y Enterprise no tienes los problemas de Alias cuando trabajas en entornos MDI porque Xailer te provee de los DataSets que usualmente ya van "atados" a cada formulario y como expliqué antes, te ahorran el tema de tener que lidiar con alias, en FiveWin o en la versión Personal de Xailer, el uso de esta técnica es indispensable para trabajar con entornos MDI, en (x)Harbour modo consola, esta ténica te puede ayudar a eliminar muchos vicios de programación y a ahorrar muchas líneas de código. Cabe señalar que esto mismo es aplicable a Advantage DataBase Server, de hecho el manual de ADS recomienda que todas las tablas estén abiertas desde que arranca la aplicación, esto es aplicable tanto a archivos DBF con NTX o CDX o bien a tablas ADT con índices ADI. 

 En un próximo artículo discutiremos otra técnica relacionada con esta llamada "alias dinámicos" que hará que no tengas que preocuparte prácticamente por nada que tenga que ver con el acomodo de las tablas en memoria.