Spec-Zone.ru › Perl 5.30

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
    • API фильтра DBM низкого уровня
    • Фильтр
    • Пример — проблема нулевого завершения.
    • Другой пример — ключ является C int.
  • УКАЗАНИЯ И СОВЕТЫ
    • Блокировка: проблема с fd
    • Безопасные способы блокировки базы данных
    • Работа с базами данных с C-приложениями
    • Ловушка untie()
  • ЧАСТЫЕ ВОПРОСЫ
    • Почему в моей базе данных есть исходный код Perl?
    • Как хранить сложные структуры данных с DB_File?
    • Что означает "wide character in subroutine entry"?
    • Что означает "Invalid Argument"?
    • Что означает "Bareword 'DB_File' not allowed"?
  • СПИСОК ЛИТЕРАТУРЫ
  • ИСТОРИЯ
  • ОШИБКИ
  • ДОСТУПНОСТЬ
  • АВТОРСКИЕ ПРАВА
  • СМОТРИТЕ ТАКЖЕ
  • АВТОР

ИМЯ

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 — это 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() для открытия или создания базы данных. Вот C-прототип для dbopen():

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 = new DB_File::HASHINFO ;
$a->{'bsize'} ;
$a->{'cachesize'} ;
$a->{'ffactor'};
$a->{'hash'} ;
$a->{'lorder'} ;
$a->{'nelem'} ;

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

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

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

$a = new DB_File::HASHINFO ;
$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 ;

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

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. Обратите внимание, что значения будут возвращены в, по-видимому, случайном порядке.

В контексте списка, если второй параметр присутствует и оценивается как TRUE, метод возвращает ассоциативный массив. Ключи ассоциативного массива соответствуют значениям, которые совпали в 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 из руководства recno Berkeley DB 1.85:

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 ;

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

Все функции, определённые в 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 ;

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

См. «Блокировка: Проблема с 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 предполагает, что все ключи и значения имеют нулевое завершение. К сожалению, когда Perl записывает в базы данных DBM, он не использует нулевое завершение, поэтому вашему приложению на 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 является чтение начального блока из базы данных и его кэширование в памяти.

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

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

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

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

Вышеупомянутое не произойдёт каждый раз, когда конкурирующие процессы обновляют одну и ту же базу данных 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, настоятельно рекомендуется ознакомиться с "Особенности untie()" в 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, не будет вызван, пока все ссылки на связанный объект не будут уничтожены. Переменная tied, %x, и $X выше содержат ссылку на объект. Вызов untie() уничтожит первую, но $X по-прежнему содержит действительную ссылку, поэтому деструктор не будет вызван, и файл базы данных tst.fil останется открытым. Факт, что затем Berkeley DB сообщает об попытке открыть базу данных, которая уже открыта, посредством универсального сообщения «Недопустимый аргумент», не помогает.

Если вы запустите скрипт с флагом -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.

Самый простой способ справиться с этой проблемой — использовать предварительно определённый «utf8» DBM_Filter (см. 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;

Что означает «Недопустимый аргумент»?

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

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

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

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

Что означает «Не разрешено использование слова «DB_File»»?

Вы столкнётесь с этой конкретной ошибкой, когда у вас включен strict 'subs' pragma (или полный 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" ;

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

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

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

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

ИЗМЕНЕНИЯ

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

ОШИБКИ

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

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

ДОСТУПНОСТЬ

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

Эта версия DB_File будет работать с версиями 1.x, 2.x или 3.x Berkeley DB, но ограничена функциональностью, предоставляемой версией 1.

Официальный веб-сайт 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-2016 Пол Маркесс. Все права защищены. Эта программа — свободное программное обеспечение; вы можете распространять и/или изменять её на тех же условиях, что и Perl сам по себе.

Хотя DB_File защищено лицензией Perl, библиотека, которую она использует, а именно Berkeley DB, — нет. Berkeley DB имеет свои авторские права и свою лицензию. Пожалуйста, найдите время, чтобы её прочитать.

Вот несколько слов из FAQ Berkeley DB (по адресу http://www.oracle.com/technology/products/berkeley-db/db/index.html) относительно лицензии:

Do I have to license DB to use it in Perl scripts? 

No. The Berkeley DB license requires that software that uses
Berkeley DB be freely redistributable. In the case of Perl, that
software is Perl, and not your scripts. Any Perl scripts that you
write are your property, including scripts that make use of
Berkeley DB. Neither the Perl license nor the Berkeley DB license
place any restriction on what you may do with them.

Если у вас есть сомнения относительно ситуации с лицензией, обратитесь либо к авторам Berkeley DB, либо к автору DB_File. См. "АВТОР" для получения подробностей.

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

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

АВТОР

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

© 1993–2020 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.30.3/DB_File

Spec-Zone.ru

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