Arquitectura Xbase, ¿ Cómo es por dentro un DBF ?

René Flores López 03.may.2002

Xbase es el nombre que se le da genéricamente a todos aquellos lenguajes de programación cuyo conjunto de instrucciones y funciones son un derivado de los de dBase III+, y que manejan archivos de datos que tienen la estructura conocida como DBF (DataBase File).

Cuando desarrollamos un sistema, la mayoría de las veces nos preocupamos por los campos que deben contener las estructuras de los archivos de datos, sus relaciones, sus índices y sobre todo, como los tiene que  utilizar nuestro programa para lograr los resultados que nos han solicitado, pero… ¿ te has puesto a pensar alguna vez como ocure el amacenamiento y búsqueda de datos internamente dentro de Clipper ?, seguramente no, simplemente sabemos que lo hcae, pero pocas veces reparamos en COMO lo hace. Este pequeño articulo te enseñara como es por dentro ese viejo desconocido nuestro que es el archivo DBF.

El formato nativo de archivos de datos de Clipper es la estructura conocida como DBF, que fue mostrada por primera vez en el dBase III+, si bien muchos programadores "modernos" la consideran vieja, obsoleta y decadente, el archivo DBF tiene la sencillez de un clasico, visto desde el punto de vista romántico, un DBF es como las Piramides de Egipto, un ejemplo vivo de una cultura antigua pero que ha durado por siglos, por mucho, más tiempo que obras maestras de la arquitecutra moderna como Las Torres Gemelas de NY,  porque fue creado para durar.

Un archivo DBF puede almacenar los siguientes tipos de datos

Caracter (Character) Cadenas de caracteres de longitud variable de hasta 64 kbytes de longitud
Numérico (Numeric) Hasta 19 dígitos de longitud, contando el punto decimal en campos no enteros
Fecha (Date) Longitud fija de 8 bytes
Lógica (Logic) Valores booleanos (.T. y .F.) de 1 byte de longitud
Memo Cadenas de caracteres de longitud variable los cuales se almacenan en un archivo con la extensión .DBT para el NTX y FPT para el CDX.
Nota: El valor mínimo para un campo memo es de 512 bytes cuando se utiliza el RDD NTX y va aumentando en la misma proporción, es decir, si tenemos un texto de 514 bytes, se utilizarán 2 bloques de 512 bytes para guardarlo, aunque solo se utilicen 2 bytes del segundo bloque, un DBT no "recicla" el espacio una vez borrado el registro con campo memo, lo que significa que el espacio ocupado por el memo del registro borrado permanecerá desperdiciado hasta que se realice una operación de PACK sobre la base de datos.
En el caso de los campos memo para CDX, el valor mínimo para un campo memo es de 1 byte, aunque por omisión se maneje un valor de 15 bytes, estos memos ofrecen la ventaja de que su tamaño mínimo puede ser establecido por el usuario desde programa.

El archivo DBF está dividido en 2 partes: el Area de Encabezado (Header Area) y el Area de Datos (Data Area).

El Area de Encabezado almacena información sumamente importante, a su vez está dividida en 2 partes: Informacion del archivo (file information) y Descripción de campos (Field description).

Inmediatamente después de terminar el Area de Encabezado comienza el Area de Datos, donde se almacena la información como tal de la base de datos, lo datos son almacenados en bloques x+1 bytes donde x es la longitud total de todos los campos descritos y el byte adicional (+1) es la bandera de borrado. Este byte de borrado tiene un valor de 20h (espacio) cuando el registro está activo y un 2Ah (asterisco *) cuando el registro ha sido marcado para borrar, esto en el caso de los drivers NTX y CDX, si se esta utilizando Advantage Database Server este byte se utiliza para controlar los procesos de transacciones cuando estas se manejan en entornos Cliente / Servidor.

Es necesario recordar que DOS no elimina nada cuando se graba un archivo en el disco; cuando un DBF es creado en un área de disco donde previamente se ha escrito y borrado información, existe la posiblidad de que los espacios que permanecen vacios en el header (ver siguiente tabla) se llenen de caracteres de basura, sin embargo, esto no tiene consecuencias en la estructura de la base de datos.

