Глава 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";

Комментарий

Возможно, удаление файла с последующим созданием выполняется быстрее, чем очистка, но при этом возникает опасность подмены, которая может нарушить работу неосторожной программы или сделать ее уязвимой для нападения. В промежуток между удалением файла и ег
При удалении базы данных 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-файлов затраты могут стать непозволительно большими. Д

> Смотри также -------------------------------
Документация но стандартным модулям 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 обладает тем же недостатком. Объединение хэшей посредством списковой интерпретации требует, чтобы хэши были предварительно загружены в память, что может привести к созданию огромных временных списков. Если вы работаете
Между этими двумя способами объединения есть еще одно отличие - в том, как они поступают с ключами, присутствующими в обоих базах. Присваивание пустого списка просто заменяет первое значение вторым. Итеративный перебор позволяет принять решение, как поступить с дубликатом. Возможные варианты - выдача предупреждения или ошибки, сохранение первого экземпляра, замена первого экземпляра вторым, конка

> Смотри также - Рецепты 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 позволяет создать хэш в памяти с сохранением порядка вставки, но это не поможет при р
Модуль DB_File содержит изящное решение этой проблемы за счет использования В-деревьев. Одно из преимуществ В-дерева перед обычным DBM-хэшем -его упорядоченность. Когда пользователь определяет функцию сравнения, любые вызовы keys, values и each автома Пример 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 позволяет удобно работать с файлом как с простым массивом строк - как правило, все полагают, что именно этот вариант яв
Однако этот способ работы с файлами отличается некоторыми странностями. Прежде всего, нулевой элемент связанного массива соответствует первой строке файла. Еще важнее то, что связанные массивы не обладают такими богатыми возможностями, как связанные х Как видно из приведенного выше примера, интерфейс связанного массива ограничен. Чтобы расширить его возможности, методы DB_File имитируют стандартные операции с массивами, в настоящее время не реализованные в шгп 'п-фейс связанных массивов Perl. Сохраните
$Х->рush(СПИСОК)

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

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

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

Возвращает количество элементов в массиве.
Пример 14.5 показывает, как все эти методы используются на практике. Кроме того, он работает с интерфейсом API так, как рассказано в документации модуля DB_File (большая часть рецепта позаимствована из документации DB_filec согласия Пола Маркесса, авт
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, в которых размер ключей или значений не ограничивается.

> Смотри также --------------------------------
Документация по модулям 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
Пример 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, который работает медле use MLDBM qw(DB_File Storable):

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

Используются технологии uCoz