Sierra de Aizkorri

Menú

Enlaces

 

Berkeley DB



La base de datos de berkeley es una librería mediante la cual podemos crear bases de datos sin la necesidad de un servidor SQL. Tiene todo lo necesario para poder almacenar datos, borrarlos, navegar por ellos, duplicarlos, etc.

Esto no es un mega-howto de qué es y para qué sirve esta librería, solo voy a explicar por encima cómo crear la base de datos, tablas e insertar, borrar, buscar, y navegar por los registros.

para más información sobre esta librería podeis ir a la web de sleepycat (www.sleepycat.com), o en el caso de que la tengais ya instalada podeis acceder a la documentación de la API en file:///usr/share/doc/db4.2-doc/index.html

Muy buena ayuda, clara y explicativa, no se puede pedir más. No he encontrado prácticamente ningún howto sobre esta librería, alguno sobre como “tunear” la BD, pero no sobre cómo usar la ApI, probablemente debido a la buena ayuda que tiene.

De todas formas espero que le sirva a alguien :)



1.- Instalar la librería

2.- Leyendo la documentación

3.- Lo básico

4.- preparando los datos

5.- Insertar registros

6.- Extraer registros

7.- Borrar registros

8.- Usando la clase Cursor()

9.- Creando varias tablas

10.- Listar tablas de una BD

11.- Uso de la clase DbException para control de errores















1.- Instalar la librería

Dependiendo de la distribución de GNU/Linux que estemos usando la instalación será más o menos fácil. En mi caso uso GNU/Debian, y esta es la forma de instalarlo:

apt-get install libdb4.2++ libdb4.2++-dev

La razón de instalar libdb4.2++ en vez de libdb4.2, es que se va a explicar cómo programar esta BD en C++.

Recomiendo encarecidamente el paquete db4.2-doc y db4.2-util:

apt-get install db4.2-doc db4.2-util

La razón es porque el primer paquete es la documentación de la librería, muy completa; y el segundo son utilidades para comprobar y testear la BD una vez creada, sin tener que usar nuestro programa.







2.- Leyendo la documentación

Recomiendo encarecidameente que sea lea la documentación. Aunque esté en inglés es muy clara, y si se sabe un poco de inglés nos va a ser muy útil para comprender el funcionamiento de esta BD.

Además hay ejemplos muy claros, guía de errores comunes al programar con esta librería, una especie de manual o guía...







3.- Lo básico

Veamos que necesitamos como mínimo para compilar un progama usando esta BD:

#include <iostream> // -> Cabecera para salida/entrada estándar
#include <db_cxx.h> // -> Cabecera para las definiciones de clases de la librería
using namespace std;
int main()
{
return 0;
}

Compilación:

g++ prueba.cc -o prueba -ldb_cxx

podemos añadir a esta linea -ggdb para que nos compile con información extra para el depurador gdb, muy útil a la hora de encontrar problemas.

para crear nuestra primera base de datos, sabiendo lo de arriba haremos:

#include <iostream>
#include <db_cxx.h>
using namespace std;
int main()
{
Db d(0, 0);
if (d.open (NULL, “prueba.db”, “tabla”, DB_BTREE, DB_CREATE, 0) == 0){
cout << “BD prueba.db abierta y creada” << endl;
}
return 0;
}

Hemos creado una instancia de la clase Db, que la hemos llamado d; y hemos usado el método open() para crearla y abrirla.

La clase DbTxn es para recuperar una base de datos después de algún fallo, por ejemplo algún cuelgue del sistema o un segmentation fault. No la voy a usar en este howto, así que será siempre NULL.

open (NULL,

“prueba.db” // -> Nombre de la base de datos

“tabla” // -> Una tabla

DB_BtrEE // -> Formato de la base de datos, puede ser DB_HASH, DB_QUEUE y DB_RECNO

DB_CREATE // -> Esto son los atributos de la BD, se pueden combinar con el signo || (DB_CREATE || DB_EXCL)

0) // -> Son los permisos con los que se creará la base de datos, si es 0 se crea con permisos de lectura y escritura para el usuario y el grupo del usuario.