Todos los datos en el Area de encabezado están escritos en formato hexadecimal.

El formato del encabezado en su sección Información de Archivo es como sigue:

Byte Información almacenada
0 Este byte indica si se ha especificado la existencia de campos memo. Si no hay memos, el valor es 03h, si los campos memo existen para este archivo el valor es un 83h.
Tip: Todos los programas que puedan leer archivos DBFs, como Excel o Access, utilizan este byte para identificar al archivo como un DBF, asi que una buena técnica de protección consiste en cambiar estos valores por cualquier otro, asi ninguna otra herramienta tendra acceso a nuestros datos. En futuros artículos explicaré con mayores detalles esta técnica.
1 Almacena el año de la última actualización, por ejemplo si el archivo fue modificado en 1999 contendrá un valor de 63h, si el año fue el 2000 tendrá un valor de 00h (NUL) y así sucesivamente, si te das cuenta sólo se almacenan los dos últimos dígitos del año.
2 Almacena el mes de la última actualización, Enero (01h), Febrero (02h), Marzo (03h), Abril (04h), Mayo (05h), Junio (06h), Julio (07h), Agosto (08h), Septiembre (09h), Octubre (0Ah), Noviembre (0Bh) y Diciembre (0Ch)
3 Almacena el día de la última actualización, también en formato hexadecimal, su valor mínimo es 1 (01h) y el máximo es 31 (1Fh)
4 al 7 Estos 4 bytes almacenan el número de registros dentro de la base de datos. De aquí se obtiene el valor de la función LastRec().
 
Mito: ¿ Cuantos registros le caben a un DBF ?, ¿ es cierto que su tamaño es infinito ?, ¿estan realmente limitados?, de una forma u otra, un DBF SI ESTA LIMITADO en el número de registros que puede tener, el valor de estos 4 bytes nos puede dar una buena idea de cuantos registros le caben a un dbf si pensamos que su valor máximo es de FFFFFFFFh, asi que en teoría, el número máximo de registros que le caben a un DBF es de 4,294,967,295 (si, leiste bien, de acuerdo al valor máximo de esta parte de la estructura, a un DBF le caben CUATRO MIL DOSCIENTOS NOVENTA Y CUATRO MILLONES, NOVECIENTOS SESENTA Y SIETE MIL DOSCIENTOS NOVENTA Y CINCO registros).
8 y 9 Declaran la longitud del encabezado (ver mas adelante la sección de Descripcion de campos)
10 y 11 Indican la longitud total del registro, incluyendo la bandera de borrado
 
 
Mito: Se ha hablado mucho del número de campos que puede contener la estructura de un DBF, unos dicen que 100, unos que mas, otros que menos, lo cierto es que el número de campos que puede contener un DBF, va en razón directa de su longitud, entre menor sea la longitud mas campos podemos tener, la pregunta es ¿ existe un límite real para el número de campos ?, la respuesta es : SI, el número de campos esta precisamente establecido por estos 2 bytes, cuyo máximo valor es FFFFh (65535), mientras la longitud de todos los campos de la base de datos no exceda este valor, podremos tener tantos campos como necesitemos.
12 al 31 No se usan.

Descripción de campos: Después del los primeros 31 bytes de la sección de Información de archivo, viene la sección de Descripción de campos, que comienza en el byte 32 (20h). Cada campo dentro de la estructura del DBF, sin importar su tipo ni su longitud, genera 32 bytes dentro de esta sección, de tal forma que si nuestra base de datos tiene 10 campos, esta sección medirá 320 bytes, si tiene 100 medirá 3200 bytes y as sucesivamente, como es fácil adivinar esta sección no tiene una longitud fija, ya que está en función del número de campos que tenga nuestro DBF.

En general, cada campo proporciona la siguiente información a la sección de descripción de campo:

Byte Información almacenada
0 al 10 Estos 11 bytes almacenan el nombre del campo. Los nombres de los campos pueden tener de 1 a 10 caracteres de longitud. El caracter que se utiliza para indicar la terminación del nombre de campo es un 00h, lo que signfica que si el nombre es seguido por 00h y tiene menos de los 10 bytes reservados para el nombre del campo, los bytes sobrantes se desperdician.
11 Especifica el tipo de campo
 
