DBMs Multinivel

La capacidad de un sistema o módulo para hacer que la vida o ámbito de los datos de una aplicación se prolongen mas allá de la ejecución de la aplicación se conoce con el nombre de persistencia.

Se conoce por serialización a los procesos encargados de empaquetar estructuras de datos anidadas como arrays de hashes etc. en una estructura plana y lineal. Es deseable que la representación de los datos sea independiente de la máquina, evitando problemas de representación de enteros y flotantes o del orden de los bytes.

Filtros para DBM

Es posible añadir un filtro que procese los accesos a un DBM. Podemos definir una subrutina que es llamada cada vez que almacenamos un valor en el hash DBM y otra que sea llamada cada vez que se lee una entrada del hash DBM.

El formato general de uso es:

   $db = tie %hash, 'DBM', ...

   $old_filter = $db->filter_store_key  ( sub { ... } );
   $old_filter = $db->filter_store_value( sub { ... } );
   $old_filter = $db->filter_fetch_key  ( sub { ... } );
   $old_filter = $db->filter_fetch_value( sub { ... } );

Usando estos filtros podemos serializar valores del hash que sean estructuras de datos complejas. En el siguiente código el hash DBM %h contiene como valores referencias a cadenas. El filtro vuelca con Data::Dumper el valor complejo y lo comprime usando la función compress del módulo Compress::Zlib. El proceso inverso consiste en descomprimir y evaluar:

$ cat -n  dbwithfilter.pl
 1  #!/usr/bin/perl
 2  use warnings;
 3  use Compress::Zlib;
 4  use DB_File;
 5  use Data::Dumper;
 6
 7  unlink 'mldbmtest.dat';
 8
 9  $h = tie my %db1, 'DB_File', 'mldbmtest.dat', O_CREAT | O_RDWR, 0666
10      or die "No se pudo inicializar el fichero MLDBM: $!\n";
11
12  $h->filter_store_value(sub { $_ = compress(Dumper($_)) });
13  $h->filter_fetch_value(sub { $_ = eval(uncompress($_)) });
14
15  %db1 = (
16      'alu2511' => [ 'a'x30 ],
17      'alu2233' => [ 'b'x30 ]
18  );
19
20  print Data::Dumper->Dump( [ \%db1 ] );

Este otro programa lee el hash DBM creado por el programa anterior:

$ cat -n dbwithfilterretrieve.pl
 1  #!/usr/bin/perl
 2  use warnings;
 3  use Compress::Zlib;
 4  use DB_File;
 5  use Data::Dumper;
 6
 7  my $h = tie my %db2, 'DB_File', 'mldbmtest.dat', O_RDWR, 0666
 8      or die "No se pudo leer el fichero MLDBM: $!\n";
 9  $h->filter_store_value(sub { $_ = compress(Dumper($_)) });
10  $h->filter_fetch_value(sub { $_ = eval(uncompress($_)) });
11
12  print Data::Dumper->Dump( [ \%db2 ] );

Cuando se ejecutan estos dos programas obtenemos la siguiente salida:

