HTML page

Глава 14 Базы данных

14.2. Очистка DMB-файла

Проблема

Требуется стереть все содержимое DBM-файла.

Решение

Откройте базу данных и присвойте ей (). При этом можно использовать функцию dbmopen:
dbmopen(%HASH, $FILENAME, 0666)
or die "Can't open FILENAME: $!\n";
%HASH =();
dbmclose %HASH;
или tie:
use DB_File;
tie(%HASH, "DB_File", $FILENAME)
or die "Can't open FILENAME: $!\n";
%HASH =();
untie %hash;

Существует и другое решение - удалить файл и открыть его заново в режиме создания:
unlink $FILENAME
or die "Couldn't unlink $FILENAME
to empty the database: $!\n"; -dbmopen(%HASH, $FILENAME, 0666)
or die "Couldn't create $FILENAME database: $!\n";

Комментарий

Возможно, удаление файла с последующим созданием выполняется быстрее, чем очистка, но при этом возникает опасность подмены, которая может нарушить работу неосторожной программы или сделать ее уязвимой для нападения. В промежуток между удалением файла и его повторным созданием нападающий может создать ссылку, указывающую на жизненно важный файл /etc/precious, с тем же именем, что и у вашего файла. При открытии файла библиотекой DBM содержимое /etc/precious будет уничтожено.
При удалении базы данных DB_File с повторным созданием теряются значения всех настраиваемых параметров - размер страницы, фактор заполнения и т. д. Это еще один веский довод в пользу присваивания связанному хэшу пустого списка.

