Журнал Системный Администратор, Сентябрь 2003

Журнал Системный Администратор

Сентябрь 2003

Цена: $4.5 US

  Подписаться

Зарегистриванные пользователи, пожалуйста следуйте этой ссылке

Версия для печати Вернуться к оглавлению

Работа с базами данных на Perl

Владислав Гошко

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

Name1|Value1#Value2#Value3#...#ValueN

Name2|Value1#Value2#Value3#...#ValueN

Name3|Value1#Value2#Value3#...#ValueN

...

NameN|Value1#Value2#Value3#...#ValueN

Почему разделителями я выбрал именно «|» и «#»? Потому что они не являются безопасными символами (из RFC 2396). По той же причине можно было выбрать разделителями любой символ, кроме латинских букв, цифр, и вот этих:

";", "/", "?", ":", "@", "&", "=", "+", "$", ",", "-", "_", ".", "!", "~", "*", "'", "(", ")"

Эти разделители (кроме вышеописанных символов) могут использоваться в значениях или в именах переменных, только нужно будет использовать модуль URI::Escape, для того чтобы переменные с этими разделителями не испортили структуру. Специально для работы с нашими базами данных создадим модуль simple_db.pm. Итак, начнем:

#!/usr/bin/perl

 

# создаем пакет

package simple_db;

 

use Exporter;

# как и говорилось, используем модуль для перевода небезопасных символов в формат %XX

use URI::Escape;

 

@ISA=("Exporter");

@EXPORT=qw(&opendb &closedb %g);

# экспортируем используемые функции, а также хеш %g, для того чтобы функция знала,

# в какой файл записывать изменения хеша, вводимого в аргументах

 