$ dbwithfilter.pl
$VAR1 = {
          'alu2233' => [ 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' ],
          'alu2511' => [ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ]
        };
lhp@nereida:~/Lperl/src$ dbwithfilterretrieve.pl
$VAR1 = {
          'alu2233' => [ 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' ],
          'alu2511' => [ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ]
        };

MLDBM

El módulo Multi level DBM MLDBM permite guardar estructuras de datos Perl en ficheros DBM (Véase 9.2). Para tenerlo instalado es necesario tener previamente instaladas las rutinas Berkeley Database Manager. El módulo utiliza DBM, proveyendo la funcionalidad para serializar estructuras de datos anidadas. DBM es una librería que maneja tablas hash en disco. La librería ha dado lugar a un buen número de variantes: SDBM, NDBM, GDBM, etc las cuales pueden ser accedidas a través de los correspondientes módulos Perl, los cualés hacen uso de tie para proporcionar un acceso transparente a la tabla almacenada en el disco.

Es por esto que cuando se usa admite como parámetros una especificacción de las librerías de manejo de DBM y del serializador. Por ejemplo:

     use MLDBM;                          # Manejadores por defecto: SDBM
     #use MLDBM qw(DB_File FreezeThaw);  # Usaremos FreezeThaw para serialización
     #use MLDBM qw(DB_File Storable);    # Usaremos Storable para serialización

Véase un ejemplo de uso del módulo MLDBM:

$ cat -n mldbmtest2.pl
 1  #!/usr/bin/perl
 2  use warnings;
 3  use Data::Dumper;
 4  use MLDBM qw( DB_File Storable );
 5  use Fcntl; # Para importar constantes O_CREAT O_RDWR
 6
 7  unlink 'mldbmtest.dat';
 8
 9  tie my %db1, 'MLDBM', 'mldbmtest.dat', O_CREAT | O_RDWR, 0666
10      or die "No se pudo inicializar el fichero MLDBM: $!\n";
11
12  %db1 = (
13      'alu2511' => {
14          nombre => 'Josefina Fernández Pérez',
15          tel => '922 00 00 00',
16          fecha => '22/07/84'
17      },
18      'alu2233' => {
19          nombre => 'Ana Feliú Forner',
20          tel => '922 00 11 22',
21          fecha => '14/06/85'
22      }
23  );
24
25  untie %db1;
26
27  tie my %db2, 'MLDBM', 'mldbmtest.dat', O_RDWR, 0666
28      or die "No se pudo leer el fichero MLDBM: $!\n";
29
30  print Data::Dumper->Dump( [ \%db2 ] );
31
32  untie %db2;
33
34  exit;
Cuando se ejecuta se obtiene el siguiente resultado:
 ./mldbmtest2
$VAR1 = {
  'alu2511' => {
     'fecha' => '22/07/84',
     'tel' => '922 00 00 00',
     'nombre' => 'Josefina Fernández Pérez'
  },
  'alu2233' => {
     'fecha' => '14/06/85',
     'tel' => '922 00 11 22',
     'nombre' => 'Ana Feliú Forner'
  }
};

Limitaciones

Una limitación que proviene de que los MLDBM y los DBM son atados es que sólo la escrituras directas del tipo $db1{one} = { a => 'e' } produce actualización. Una escritura indirecta como $db1{one}{a} = 'e' no produce actualización. Véase el siguiente ejemplo:

$ cat -n dbwithfilter2.pl
 1  #!/usr/bin/perl
 2  use warnings;
 3  use DB_File;
 4  use Data::Dumper;
 5
 6  unlink 'mldbmtest.dat';
 7
 8  $h = tie my %db1, 'DB_File', 'mldbmtest.dat', O_CREAT | O_RDWR, 0666
 9      or die "No se pudo inicializar el fichero MLDBM: $!\n";
10
11  $h->filter_store_value(sub { $_ = Dumper($_) });
12  $h->filter_fetch_value(sub { $_ = eval($_) });
13
14  %db1 = (
15      'one' => { a => 'b' },
16      'two' => { c => 'd' }
17  );
18
19  $db1{one}{a} = 'e';
20
21  $Data::Dumper::Indent = 0;
22  print "Asignacion indirecta:\n",Dumper( \%db1 ),"\n";
23
24  $db1{one} = { a => 'e' };
25
26  print "Asignacion directa:\n",Dumper( \%db1  ),"\n";
Cuando se ejecuta produce la siguiente salida:

lhp@nereida:~/Lperl/src$ dbwithfilter2.pl
Asignacion indirecta:
$VAR1 = {'one' => {'a' => 'b'},'two' => {'c' => 'd'}};
Asignacion directa:
$VAR1 = {'one' => {'a' => 'e'},'two' => {'c' => 'd'}};

La explicación reside en que al hacer $db1{one}{a} = 'e' no se produce una llamada a STORE; de hecho se produce una llamada a FETCH. Como no se llama a STORE no se produce la modificación de la estructura de datos atada.



Subsecciones
Casiano Rodríguez León
2009-10-04