Acceder a Berkeley DB 5.x desde Delphi [english version] <Delphi <Indice

¿Qué es Berkeley DB? (Wikipedia) - Página oficial (Web de Oracle)

Introducción a Berkeley DB

Berkeley DB (BDB) es una librería multiplataforma escrita en C que proporciona una base de datos embebida de alto rendimiento para datos de tipo clave/valor. Soporta miles de threads o procesos accediendo simultáneamente a las bases de datos, que pueden llegar a ocupar hasta 256 terabytes.
No es una base de datos relacional ni soporta SQL ni cliente-servidor. Tampoco tiene conocimiento sobre el formato de los datos que almacenamos en ella, sino que se le suministran pares clave/valor y los almacena byte a byte tal cual se los pasamos. Sí soporta transacciones ACID, bloqueos, caché, snapshots, copia de seguridad en caliente y replicación.
Nota: Oracle ha incluido en las últimas versiones soporte para SQL incorporando una versión de la librería SQLite modificada para que use Berkeley DB como gestor de almacenamiento. En lugar del API tradicional de BDB utiliza el de SQLite, por lo que puede sustituirla en aplicaciones que la usen. La wiki de SQLite tiene una recopilación de librerías de acceso desde múltiples lenguajes, entre ellos Delphi.

Almacenamiento de datos

Una base de datos BDB equivale a una tabla más un índice en una base de datos relacional típica. Es posible tener índices secundarios, ocupando cada uno su propia base de datos, y, si se desea, pueden almacenarse varias bases de datos en un mismo fichero.
El funcionamiento básico gira alrededor del almacenamiento y recuperación del valor asociado a una clave de búsqueda. No hay restricciones en el contenido de ninguna de ellas y tanto una como otra pueden ocupar hasta 4 gigabytes.
En cierto modo, una base de datos equivale a una tabla con dos columnas, clave y valor; sólo que tanto la clave como el valor pueden tener una estructura compleja (p.ej. en Delphi podrían ser cada uno de ellos un record).
Hay 4 métodos básicos de almacenamiento:
  • BTree: los datos se almacenan en un árbol B. Pueden hacerse búsquedas por clave, filtros por un rango de valores de la clave y recorridos por orden de clave.
  • Hash: los datos se almacenan en una tabla hash. No hay un orden definido. Es preferible a BTree en bases de datos extremadamente grandes.
  • Queue: los datos se almacenan como una cola de registros de longitud fija y la clave es un número de registro lógico. Permite inserciones muy rápidas por el final y recuperar y borrar el registro que esté al principio.
  • Recno: los datos se almacenan como registros de longitud fija o variable y la clave es un número de registro lógico. Tiene la ventaja respecto a Queue de que el número de registro es modificable y de que los registros pueden tener longitud fija o variable. La desventaja es que los bloqueos son a nivel de página, mientras que en Queue son a nivel de registro.

Licencia

BDB es open source y se distribuye bajo licencia dual:
  1. La Sleepycat Public License, una licencia open source de tipo copyleft, que obliga a que si distribuimos nuestra aplicación distribuyamos también o demos acceso al código fuente.
  2. Una licencia comercial de Oracle (actual propietaria de los derechos de BDB), que permite utilizar el producto en una aplicación de código cerrado y distribuirla.
Nota: si la aplicación no se distribuye externamente (p.ej. se va a utilizar exclusivamente dentro de la misma empresa que la desarrolla), estará dentro de los términos de la Sleepycat Public License, por lo no será necesario adquirir la licencia comercial de Oracle. Al no distribuirla tampoco será necesario que el código fuente esté accesible.

Acceso desde Delphi

Existen librerías para acceder a BDB desde múltiples lenguajes de programación, pero no había ninguna para Delphi.
Dado que Delphi es capaz de acceder directamente a la DLL que contiene el motor BDB, sirve con convertir el fichero db.h que contiene el API de acceso original para C.
En realidad sí existe una conversión para Berkeley DB 4.5.20 que forma parte de un intento inacabado y aparentemente abandonado de encapsulación del API en una librería de componentes Delphi, pero no sirve para las versiones actuales 5.x y no es fácil actualizarla debido a que el API ha cambiado bastante y a que la peculiar estructura que tiene obliga a a que la conversión sea perfecta para que funcione bien, por lo que he optado por hacer una nueva conversión.
En internet pueden encontrarse varios conversores automáticos (p.ej. C to Pascal Converter y JEDI Darth Header Conversion Tool), pero son bastante rudimentarios y no son muy útiles en este caso dada la especial complejidad de este API, que utiliza extensivamente punteros a funciones, por lo que ha sido imprescindible convertir manualmente grandes partes de él.