Caracter 43h (C)
Numérico 4Eh (N)
Fecha 44h (D)
Lógico 4Ch (L)
Memo 4Dh (M)
12 al 15 Cuatro bytes que especifican el espacio que ocupará en la memoria el campo.
16 Especifica la longitud del campo
17 Especifica la longitud decimal del campo, si se trata de un campo numérico no entero
18 al 31 No se usan.

Después de la última descripción de campo, se escribe el caracter que indica la terminación, que es un 0Dh. A partir de este punto, y hasta el final del archivo se extiende el Area de datos, que estudaremos mas adelante.

Como podrás observar, la longitud del encabezado depende del número de campos especificados. Cada campo agrega 32 bytes a la longitud total del área de encabezado; lo que quiere decir que si tu base de datos tiene dos campos, su Area de Encabezado tendrá una longitud de 96 bytes (32 bytes de información de archivo, mas 64 bytes de descripción de campos, 32 por cada campo).

El Area de datos y el almacenamiento de la información:

El área que sigue inmediatamente después del Area de Encabezado es el Area de Datos (Data Area). Cada registro dentro de nuestro DBF tiene una longitud fija, como la descripción de campos. Los datos son recuperados siempre por su posición en relación al principio del registro; no se utiliza ninguna señal de delimitación para separar los campos, el motor de base de datos utiliza la información de la sección Información de Archivo bytes 10 y 11 para calcular en donde comienza y donde termina cada registro y recuperarlos de manera óptima, porque en esta sección todo se almacena en formato ASCII, en otras palabras el Area de datos es un simple archivo de texto.

La forma de almacenamiento es como sigue:

  • Las cadenas de caracteres se almacenan en formato ASCII
  • Los valores numéricos se almacenan en formato decimal usando el punto (.) como un separador entre la parte entera del número y la parte decimal. Un signo de menos (-) puede preceder a los valores numéricos.
  • Los campos lógicos contienen un signo de interrogación (?) hasta que se modifique el campo, los valores válidos son Y,N,T,F,y,n,t,f
  • Los campos de fecha contienen 4 bytes. Los primeros dos bytes almacenan la porción correspondiente al año (incluyendo el siglo), el tercer byte contiene el mes y el cuarto contiene el día (los separadores de día, mes y año NO SON PARTE DEL CAMPO FECHA, como muchos programadores piensan).
  • Finalmente los campos memo contienen una referencia de 10 bytes al bloque correspondiente dentro del archivo .DBT, cuando se utiliza el RDD NTX, cuando se utiliza el CDX, estos mismos 10 bytes contienen adicionalmente un apuntador de doble liga al archivo FPT correspondiente, lo cual garantiza una comunicación de 2 vias entre DBF y FPT, reduciendo el riesgo de generar corrupción de datos en uno u otro archivo.

El algoritmo de recuperación de datos es bastante sencillo de imaginar, si yo ejecuto desde mi programa una instrucción DBGOTO(5), internamente el motor de datos obtendra la longitud de todo el registro a partir de la informacion contenida en los bytes 10 y 11 de la sección  Información de archivo, una vez calculada la longitud total del registro, esta se multiplica por 5, y el motor de base de datos sabe el número de bytes que deberá desplazarse a partir del comienzo del area de datos hasta el comienzo del registro 5, una vez que ha llegado al principio del registro que queremos recuperar, nuevamente se utiliza la longitud del registro para saber cuantos bytes tiene que leer y acomodarlos en sus respectivas variables de nombre de campo. Como podrás apreciar, la estructura DBF provee de una solucion sencilla y elegante para la manipulación de datos, aunque muchos la consideren vieja y obsoleta.

Dedicado con todo cariño a Dorien Mast,
Esperamos verte recuperada pronto de tu Rizotomia Dorsal Selectiva
¿ Quieres conocer a Dorien ? Visita http://www.dorienmast.be

Programación supermodular

Manuel Calero Solís 24.abr.2002

«Cuanto más se dividen los obstáculos son más fáciles de vencer«.
Concepción Arenal (1820-1893); escritora y socióloga española.

