Spec-Zone.ru › Perl 5.36

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 целым числом.
  • СОВЕТЫ И ПРИЕМЫ
    • Блокировка: проблема с 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 ;

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

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. Этот метод позволяет получить последовательный доступ к парам ключ/значение. Подробнее о методе seq и API в целом см. "ИНТЕРФЕЙС 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 предоставляет интерфейс к плоским текстовым файлам. Поддерживаются как переменные, так и фиксированные записи.

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

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

$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 предполагает, что все ключи и значения нуль-завершаются. К сожалению, 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 как строка «12345» длиной 5 байтов. Если вы хотите, чтобы ключ сохранялся в базе данных 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 заключался во флокке файлового дескриптора, возвращаемого функцией «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. Используйте «связывание» для открытия базы данных.

  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-ФИЛЬТРЫ" для универсального способа решения этой проблемы.

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

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

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

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

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

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

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

Что означает "Не разрешено неприводимое слово 'DB_File'"?

Вы столкнётесь с этой конкретной ошибкой, когда в вашем скрипте есть директива strict 'subs' (или полная директива 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.

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

ПОДДЕРЖКА

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

Вот несколько слов из часто задаваемых вопросов 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–2021 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.36.0/DB_File

Spec-Zone.ru

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