sub opendb{

    my($h,$file)=@_;

    my($name,$vals,@values);

    local(*DB);

    open(DB,$file) or return 0;

    # блокируем файл на тот случай, если во время чтения кто-то захочет изменить

    # файл (совместная блокировка – для чтения)

    flock(DB,1);

    while(<DB>){

           chomp;

           ($name,$vals)=split(/\|/,$_,2);

           next if !$name;

           $name = uri_unescape($name);

           @values = split(/#/,$vals);

           # если значений много, создаем анонимный массив, иначе просто

           # присваиваем одно значение

           if($#values){

                 for(0..$#values){

                        # переводим символы из %XX-формата

                        # в нормальный вид

                        $values[$_]=uri_unescape ($values[$_]);

                 }

                 $$h{$name}=[@values];

           }else{

                 $vals = uri_unescape($vals);

                 $$h{$name}=$vals;                

           }

    }

    close(DB);

    # записываем в  глобальный хеш ассоциацию ссылки на хеш

    # с открытым файлом

    $g{$h}=$file;

}

 

sub сlosedb{

    my($h)=@_;

    my($key,$val,$fn);

    # по имени хеша получаем имя файла

    $fn = $g{$h};

    local(*DB);

    # выходим из функции, если файла не существует

    return if !(-e $fn);

    open(DB,">$fn") or return 0;

    # замыкаем файл

    # (монопольная блокировка – для записи)

    flock(DB,2);

    while(($key,$val) = each %$h){

           # обратно создаем формат, переводя небезопасные

           # символы в формат %XX

           print DB uri_escape($key)."|";

           if(ref $val){

                 for(0..$#$val){

                        $$val[$_]=uri_escape($$val[$_]);

                 }

                 print DB join "#",@$val;

           }else{

                 $val=uri_escape($val);

                 print DB $val;

           }

           print DB "\n";

    }

    close(DB);

}

1;

Если вам нужно открывать файл с определенными правами, то в функциях opendb() и closedb() нужно просто заменить open на sysopen по следующему формату: sysopen FILEHANDLE, FILENAME, MODE, PERMS; и все. Использование довольно простое, возьмем какой-нибудь файл test.pl. В этой же директории должен лежать и модуль simple_db.pm. Вот test.pl:

#!/usr/bin/perl

 

use simple_db;

 

# открываем файл test.db и ассоциируем с ним хеш %h,

# иначе умираем

opendb(\%h,"test.db") or die $!;

# добавляем значения в хеш

$h{'supa|var'}=["special#","tes#t"];

# также легко можно добавить массив

@tmp = ("array","tester");

# добавили ...

$h{'arr'}=\@tmp;

# или добавляем массив так

$h{'arr2'}=["some","vars"];

closedb(\%h) or die $!;

Данным образом будет создан файл test.db в текущей директории. С переменной «supa|var» и значениями «speacial#» и «tes#t», и т. д. Я специально использовал небезопасные символы, а со второй и третьей переменной все в порядке – там нет небезопасных символов...

А теперь давайте посмотрим, что записано в файле test.db:

supa%7Cvar|special%23#tes%23t

arr|array#tester

arr2|some#vars

Т.е. эти символы не помешали нашей структуре файла, они всего лишь были переведены в %XX-формат. А теперь просмотрим всю базу:

#!/usr/bin/perl

 

use simple_db;

 

opendb(\%h,"test.db") or die $!;

while(($key,$val) = each %h){

    print $key." = ";

    if(ref $val){

           # здесь, если значение переменной ссылка на массив

           print join " ; ",@$val;

    }else{

           print $val;

    }

    print "\n";

}

closedb(\%h) or die $!;

При выводе база будет выглядеть так:

supa|var = special# ; tes#t

arr = array ; tester

arr2 = some ; vars

Очищаем базу, просто очищая хеш, т.е. %h=(), или можно просто удалить файл следующим образом:

unlink "test.db" or die $!;

А при открытии базы открыть файл в режиме создания, т.е. перед opendb() сделать следующее:

# $filename - в данном случае имя нашего файла

if(!(-e "$filename")){

    open(FILE,">$filename") or die $!;

    close(FILE);

}

Но эта база не может претендовать на большую скорость и большие размеры и, как вы уже успели заметить, в ней не могут быть созданы сложные структуры. Данная база данных вполне подойдет для небольших объемов информации, например, для небольших сайтов или веб-сервисов. С помощью классов можно создать свою вполне приличную базу данных. А теперь давайте расмотрим другие базы данных. Начнем с портированной в вашу систему базу данных (их может быть несколько). Функция dbmopen() довольна стара и поэтому позволяет использовать лишь ту библиотеку DBM, c которой был построен Perl. Но по ходу статьи рассмотрим и более новые методы.

Вот пример с dbmopen():

#!/usr/bin/perl

 

use Fcntl;

# специально для таких переменных, как O_RDWR, O_CREAT

# O_RDWR - права: чтение, запись

# O_CREAT - создать файл, если он не существует

 

dbmopen(%HASH,$FILENAME,O_RDWR|O_CREAT, 0666) or die "Cant open $FILENAME: $!\n";

# заносим данные в базу данных

$HASH{KEY}="VALUE";

# проверяем, существует ли ключ

if(exists $HASH{KEY}){

    # что-то делаем с информацией, полученной из базы данных

    $info = $HASH{KEY};

}

# удаляем какой-нибудь ключ из базы

delete $HASH{SOME_KEY};

dbmclose(%HASH);

Также в начале программы можно добавить: use NDBM_File; или use SDBM_File; или use GDBM_File; или use DB_File; – данные модули просто переопределяют стандартный вариант, с которым был построен Perl. Есть также другой способ открытия базы данных – tie и untie. Делается это так:

#!/usr/bin/perl

 

use DB_File;

# здесь это обязательно, т.к. в функции tie() мы задаем модуль

use Fcntl;

 

tie(%HASH,"DB_File",$FILENAME,O_RDWR|O_CREAT, 0666) or die "Can't open $FILENAME: $!\n";

# все те же манипуляции с хешем, которые описывались выше

# ...

# а потом

untie(%HASH);

Очищаются базы так же, как и в моем примере, т.к. я свой пример делал по подобию этих баз. Но так же, как и мой пример, данные базы не могут хранить сложные структуры. Для этого требуется модуль DB_File и модуль MLDBM. Модуль MLDBM может хранить в хеше более сложные структуры, чем просто числа и скаляры. Если его у ваc нет, то вы можете его скачать из Интернета: запускаете программу ppm из дистрибутива Perl. И пишите: «install MLDBM» – все должно пройти успешно. Использование вот такое:

#!/usr/bin/perl

 

use MLDBM 'DB_File';

use Fcntl; # для O_RDWR, O_CREAT и т.д.

 

tie(%h, "MLDBM", "glob.db", O_RDWR|O_CREAT, 0666) or die "Couldn't tie DB_File $users: $!; aborting";

$usr{synthetic}->{password}="matrix reloaded";

$h{users}=\%usr;

untie %h;

Таким образом была создана база данных glob.db, в которой есть ключ «users», в котором есть пользователь «synthetic» с еще одним вложенным хешем, в котором есть ключ «password» со значением «matrix reloaded». Такая структура очень удобна и легко запрашиваема. Далее посмотрим, как мы проверяем то, что создали:

#!/usr/bin/perl

 

use MLDBM 'DB_File';

use Fcntl; # для O_RDWR, O_CREAT и т. д.

 

$access=0;

tie(%h,"MLDBM","glod.db",O_RDWR|O_CREAT,0666) or die "Couldn't tie DB_File $users: $!; aborting";

while(($key,$val) = each %h){

    if($key eq "users"){

           if($val->{synthetic}->{password} eq "matrix reloaded"){

                 $access=1;last;

                 # если все правильно, завершаем цикл

           }

    }

}

untie(%h);

if($access){

    print "Matrix has you...";

}else{

    print "Follow the white rabbit";

}

С удалением нужно немного по-другому, через временный хеш:

tie(%h,"MLDBM","glob.db",O_RDWR|O_CREAT,0666) or die "Couldn't tie DB_File $users: $!; aborting";

%tmp=%h;

delete $tmp{users}->{'somebody'};

%h=%tmp;

untie %h;

Добавлять тоже через временный хеш:

tie(%h,"MLDBM","glob.db",O_RDWR|O_CREAT,0666) or die "Couldn't tie DB_File $users: $!; aborting";

%tmp=%h;

$tmp{users}->{'morpheus'}->{password}='zion';

%h=%tmp;

untie %h;

 

И в моем примере, и в других базах используется работа с хешами, поэтому давайте рассмотрим несколько примеров работы с хешами...

n  Сортировка ключей хеша по алфавиту:

 

foreach $key (sort keys %unsorted){

$val = $unsorted{$key};

# здесь переборка ключей хеша по алфавиту

# делаем что-то c $key и $val

}

 

n  Сортировка по ассоциированным значениям:

 

foreach $key (sort {$unsorted{$a} cmp $unsorted{$b} } keys %unsorted){

$val = $unsoreted{$key};

# что-то делаем с $key и $val...

}

 

n  Сортировка по длине значений (почти также, как и просто по значениям):

 

foreach $key (sort {length($unsorted{$a}) <=> length($unsorted{$b}) } keys %unsorted){

$val = $unsoreted{$key};

# что-то делаем с $key и $val...

}

Дальше рассмотрим небольшие примеры работы с базами данных MySQL, при помощи модуля DBI и драйвера для работы с MySQL – DBD::mysql. Эти модули также можно установить через ppm. А теперь посмотрим пример работы с MySQL:

#!/usr/bin/perl

 

use DBI;

 

# настройки SQL сервера

$user     = "synthetic";       # логин и

$password = "test";            # пароль для доступа к серверу

$host     = "localhost";       # адрес SQL-сервера

$db       = "site";            # база данных, с которой соединяемся

$port     = 3306;              # порт (взят по умолчанию)

$driver   = "mysql";           # это драйвер для базы данных, т.е.  вы можете указать драйвер своей

  # базы и спокойно соединятся с ней (естественно зная ее семантику)

 

# данные - просто для проверки

$login = "Vlad";

$pass  = "isitreal";

 

$conn = "DBI:$driver:database=$db;host=$host;port=$port";

# RaiseError => 1 - сообщать об ошибках

$dbh = DBI->connect($conn, $user, $password, {RaiseError => 1});

# задали название тэйбла - для дальнейшего использования

$table = "users";

# создаем тэйбл "users"

$query = "CREATE TABLE .$table(username char(16) not null,pass char(16) not null)";

# создали

$dbh->do($query);

# отсоединилсь

$dbh->disconnect();

Не особо сложными манипуляциями добавляем данные в тэйбл:

# вся предыдущая инициализация

$query = sprintf("INSERT INTO .$table (username, pass) VALUES ('%s', '%s')", $login, $pass);

# выполнили...

$dbh->do($query);

# и т. д.

Выбираем из базы:

# вся предыдущая инициализация.

# создаем запрос к базе и выбираем все из тэйбла users

$sth = $dbh->prepare("select * from .users");

# выполнили

$sth->execute();

while($row = $sth->fetchrow_arrayref()){

    # в данном случае:

    # $row->[0] - логин (username)

    # $row->[1] - пароль (pass)

    print $row->[0]." ".$row->[1];

}

# обязательно (!) говорим, что завершили

$sth->finish();

Удаляем тэйбл, если он существует:

# вся предыдущая инициализация

$query = "DROP TABLE IF EXISTS .$table";

# выполнили...

$dbh->do($query);

# и т. д.

Вот, собственно, и все, в данной статье я не стал особенно глубоко рассматривать работу с базами SQL, потому как это довольно большая тема и требует отдельной статьи (если не книжки). Удачи в создании систем управления базами данных!




Все права зарезервированы. Этот материал принадлежит или лицензирован компании PLARANA INC. Только для частного использования. Любое распространение запрещено без письменного разрешения PLARANA INC
Версия для печати Вернуться к оглавлению