Hace algunos meses en una conversación a través del messenger con mi amigo Paco, empezamos a hablar de las distintas técnicas de programación y hablamos de una que yo llevaba tiempo practicando pero sin haberla aprendido de ningún libro, solo llegando a ella por la experiencia propia.

Posiblemente tenga un nombre, posiblemente usted ya la este practicando,  y posiblemente este en los libros, pero como a mi me ha valido de mucho la expongo aquí.

Yo la llamo programación «Supermodular». No espere nada revolucionario, es un nombre que se me ha ocurrido y creo que no la define muy bien pero es el nombre que he encontrado.

¿De que se trata? Se trata de crear muchas funciones o metodos que hagan cálculos mínimos e ir subiendo hasta obtener el resultado deseado.

Me explico,  o lo intento,  con un caso concreto: imagine que estamos trabajando en un programa, mas concretamente en un modulo del programa, por ejemplo en el calculo de una factura, y decimos mentalmente, «Total de factura es igual a unidades por precios de tantas líneas como contenga la factura, mas el I.V.A.». Esto seria de manera simple y sin tener en cuenta muchos otros factores, descuentos, comisiones, etc., bueno estamos en un ejemplo.

 Como resolvemos el problema.

Codigo 1
//-------------
Metodo Total()
nTotalFactura := 0
while cCodigoFactura == cCodigoFacturaLinea
   nTotalFactura += nUnidades * nPrecio
end while
nTotalFactura += nTotalFactura * nPorcentajeIVA / 100
return ( nTotalFactura)
//--------------

¿Pero que tiene de malo este código?

En principio nada o mucho según se mire, el problema esta resuelto pero no hemos pensado en el futuro, en lo que pasará mañana o a mas tardar pasado.

Mañana ha llegado, y nuestro jefe nos dice que han decidido que ahora las facturas deben de soportar Cajas, o sea que puedas facturar por Cajas. Fácil ¿ no ?.

Codigo 2
//------------
Metodo Total()
TotalFactura := 0
while cCodigoFactura == cCodigoFacturaLinea
   nTotalFactura += nUnidades * nCajas * nPrecio
end while
nTotalFactura += nTotalFactura * nPorcentajeIVA / 100
return ( nTotalFactura)
//--------------

El tema esta resuelto, pero hemos vuelto a hacerlo mal. Hemos resuelto el calculo de la factura pero debemos de tocar ahora todos los informes donde se nos pedían las unidades vendidas de una factura, y todos los gráficos, toda la impresión de la factura, y todo lo que mantenga una relación directa con la factura.

¿Qué propongo? Supermodular o dicho de otra manera menos rara: hacer funciones o métodos por cada dato que se necesite para hacer un calculo superior o por cada dato que será empleado en otra parte del programa. Pasemos a la practica.

Codigo 3
//--------
Metodo nUnidades()
Return ( nUnidades )
//-------
Metodo nPrecio()
Return  ( nPrecio )
//-------
Metodo nTotalLinea()
Return ( nUnidades() * nPrecio() )
//-------
Metodo nBaseFactura()
nTotalFactura := 0
while cCodigoFactura == cCodigoFacturaLinea
   nTotalFactura += nTotalLinea()
   skip
end while
return ( nTotalFactura )
//-------
Metodo nIva(nTotalFactura)
nIva := nTotalFactura * nPorcentajeIVA / 100
//-------
Metodo Total()
nTotalFactura := nBaseFactura() + nIva( nTotalFactura )
return ( nTotalFactura)
//--------

Si ahora me proponen el cambio en el calculo de las unidades se me pone una sonrisa de oreja a oreja y contesto a mi jefe ‘sin problemas jefe’

Codigo 4
//----------
Metodo nUnidades()
Return ( nUnidades * Cajas )
//----------

Pasado mañana mi jefe dirá que nuestra factura debe soportar el punto verde, ‘ ningún problema jefe’.

Codigo 5
//----------
Metodo nPrecio()
Return ( nPrecio + nPuntoVerde )
//----------

