Spec-Zone.ru › Perl 5.38

DB_File

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • СИНТАКСИС
  • ОПИСАНИЕ
    • Использование DB_File с Berkeley DB версии 2 или выше
    • Интерфейс с Berkeley DB
    • Открытие файла базы данных Berkeley DB
    • Параметры по умолчанию
    • Базы данных в оперативной памяти
  • DB_HASH
    • Простой пример
  • DB_BTREE
    • Изменение порядка сортировки BTREE
    • Обработка дублирующихся ключей
    • Метод get_dup()
    • Метод find_dup()
    • Метод del_dup()
    • Сопоставление частичных ключей
  • DB_RECNO
    • Опция 'bval'
    • Простой пример
    • Дополнительные методы RECNO
    • Еще один пример
  • ИНТЕРФЕЙС API
  • ФИЛЬТРЫ DBM
    • Базовый интерфейс фильтра DBM
    • Фильтр
    • Пример — проблема с завершением NULL
    • Еще один пример — ключ — целочисленная переменная C
  • СОВЕТЫ И ХИТРОСТИ
    • Блокировка: Проблема с fd
    • Безопасные способы блокировки базы данных
    • Обмен базами данных с приложениями C
    • Проблема с untie()
  • ЧАСТО ЗАДАВАЕМЫЕ ВОПРОСЫ
    • Почему в моей базе данных есть исходный код Perl?
    • Как хранить сложные структуры данных с помощью DB_File?
    • Что означает «широкий символ в записи подпрограммы»?
    • Что означает «Неверный аргумент»?
    • Что означает «Не разрешен необработанный идентификатор 'DB_File'»?
  • СПИСОК ЛИТЕРАТУРЫ
  • ИСТОРИЯ
  • ОШИБКИ
  • ПОДДЕРЖКА
  • ДОСТУПНОСТЬ
  • АВТОРСКИЕ ПРАВА
  • СМОТРИТЕ ТАКЖЕ
  • АВТОР

НАЗВАНИЕ

DB_File — доступ к Berkeley DB версии 1.x с помощью Perl5

СИНТАКСИС

use DB_File;

[$X =] tie %hash,  'DB_File', [$filename, $flags, $mode, $DB_HASH] ;
[$X =] tie %hash,  'DB_File', $filename, $flags, $mode, $DB_BTREE ;
[$X =] tie @array, 'DB_File', $filename, $flags, $mode, $DB_RECNO ;

$status = $X->del($key [, $flags]) ;
$status = $X->put($key, $value [, $flags]) ;
$status = $X->get($key, $value [, $flags]) ;
$status = $X->seq($key, $value, $flags) ;
$status = $X->sync([$flags]) ;
$status = $X->fd ;

# BTREE only
$count = $X->get_dup($key) ;
@list  = $X->get_dup($key) ;
%list  = $X->get_dup($key, 1) ;
$status = $X->find_dup($key, $value) ;
$status = $X->del_dup($key, $value) ;

# RECNO only
$a = $X->length;
$a = $X->pop ;
$X->push(list);
$a = $X->shift;
$X->unshift(list);
@r = $X->splice(offset, length, elements);

# DBM Filters
$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 { ... } ) ;

untie %hash ;
untie @array ;

ОПИСАНИЕ

DB_File — модуль, который позволяет программам Perl использовать возможности Berkeley DB версии 1.x (если у вас более новая версия DB, см. "Использование DB_File с Berkeley DB версии 2 или выше"). Предполагается, что у вас под рукой есть руководство Berkeley DB при чтении этой документации. Здесь определенный интерфейс точно соответствует интерфейсу Berkeley DB.

Berkeley DB — это библиотека C, которая обеспечивает единый интерфейс к ряду форматов баз данных. DB_File предоставляет интерфейс ко всем трём типам баз данных, которые в настоящее время поддерживаются Berkeley DB.

Типы файлов:

DB_HASH

Этот тип базы данных позволяет хранить произвольные пары ключ/значение в файлах данных. Это эквивалентно функциональности, предоставляемой другими пакетами хеширования, такими как DBM, NDBM, ODBM, GDBM и SDBM. Однако помните, что файлы, созданные с помощью DB_HASH, несовместимы ни с одним из упомянутых пакетов.

В Berkeley DB встроена алгоритм хеширования по умолчанию, который будет достаточным для большинства приложений. Если вам всё же нужно использовать свой алгоритм хеширования, вы можете написать его на Perl и заставить DB_File использовать его вместо него.

DB_BTREE

Формат btree позволяет хранить произвольные пары ключ/значение в отсортированном, сбалансированном двоичном дереве.

Как и с форматом DB_HASH, можно предоставить пользовательскую Perl-программу для сравнения ключей. Однако по умолчанию ключи хранятся в лексикографическом порядке.

DB_RECNO

DB_RECNO позволяет манипулировать как файлами с фиксированной длиной, так и файлами с переменной длиной, используя тот же интерфейс ключ/значение, что и в DB_HASH и DB_BTREE. В этом случае ключ будет состоять из номера записи (строки).

Использование DB_File с Berkeley DB версии 2 или выше

Хотя DB_File предназначен для использования с Berkeley DB версии 1, он также может использоваться с версиями 2, 3 или 4. В этом случае интерфейс ограничен функциональностью Berkeley DB 1.x. В местах, где интерфейс версии 2 или выше отличается, DB_File организует его работу как в версии 1. Эта функция позволяет DB_File скриптам, созданным с помощью версии 1, мигрировать на версию 2 или выше без каких-либо изменений.

Если вы хотите воспользоваться новыми возможностями, доступными в Berkeley DB 2.x или выше, используйте модуль Perl BerkeleyDB вместо него.

Примечание: Формат файла базы данных несколько раз менялся в Berkeley DB версии 2, 3 и 4. Если вы не можете воссоздать свои базы данных, вы должны выполнить дамп любых существующих баз данных с помощью утилиты db_dump или db_dump185, которые поставляются с Berkeley DB. После того, как вы перестроили DB_File для использования Berkeley DB версии 2 или выше, ваши базы данных могут быть восстановлены с помощью db_load. Для получения дополнительной информации обратитесь к документации Berkeley DB.

Перед использованием версии 2.x или выше Berkeley DB с DB_File, пожалуйста, ознакомьтесь с "АВТОРСКИМИ ПРАВАМИ".

Интерфейс с Berkeley DB

DB_File позволяет получить доступ к файлам Berkeley DB с помощью механизма tie() в Perl 5 (подробности см. в "tie()" в perlfunc). Эта функция позволяет DB_File получать доступ к файлам Berkeley DB, используя либо ассоциативный массив (для типов файлов DB_HASH и DB_BTREE), либо обычный массив (для типа файла DB_RECNO).

В дополнение к интерфейсу tie(), также можно напрямую обратиться к большинству функций, предоставленных API Berkeley DB. См. "ИНТЕРФЕЙС API".

