ВЛАДИСЛАВ ГОШКО
Работа с базами данных на 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 " ";
}
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: $! ";
# заносим данные в базу данных
$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: $! ";
# все те же манипуляции с хешем, которые описывались выше
# ...
# а потом
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;
И в моем примере, и в других базах используется работа с хешами, поэтому давайте рассмотрим несколько примеров работы с хешами...
- Сортировка ключей хеша по алфавиту:
foreach $key (sort keys %unsorted){
$val = $unsorted{$key};
# здесь переборка ключей хеша по алфавиту
# делаем что-то c $key и $val
}
- Сортировка по ассоциированным значениям:
foreach $key (sort {$unsorted{$a} cmp $unsorted{$b} } keys %unsorted){
$val = $unsoreted{$key};
# что-то делаем с $key и $val...
}
- Сортировка по длине значений (почти также, как и просто по значениям):
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, потому как это довольно большая тема и требует отдельной статьи (если не книжки). Удачи в создании систем управления базами данных!