4.- Preparando los datos

Tenemos que pararnos a pensar qué es lo que vamos a guardar en un registro de la base de datos. ¿Una cadena? ¿Un entero? ¿Una estructura? ¿Una clase quizás?

Efectivamente, a diferencia de las bases de datos normales, donde comúnmente guardamos solo un dato por registro (dni, nombre, apellidos, etc), aquí podemos guardar lo que queramos por registro.

En el caso de que sea un dato solamente no hay problema, pero en el caso de estructuras y/o objetos nos puede dar unos pocos dolores de cabeza.

En el caso de estructuras y objetos, lo que tenemos que hacer es calcular el tamaño de cada miembro de la estructura/clase y sumarlo, para que le pasemos el tamaño correcto. Aunque por lo que he podido comprobar, si hacemos un cast de los datos obtenidos y lo copiamos a nuestra estructura también funciona.

Ejemplo:

struct datos{ .... }; struct datos temp;
if (d->get (0, key, datos, 0) == 0){
memove (&temp, (struct prueba *)datos->get_data(), sizeof(temp));
cout << “Dato 1: “ << temp.dato << endl;
...
}

Antes de pasarle los datos a los métodos, tenemos que inicializar las clases de clave y datos de la siguiente manera:

Dbt key, data;
memmove (&k, 0, sizeof(k));
memmove (&data, 0, sizeof(data));

Veamos en el siguiente punto qué son la “key” y el “data”.





5.- Insertar registros

A la hora de insertar registros tenemos que tener en cuenta 2 cosas, la clave y los datos. Las 2 son la misma clase, la Dbt, que nos permite almacenar en un buffer datos; la diferencia radica en que la primera la vamos a usar como campo clave y la segunda para los registros en sí.

Digamos que si normalmente defines en SQL un campo como clave, aquí, en la estructura/clase/dato que crees, tendrás que definir un campo para usarlo como clave (por ejemplo DNI). Esto nos va a facilitar enormemente buscar registros.

Ejemplo:

Dbt key (“80900234”) // -> DNI único, campo de clave único.

|___ Dbt datos

|__ Nombre: pepe – Apellidos: Goikoetxea – Calle: Bertonga – Edad: 23



para insertar registros usamos el método put:

Db::put(DbTxn *txnid, Dbt *key, Dbt *data, u_int32_t flags);

El primer parámetro será NULL en nuestro caso.

El segundo y el tercero las clases de datos.

y el tercero puede ser uno de estos:

- QUEUE y RECNO

DB_AppEND – Añade al final de la BD los datos

- BtrEE y HASH

DB_NODUpDATA – No permite insertar registros si ya existe uno igual

DB_NOOVERWRITE – En el caso de permitir datos duplicados, si especificamos esta opción, fallará si la CLAVE ya existe.

* Si la operación ha sido correcta, nos habrá devuelto un 0; en caso contrario nos devolverá un error.

* Si ha fallado puede ser uno de estos valores:

DB_KEYEXIST – La clave especificada ya existe

...



¿Cómo podemos insertar los datos?

Veamos un ejemplo:

struct prueba{

...

};

Dbt key, datos;

struct prueba pt;



memset (&datos, 0, sizeof(datos));

memset (&key, 0, sizeof(key));

cout << "Introduce datos" << endl;

cout << "Nombre: ";

cin >> pt.nombre;

cout << "Apellidos: ";

cin >> pt.apellidos;

cout << "DNI: ";

cin >> pt.dni;


datos->set_data (&pt);

datos->set_size (sizeof(pt));

key->set_data (&pt.dni); // Clave para buscar posteriormente registros

key->set_size (strlen (pt.dni) + 1);



switch (d->put (0, key, datos, 0)) // añade al inicio de la BD los regs

{

case DB_NOTFOUND:

printf ("cursor->put , notfound\n");

case DB_KEYEXIST:

printf ("cursor: Existe la clave\n");

case EACCES:

printf ("cursor: Intentando escribir en una db de solo lectura\n");

}









6.- Extraer/Buscar registros

// Buscar registros por clave, por clave y algún campo de la estructura (strcmp)