Открытие файла базы данных Berkeley DB

Berkeley DB использует функцию dbopen() для открытия или создания базы данных. Вот прототип функции dbopen() на C:

DB*
dbopen (const char * file, int flags, int mode,
        DBTYPE type, const void * openinfo)

Параметр type — это перечисление, которое определяет, какой из 3 методов интерфейса (DB_HASH, DB_BTREE или DB_RECNO) следует использовать. В зависимости от того, какой из них выбран, последний параметр, openinfo, указывает на структуру данных, которая позволяет настроить конкретный метод интерфейса.

В DB_File этот интерфейс обрабатывается немного по-другому. Вот эквивалентный вызов с использованием DB_File:

tie %array, 'DB_File', $filename, $flags, $mode, $DB_HASH ;

Параметры filename, flags и mode напрямую соответствуют своим аналогам в dbopen(). Последний параметр $DB_HASH выполняет функцию параметров type и openinfo в dbopen().

В приведенном выше примере $DB_HASH фактически является предопределенной ссылкой на объект хеша. У DB_File есть три таких предопределенных ссылки. Помимо $DB_HASH, есть также $DB_BTREE и $DB_RECNO.

Разрешенные ключи в каждой из этих предопределённых ссылок ограничены именами, используемыми в эквивалентной структуре C. Например, ссылка $DB_HASH будет допускать только ключи с названиями bsize, cachesize, ffactor, hash, lorder и nelem.

Чтобы изменить один из этих элементов, просто присвойте ему значение, как показано ниже:

$DB_HASH->{'cachesize'} = 10000 ;

Три предопределённые переменные $DB_HASH, $DB_BTREE и $DB_RECNO обычно подходят для большинства приложений. Если вам нужно создать дополнительные экземпляры этих объектов, для каждого типа файла доступны конструкторы.

Ниже приведены примеры конструкторов и допустимые опции для DB_HASH, DB_BTREE и DB_RECNO соответственно.

$a = DB_File::HASHINFO->new();
$a->{'bsize'} ;
$a->{'cachesize'} ;
$a->{'ffactor'};
$a->{'hash'} ;
$a->{'lorder'} ;
$a->{'nelem'} ;

$b = DB_File::BTREEINFO->new();
$b->{'flags'} ;
$b->{'cachesize'} ;
$b->{'maxkeypage'} ;
$b->{'minkeypage'} ;
$b->{'psize'} ;
$b->{'compare'} ;
$b->{'prefix'} ;
$b->{'lorder'} ;

$c = DB_File::RECNOINFO->new();
$c->{'bval'} ;
$c->{'cachesize'} ;
$c->{'psize'} ;
$c->{'flags'} ;
$c->{'lorder'} ;
$c->{'reclen'} ;
$c->{'bfname'} ;

Значения, хранящиеся в вышеуказанных хешах, в основном являются прямым аналогом своих C-аналогов. Как и их C-аналоги, все они устанавливаются по умолчанию — это означает, что вам не нужно устанавливать все значения, когда вы хотите изменить только один. Вот пример:

$a = DB_File::HASHINFO->new();
$a->{'cachesize'} =  12345 ;
tie %y, 'DB_File', "filename", $flags, 0777, $a ;

Некоторые опции требуют дополнительных обсуждений. При использовании C-эквиваленты ключей hash, compare и prefix хранят указатели на C-функции. В DB_File эти ключи используются для хранения ссылок на Perl-подпрограммы. Ниже приведены шаблоны для каждой из подпрограмм:

sub hash
{
    my ($data) = @_ ;
    ...
    # return the hash value for $data
    return $hash ;
}

sub compare
{
    my ($key, $key2) = @_ ;
    ...
    # return  0 if $key1 eq $key2
    #        -1 if $key1 lt $key2
    #         1 if $key1 gt $key2
    return (-1 , 0 or 1) ;
}

sub prefix
{
    my ($key, $key2) = @_ ;
    ...
    # return number of bytes of $key2 which are
    # necessary to determine that it is greater than $key1
    return $bytes ;
}

См. "Изменение порядка сортировки BTREE" для примера использования шаблона compare.

Если вы используете интерфейс DB_RECNO и намерены использовать bval, ознакомьтесь с "Опцией 'bval'".

Параметры по умолчанию

Можно опустить некоторые или все последние 4 параметра в вызове tie и позволить им принять значения по умолчанию. Так как DB_HASH — наиболее распространённый формат файла, вызов:

tie %A, "DB_File", "filename" ;

эквивалентно:

tie %A, "DB_File", "filename", O_CREAT|O_RDWR, 0666, $DB_HASH ;

Также можно опустить параметр filename, поэтому вызов:

tie %A, "DB_File" ;

эквивалентен:

tie %A, "DB_File", undef, O_CREAT|O_RDWR, 0666, $DB_HASH ;

См. "Базы данных в памяти" для обсуждения использования undef вместо имени файла.

Базы данных в памяти

Berkeley DB позволяет создавать базы данных в памяти, используя NULL (то есть, (char *)0 в C) вместо имени файла. DB_File использует undef вместо NULL для обеспечения этой функциональности.

DB_HASH

Формат файла DB_HASH, вероятно, является наиболее часто используемым из трех поддерживаемых DB_File форматов файлов. Он также очень прост в использовании.

Простой пример

В этом примере показано, как создать базу данных, добавить пары ключ/значение в базу данных, удалить пары ключ/значение и, наконец, как перечислить содержимое базы данных.

use warnings ;
use strict ;
use DB_File ;
our (%h, $k, $v) ;

unlink "fruit" ;
tie %h, "DB_File", "fruit", O_RDWR|O_CREAT, 0666, $DB_HASH
    or die "Cannot open file 'fruit': $!\n";

# Add a few key/value pairs to the file
$h{"apple"} = "red" ;
$h{"orange"} = "orange" ;
$h{"banana"} = "yellow" ;
$h{"tomato"} = "red" ;

# Check for existence of a key
print "Banana Exists\n\n" if $h{"banana"} ;

# Delete a key/value pair.
delete $h{"apple"} ;

# print the contents of the file
while (($k, $v) = each %h)
  { print "$k -> $v\n" }

untie %h ;

вот вывод:

Banana Exists

orange -> orange
tomato -> red
banana -> yellow

Обратите внимание, что, как и обычные ассоциативные массивы, порядок извлеченных ключей имеет, по-видимому, случайный порядок.

DB_BTREE

Формат DB_BTREE полезен, когда вы хотите хранить данные в определенном порядке. По умолчанию ключи хранятся в лексикографическом порядке, но, как вы увидите из примера, показанного в следующем разделе, очень легко определить собственную функцию сортировки.

Изменение порядка сортировки BTREE

Этот скрипт демонстрирует, как переопределить алгоритм сортировки по умолчанию, используемый BTREE. Вместо использования обычного лексикографического порядка будет использоваться функция сравнения, не учитывающая регистр.