Y todo el programa saldrá funcionando, sin más problemas. Como veis la idea es muy simple lo verdaderamente complicado es saber que cálculos debemos de atomizar y cuales no, como norma todo aquellos datos que sospechéis van a ser utilizados en otras partes del programa, por ejemplo, es muy probable q necesitemos durante la vida de nuestro programa saber el numero de unidades facturadas de un determinado producto, ese dato nos lo da el método nUnidades(), y esta filosofía trasladarla a todo el programa.

Si algo he aprendido es que merece la pena pararse 30 min. sobre lo que vamos a hacer. Antes de codificar debemos coger un papel en blanco y pintar, meditar y reflexionar lo que se desea antes de escribir una sola línea de código, lo he aprendido pero a veces aun me precipito y lo pago siempre.

Saludos.

Fivewin no necesita un IDE, necesita un Glade

Dentro de la comunidad de desarrolladores que usamos Fivewin el tema del IDE ha sido siempre recurrente. Cada cierto tiempo aparece uno o varios mensajes en el foro de Fivetech sacando el tema a relucir y pidiendo que de una vez se termine el dichoso IDE. Hace poco tiempo Manuel Mercado, miembro destacado de la comunidad de desarrolladores, lanzó un guante con el proyecto VisualFivewin, y a raíz de los trabajos que ha realizado no pongo en duda de que sea capaz de llevarlo a cabo. Antes de seguir quiero dejar claro mi apoyo a Manuel en este proyecto y que he realizado la aportación para suscribir el proyecto porque pienso que una herramienta de este tipo va a ser beneficiosa para la comunidad de desarrolladores.

A mi nunca me han gustado los IDE, principalmente porque vengo de la escuela de la programación imperativa donde un programa tiene un principio y un final. Un IDE lo que hace es encapsular el código de los eventos de los widgets de un formulario con el formulario en sí, de manera que todo el proceso de diseño de formularios y la creación del código se realiza desde un entorno cerrado y que no hay manera de desenlatar. Además esto lleva a una micro fragmentación del código del programa: cada evento asociado a cada widget guarda su código por separado y es prácticamente imposible ver la secuencia lógica de las acciones que realiza el programa.

En Fivewin las cosas se han hecho tradicionalmente de otra manera. Usamos el editor de recursos de Borland – Resource Workshop – para dibujar los formularios de nuestra aplicación y con un editor de código definimos el comportamiento de estos controles. A mi entender esto es fantástico, pues por una parte tenemos una herramienta – hay que reconocer que un tanto anticuada – para pintar los formularios y por otra tenemos nuestro código que podemos leer y analizar secuencialmente aunque se trate de eventos asociados a los widgets de un formulario y que se pueden disparar en cualquier orden. El editor de recursos guarda los recursos como archivos DLL o RC y Fivewin se encarga de reproducirlos los formularios en tiempo de ejecución y de que se comporten de acuerdo al código que nosotros hayamos creado.

En gnome han planteado un entorno de desarrollo muy similar al que utiliza Fivewin, pero añadiendo una capa más. Todo el entorno gráfico esta realizado sobre las librerías GTK y existe una herramienta que sirve para crear formularios que con los widgets de GTK. Esta herramienta se llama GLADE y existen las librerías libglade que al enlazarlas con una aplicación permite que la aplicación reproduzca los formularios que se han creado con GLADE. Por si fuera poco GLADE permite almacenar los formularios en formato XML y libglade se puede enlazar con cualquier compilador que soporte interfaz con el lenguaje C. Asi desde casi cualquier lenguaje de los disponibles para gnome – C, perl, python,… – podemos utilizar un formulario hecho con GLADE. GLADE también es capaz de generar código en para distintos lenguajes de programación como C, C++, Ada,… En resumidas cuentas la idea es tener una herramienta independiente para crear el aspecto de la aplicación y otra separada para definir su comportamiento. A raiz por lo visto con gnome2 este enfoque es muy bueno. Parece que hay un proyecto para migrar GLADE a Windows de la misma manera que se está haciendo con GTK, esto es algo que habrá que seguir atentamente.

Sinceramente creo que la filosofía de separar la creación de los formularios del código que definirá su comportamiento tiene muchas ventajas sobre la aproximación de un IDE cerrado. Hasta ahora Fivewin no ha tenido un IDE y sinceramente pienso que no le hace falta. Mejor sería contar con algo parecido a GLADE.