// Usar varias Bds físicas para linkar (simular) enlaces entre tablas.

// ¿Y usar varias instancias de Db y abrir la misma BD física pero distintas tablas para hacer búsquedas?

//



7.- Borrar registros

Para borrar registros de la BD es igual que para insertarlos, usamos una clave para definir el registro a borrar:

strncmp (clave, "12345", strlen(clave));
try{
Dbt k;
Db *dbtemp = new class Db (0, 0);
memset (&k, 0, sizeof(k));
k.set_data (&clave);
k.set_size (strlen (clave) + 1);
if (dbtemp->open (NULL, "prueba.db", NULL, DB_BTREE, 0, 0) == 0){
if (dbtemp->del (NULL, &k, 0) == 0)
mensaje ("Datos borrados correctamente", MESSAGE_INFO, BUTTONS_OK);
}
dbtemp->close (0);







8.- Usando la clase Cursor()

Con la clase Cursor() podemos recorrer todos los registros de la base de datos, así como ir al primero, o al último, en vez de proporcionarle una clave para sacar el registro que queremos.

El "truco" está en abrir la BD con formato desconocida (DB_UNKNOWN) y como solo lectura (DB_RDONLY). Lo que nos va a devolver va a ser el nombre de la clave con que hemos guardado ese dato.

try{

Db *db = new class Db (0, 0);

Dbc *dc;

Dbt k, data;

char datos[100];

memset (&data, 0, sizeof(data));

memset (&k, 0, sizeof(k));

switch (db->open (NULL, "texto.db", NULL, DB_UNKNOWN, DB_RDONLY, 0))

{

case 0:

{

db->cursor (0, &dc, 0);

while (dc->get (&k, &data, DB_NEXT) != DB_NOTFOUND){

memcpy (&datos, (char *)k.get_data(), sizeof(k));

}

break;

}







9.- Creando varias tablas









10.- Listar tablas de una BD

Tal y como dice la ayuda, no está mal abrir una base de datos sin especificar el tipo (BTREE, HASH, etc), pero para listar tablas tenemos que abrirla como DB_UNKNOWN y además como sólo lectura (DB_RDONLY).

if ((d.open (NULL, “prueba.db”, NULL, DB_UNKNOWN, DB_RDONLY, 0) == 0)
cout << “BD abierta correctamente” << endl;



Los nombres de las tablas van a ser los datos que extraigamos de la clave.

Ahora que ya la tenemos abierta, tenemos que navegar por los registros, para ello usamos la clase Cursor()



d->cursor (0, &pos, 0);
while (pos->get (&k, &data, DB_NEXT) != DB_NOTFOUND){
memove (dbs, (char *)k.get_data(), sizeof(char));
cout << “BD: ” << dbs << endl;
}
pos->close();









11.- Uso de la clase DbException para control de errores

Una manera muy elegante para controlar los errores que generemos sin querer al programar con esta librería, es usar la clase DbException. Nos mostrará un mensaje formateado con el error en cuestión, y evitaremos que se nos cierre el programa en caso de error.

Ejemplo:

try{

...

...

}catch (DbException &e){

cout << “Error” << e->what() << endl;

}



Si estamos programando con gtk o gtkmm, y pasamos a un MessageDialog la cadena del error, obtendremos una manera fácil y elegante de mostrar errores por pantalla.

Y si es el caso de estar usando gtk, gtkmm o glib/mm podemos usar también la clase Glib::Exception para capturar los errores y mostrarlos por pantalla.

try{

...

}catch(Glib::Exception &e){

cout << “Error:” << e->what() << endl;

}


 

Creative Commons License
This obra by http://kutxa.homeunix.org is licensed under a Creative Commons Reconocimiento-Compartir bajo la misma licencia 3.0 España License.

Esta publicación esta bajo la licencia creative commons, ello no evita la publicación de otros materiales en otro tipo de licencias libres. Por tanto, se permite difundir, citar y copiar literalmente sus materiales, de forma íntegra o parcial, por cualquier medio y para cualquier propósito, siempre que se mantenga esta nota y se cite procedencia.