use warnings ;
use strict ;
use DB_File ;

my %h ;

sub Compare
{
    my ($key1, $key2) = @_ ;
    "\L$key1" cmp "\L$key2" ;
}

# specify the Perl sub that will do the comparison
$DB_BTREE->{'compare'} = \&Compare ;

unlink "tree" ;
tie %h, "DB_File", "tree", O_RDWR|O_CREAT, 0666, $DB_BTREE
    or die "Cannot open file 'tree': $!\n" ;

# Add a key/value pair to the file
$h{'Wall'} = 'Larry' ;
$h{'Smith'} = 'John' ;
$h{'mouse'} = 'mickey' ;
$h{'duck'}  = 'donald' ;

# Delete
delete $h{"duck"} ;

# Cycle through the keys printing them in order.
# Note it is not necessary to sort the keys as
# the btree will have kept them in order automatically.
foreach (keys %h)
  { print "$_\n" }

untie %h ;

Вот вывод из приведенного выше кода.

mouse
Smith
Wall

Есть несколько моментов, которые следует учитывать, если вы хотите изменить порядок в базе данных BTREE:

  1. Новая функция сравнения должна быть указана при создании базы данных.

  2. Вы не можете изменить порядок после создания базы данных. Таким образом, вы должны каждый раз использовать ту же функцию сравнения при обращении к базе данных.

  3. Повторные ключи полностью определяются функцией сравнения. В приведенном выше примере без учета регистра ключи "KEY" и "key" считались бы дубликатами, и присваивание второму перезаписало бы первый. Если дубликаты разрешены (с флагом R_DUP, обсуждаемым ниже), в базе данных хранится только одна копия дублирующихся ключей — так (опять же, с приведенным выше примером) присвоение трех значений ключам "KEY", "Key" и "key" оставит только первый ключ "KEY" в базе данных со тремя значениями. В некоторых ситуациях это приводит к потере информации, поэтому необходимо с осторожностью предоставлять полностью квалифицированные функции сравнения при необходимости. Например, приведенную выше функцию сравнения можно модифицировать, чтобы дополнительно сравнивать ключи с учетом регистра, если два ключа равны в сравнении без учета регистра:

    sub compare {
        my($key1, $key2) = @_;
        lc $key1 cmp lc $key2 ||
        $key1 cmp $key2;
    }

    И теперь у вас будут дубликаты только тогда, когда сами ключи действительно одинаковы. (Примечание: в версиях библиотеки db до ноября 1996 года такие дублирующиеся ключи сохранялись, поэтому было возможно восстановить исходные ключи в наборах ключей, которые сравнивались как равные).

Обработка дублирующихся ключей

Тип файла BTREE необязательно позволяет одному ключу быть связанным с произвольным количеством значений. Этот параметр включается путем установки элемента флагов $DB_BTREE в R_DUP при создании базы данных.

Существуют некоторые трудности при использовании привязанного интерфейса хеширования, если вы хотите манипулировать базой данных BTREE с дублирующимися ключами. Рассмотрим этот код:

use warnings ;
use strict ;
use DB_File ;

my ($filename, %h) ;

$filename = "tree" ;
unlink $filename ;

# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;

tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
    or die "Cannot open $filename: $!\n";

# Add some key/value pairs to the file
$h{'Wall'} = 'Larry' ;
$h{'Wall'} = 'Brick' ; # Note the duplicate key
$h{'Wall'} = 'Brick' ; # Note the duplicate key and value
$h{'Smith'} = 'John' ;
$h{'mouse'} = 'mickey' ;

# iterate through the associative array
# and print each key/value pair.
foreach (sort keys %h)
  { print "$_  -> $h{$_}\n" }

untie %h ;

Вот вывод:

Smith   -> John
Wall    -> Larry
Wall    -> Larry
Wall    -> Larry
mouse   -> mickey

Как видите, 3 записи успешно созданы с ключом Wall — единственное, когда они извлекаются из базы данных, они кажутся имеющими то же самое значение, а именно Larry. Проблема вызвана способом работы интерфейса ассоциативного массива. В основном, когда используется интерфейс ассоциативного массива для извлечения значения, связанного с данным ключом, он всегда извлекает только первое значение.

Хотя из приведенного выше кода это может не быть очевидно, интерфейс ассоциативного массива можно использовать для записи значений с дублирующимися ключами, но нельзя использовать его для чтения их обратно из базы данных.

Чтобы обойти эту проблему, используйте метод API Berkeley DB под названием seq. Этот метод позволяет получить последовательный доступ к парам ключ/значение. См. "ИНТЕРФЕЙС API" для получения подробной информации о методе seq и API в целом.

Вот переписанный выше скрипт, использующий метод API seq.

use warnings ;
use strict ;
use DB_File ;

my ($filename, $x, %h, $status, $key, $value) ;

$filename = "tree" ;
unlink $filename ;

# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;

$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
    or die "Cannot open $filename: $!\n";

# Add some key/value pairs to the file
$h{'Wall'} = 'Larry' ;
$h{'Wall'} = 'Brick' ; # Note the duplicate key
$h{'Wall'} = 'Brick' ; # Note the duplicate key and value
$h{'Smith'} = 'John' ;
$h{'mouse'} = 'mickey' ;

# iterate through the btree using seq
# and print each key/value pair.
$key = $value = 0 ;
for ($status = $x->seq($key, $value, R_FIRST) ;
     $status == 0 ;
     $status = $x->seq($key, $value, R_NEXT) )
  {  print "$key -> $value\n" }

undef $x ;
untie %h ;

что выводит:

Smith   -> John
Wall    -> Brick
Wall    -> Brick
Wall    -> Larry
mouse   -> mickey

На этот раз мы получили все пары ключ/значение, включая несколько значений, связанных с ключом Wall.

Для облегчения работы с дублирующимися ключами DB_File поставляется с несколькими утилитами.

Метод get_dup()

Метод get_dup помогает в чтении дублирующихся значений из баз данных BTREE. Метод может принимать следующие формы:

$count = $x->get_dup($key) ;
@list  = $x->get_dup($key) ;
%list  = $x->get_dup($key, 1) ;

В скалярном контексте метод возвращает количество значений, связанных с ключом, $key.

В контексте списка он возвращает все значения, соответствующие $key. Обратите внимание, что значения будут возвращены в, по-видимому, случайном порядке.

В контексте списка, если второй параметр присутствует и оценивается как ИСТИНА, метод возвращает ассоциативный массив. Ключи ассоциативного массива соответствуют значениям, которые совпали в BTREE, а значения массива — количеству раз, когда это конкретное значение встречалось в BTREE.

Итак, предположив, что база данных создана выше, мы можем использовать get_dup следующим образом:

use warnings ;
use strict ;
use DB_File ;

my ($filename, $x, %h) ;

$filename = "tree" ;

# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;

$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
    or die "Cannot open $filename: $!\n";

