Sistema de base de datos de Euphoria (EDS)
Introducción
Mucha gente ha mostrado interés en acceder a
bases de datos usando programas Euphoria. Algunos querían acceder
a sistemas de gestión de base de datos de marca conocida desde
Euphoria, otros querían una base de datos orientada a Euphoria
simple y fácil de usar. EDS es lo más reciente. Provee
un sistema de base de datos simple y extremadamente flexible, para usar
con programas Euphoria.
Estructura de una base de datos EDS
En EDS, una base de datos
es un archivo único con extensión .edb. Una base
de datos EDS contiene 0 o más tablas.
Cada tabla tiene un nombre, y contiene
0 o más registros. Cada registro
consta de una parte de clave, y otra
de datos. Laclave puede ser cualquier
objeto de Euphoria - un átomo, una secuencia, secuencias anidadas,
etc. Del mismo modo, los datos pueden ser cualquier
objeto de Euphoria. No hay restricciones
en el tamaño o estructura de la clave o los datos. Dentro de una
tabla, las claves son únicas. Esto significa, no hay dos registros
que tengan la misma clave.
Los registros de una tabla se almacenan en el orden
ascendente del valor de la clave. Se utiliza una eficiente búsqueda
binaria para referirse a los registros por clave. También se puede
acceder a un registro directamente, sin búsqueda alguna, conociendo
el número de registro dentro
de la tabla. Los números de registro son enteros desde 1 al tamaño
de la tabla (cantidad de registros). Incrementando el número de
registro se pueden recorrer eficientemente todos los registros. Sin embargo
tenga en cuenta que el número de registro puede cambiar toda vez
que se inserte un nuevo registro, o cuando un registro se borra.
Las partes de datos y claves se almacenan en forma compacta,
pero no se pierde exactitud al guardar
o recuperar números de punto flotante o
cualquier otro dato de Euphoria.
database.e trabajará
sin cambios, en Windows, DOS,
Linux o FreeBSD.
El código se ejecuta casi el doble de rápido en Linux/FreeBSD
que en DOS/Windows. Los archivos de la base de
datos EDS se pueden copiar y compartir entre programas que corren en Linux/FreeBSD
y DOS/Windows. Asegúrese de hacer una copia exacta byte
por byte usando el modo de copiado "binario", en lugar del modo
"texto" o "ASCII" porque podrían cambiar los
terminadores de línea.
Ejemplo:
base de datos: "misdatos.edb"
primera tabla: "accesos"
registro #1: clave: "García" dato: "gordito"
registro #2: clave: "Pérez" dato: "delgado"
segunda tabla: "repuestos"
registro #1: clave: 134525 dato: {"martillo", 15.95, 500}
registro #2: clave: 134526 dato: {"sierra", 25.95, 100}
registro #3: clave: 134530 dato: {"destornillador", 5.50, 1500}
Este ejemplo es para interpretar el significado de
la clave y de los datos. En consonancia con el
espíritu de Euphoria, Ud. tiene flexibilidad total.
A diferencia de otros sistemas de bases de datos, no
se necesita que un registro EDS tenga un número fijo de campos,
o campos con una longitud máxima predefinida.
En algunos casos no habrá ningún valor
natural para la clave en los registros. En esos casos, se debería
crear una clave entera sin significado alguno, pero única. Recordar
que siempre se puede acceder a los datos por número de registro.
Es fácil recorrer los registros buscando el valor de un campo en
particular.
¿Cómo acceder a los datos?
Para reducir la cantidad de parámetros que se
tienen que pasar, existe la noción de base
de datos en uso, y tabla en uso.
La mayoría de las rutinas usan este valor en uso
(o actual) automáticamente. Normalmente se empieza por abrir (o
crear) un archivo de base de datos, entonces se selecciona la tabla con
la que se trabajará.
Usando db_find_key(),
se puede mapear una clave a un número de registro. Este comando
usa una eficiente búsqueda binaria. La mayoría de las otras
rutinas a nivel de registro, esperan como argumento el número de
registro. Dado el número de registro, se accede muy rápidamente
a cualquier registro. También se puede acceder a todos los registros
comenzando por el número 1 y recorriéndolos todos hasta
el número de registro devuelto por db_table_size().
¿Cómo se recicla el almacenamiento?
Al borrar algo, como un registro, el espacio de ese
ítem se marca como disponible en una lista áreas libres
para uso posterior. Las áreas libres adyacentes se combinan en
grandes áreas libres. Al necesitar más espacio y no teniéndolo
disponible en la lista de áreas libres, el archivo crece en tamaño.
Normalmente no hay un método automático para comprimir el
tamaño del archivo, pero se puede usar db_compress()
para reescribir completamente la base de datos, removiéndole los
espacios sin uso.
Seguridad / Acceso multiusuario
Esta versión provee una forma sencilla de bloqueo de la base
de datos para prevenir de accesos inseguros de parte de otros procesos.
Escalabilidad
Los punteros internos son de 4 bytes. En teoría,
el tamaño máximo del archivo de una base de datos es 4 Gb.
En la práctica, el límite es de 2 Gb debido a limitaciones
en varias funciones de archivo de C usadas por Euphoria. Bajo pedido de
los usuarios, las bases de datos EDS podrían expendirse en el futuro,
más allá de los 2 Gb.
El algoritmo actual asigna 4 bytes de memoria por registro en la tabla
actual. Por lo tanto, se necesitan como mínimo 4Mb de RAM por cada
millón de registros en disco.
La búsqueda binaria por claves debería funcionar razonablemente
bien con tablas grandes.
Las inserciones y borrados tardan levemente un poco más, según
la tabla se haga más grande.
En el otro extremo de la escala, es posible crear bases
de datos extremadamente pequeñas sin incurrir en mucho gasto de
espacio en disco.
Renuncia
No almacenar datos importantes sin realizar una copia
de seguridad. RDS no se hará responsable de cualquier daño
o pérdida de información.
Rutinas de base de datos
En las descripciones posteriores, se usan los siguientes
prefijos para indicar el tipo de objeto
que se puede pasar o devolver:
db_create
Sintaxis: |
include database.e
i1 = db_create(s, i2)
|
Descripción: |
Crea una nueva base de datos. Una nueva base
de datos se creará en el archivo con ruta dada por 's'. 'i2'
indica el tipo de bloqueo que se aplicará al archivo creado.
'i1' es un código de error que indica éxito o fracaso.
Los valores de 'i2' pueden ser tanto DB_LOCK_NO (sin bloqueo) o
DB_LOCK_EXCLUSIVE (bloqueo exclusivo). Si 'i1' es DB_OK, significa
que la base de datos se creó exitosamente. Esta base de datos
se convierte en la base de datos en uso
a la que se aplicarán las demás operaciones de base
de datos. |
Comentarios: |
Si la ruta 's' no termina en .edb, esta extensión
se agregará automáticamente.
Si la base de datos ya existe, no se sobreescribirá.
db_create() devolverá
DB_EXISTS_ALREADY.
El número de versión se almacena
en el archivo de base de datos. De esta forma, las futuras versiones
del software de la base de datos podrán reconocer el formato,
leerlo y operar con él de alguna manera. |
Ejemplo: |
|
|
if db_create("misdatos", DB_LOCK_NO) != DB_OK then
puts(2, "No se puede crear la base de datos!\n")
abort(1)
end if
|
Ver también: |
db_open,
db_close
|
db_open
Sintaxis: |
include database.e
i1 = db_open(s, i2)
|
Descripción: |
Abre una base de datos Euphoria existente.
El archivo que contiene la base de datos está dado por 's'.
'i1' es un código de retorno que indica el éxito o
fracaso de la operación. 'i2' indica el tipo de bloqueo que
quiere aplicar a la base de datos mientras la abre. Esta base de
datos se convierte en la base de datos
en uso a la que se aplicarán las demás
operaciones de base de datos.
Los códigos de retorno son: |
|
global constant DB_OK = 0 -- éxito
DB_OPEN_FAIL = -1 -- no se puede abrir el archivo
DB_LOCK_FAIL = -3 -- no se puede bloquear el archivo de la
-- forma pedida |
Comentarios: |
Los tipos de bloqueo que se pueden usar son:
DB_LOCK_NO (sin bloqueo), DB_LOCK_SHARED (bloqueo compartido para
acceso de lectura solamente) y DB_LOCK_EXCLUSIVE (para acceso de
lectura/escritura). DB_LOCK_SHARED está soportado solamente
por Linux/FreeBSD.
Permite leer la base de datos, sin escribir absolutamente nada en
ella. Si se solicita DB_LOCK_SHARED en WIN32
o DOS32, será tratado
como si el pedido se hubiera efectuado con DB_LOCK_EXCLUSIVE.
Si el bloqueo falla, su programa debería
esperar unos segundos antes de reintentarlo. Otro proceso podría
estar accediendo a la base de datos en ese momento.
Típicamente, los programas DOS mostrarán
un mensaje de "error crítico" si intentan acceder a una base
de datos que está bloqueada.
|
Ejemplo: |
|
|
tries = 0
while 1 do
err = db_open("misdatos", DB_LOCK_SHARED)
if err = DB_OK then
exit
elsif err = DB_LOCK_FAIL then
tries += 1
if tries > 10 then
puts(2, "demasiados intentos, lo siento\n")
abort(1)
else
sleep(5)
end if
else
puts(2,"No se puede abrir la base de datos!\n")
abort(1)
end if
end while
|
Ver también: |
db_create,
db_close
|
db_select
Sintaxis: |
include database.e
i = db_select(s)
|
Descripción: |
Selecciona una nueva base de datos ya abierta,
para convertirla en la base de datos en
uso. Las operaciones subsecuentes de base de datos se
aplicarán a esta base. 's' es la ruta del archivo de base
de datos que originalmente se abrió con db_open()
o db_create().
'i' es un código de retorno que indica el éxito de
la operación (DB_OK) o su fracaso. |
Comentarios: |
Al crear ( db_create())
o abrir ( db_open())
una base de datos, ésta se convierte en la base
de datos en uso. Use db_select()
cuando quiera cambiar entre bases de datos abiertas, por ejemplo,
para copiar registros de una a otra.
Después de seleccionar una nueva base de
datos, se debería seleccionar una tabla de esa base, usando
db_select_table().
|
Ejemplo: |
|
|
if db_select("empleados") != DB_OK then
puts(2, "No se puede seleccionar la base de datos de empleados\n")
end if
|
Ver también: |
db_open
|
db_close
Sintaxis: |
include database.e
db_close()
|
Descripción: |
Desbloquea y cierra la base de datos en
uso. |
Comentarios: |
Llamar a este procedimiento cuando se haya
terminado de usar la base de datos en uso.
Se removerá cualquier bloqueo, permitiendo a otros procesos
acceder al archivo de base de datos. |
Ver también: |
db_open |
db_create_table
Sintaxis: |
include database.e
i = db_create_table(s)
|
Descripción: |
Crea una tabla nueva dentro de la base
de datos en uso. El nombre de la tabla está dado
por la secuencia de caracteres 's' y no puede ser igual al de ninguna
otra tabla existente en la base de datos
en uso. |
Comentarios: |
La tabla que se crea tiene inicialmente 0 registros y además,
se convierte en la tabla en uso.
|
Ejemplo: |
|
|
if db_create_table("mi_nueva_tabla") != DB_OK then
puts(2, "No se puede crear mi_nueva_tabla!\n")
end if
|
Ver también: |
db_delete_table
|
db_select_table
Sintaxis: |
include database.e
i = db_select_table(s) |
Descripción: |
La tabla cuyo nombre está dado por 's',
se convierte en la tabla en uso.
El código de retorno 'i' será DB_OK si la tabla existe
en la base de datos en uso,
de lo contrario obtendrá DB_OPEN_FAIL. |
Comentarios: |
All record-level database operations apply
automatically to the tabla en uso. |
Ejemplo: |
|
|
if db_select_table("salario") != DB_OK then
puts(2, "No se puede hallar la tabla salario!\n")
abort(1)
end if
|
Ver también: |
db_create_table,
db_delete_table
|
db_delete_table
Sintaxis: |
include database.e
delete_table(s) |
Descripción: |
Borrar una tabla de la base de datos en uso. El nombre de la tabla está dado
por 's'. |
Comentarios: |
Se borran todos los registros y el espacio
ocupado por la tabla se libera. Si la tabla es la tabla
en uso, ésta queda indefinida.
Si no existe una tabla con el nombre dado por
's', no ocurre nada. |
Ver también: |
db_create_table,
db_select_table
|
db_table_list
Sintaxis: |
s = db_table_list()
|
Descripción: |
Devuelve una secuencia con todos los nombres
de tablas de la base de datos en uso.
Cada elemento de 's' es una secuencia de caracteres que contiene
el nombre de una tabla. |
Ejemplo: |
|
|
sequence names
names = db_table_list()
for i = 1 to length(names) do
puts(1, names[i] & '\n')
end for
|
Ver también: |
db_create_table
|
db_table_size
Sintaxis: |
include database.e
i = db_table_size()
|
Descripción: |
Devuelve la cantidad de registros de la tabla
en uso. |
Ejemplo: |
|
|
-- recorre todos los registros de la tabla en uso
for i = 1 to db_table_size() do
if db_record_key(i) = 0 then
puts(1, "No se encontró la clave\n")
exit
end if
end for
|
Ver también: |
db_select_table
|
db_find_key
Sintaxis: |
include database.e
i = db_find_key(x)
|
Descripción: |
Busca el registro de la tabla
en uso con el valor de clave 'x'. Si se encuentra, se
devuelve el número de registro. Si no se encuentra, se devuelve
el número de registro que debería tener la clave,
como número negativo. |
Comentarios: |
Se usa una búsqueda binaria para encontrar
la clave en la tabla en uso.
La cantidad de comparaciones es proporcional al logaritmo de la
cantidad de registros en la tabla.
Se puede seleccionar un rango de registros, buscando
los valores primero y último de la clave. Si esos valores
de la clave no existen, al menos se obtiene un valor negativo que
muestra donde debería estar si existiesen. Suponer que se
quiere saber cuales registros tienen claves mayores que "GGG" y
menores que "MMM". Si se obtiene -5 para la clave "GGG", significa
que un registro de clave "GGG" debería insertarse como registro
número 5. -27 para "MMM" significa que un registro de clave
"MMM" debería insertarse como registro número 27.
Esto indica rápidamente que todos los registros , >= 5 y
< 27 cumplen la condición.
|
Ejemplo: |
|
|
rec_num = db_find_key("Millennium")
if rec_num > 0 then
? db_record_key(rec_num)
? db_record_data(rec_num)
else
puts(2, "No se encuentra, pero si lo insertara,\n")
puts(2, "su número de registro sería #%d\n", -rec_num)
end if
|
Ver también: |
db_record_key,
db_record_data,
db_insert
|
db_record_key
Sintaxis: |
include database.e
x = db_record_key(i)
|
Descripción: |
Devuelve la parte de clave del número de registro indicado
por 'i' de la tabla en uso. |
Comentarios: |
Cada registro en una base de datos Euphoria
consta de una parte de clave y otra de datos, pudiendo ser cada
uno de ellos cualquier átomo o secuencia de Euphoria. |
Ejemplo: |
|
|
puts(1, "La clave del registro Nº 6 es: ")
? db_record_key(6)
|
Ver también: |
db_record_data
|
db_record_data
Sintaxis: |
include database.e
x = db_record_data(i)
|
Descripción: |
Devuelve la parte de datos del número de registro indicado
por 'i' de la tabla en uso. |
Comentarios: |
Cada registro en una base de datos Euphoria
consta de una parte de clave y otra de datos, pudiendo ser cada
uno de ellos cualquier átomo o secuencia de Euphoria. |
Ejemplo: |
|
|
puts(1, "El dato del registro Nº 6 es: ")
? db_record_data(6)
|
Ver también: |
db_record_key
|
db_insert
Sintaxis: |
include database.e
i = db_insert(x1, x2)
|
Descripción: |
Inserta un nuevo registro en la tabla
en uso. La clave del registro es 'x1' y el dato del registro
es 'x2'. Tanto 'x1' como 'x2' pueden ser cualquier objeto de datos
de Euphoria, átomos o secuencias. El código de retorno
'i1' será DB_OK si el registro se inserta. |
Comentarios: |
Dentro de una tabla, todas las claves tienen
que ser únicas. Si ya existe un registro con ese mismo valor
de clave, db_insert()
fallará obteniendo DB_EXISTS_ALREADY. |
Ejemplo: |
|
|
if db_insert("Pérez", {"Pedro", 100, 34.5}) != DB_OK then
puts(2, "Falló la inserción del registro!\n")
end if
|
Ver también: |
db_find_key,
db_record_key,
db_record_data
|
db_delete_record
Sintaxis: |
include database.e
db_delete_record(i)
|
Descripción: |
Borra el número de registro indicado por 'i' de la tabla
en uso. |
Comentarios: |
El número de registro 'i' tiene que ser un entero entre 1
y la cantidad de registros de la tabla en
uso. |
Ejemplo: |
|
|
db_delete_record(55)
|
Ver también: |
db_insert,
db_table_size
|
db_replace_data
Sintaxis: |
include database.e
db_replace_data(i, x)
|
Descripción: |
Reemplaza los datos de número de registro 'i' en la tabla
en uso, por 'x'. 'x' puede ser cualquier secuencia o átomo
de Euphoria. |
Comentarios: |
El número de registro 'i' tiene que estar entre 1 y la cantidad
de registros de la tabla en uso.
|
Ejemplo: |
|
|
db_replace(67, {"Pedro", 150, 34.5})
|
Ver también: |
db_delete_record
|
db_compress
Sintaxis: |
include database.e
i = db_compress()
|
Descripción: |
Comprime la base de
datos en uso. La base de datos
en uso se copia a un nuevo archivo, de forma tal que
los bloques de espacio sin usar se eliminan. Si no ocurre ningún
error, 'i' se establece a DB_OK, y el nuevo archivo de base de datos
mantendrá el mismo nombre. Por seguridad, el archivo original
descomprimido se renombrará con la extensión .t0 (o
.t1, .t2 ,..., .t99). Si la compresión no tiene éxito,
la base de datos permancerá sin cambios y la copia de seguridad
no se realizará. |
Comentarios: |
Al borrar ítems de una base de datos,
se crean bloques de espacio vacío dentro del archivo. El
sistema los tiene en cuenta, intentando usarlos como nuevo espacio
de almacenamiento cada vez que se realiza una inserción de
datos. db_compress()
copiará la base de datos en uso
sin incluir esas áreas libres. El tamaño del archivo
de base de datos, por ende, disminuirá.
Si los archivos de copia de seguridad alcanzan la extensión
.t99, se deberán borrar alguno de ellos.
|
Ejemplo: |
|
|
if db_compress() != DB_OK then
puts(2, "Falló la compresión!\n")
end if
|
Ver también: |
db_create
|
db_dump
Sintaxis: |
include database.e
db_dump(fn, i)
|
Descripción: |
Vuelca los contenidos de una base de datos
Euphoria ya abierta. Los contenidos se descargan en un archivo o
dispositivo 'fn'. Se muestran todos los registros de todas las tablas.
Si 'i' no es cero, entonces también se muestra el volcado
byte a byte de bajo nivel. El volcado de bajo nivel solamente será
significativo para quienes conozcan el formato interno de una base
de datos Euphoria. |
Ejemplo: |
|
|
if db_open("misdatos", DB_LOCK_SHARED) != DB_OK then
puts(2, "No se puede abrir la base de datos!\n")
abort(1)
end if
fn = open("db.txt", "w")
db_dump(fn, 0)
|
Ver también: |
db_open |
db_fatal_id
Sintaxis: |
include database.e
db_fatal_id = i
|
Descripción: |
Se pueden capturar ciertos errores fatales
de base de datos, instalando un gestor de errores. Simplemente se
sobreescribe la variable global db_fatal_id
con un identificador de una rutina creada por el programador. El
procedimiento tiene que tomar un argumento único, como una
secuencia. Al ocurrir ciertos errores, se llamará a este
procedimiento con un mensaje de error como argumento. Este procedimiento
debería terminar con una llamada a abort().
|
Ejemplo: |
|
|
procedure mi_error_fatal(sequence msg)
puts(2, "Ocurrió un error fatal - " & msg & '\n')
abort(1)
end procedure
db_fatal_id = routine_id("mi_error_fatal")
|
Ver también: |
db_close
|
|