[> Смотри также -------------------------------
Документация по стандартному модулю DB_File; рецепт 14.1. Функция unlink описана в perlfunc(1).

14.3. Преобразование DBM-файлов

Проблема

У вас имеется файл в одном формате DBM, однако другая программа желает по лучить данные в другом формате DBM.

Решение

Прочитайте ключи и значения из исходного DBM-файла и запишите их в другой файл в другом формате DBM, как показано в примере 14.2. Пример 14.2. db2gbdm
#!/usr/bin/perl -w
# db2gdbm: преобразование DB в GDBM
use strict;
use DB_File;
use GDBM_File;
unless (@ARGV == 2) {
die "usage: db2gdbm infile outfile\n";
}
my ($infile, $outfile) = @argv;
my (%db_in, %db_out);
# Открыть файлы
tie(%db_in, 'DB_File', $infile)
or die "Can't tie $infile: $!";
tie(%db_out, 'GDBM_File', $outfile, GDBM_WRCREAT, 0666)
or die "Can't tie $outfile: $!";
# Скопировать данные (не пользуйтесь %db_out = %db_in,
# потому что для больших баз это работает медленно)
while (my($k, $v) = each %db_in) { $db_out{$k} = $v;
}
# Функции untie вызываются автоматически при завершении программы
untie %db_in;
untie %db_out;

Командная строка выглядит так:
% db2gdbm /Imp/users.db /tmp/users.gdbm

Комментарий

Если в одной программе используются различные типы DBM-файлов, вам придется использовать интерфейс tie, а не dbmopen. Дело в том, что интерфейс
dbmopen позволяет работать лишь с одним форматом баз данных и поэтому считается устаревшим.
Копирование хэшей простым присваиванием (%new = %old) работает и для dbm-файлов, однако сначала все данные загружаются в память в виде списка. Для малых хэшей это несущественно, но для больших dbm-файлов затраты могут стать непозволительно большими. Для хэшей баз данных лучше использовать перебор с помощью функции each.

> Смотри также -------------------------------
Документация но стандартным модулям GDBM_File, NDBM_File, SDBM_File, DB_Filc; рецепт 14.1.

14.4. Объединение DBM-файлов

Проблема

Требуется объединить два DBM-файла в один с сохранением исходных пар "ключ/значение".

Решение

Либо объедините базы данных, интерпретируя их хэши как списки:
%OUTPUT = (%input1, %input2):

либо (более разумный вариант) организуйте перебор нар "ключ/значение":
%OUTPUT =();
foreach $href ( \%INPUT1, \%INPUT2 ) {
while (my($key, $value) = each(%$href)) { if (exists $output{$key}) {
# Выбрать используемое значение
# и при необходимости присвоить
$OUTPUT{$key} } else {
$OUTPUT{$key} = $value;
}
}
}

Комментарий

Прямолинейный подход из рецепта 5.10 обладает тем же недостатком. Объединение хэшей посредством списковой интерпретации требует, чтобы хэши были предварительно загружены в память, что может привести к созданию огромных временных списков. Если вы работаете с большими хэшами и/или не располагаете достаточной виртуальной памятью, организуйте перебор ключей в цикле each - это позволит сэкономить память.
Между этими двумя способами объединения есть еще одно отличие - в том, как они поступают с ключами, присутствующими в обоих базах. Присваивание пустого списка просто заменяет первое значение вторым. Итеративный перебор позволяет принять решение, как поступить с дубликатом. Возможные варианты - выдача предупреждения или ошибки, сохранение первого экземпляра, замена первого экземпляра вторым, конкатенация обоих экземпляров. Используя модуль MLDBM, можно даже сохранить оба экземпляра в виде ссылки на массив из двух элементов.

> Смотри также - Рецепты 5.10; 14.8.

14.5. Блокировка DBM-файлов

Проблема

Необходимо обеспечить одновременный доступ к DBM-файлу со стороны нескольких параллельно работающих программ.

Решение

Воспользуйтесь реализацией механизма блокировки DBM, если он имеется, и заблокируйте файл функцией flock либо обратитесь к нестандартной схеме блокировки из рецепта 7.21.

Комментарий

SDBM и GDBM не обладают возможностью блокировки базы данных. Вам придется изобретать нестандартную схему блокировки с применением дополнительного файла.
В GDBM используется концепция доступа для чтения или записи: файл GDBM в любой момент времени может быть открыт либо многими читающими процессами, либо одним записывающим. Тип доступа (чтение или запись) выбирается при открытии файла. Иногда это раздражает. Версия 1 Berkeley DB предоставляет доступ к файловому дескриптору открытой базы данных, позволяя заблокировать его с помощью flock. Блокировка относится к базе в целом, а не к отдельным записям. Версия 2 реализует собственную полноценную систему транзакций с блокировкой.
В примере 14.3 приведен пример блокировки базы данных с применением Berkeley DB. Попробуйте многократно запустить программу в фоновом режиме, чтобы убедиться в правильном порядке предоставления блокировок. Пример 14.3. dblockdemo
#!/usr/bin/perl
# dblockdemo - демонстрация блокировки базы данных dbm
use DB_File;
use strict;
sub LOCK_SH { 1 } # На случай, если у вас нет
sub LOCK_EX { 2 } # стандартного модуля Fcntl.
sub LOCK_NB { 4 } # Конечно, такое встречается редко,
sub LOCK_UN { 8 } # но в жизни всякое бывает.
my($oldval, $fd, $db, %db, $value, $key);
$key = shift || 'default';
$value = shift || 'magic';
$value ,= " $$";
$db = tie(%db, 'db_file', '/tmp/foo.db', 0_creat|0_rdwr, 0666)
or die "dbcreat /tmp/foo.db $!";
$fd = $db->fd; и Необходимо для блокировки
print "$$: db fd is $fd\n";
open(DB_FH, "+<&=$fd")
or die "dup $!";
unless (flock (DB_FH, LOCK_SH [ LOCK_NB)) {
print "$$: CONTENTION;
" can't read during write update! Waiting for read lock ($!) ....";
unless (flock (DB_FH, LOCK_SH)) { die "flock: $'oo } }
print "$$: Read lock granted\n";
$oldval = $db{$key};
print "$$: Old value was $oldval\n";
flock(DB_FH, LOCK_UN);
unless (flock (DB_FH, LOCK_EX | LOCK_NB)) {
print "$$: CONTENTION;
must have exclusive lock! Waiting for write lock ($!) ....";
unless (flock (DB_FH, LOCK_EX)) { die "flock: $!" }
}
print "$$: Write lock granted\n";
$db{$key} = $value;
$db->sync; # to flush sleep 10;
flock(DB_FH, LOCK_UN);
undef $db;
untie %db;
close(DB_FH);
print "$$: Updated db to $key=$value\n'


> Смотри также ----------
Документация по стандартному модулю DB_File; рецепты 7.11; 16.12.

14.6. Сортировка больших DBM-файлов

Проблема

Необходимо обработать большой объем данных, которые должны передаваться н DBM-файл в определенном порядке.

Решение

Воспользуйтесь возможностью связывания В-деревьев модуля DB_File и предо ставьте функцию сравнения:
use DB_File:
# Указать функцию Perl, которая должна сравнивать ключи
# с использованием экспортированной ссылки на хэш $DB_BTREE
$DB_BTREE->{'compare'} = sub {
my ($key1, $key2) =.@_ ;
"\L$key1" cmp "\.L$key2" ;
};
tie(%hash, "DB_File", $filename, 0_RDWR|0_CREAT, 0666, $DB_BTREE)
or die "can't tie $filename: $!";

Комментарий


Основной недостаток хэшей (как в памяти, так и в DBM-файлах) заключается в том, что они не обеспечивают нормального упорядочения элементов. Модуль Tie::IxHash с CPAN позволяет создать хэш в памяти с сохранением порядка вставки, но это не поможет при работе с базами данных DBM или произвольными критериями сортировки.
Модуль DB_File содержит изящное решение этой проблемы за счет использования В-деревьев. Одно из преимуществ В-дерева перед обычным DBM-хэшем -его упорядоченность. Когда пользователь определяет функцию сравнения, любые вызовы keys, values и each автоматически упорядочиваются. Так, программа из примера 14.4 создает хэш, ключи которого всегда сортируются без учета регистра символов. Пример 14.4. sortdemo
#! /usr/bin/per-l
# sortdemo - автоматическая сортировка dbm
use strict;
use DB_File;
$DB_BTREE->{'compare'} = sub {
my ($key1, $key2) = @>_ ;
"\L$key1" cmp "\L$key2" ;
};
my %hash;
my $filename = '/tmp/sorthash.db';
tie(%hash, "DB_File", $filename, 0_RDWR|0_CREAT, 0666, $DB_BTREE)
or die "can't tie $filename: $!";
my $i = 0;
for my $word (qw(Can't you go camp down by Gibraltar))
{ $hash{$word} = ++$i;
}
while (my($word, $number) = each %hash)
{ printf "%-12s %d\n", Sword, $number;

По умолчанию записи баз данных В-деревьев DB_File сортируются по алфавиту. Однако в данном случае мы написали функцию сравнения без учета регистра, поэтому применение each для выборки всех ключей даст следующий результат:
by 6
camp 4
Can't 1
down 5
Gibraltar 7
go 3
you 2
Эта возможность сортировки хэша настолько удобна, что ей стоит пользоваться даже без базы данных на диске. Если передать tie вместо имени файла undef, DB_File создаст файл в каталоге /tmp, а затем немедленно уничтожит его, создавая анонимную базу данных: tie(%hash, "DB_File", undef, 0_RDWR|0_CREAT, 0666, $DB_BTREE) or die "can't tie: $!"; Обеспечивая возможность сравнения для своей базы данных в виде В-дерева, необходимо помнить о двух обстоятельствах. Во-первых, при создании базы необходимо передавать новую функцию сравнения. Во-вторых, вы не сможете изменить порядок записей после создания базы; одна и та же функция сравнения должна использоваться при каждом обращении к базе.
Базы данных BTREE также допускают использование повторяющихся или неполных ключей. За примерами обращайтесь к документации.

> Смотри также --------------------------------
Рецепт 5.6.

14.7. Интерпретация текстового файла в виде строковой базы данных

Проблема

Требуется организовать работу с текстовым файлом как с массивом строк с привилегиями чтения/записи. Например, это может понадобиться для того, что-оы вы могли легко обновить N-ю строку файла.

Решение

Модуль DB_File позволяет связать текстовый файл с массивом.
use DB_File;
tie(@array, "DB_File", "/tmp/textfile", 0_RDWR|0_CREAT, 0666, $DB_RECNO)
or die "Cannot open file 'text': $!\en" ;
$array[4] = "a new line";
untie @array;

Комментарий

Обновление текстового файла на месте может оказаться на удивление нетривиальной задачей (см. главу 7 "Доступ к файлам"). Привязка RECNO позволяет удобно работать с файлом как с простым массивом строк - как правило, все полагают, что именно этот вариант является наиболее естественным.
Однако этот способ работы с файлами отличается некоторыми странностями. Прежде всего, нулевой элемент связанного массива соответствует первой строке файла. Еще важнее то, что связанные массивы не обладают такими богатыми возможностями, как связанные хэши. Положение будет исправлено в будущих B( ]'-сиях Perl - в сущности, "заплаты" существуют уже сейчас. Как видно из приведенного выше примера, интерфейс связанного массива ограничен. Чтобы расширить его возможности, методы DB_File имитируют стандартные операции с массивами, в настоящее время не реализованные в шгп 'п-фейс связанных массивов Perl. Сохраните значение, возвращаемое функцп' ,' или получите его позднее для связанного хэша функцией tied. Для этого объем:! можно вызывать следующие методы:
$Х->рush(СПИСОК)

Заносит элементы списка в конец массива.
$value = $x->pop

Удаляет и возвращает последний элемент массива.
$X->shift
Удаляет и возвращает первый элемент массива.
$X->unshift(CnHCOK)

Заносит элементы списка в начало массива.
$X->length

Возвращает количество элементов в массиве.
Пример 14.5 показывает, как все эти методы используются на практике. Кроме того, он работает с интерфейсом API так, как рассказано в документации модуля DB_File (большая часть рецепта позаимствована из документации DB_filec согласия Пола Маркесса, автора Perl-порта Berkeley DB; материал использован с его разрешения).
recno_demo
#!/usr/bin/perl -w
# recno_demo - применение низкоуровневого API для привязок recno
use strict;
use vars qw(@lines $dbobj $file $i);
use DB_File;
Stile = "/tmp/textfile";
unlink $file; # На всякий случай
$dbobj = tie(@lines, "db_file", $file, 0_rdwr|0_creat, 0666, $db_recno)
or die "Cannot open file $file: $!\n";
# Сначала создать текстовый файл.
$lines[0] = "zero":
$lines[1] = "one";
$lines[2] = "two":
$lines[3] = "three";
$lines[4] = "four";
# Последовательно вывести записи.
#
# Метод length необходим из-за того, что при использовании
# связанного массива в скалярном контексте,
# не возвращается количество элементов в массиве.
print "\nORIGINAL\n";
foreach $i (0 .. $dbobj->length - 1) { print "$i: $lines[$i]\n";
}
# Методы push и pop
$a = $dbobj->pop;
$dbobj->push("last");
print "\nThe last record was [$a]\n";
# Методы shift и unshift
$a = $dbobj->shift;
$dbobj->unshift("first");
print "The first record was [$a]\n";
# Использовать API для добавления новой записи после записи 2.
$i = 2;
$dbobj->put($i, "Newbie". R_IAFTER);
# и еще одной новой записи после записи 1.
$i = 1:
$dbobj->put($i, "New One", R_IBEFORE);
# Удалить запись З
$dbobJ->del(3);
# Вывести записи в обратном порядке
print "\nREVERSE\n";
for ($i = $dbobj->length - 1: $i >= 0: -- $i)
{ print "$i: $lines[$i]\n";
}
# To же самое, но на этот раз с использованием функций API
print "\nREVERSE again\n";
my ($s, $k, $v) = (О, О, О);
for ($s = $dbobj->seq($k, $v, R_LAST);
$s == 0;
$s = $dbobj->seq($k, $v, R_PREV))
{
print "$k: $v\n"
}
undef $dbobj:
untie alines;

Результат выглядит так:
ORIGINAL 0: zero
1: one
2: two
3: three
4: four
The last record was [four] The first record was [zero] REVERSE 5 last 4 three 3 Newbie 2 one 1 New One 0 first REVERSE again
5 last
4 three
3 Newbie
2 one
1 New One
0 first
Обратите внимание: для перебора массива @lines вместо
foreach $item (@lines) { }
следует использовать либо
foreach $1 (0 .. $dbobj->length - 1) { }
либо
for ($done_yet = $dbobj->get($k, $v, R_FIRST);
not $done_yet;
$done_yet = $dbobj->get($k, $v, R_NEXT) )
}
# Обработать ключ или значение
}

Кроме того, при вызове метода put мы указываем индекс записи с помощью переменной $i вместо того, чтобы передать константу. Дело в том, что put возвращает в этом параметре номер записи вставленной строки, изменяя его значение.
> Смотри также -------------------------------
Документация по стандартному модулю DB_File.

14.8. Хранение сложных структур данных в DBM-файлах

Проблема

В DBM-файле требуется хранить не скаляры, а что-то иное. Например, вы используете в программе хэш хэшей и хотите сохранить его в DBM-файле, чтобы с ним могли работать другие или чтобы его состояние сохранялось между запусками программы.

Решение

Воспользуйтесь модулем MLDBM от CPAN - он позволяет хранить в хэше более сложные структуры, нежели строки или числа.
use MLDBM 'DB_File';
tie(%HASH, 'MLDBM', [... прочие аргументы DBM]) or die $!;

Комментарий

MLDBM использует модуль Data::Dumper (см. рецепт 11.14) для преобразования структур данных в строки и обратно, что позволяет хранить их в DBM-файлах. Модуль не сохраняет ссылки; вместо них сохраняются данные, на которые эти ссылки указывают:
# %hash - связанный хэш
$hash{"Tom Christiansen"} = [ "book author", 'tchrist@perl.com' ];
$hash{"Tom Boutell"} = [ "shareware author", 'boutell@boutell.com' ];
# Сравниваемые имена
$name1 = "Тот christiansen";
$name2 = "Тот boutell";
$tom1 = $hash{$name1}; # Получить локальный указатель
$tom2 = $hash{$name2}; # И еще один
print "Two Toming: $tom1 $tom2\n";
ARRAY(Ox73048)ARRAY(Ox73e4c)

Каждый раз, когда MLDBM извлекает структуру данных из файла DBM, строится новая копия данных. Чтобы сравнить данные, полученные из базы данных MLDBM, необходимо сравнить значения полей этой структуры:
if ($tom1->[0] eq $tom2->[0] &&
$tom1->[1] eq $tom2->[1]) {
print "You're having runtime fun with one Tom made two.\n";
} else {
print "No two Toms are ever alike.\n";
}

Этот вариант эффективнее следующего:
if ($hash{$name1}->[0] eq $hash{$name2}->[0] && # НЕЭФФЕКТИВНО
$hash{$name1}->[1] eq $hash{$name2}->[1]) {
print "You're having runtime fun with one Tom made two.\n";
} else {
print "No two Toms are ever alike.\n";
}

Каждый раз, когда в программе встречается конструкция $hash{. . .}, происходит обращение к DBM-файлу. Приведенный выше неэффективный код обращается к базе данных четыре раза, тогда как код с временными переменными $tom111 $tom2 обходится всего двумя обращениями. Текущие ограничения механизма tie не позволяют сохранять или модифицировать компоненты MLDBM напрямую:
$hash{"Tom Boutell"}->[0] = "poet programmer"; # НЕВЕРНО


Любые операции чтения, модификации и присваивания для частей структур!. хранящейся в файле, должны осуществляться через временную переменную:
$entry = $hash{"tom boutell"}; # ВЕРНО
$entry->[0] = "poet programmer";
$hash{"Tom Boutell"} = sentry;
Если MLDBM использует базу данных с ограниченным размером значении (например, SDBM), вы довольно быстро столкнетесь с этими ограничениями. Чтобы выйти из положения, используйте GDBM_File или DB_File, в которых размер ключей или значений не ограничивается. Предпочтение отдается библиотеке DB_File, поскольку она использует нейтральный порядок байтов, что позволяет использовать базу данных в архитектурах как с начальным старшим, так и с начальным младшим байтом.

> Смотри также --------------------------------
Документация по модулям Data::Dumper, MLDBM и Storable от CPAN; рецепты 11.13; 14.9.

14.9. Устойчивые данные

Проблема

Вы хотите, чтобы значения переменных сохранялись между вызовами программы.

Решение

Воспользуйтесь модулем MLDBM для сохранения значений между вызовами программы:
use MLDBM 'DB_File';
my ($VARIABLE1,$VARIABLE2);
my $Persistent_Store = '/projects/foo/data';
BEGIN {
my %data;
tie(%data, 'MLDBM', $Persistent_Store)
or die "Can't tie to $Persistent_Store : $!";
$VARIABLE1 = $data{variable1};
$VARIABLE2 = $data{variable2};
# . . .
untie %data;
} END {
my %data;
tie (%data, 'MLDBM', $Persistent_Store)
or die "Can't tie to $Persistent_Store : $!":
$data{VARIABLE1} = $variable1;
$data{VARIABLE2} = $variable2;
# . . .
untie %data;
}

Комментарий

Существенное ограничение MLDBM заключается в том, что структуру нельзя дополнить или изменить по ссылке без присваивания временной переменной. Мы сделаем это в простой программе из примера 14.6, присваивая $array_ref перед вызовом push. Следующая конструкция просто невозможна:
push(@{$db{$user}}, $duration):

Прежде всего, этому воспротивится MLDBM. Кроме того, $db{$user} может отсутствовать в базе (ссылка на массив не создается автоматически, как это делалось бы в том случае, если бы хэш %db не был связан с DBM-файлом). Именно поэтому мы проверяем exists $db{$user} перед тем, как присваивать $array_ref исходное значение. Мы создаем пустой массив в случае, если он не существовал ранее
Пример 14.6. midbm-demo
#!/usr/bin/perl -w
# mldbm_demo - применение MLDBM с DB_File
use MLDBM "DB_File";
$db = "/tmp/mldbm-array";
tie %db, 'MLDBM', $db or die "Can't open $db : $!";
while() { chomp;
($user, $duration) = split(/\s+/, $_);
$array_ref = exists $db{$user} ? $db{$user} : [];
push(@$array_ret, $duration);
$db{$user} = $array_ref;
}
foreach $user (sort keys %db) { print "$user: ";
$total = 0;
foreach $duration (@{ $db{$user} }) {
print "$duration ";
$total += $duration;
}
print "($total)\n";
}

__END__
gnat 15.3
tchrist 2.5
jules 22.1
tchrist 15.9
gnat 8.7
Новые версии MLDBM позволяют выбрать не только модуль для работы с базами данных (мы рекомендуем DB_File), но и модуль сериализации (рекомендуем Storable). В более ранних версиях сериализация ограничивалась модулем Data::Dumper, который работает медленнее Storable. Для использования DB_File со Storable применяется следующая команда: use MLDBM qw(DB_File Storable):

Смотри также--------------------
Документация по модулям Data::Dumper, MLDBM и Storable с CPAN; рецепты 11.13; 14.8.
copyright 2000 Soft group