my $cnt  = $x->get_dup("Wall") ;
print "Wall occurred $cnt times\n" ;

my %hash = $x->get_dup("Wall", 1) ;
print "Larry is there\n" if $hash{'Larry'} ;
print "There are $hash{'Brick'} Brick Walls\n" ;

my @list = sort $x->get_dup("Wall") ;
print "Wall =>      [@list]\n" ;

@list = $x->get_dup("Smith") ;
print "Smith =>     [@list]\n" ;

@list = $x->get_dup("Dog") ;
print "Dog =>       [@list]\n" ;

и это напечатает:

Wall occurred 3 times
Larry is there
There are 2 Brick Walls
Wall =>     [Brick Brick Larry]
Smith =>    [John]
Dog =>      []

Метод find_dup()

$status = $X->find_dup($key, $value) ;

Этот метод проверяет существование конкретной пары ключ/значение. Если пара существует, курсор остается, указывающий на пару, и метод возвращает 0. В противном случае метод возвращает ненулевое значение.

Предположим базу данных из предыдущего примера:

use warnings ;
use strict ;
use DB_File ;

my ($filename, $x, %h, $found) ;

$filename = "tree" ;

# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;

$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
    or die "Cannot open $filename: $!\n";

$found = ( $x->find_dup("Wall", "Larry") == 0 ? "" : "not") ;
print "Larry Wall is $found there\n" ;

$found = ( $x->find_dup("Wall", "Harry") == 0 ? "" : "not") ;
print "Harry Wall is $found there\n" ;

undef $x ;
untie %h ;

выводит это

Larry Wall is  there
Harry Wall is not there

Метод del_dup()

$status = $X->del_dup($key, $value) ;

Этот метод удаляет определенную пару ключ/значение. Он возвращает 0, если они существуют и были успешно удалены. В противном случае метод возвращает ненулевое значение.

Опять же, предполагая существование базы данных tree

use warnings ;
use strict ;
use DB_File ;

my ($filename, $x, %h, $found) ;

$filename = "tree" ;

# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;

$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
    or die "Cannot open $filename: $!\n";

$x->del_dup("Wall", "Larry") ;

$found = ( $x->find_dup("Wall", "Larry") == 0 ? "" : "not") ;
print "Larry Wall is $found there\n" ;

undef $x ;
untie %h ;

выводит это

Larry Wall is not there

Сопоставление частичных ключей

Интерфейс BTREE имеет функцию, которая позволяет сопоставлять частичные ключи. Эта функциональность только доступна, когда метод seq используется вместе с флагом R_CURSOR.

$x->seq($key, $value, R_CURSOR) ;

Вот соответствующая цитата из страницы руководства dbopen, где определено использование флага R_CURSOR с seq:

Note, for the DB_BTREE access method, the returned key is not
necessarily an exact match for the specified key. The returned key
is the smallest key greater than or equal to the specified key,
permitting partial key matches and range searches.

В приведенном ниже скрипте подпрограмма match использует эту функцию для поиска и вывода первой соответствующей пары ключ/значение, заданной частичным ключом.

use warnings ;
use strict ;
use DB_File ;
use Fcntl ;

my ($filename, $x, %h, $st, $key, $value) ;

sub match
{
    my $key = shift ;
    my $value = 0;
    my $orig_key = $key ;
    $x->seq($key, $value, R_CURSOR) ;
    print "$orig_key\t-> $key\t-> $value\n" ;
}

$filename = "tree" ;
unlink $filename ;

$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
    or die "Cannot open $filename: $!\n";

# Add some key/value pairs to the file
$h{'mouse'} = 'mickey' ;
$h{'Wall'} = 'Larry' ;
$h{'Walls'} = 'Brick' ;
$h{'Smith'} = 'John' ;


$key = $value = 0 ;
print "IN ORDER\n" ;
for ($st = $x->seq($key, $value, R_FIRST) ;
     $st == 0 ;
     $st = $x->seq($key, $value, R_NEXT) )

  {  print "$key    -> $value\n" }

print "\nPARTIAL MATCH\n" ;

match "Wa" ;
match "A" ;
match "a" ;

undef $x ;
untie %h ;

Вот вывод:

IN ORDER
Smith -> John
Wall  -> Larry
Walls -> Brick
mouse -> mickey

PARTIAL MATCH
Wa -> Wall  -> Larry
A  -> Smith -> John
a  -> mouse -> mickey

DB_RECNO

DB_RECNO предоставляет интерфейс к плоским текстовым файлам. Поддерживаются как переменные, так и фиксированные записи.

Для большей совместимости с Perl, смещение массива для всех массивов RECNO начинается с 0, а не с 1, как в Berkeley DB.

Как и в обычных Perl-массивах, к массиву RECNO можно обращаться с помощью отрицательных индексов. Индекс -1 относится к последнему элементу массива, -2 — к предпоследнему и так далее. Попытка получить доступ к элементу до начала массива приведет к возникновению фатальной ошибки во время выполнения.

Параметр 'bval'

Работа параметра bval заслуживает обсуждения. Вот определение bval из страницы руководства Berkeley DB 1.85 recno:

The delimiting byte to be used to mark  the  end  of  a
record for variable-length records, and the pad charac-
ter for fixed-length records.  If no  value  is  speci-
fied,  newlines  (``\n'')  are  used to mark the end of
variable-length records and  fixed-length  records  are
padded with spaces.

Второе предложение неверно. На самом деле, bval по умолчанию будет равен "\n" только тогда, когда параметр openinfo в dbopen равен NULL. Если используется ненулевой параметр openinfo, значение, которое в данный момент находится в bval, будет использоваться. Это означает, что вы всегда должны указывать bval при использовании любого из параметров в параметре openinfo. Эта ошибка в документации будет исправлена в следующей версии Berkeley DB.

Это проясняет ситуацию в отношении самого Berkeley DB. А как насчет DB_File? Ну, поведение, определенное в цитате выше, довольно полезно, поэтому DB_File соответствует ему.

Это означает, что вы можете указать другие параметры (например, cachesize) и все равно иметь bval по умолчанию равным "\n" для переменных записей и места для фиксированных записей.

Также обратите внимание, что параметр bval позволяет указывать только один байт в качестве разделителя.

Простой пример

Вот простой пример, использующий RECNO (если вы используете версию Perl, более раннюю, чем 5.004_57, этот пример не будет работать — см. "Дополнительные методы RECNO" для обходного пути).

use warnings ;
use strict ;
use DB_File ;

my $filename = "text" ;
unlink $filename ;

my @h ;
tie @h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_RECNO
    or die "Cannot open file 'text': $!\n" ;

# Add a few key/value pairs to the file
$h[0] = "orange" ;
$h[1] = "blue" ;
$h[2] = "yellow" ;

push @h, "green", "black" ;

my $elements = scalar @h ;
print "The array contains $elements entries\n" ;

my $last = pop @h ;
print "popped $last\n" ;