Descargas (versión 5.1.25)

Descargas principales:
Unit db_h.pas con el API de acceso a Berkeley DB (conversión de db.h) descargar
Fichero con las DLLs necesarias para utilizar Berkeley DB descargar
Proyecto Delphi con ejemplos de acceso a Berkeley DB descargar
Descargas suplementarias:
Validador de que la conversión a Delphi es correcta descargar
Fichero db.h a partir del cual se creó el db_h.pas descargar
Berkeley DB 5.1.25 completo (instalador) descargar

Compatibilidad entre versiones

Cada versión de BDB tiene su propio db.h, con las declaraciones del API de acceso, y una DLL asociada (libdb5x.DLL) que es la que implementa el motor de la base de datos. Es muy importante tener en cuenta que no pueden mezclarse ficheros de versiones distintas.
Como consecuencia, tampoco puede mezclarse el db_h.pas con una DLL que no sea la que coincida exactamente con la versión del db.h a partir del cual se convirtió.
Un simple cambio del número de revisión, p.ej. de 5.0 a 5.1, puede implicar cambios en valores de constantes y en la composición de las estructuras del API, cambios que obligan a recompilar utilizando el nuevo db.h los programas que queramos que accedan a la DLL de la nueva versión.
Con respecto a db_h.pas, cada vez que haya un cambio de versión/revisión, habrá que volver a convertirlo partiendo del db.h de la nueva versión. También es posible actualizarlo convirtiendo y aplicando los cambios que haya habido entre el db.h a partir del cual se hubiera obtenido y el de la nueva versión.

Validar db_h.pas contra db.h

La validación comprueba:
  • Los valores de las constantes.
  • El tamaño de los tipos enumerados y los valores de los elementos de la enumeración.
  • El tamaño de los tipos de datos, incluyendo los struct/record.
  • El tamaño y la posición de los campos de los struct/record, incluyendo los punteros a funciones.
    Es muy importante comprobar ambos para evitar posibles errores de alineación.
Si se tiene instalada la distribución de Berkeley DB y se dispone de un compilador de C++ para Windows:
  1. Compilar bdb_test_c.cpp (utiliza el db.h de la distribución BDB)
    p.ej. desde el Visual Studio Command Prompt: cl /EHsc bdb_test_c.cpp
  2. Ejecutar bdb_test_c.exe (generará el fichero bdb_test_c.txt)
Si no se tiene instalada la distribución de Berkeley DB o no se dispone de un compilador de C++:
  1. Utilizar el fichero bdb_test_c.txt suministrado con el validador
En ambos casos:
  1. Compilar bdb_test_pas.dpr con Delphi (generará el fichero bdb_test_pas.exe)
  2. Ejecutar bdb_test_pas.exe (generará el fichero bdb_test_pas.txt)
  3. Comparar los dos ficheros txt: fc bdb_test_c.txt bdb_test_pas.txt
  4. La comparación debe concluir que los ficheros son iguales.

Ejemplo de uso del API de Berkeley DB desde Delphi

type
  PKey = ^TKey;
  TKey = record
    Id: integer;
  end;

  PData = ^TData;
  TData = record
    Name: array[0..50] of AnsiChar;
  end;
  
var
  FDB: PDB;
  cursor: PDBC;
  dbtKey, dbtData: DBT;
  key: PKey;
  data: PData;
  
begin
  // inicializa el API
  InitBerkeleyDB;

  // crea el handle de acceso
  CheckBDB(db_create(FDB, nil, 0));
  try
    // abre la base de datos
    CheckBDB(FDB.open(FDB, nil, 'Test.db', 'Test', DB_BTREE, DB_CREATE_, 0));

    // crea un cursor para recorrer los registros por orden de clave
    CheckBDB(FDB.cursor(FDB, nil, @cursor, 0));
    try
      FillChar(dbtKey, Sizeof(DBT), 0);
      FillChar(dbtData, Sizeof(DBT), 0);
  
      // recorre todos los registros e imprime cada uno
      while CheckAndFoundBDB(cursor.get(cursor, @dbtKey, @dbtData, DB_NEXT)) do begin
        key := PKey(dbtKey.data);
        data := PData(dbtData.data);
        Memo.Lines.Add('Key: '+IntToStr(key.Id));
        Memo.Lines.Add('Data: '+data.Name);
      end;
    finally
      // libera el cursor
      CheckBDB(cursor.close(cursor));
    end;
  finally
    // cierra la base de datos y libera el handle
    CheckBDBandNil(FDB.close(FDB, 0), FDB);
  end;
end;

Página modificada el 20/3/2021. © 2008-2021 JRL - A Coruña, Spain. All rights reserved.