unshift @h, "white" ;
my $first = shift @h ;
print "shifted $first\n" ;

# Check for existence of a key
print "Element 1 Exists with value $h[1]\n" if $h[1] ;

# use a negative index
print "The last element is $h[-1]\n" ;
print "The 2nd last element is $h[-2]\n" ;

untie @h ;

Вот вывод из скрипта:

The array contains 5 entries
popped black
shifted white
Element 1 Exists with value blue
The last element is green
The 2nd last element is yellow

Дополнительные методы RECNO

Если вы используете версию Perl, более раннюю, чем 5.004_57, интерфейс привязанного массива довольно ограничен. В приведенном выше примере push, pop, shift, unshift или определение длины массива не будут работать с привязанным массивом.

Чтобы сделать интерфейс более полезным для более старых версий Perl, с DB_File предоставляется ряд методов для имитации отсутствующих операций с массивами. Все эти методы доступны через объект, возвращенный из вызова tie.

Вот эти методы:

$X->push(list) ;

Добавляет элементы list в конец массива.

$value = $X->pop ;

Удаляет и возвращает последний элемент массива.

$X->shift

Удаляет и возвращает первый элемент массива.

$X->unshift(list) ;

Добавляет элементы list в начало массива.

$X->length

Возвращает количество элементов в массиве.

$X->splice(offset, length, elements);

Возвращает срез массива.

Еще один пример

Вот более полный пример, который использует некоторые из описанных выше методов. Он также напрямую использует интерфейс API (см. "ИНТЕРФЕЙС API").

use warnings ;
use strict ;
my (@h, $H, $file, $i) ;
use DB_File ;
use Fcntl ;

$file = "text" ;

unlink $file ;

$H = tie @h, "DB_File", $file, O_RDWR|O_CREAT, 0666, $DB_RECNO
    or die "Cannot open file $file: $!\n" ;

# first create a text file to play with
$h[0] = "zero" ;
$h[1] = "one" ;
$h[2] = "two" ;
$h[3] = "three" ;
$h[4] = "four" ;


# Print the records in order.
#
# The length method is needed here because evaluating a tied
# array in a scalar context does not return the number of
# elements in the array.

print "\nORIGINAL\n" ;
foreach $i (0 .. $H->length - 1) {
    print "$i: $h[$i]\n" ;
}

# use the push & pop methods
$a = $H->pop ;
$H->push("last") ;
print "\nThe last record was [$a]\n" ;

# and the shift & unshift methods
$a = $H->shift ;
$H->unshift("first") ;
print "The first record was [$a]\n" ;

# Use the API to add a new record after record 2.
$i = 2 ;
$H->put($i, "Newbie", R_IAFTER) ;

# and a new record before record 1.
$i = 1 ;
$H->put($i, "New One", R_IBEFORE) ;

# delete record 3
$H->del(3) ;

# now print the records in reverse order
print "\nREVERSE\n" ;
for ($i = $H->length - 1 ; $i >= 0 ; -- $i)
  { print "$i: $h[$i]\n" }

# same again, but use the API functions instead
print "\nREVERSE again\n" ;
my ($s, $k, $v)  = (0, 0, 0) ;
for ($s = $H->seq($k, $v, R_LAST) ;
         $s == 0 ;
         $s = $H->seq($k, $v, R_PREV))
  { print "$k: $v\n" }

undef $H ;
untie @h ;

и вот что он выводит:

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

Примечания:

  1. Вместо итерирования по массиву, @h следующим образом:

    foreach $i (@h)

    необходимо использовать либо это:

    foreach $i (0 .. $H->length - 1)

    или это:

    for ($a = $H->get($k, $v, R_FIRST) ;
         $a == 0 ;
         $a = $H->get($k, $v, R_NEXT) )
  2. Обратите внимание, что оба раза, когда использовался метод put, индекс записи указывался с помощью переменной, $i, а не с помощью самого литерального значения. Это потому, что put вернет номер записи вставленной строки через этот параметр.

ИНТЕРФЕЙС API

Помимо доступа к Berkeley DB с помощью связанного хэша или массива, также можно напрямую использовать большинство функций API, определенных в документации Berkeley DB.

Для этого необходимо сохранить копию объекта, возвращаемого функцией связывания.

$db = tie %hash, "DB_File", "filename" ;

После этого вы можете обращаться к функциям API Berkeley DB как к методам DB_File напрямую, например, так:

$db->put($key, $value, R_NOOVERWRITE) ;

Важно: Если вы сохранили копию объекта, возвращаемого функцией tie, файл базы данных, находящийся в основе, не будет закрыт до тех пор, пока связанная переменная не будет отвязана, и все копии сохраненного объекта не будут уничтожены.

use DB_File ;
$db = tie %hash, "DB_File", "filename"
    or die "Cannot tie filename: $!" ;
...
undef $db ;
untie %hash ;

См. «The untie() Gotcha» для получения более подробной информации.

Доступны все функции, определенные в dbopen, за исключением close() и самой dbopen(). Интерфейс методов DB_File для поддерживаемых функций реализован таким образом, чтобы отразить работу Berkeley DB, насколько это возможно. Обратите особое внимание на следующие моменты:

  • Методы возвращают значение состояния. Все методы возвращают 0 при успешном выполнении. Все методы возвращают -1 для обозначения ошибки и устанавливают $! в точное значение кода ошибки. Код возврата 1 обычно (но не всегда) означает, что указанный ключ не существовал в базе данных.

    Определены и другие коды возврата. Для получения подробной информации см. ниже и в документации Berkeley DB. Документация Berkeley DB должна использоваться в качестве окончательного источника.

  • В тех случаях, когда функция Berkeley DB возвращает данные через один из своих параметров, эквивалентный метод DB_File делает точно то же самое.

  • При соблюдении осторожности возможно смешивание вызовов API с интерфейсом связанного хэша/массива в одном коде. Хотя лишь некоторые методы, используемые для реализации связанного интерфейса, в настоящее время используют курсор, всегда следует предполагать, что курсор был изменён всякий раз, когда используется связанный интерфейс хэша/массива. Например, этот код, вероятно, не сделает того, что вы ожидаете:

    $X = tie %x, 'DB_File', $filename, O_RDWR|O_CREAT, 0777, $DB_BTREE
        or die "Cannot tie $filename: $!" ;
    
    # Get the first key/value pair and set  the cursor
    $X->seq($key, $value, R_FIRST) ;
    
    # this line will modify the cursor
    $count = scalar keys %x ;
    
    # Get the second key/value pair.
    # oops, it didn't, it got the last key/value pair!
    $X->seq($key, $value, R_NEXT) ;

    Код выше можно переупорядочить, чтобы обойти проблему, например, так:

    $X = tie %x, 'DB_File', $filename, O_RDWR|O_CREAT, 0777, $DB_BTREE
        or die "Cannot tie $filename: $!" ;
    
    # this line will modify the cursor
    $count = scalar keys %x ;
    
    # Get the first key/value pair and set  the cursor
    $X->seq($key, $value, R_FIRST) ;
    
    # Get the second key/value pair.
    # worked this time.
    $X->seq($key, $value, R_NEXT) ;

Все константы, определенные в dbopen для использования в параметрах флагов в методах, определенных ниже, также доступны. Обратитесь к документации Berkeley DB для точного значения значений флагов.

Ниже приведен список доступных методов.

$status = $X->get($key, $value [, $flags]) ;

С помощью этого метода, учитывая ключ ($key), считывается связанное с ним значение из базы данных. Считанное из базы данных значение возвращается в параметре $value.

Если ключ не существует, метод возвращает 1.

В настоящее время для этого метода не определены флаги.

$status = $X->put($key, $value [, $flags]) ;

Сохраняет пару ключ/значение в базе данных.

Если вы используете флаги R_IAFTER или R_IBEFORE, параметр $key будет содержать номер записи вставленной пары ключ/значение.

Допустимые флаги: R_CURSOR, R_IAFTER, R_IBEFORE, R_NOOVERWRITE и R_SETCURSOR.

$status = $X->del($key [, $flags]) ;

Удаляет все пары ключ/значение с ключом $key из базы данных.

Код возврата 1 означает, что запрашиваемый ключ отсутствует в базе данных.

В настоящее время единственным допустимым флагом является R_CURSOR.

$status = $X->fd ;

Возвращает дескриптор файла для подлежащей базы данных.

См. "Locking: The Trouble with fd" для объяснения, почему не следует использовать fd для блокировки вашей базы данных.

$status = $X->seq($key, $value, $flags) ;

Этот интерфейс позволяет последовательно извлекать данные из базы данных. Подробную информацию см. в dbopen.

И параметры $key и $value будут установлены в пару ключ/значение, считанную из базы данных.

Параметр флагов обязателен. Допустимые значения флагов: R_CURSOR, R_FIRST, R_LAST, R_NEXT и R_PREV.

$status = $X->sync([$flags]) ;

Очищает все кэшированные буферы на диск.

В настоящее время единственным допустимым флагом является R_RECNOSYNC.

DBM-ФИЛЬТРЫ

DBM-фильтр — это фрагмент кода, который используется всякий раз, когда вам всегда нужно выполнять одно и то же преобразование со всеми ключами и/или значениями в базе данных DBM. Примером является ситуация, когда вам необходимо закодировать данные в UTF-8 перед записью в базу данных и затем декодировать UTF-8 при чтении из файла базы данных.

Существует два способа использования DBM-фильтра.

  1. Использование низкоуровневого API, определенного ниже.

  2. Использование модуля DBM_Filter. Этот модуль скрывает сложность API, определённого ниже, и поставляется с рядом «готовых» фильтров, охватывающих некоторые распространённые случаи использования.

Рекомендуется использовать модуль DBM_Filter.

Низкоуровневый API DBM-фильтра

С DBM-фильтрами связано четыре метода. Все они работают одинаково, и каждый используется для установки (или отключения) одного DBM-фильтра. Каждый ожидает один параметр — ссылку на подпрограмму. Единственное различие между ними заключается в месте установки фильтра.

В кратце:

filter_store_key

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

filter_store_value

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

filter_fetch_key

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

filter_fetch_value

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

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

Все методы фильтров возвращают существующий фильтр, если он присутствует, или undef в противном случае.

Для удаления фильтра передайте undef в него.

Фильтр

Когда Perl вызывает каждый фильтр, локальная копия $_ будет содержать ключ или значение, которые нужно отфильтровать. Фильтрация достигается путём изменения содержимого $_. Код возврата фильтра игнорируется.

Пример — проблема с нулевым завершением.

Рассмотрим следующий сценарий. У вас есть база данных DBM, которую нужно поделиться с приложением на C. Приложение на C предполагает, что все ключи и значения завершаются нулём. К сожалению, при записи в базы данных DBM Perl не использует нулевое завершение, поэтому вашему приложению на Perl придётся самостоятельно обрабатывать нулевое завершение. При записи в базу данных вам потребуется использовать что-то вроде этого:

$hash{"$key\0"} = "$value\0" ;

Аналогично, при рассмотрении длины существующих ключей/значений необходимо учитывать нуль.

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

use warnings ;
use strict ;
use DB_File ;

my %hash ;
my $filename = "filt" ;
unlink $filename ;

my $db = tie %hash, 'DB_File', $filename, O_CREAT|O_RDWR, 0666, $DB_HASH
  or die "Cannot open $filename: $!\n" ;

# Install DBM Filters
$db->filter_fetch_key  ( sub { s/\0$//    } ) ;
$db->filter_store_key  ( sub { $_ .= "\0" } ) ;
$db->filter_fetch_value( sub { s/\0$//    } ) ;
$db->filter_store_value( sub { $_ .= "\0" } ) ;

$hash{"abc"} = "def" ;
my $a = $hash{"ABC"} ;
# ...
undef $db ;
untie %hash ;

Надеюсь, содержимое каждого фильтра будет понятным. Оба фильтра «чтения» удаляют завершающий нуль, а оба фильтра «записи» добавляют завершающий нуль.

Ещё один пример — ключ — целое число C.

Вот ещё один реальный пример. По умолчанию, всякий раз, когда Perl записывает в базу данных DBM, он всегда записывает ключ и значение как строки. Таким образом, когда вы используете это:

$hash{12345} = "something" ;

ключ 12345 будет сохранён в базе данных DBM в виде строки длиной 5 байт "12345". Если вы хотите, чтобы ключ был сохранён в базе данных DBM как целое число C, вам нужно использовать pack при записи и unpack при чтении.

Вот DBM-фильтр, который это делает:

use warnings ;
use strict ;
use DB_File ;
my %hash ;
my $filename = "filt" ;
unlink $filename ;


my $db = tie %hash, 'DB_File', $filename, O_CREAT|O_RDWR, 0666, $DB_HASH
  or die "Cannot open $filename: $!\n" ;

$db->filter_fetch_key  ( sub { $_ = unpack("i", $_) } ) ;
$db->filter_store_key  ( sub { $_ = pack ("i", $_) } ) ;
$hash{123} = "def" ;
# ...
undef $db ;
untie %hash ;

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

СОВЕТЫ И ПРИЁМЫ

Блокировка: проблема с fd

До версии 1.72 этого модуля рекомендуемым методом блокировки баз данных DB_File было использование flock для дескриптора файла, возвращаемого функцией «fd». К сожалению, этот метод оказался принципиально неверным (спасибо Дэвиду Харрису за то, что он это обнаружил). Используйте его на свой страх и риск!

Метод блокировки выглядел так.

$db = tie(%db, 'DB_File', 'foo.db', O_CREAT|O_RDWR, 0644)
    || die "dbcreat foo.db $!";
$fd = $db->fd;
open(DB_FH, "+<&=$fd") || die "dup $!";
flock (DB_FH, LOCK_EX) || die "flock: $!";
...
$db{"Tom"} = "Jerry" ;
...
flock(DB_FH, LOCK_UN);
undef $db;
untie %db;
close(DB_FH);

Проще говоря, вот что происходит:

  1. Используйте «tie» для открытия базы данных.

  2. Заблокируйте базу данных с помощью fd и flock.

  3. Читаем и пишем в базу данных.

  4. Разблокируйте и закройте базу данных.

Суть проблемы в следующем. Побочным эффектом открытия базы данных DB_File на шаге 2 является чтение начального блока из базы данных с диска и его кеширование в памяти.

Чтобы понять, почему это проблема, представьте, что два процесса, скажем, «A» и «B», оба хотят обновить одну и ту же базу данных DB_File с помощью описанных выше шагов блокировки. Предположим, что процесс «A» уже открыл базу данных и имеет блокировку записи, но он ещё не обновил базу данных (он завершил шаг 2, но ещё не начал шаг 3). Теперь процесс «B» пытается открыть ту же базу данных — шаг 1 будет успешным, но он будет заблокирован на шаге 2 до тех пор, пока процесс «A» не освободит блокировку. Важно заметить, что в этот момент оба процесса будут иметь кэшированные одинаковые начальные блоки из базы данных.

Теперь процесс «A» обновляет базу данных и случайно изменяет некоторые данные, хранящиеся в начальном буфере. Процесс «A» завершается, записывая все кэшированные данные на диск и освобождая блокировку базы данных. В этот момент база данных на диске будет правильно отражать изменения, внесённые процессом «A».

После освобождения блокировки процесс «B» может продолжить работу. Он также обновляет базу данных и, к сожалению, тоже изменяет данные, которые были в его начальном буфере. После записи этих данных на диск они перезапишут некоторые/все изменения, внесённые процессом «A» в базу данных.

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

Вышеописанное не произойдёт каждый раз, когда конкурирующие процессы обновляют одну и ту же базу данных DB_File, но это иллюстрирует, почему этот метод не следует использовать.

Безопасные способы блокировки базы данных

Начиная с версии 2.x, Berkeley DB имеет встроенную поддержку блокировки. Сопутствующий модуль к этому, BerkeleyDB, предоставляет интерфейс к этой функциональности блокировки. Если вы серьёзно относитесь к блокировке баз данных Berkeley DB, я настоятельно рекомендую использовать BerkeleyDB.

Если использование BerkeleyDB не подходит, существует ряд модулей на CPAN, которые можно использовать для реализации блокировки. Каждый из них реализует блокировку по-разному и имеет разные цели. Поэтому стоит знать разницу, чтобы выбрать подходящий для вашего приложения. Вот три оболочки блокировки:

Tie::DB_Lock

Оболочка DB_File, которая создаёт копии файла базы данных для чтения, чтобы у вас была своего рода система одновременного чтения с несколькими версиями. Однако обновления всё ещё выполняются последовательно. Используйте для баз данных, где чтение может быть длительным и могут возникнуть проблемы с согласованностью.

Tie::DB_LockFile

Оболочка DB_File, которая имеет возможность блокировать и разблокировать базу данных во время её использования. Избегает проблемы tie-before-flock, просто повторно связывая базу данных при получении или снятии блокировки. Благодаря гибкости снятия и повторного получения блокировки посреди сессии, это может быть адаптировано к системе, которая будет работать с длительными обновлениями и/или чтением, если приложение следует подсказкам в документации POD.

DB_File::Lock

Очень лёгкая оболочка DB_File, которая просто блокирует файл блокировки перед связыванием базы данных и снимает блокировку после разрыва связи. Позволяет использовать один и тот же файл блокировки для нескольких баз данных, чтобы избежать тупиковых ситуаций, если это необходимо. Используйте для баз данных, где обновления и чтение быстрые и простые семантики блокировки flock достаточно.

Обмен базами данных с приложениями C

Нет технических причин, по которым базу данных Berkeley DB нельзя использовать совместно с приложением Perl и C.

Большинство проблем, которые сообщаются в этой области, сводятся к тому, что строки C завершаются нулём, в то время как строки Perl — нет. См. "DBM FILTERS" для универсального способа решения этой проблемы.

Вот реальный пример. Netscape 2.0 сохраняет запись о посещенных вами местах и времени последнего посещения в базе данных DB_HASH. Обычно она хранится в файле ~/.netscape/history.db. Поле ключа в базе данных — строка места, а поле значения — время последнего посещения места, хранящееся как 4-байтовое двоичное значение.

Если вы ещё не догадались, строка места хранится с завершающим нулём. Это означает, что при обращении к базе данных нужно быть осторожным.

Вот фрагмент кода, основанный на скрипте ggh Тома Кристиана (доступен в вашем ближайшем архиве CPAN в authors/id/TOMC/scripts/nshist.gz).

use warnings ;
use strict ;
use DB_File ;
use Fcntl ;

my ($dotdir, $HISTORY, %hist_db, $href, $binary_time, $date) ;
$dotdir = $ENV{HOME} || $ENV{LOGNAME};

$HISTORY = "$dotdir/.netscape/history.db";

tie %hist_db, 'DB_File', $HISTORY
    or die "Cannot open $HISTORY: $!\n" ;;

# Dump the complete database
while ( ($href, $binary_time) = each %hist_db ) {

    # remove the terminating NULL
    $href =~ s/\x00$// ;

    # convert the binary time into a user friendly string
    $date = localtime unpack("V", $binary_time);
    print "$date $href\n" ;
}

# check for the existence of a specific key
# remember to add the NULL
if ( $binary_time = $hist_db{"http://mox.perl.com/\x00"} ) {
    $date = localtime unpack("V", $binary_time) ;
    print "Last visited mox.perl.com on $date\n" ;
}
else {
    print "Never visited mox.perl.com\n"
}

untie %hist_db ;

Особенности untie()

Если вы используете API Berkeley DB, крайне рекомендуется прочитать "The untie Gotcha" в perltie.

Даже если вы сейчас не используете интерфейс API, стоит его прочитать.

Вот пример, который иллюстрирует проблему с точки зрения DB_File:

use DB_File ;
use Fcntl ;

my %x ;
my $X ;

$X = tie %x, 'DB_File', 'tst.fil' , O_RDWR|O_TRUNC
    or die "Cannot tie first time: $!" ;

$x{123} = 456 ;

untie %x ;

tie %x, 'DB_File', 'tst.fil' , O_RDWR|O_CREAT
    or die "Cannot tie second time: $!" ;

untie %x ;

При запуске скрипта будет выведено следующее сообщение об ошибке:

Cannot tie second time: Invalid argument at bad.file line 14.

Хотя сообщение об ошибке выше относится ко второму оператору tie() в скрипте, источником проблемы на самом деле является оператор untie(), который ему предшествует.

Прочитав perltie, вы, вероятно, уже догадались, что ошибка вызвана дополнительной копией связанного объекта, хранящейся в $X. Если вы ещё не догадались, проблема сводится к тому, что деструктор DB_File, DESTROY, будет вызван только после уничтожения всех ссылок на связанный объект. Переменная связанного объекта %x и $X выше содержат ссылку на объект. Вызов untie() уничтожит первую, но $X всё ещё содержит действительную ссылку, поэтому деструктор не будет вызван, и файл базы данных tst.fil останется открытым. Факт того, что Berkeley DB затем сообщает об попытке открыть базу данных, которая уже открыта, с помощью универсального «Invalid argument», не помогает.

Если вы запустите скрипт с флагом -w, сообщение об ошибке будет:

untie attempted while 1 inner references still exist at bad.file line 12.
Cannot tie second time: Invalid argument at bad.file line 14.

что определяет реальную проблему. Наконец, скрипт можно изменить, чтобы исправить исходную проблему, уничтожив объект API перед untie:

...
$x{123} = 456 ;

undef $X ;
untie %x ;

$X = tie %x, 'DB_File', 'tst.fil' , O_RDWR|O_CREAT
...

ОБЩИЕ ВОПРОСЫ

Почему в моей базе данных есть исходный код Perl?

Если вы посмотрите на содержимое файла базы данных, созданного DB_File, там иногда может быть часть скрипта Perl.

Это происходит потому, что Berkeley DB использует динамическую память для выделения буферов, которые впоследствии будут записаны в файл базы данных. Так как память динамическая, её можно было использовать для чего угодно до того, как DB выделила её. Поскольку Berkeley DB не очищает память после её выделения, неиспользуемые части будут содержать случайный мусор. В случае, когда скрипт Perl записывается в базу данных, случайный мусор будет соответствовать области динамической памяти, которая использовалась во время компиляции скрипта.

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

Как хранить сложные структуры данных с DB_File?

Хотя DB_File не может сделать этого напрямую, есть модуль, который может прозрачно работать поверх DB_File, чтобы выполнить эту задачу.

Обратите внимание на модуль MLDBM, доступный на CPAN в каталоге modules/by-module/MLDBM.

Что означает «широкий символ в вызове подпрограммы»?

Вы обычно получаете это сообщение, если работаете с данными UTF-8 и хотите читать/записывать их из/в файл базы данных Berkeley DB.

Самый простой способ решения этой проблемы — использовать предварительно определенный DBM_Filter «utf8» (см. DBM_Filter), который был разработан для решения этой ситуации.

Пример ниже показывает, что вам нужно, если и ключ, и значение должны быть в UTF-8.

use DB_File;
use DBM_Filter;

my $db = tie %h, 'DB_File', '/tmp/try.db', O_CREAT|O_RDWR, 0666, $DB_BTREE;
$db->Filter_Key_Push('utf8');
$db->Filter_Value_Push('utf8');

my $key = "\N{LATIN SMALL LETTER A WITH ACUTE}";
my $value = "\N{LATIN SMALL LETTER E WITH ACUTE}";
$h{ $key } = $value;

Что означает «Invalid Argument»?

Вы получите это сообщение об ошибке, когда один из параметров в вызове tie неверен. К сожалению, параметров довольно много, поэтому трудно понять, какой именно.

Вот несколько возможностей:

  1. Попытка повторного открытия базы данных без её закрытия.

  2. Использование флага O_WRONLY.

Что означает «Bareword 'DB_File' not allowed»?

Вы столкнетесь с этой ошибкой, когда в вашем скрипте есть pragma strict 'subs' (или полное pragma strict). Рассмотрим этот скрипт:

use warnings ;
use strict ;
use DB_File ;
my %x ;
tie %x, DB_File, "filename" ;

Запуск его приводит к ошибке:

Bareword "DB_File" not allowed while "strict subs" in use

Чтобы обойти ошибку, поместите слово DB_File в одинарные или двойные кавычки, как в этом примере:

tie %x, "DB_File", "filename" ;

Хотя это может показаться проблемой, это действительно стоит усилий, чтобы иметь pragma use strict во всех ваших скриптах.

СПИСОК ЛИТЕРАТУРЫ

Статьи, которые либо о DB_File, либо используют его.

  1. Full-Text Searching in Perl, Тим Кьентцле (tkientzle@ddj.com), Dr. Dobb's Journal, Выпуск 295, январь 1999, стр. 34-41

ИСТОРИЯ

Перемещено в файл изменений.

ОШИБКИ

В некоторых старых версиях Berkeley DB были проблемы с записями фиксированной длины, использующими формат файла RECNO. Эта проблема была исправлена начиная с версии 1.85 Berkeley DB.

Я уверен, что в коде есть ошибки. Если вы найдёте какие-либо или сможете предложить улучшения, я буду рад вашим комментариям.

ПОДДЕРЖКА

Общие отзывы/вопросы/отчёты об ошибках следует отправлять на https://github.com/pmqs/DB_File/issues (предпочтительный вариант) или https://rt.cpan.org/Public/Dist/Display.html?Name=DB_File.

ДОСТУПНОСТЬ

DB_File поставляется со стандартным дистрибутивом Perl. Посмотрите в каталоге ext/DB_File. Учитывая промежуток времени между выпусками Perl, версия, поставляемая с Perl, довольно устарела, поэтому последнюю версию всегда можно найти на CPAN (см. "CPAN" в perlmodlib для получения подробностей), в каталоге modules/by-module/DB_File.

DB_File разработан для работы с любой версией Berkeley DB, но ограничен функциональностью, предоставленной версией 1. Если вы хотите использовать новые функции, доступные в Berkeley DB 2.x или выше, используйте модуль Perl BerkeleyDB вместо него.

Официальный веб-сайт Berkeley DB находится по адресу http://www.oracle.com/technology/products/berkeley-db/db/index.html. Там доступны все версии Berkeley DB.

Кроме того, Berkeley DB версии 1 доступна в вашем ближайшем архиве CPAN в src/misc/db.1.85.tar.gz.

АВТОРСКИЕ ПРАВА

Авторские права (c) 1995-2022 Пол Маркесс. Все права защищены. Эта программа является свободной программой; вы можете распространять и/или изменять её на тех же условиях, что и сам Perl.

Хотя DB_File подпадает под лицензию Perl, библиотека, которую он использует, а именно Berkeley DB, нет. У Berkeley DB есть своя собственная лицензия. См. AGPL для получения дополнительной информации. Пожалуйста, уделите время, чтобы прочитать лицензию Berkeley DB и решить, как она влияет на ваше использование этого модуля Perl.

СМОТРИТЕ ТАКЖЕ

perl, dbopen(3), hash(3), recno(3), btree(3), perldbmfilter, DBM_Filter

АВТОР

Интерфейс DB_File был написан Полом Маркессом <pmqs@cpan.org>.

© 1993–2023 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.38.0/DB_File

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API