Развиваем модуль DBI::Журнал СА 9.2006
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Дата-центры  

Дата-центры: есть ли опасность утечки данных?

Российские компании уже несколько лет испытывают дефицит вычислительных мощностей. Рост числа проектов,

 Читать далее...

Книжная полка  

Защиты много не бывает

Среди книжных новинок издательства «БХВ» есть несколько изданий, посвященных методам социальной инженерии

 Читать далее...

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6366
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 7072
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4350
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 3074
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3867
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3885
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6378
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3225
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3518
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7342
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10704
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12430
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 14073
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9186
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7135
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5441
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4674
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3483
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3205
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3444
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 3078
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Развиваем модуль DBI

Архив номеров / 2006 / Выпуск №9 (46) / Развиваем модуль DBI

Рубрика: Программирование /  Автоматизация

АЛЕКСЕЙ МИЧУРИН

Развиваем модуль DBI

DBI – это Perl-модуль для взаимодействия с базами данных. Наверно, я не сильно ошибусь, если скажу, что он используется в доброй половине всех существующих Perl-программ. Можно ли его усовершенствовать или адаптировать, пользуясь его объектно-ориентированной природой? Конечно!

Покопаться во внутреннем устройстве модуля DBI интересно по двум причинам. Во-первых, он весьма популярен и используется в проектах самого разного масштаба. Вам, я думаю, знакома ситуация: крупный проект развивался на MySQL 4.0, и, когда было накоплено немало кода, MySQL 4.0 сменили на 4.1. В этом случае сразу возникают проблемы с кодировками. Наиболее универсальное решение – выставлять кодировку после каждого вызова connect, но это может потребовать скорректировать код многих сценариев.

Аналогичная ситуация возникает, когда с одной базой работают комплексы программ, разработанные из расчёта на разные установки времени; например, одна программа предполагает, что системные часы идут по Гринвичу, а другая – по московскому времени. Тогда выставить единое время для базы не представляется возможным и каждый клиент должен выставлять локальную переменную time_zone. Как и в предыдущем, в этом случае есть два выхода: первый – «перелопатить» весь код; второй – написать модуль, наследующий все свойства DBI, но с некоторыми коррективами.

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

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

Вторая причина, по которой обратиться к модулю DBI будет весьма интересно, – это нетривиальность самого модуля. Как мы скоро увидим, чтобы унаследовать методы этого модуля, совсем недостаточно строчки:

# для DBI это не работает

@ISA=qw(DBI);

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

Весь SQL-код и код, зависящий от используемой базы данных, я буду писать, ориентируясь на MySQL, если вы работаете с другой СУБД, то, возможно, вам придётся внести в код незначительные изменения. Perl-составляющая кода и суть обсуждаемых в статье вопросов, конечно, от базы не зависят.

Внутренняя структура DBI

Чтобы понять логику модуля DBI, следует сперва обратить внимание на то, что этот модуль предоставляет два класса принципиально различных объектов: дескрипторы базы данных (или соединения) и дескрипторы запросов. Каждый из этих классов является самостоятельным и полноценным. Для каждого имеются конструкторы (connect для баз данных и prepare для запросов), множество различных методов и деструкторы (disconnect и finish, соответственно).

То есть DBI описывает два класса и, следуя логике Perl, должен быть не одним, а двумя модулями или пакетами. В любом случае должны быть использованы два пространства имён и две директивы package, оформлено ли это одни файлом или двумя, пакетами или модулями.

Так оно и есть.

В недрах модуля DBI существуют два разных класса (пакета или модуля; в терминах Perl это практически одно и тоже). Первый – класс соединений – DBI::db, второй – класс запросов – DBI::st. То есть когда вы инициируете новое соединение:

my $dbh=DBI->connect(...);

вы порождаете объект класса DBI::db (а не класса DBI, как можно было бы подумать). Аналогично, когда вы конструируете новый дескриптор запроса:

my $sth=$dbh->prepare("select ...");

вы получаете объект класса DBI::st. Причём обратите внимание, метод prepare, который мы только что вызвали и который является конструктором для объектов класса DBI::st, принадлежит объекту класса DBI::db. Методы дескрипторов запросов, соответственно, принадлежат объектам класса DBI::st.

Таким образом, DBI – это не один, а два тесно связанных класса, что незаметно для прикладного программиста, но при детальном рассмотрении оказывается вполне логично.

Пустой модуль, наследующий от DBI

Теперь становится понятно, как унаследовать методы модуля DBI. Вот развёрнутый пример, простой и понятный, как букварь:

 1: package DBItoo;

 2:  use strict;

 3:  use DBI;

 4:  use vars qw(@ISA);

 5:  @ISA = qw(DBI);

 6: package DBItoo::db;

 7:  use vars qw(@ISA);

 8:  @ISA = qw(DBI::db);

 9: package DBItoo::st;

10:  use vars qw(@ISA);

11:  @ISA = qw(DBI::st);

12: 1;

Этот код, естественно, должен находиться в файле DBItoo.pm. Как видите, это три модуля в одном, содержимое каждого модуля я дополнительно выделил отступами. Все просто: первый модуль – DBItoo – является наследником DBI, второй – DBItoo::db – наследник DBI::db, третий – DBItoo::st – DBI::st.

Разные способы использования собственных модулей

Наш модуль DBItoo просто наследует все свойства DBI, не привнося ничего нового. Использовать его можно точно так же, как DBI:

#!/usr/bin/perl -w

use strict;

use DBItoo;

my $dbh=DBItoo->connect('DBI:mysql:test', 'root', '');

my $sth=$dbh->prepare('SELECT id, txt FROM tbl');

$sth->execute();

while (my ($id, $txt)=$sth->fetchrow_array()) {

  print "$id $txt\n";

}

$sth->finish();

$dbh->disconnect();

Однако есть и другой способ:

use DBI;

my $dbh=DBI->connect('DBI:mysql:test', 'root', '', {RootClass => 'DBItoo'});

Здесь мы загрузили обычный DBI, но при создании соединения указали атрибут RootClass=>'DBItoo'. После такого вызова модуль DBItoo будет загружен автоматически и вместо обычного объекта DBI::db будет создан объект DBItoo::db со всеми вытекающими последствиями.

Обратите внимание, что при втором способе загрузки строка:

 3: use DBI

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

Разные варианты оформления модулей

Рассмотренный нами модуль DBItoo можно назвать «прилежно составленным». В реальной жизни модуль-наследник DBI может выполнять множество разных функций, тогда, возможно, его будет удобнее разбить на отдельные файлы; или, наоборот, вам может понадобиться лишь чуть дополнить стандартный DBI, тогда излишний код будет только помехой. Минимальный модуль-наследник (я назвал его DBItoo_cmpct) выглядит так:

@DBItoo_cmpct::ISA = qw(DBI);

@DBItoo_cmpct::db::ISA = qw(DBI::db);

@DBItoo_cmpct::st::ISA = qw(DBI::st);

1;

Если вы хотите, чтобы модуль можно было подключать всеми возможными способами, то в него следует добавить строку «use DBI;». Использование подобных сокращённых конструкций можно одобрять или не одобрять. Я ни в коем случае не настаиваю на их применении, но буду использовать их далее в статье, просто чтобы не перегружать листинги большими повторяющимися кусками кода.

Несколько слов о наследовании в Perl

Прежде чем перейти к практике, давайте вспомним несколько терминов из теории ОО-программирования, относящихся к наследованию.

Допустим, у нас имеется объект A класса ClassA. У него имеются два метода – A->a и A->b. Причём метод A->a в процессе своей работы вызывает метод A->b. Теперь мы создаём класс ClassB, который является наследником ClassА и содержит собственный метод b. Как будет вести себя объект B класса ClassB? Что будет происходить при вызове метода B->a (унаследованного от A->a)? Опытные ОО-программисты легко ответят на этот вопрос: если метод b виртуальный, то ответ – B->b; если нормальный – A->b.

В Perl нет специального синтаксиса описания виртуальных и нормальных методов. Вместо этого существует простое соглашение: при работе с методами класса всё работает нормально, при работе от имени объекта – виртуально. (То есть один и тот же метод, допускающий два разных вызова, может работать по разным схемам.)

Напомню, что методами класса называются методы, вызываемые от имени класса (не требующие существования объекта).

Типичными представителями таких методов являются конструкторы:

$dbh=DBI->connect(...);

Но это могут быть и методы, устанавливающие некоторые глобальные настройки для всех объектов класса (например, DBI->trace) или сообщающие о ресурсах, доступных в равной мере всем объектам класса (DBI->available_drivers). Методами объекта называются методы, оперирующие с данными определённого объекта. Это, например, $dbh->do().

Манипуляции с методами базы данных

Давайте рассмотрим несколько примеров выполнения различных действий с методами объекта-соединения.

Метод connect (замещение существующего виртуального метода)

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

Для нас существенно то, что метод connected вызывается не как метод класса, а как метод «вновь испечённого» объекта-дескриптора соединения. В качестве аргументов методу connected передаются все аргументы соответствующего connect.

Внимательный читатель уже успел оценить всё изящество конструкции. Метод connected является методом объекта, то есть он является виртуальным, а это означает, что мы можем его переопределить в модуле-наследнике и метод connect из родителя (DBI) будет вызывать наш метод, а не метод-пустышку из DBI.

Если вы используете MySQL старше 4.1, то наверняка сталкивались с проблемой кодировок. В идеале, сразу после создания соединения клиент должен оговорить, в какой кодировке он работает.

Давайте создадим модуль, который при соединении сразу устанавливает кодировку КОИ8. Я назвал его DBIru (файл DBIru.pm):

 1: use strict;

 2: @DBIru::ISA = qw(DBI);

 3: @DBIru::st::ISA = qw(DBI::st);

 4:

 5: package DBIru::db;

 6: @DBIru::db::ISA = qw(DBI::db);

 7: sub connected {

 8:   my ($dbh, $src, $login, $passwd, $attr)=@_;

 9:   $dbh->do('SET CHARACTER SET koi8r');

10: }

11:

12: 1;

Как и следовало ожидать, метод connected принадлежит к пространству имён DBIru::db, так как он является методом дескриптора соединения. Ничего экзотического он не делает, просто устанавливает кодировку.

Здесь же можно выставить часовой пояс:

$dbh->do(q|SET time_zone='+04:00'|);

Другие способы инициализации соединения

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

Сперва создаём файл следующего содержания:

[dbi_options]

default-character-set=koi8r

Допустим, мы назвали его /home/etc/my.cfg.

Теперь при создании соединения мы должны указать модулю DBI (а он передаст эту информацию драйверу DBD::mysql), что при соединении следует обратить внимание на настроечный файл:

$db=DBI->connect("DBI:mysql:test;".

 "mysql_read_default_file=/home/etc/my.cfg;".

 "mysql_read_default_group=dbi_options",

 "root", "");

Имя файла указывается в параметре mysql_read_default_file, а секция файла, которую надо прочитать, – в параметре mysql_read_default_group. Конечно, это работает только для MySQL, подход, которому посвящена статья, гораздо более универсален, но создать конфигурационный файл всё-таки несколько проще, чем модуль-наследник.

К сожалению, выставить часовой пояс таким образом не получится. Если вы являетесь единственным пользователем базы данных (или другим пользователям безразлично, какое время возвращают SQL-функции типа NOW()), то зону можно выставить глобально:

mysql> SET GLOBAL time_zone = "+04:00";

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

Метод ping (страхующее замещение метода)

Итак, мы попробовали переопределять методы DBI. Давайте теперь попробуем создать собственный новый метод. Наиболее полезным примером мне представляется метод ping. Конечно, модуль DBD::mysql поддерживает метод ping, но если вы мигрируете на другую базу данных, то можете не обнаружить метода ping в соответствующем DBD-драйвере. (Особенно этим грешат драйверы для работы с файлами.) Модуль DBI содержит только «пустышку», которая всегда возвращает «0 but true». Вместе с тем если вы использовали собственный метод ping, то не почувствуете смены DBD-модуля.

Вот текст модуля DBIping.pm:

 1: use strict;

 2: @DBIping::ISA = qw(DBI);

 3: @DBIping::st::ISA = qw(DBI::st);

 4:

 5: package DBIping::db;

 6: @DBIping::db::ISA = qw(DBI::db);

 7: sub ping {

 8:   my ($dbh)=@_;

 9:   my $r=0;

10:   eval {

11:     local $SIG{__DIE__}  = sub { return (0); };

12:     local $SIG{__WARN__} = sub { return (0); };

13:     $r=$dbh->do("select 1");

14:   };

15:   return ($@)?0:$r;

16: }

17: 1;

Метод ping, как и положено, проверяет, не было ли потеряно соединение с базой. Для этого он пытается сделать элементарный запрос к базе и анализирует результат. Конечно, многие базы данных имеют в своём API самостоятельный метод ping, но наш ping сможет работать практически с любой базой данных.

Если вы используете модуль DBIping вместо DBI, то можете навсегда забыть о проблемах с методом ping.

Другие методы соединения

Отмечу, что к методам соединения относятся не только connect, disconnect, rollback, begin_work, но и prepare, и do, и даже last_insert_id, selectrow_array, которые, на первый взгляд, работают с данными и интуитивно могут быть отнесены к методам запроса.

Не стоит обманываться, смотрите в корень – на объект, для которого вызывается метод.

Наследование методов запросов

Аналогично можно переопределить или добавить методы запроса. Я не буду приводить примеры этих действий. Давайте теперь подправим существующий метод – execute.

Модернизация метода execute

Я предлагаю заставить метод execute протоколировать все запросы. Зачем это может понадобиться? Например, вы поддерживаете большой проект. Со временем ресурс набирает популярность, нагрузка на базу данных возрастает, и вы замечаете, что сервер начинает «задыхаться». Причину установить бывает трудно, так как все запросы достаточно легки, просто их много. В таком случае бывает очень полезно собрать статистику по запросам за недельку и проанализировать их: какие запросы выполняются чаще, не надо ли где-то создать индексы... Из такой статистики порой можно узнать самые неожиданные вещи и не раз воскликнуть «Эврика!».

Вот текст модуля DBIlog:

 1: use strict;

 2: @DBIlog::ISA = qw(DBI);

 3: @DBIlog::db::ISA = qw(DBI::db);

 4:

 5: package DBIlog::st;

 6: @DBIlog::st::ISA = qw(DBI::st);

 7: sub execute {

 8:   my $st=shift;

 9:   my @args=@_;

10:   my $s=$st->{"Statement"};

11:   $s=~s/s+/ /g;

12:   open FH, ">>statments" or die;

13:   print FH "$s ";

14:   close FH;

15:   return $st->SUPER::execute(@args);

16: }

17: 1;

Начинается этот модуль аналогично уже рассмотренным, в пространстве имён DBIlog::st расположена одна единственная функция execute, которая заменит одноимённый стандартный метод.

В восьмой строке мы получаем указатель на объект (this), в девятой – аргументы, переданные явно.

В десятой строке мы получаем строку SQL-запроса. Далее мы выкидываем из неё все повторяющиеся пробелы, а заодно заменяем разрывы строк на пробелы. Это сильно облегчит нам работу с log-файлом; формат у него получается очень простой: одна строка – одна команда.

Далее (строки 12-14) открываем файл, записываем строку и закрываем файл.

В пятнадцатой строке мы вызываем «родной» метод execute, пользуясь специальным префиксом SUPER::, и возвращаем результат.

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

Работа со свойствами объекта

С методами объектов мы уже выполнили все возможные операции, давайте теперь рассмотрим более тонкую материю – данные объекта. Например, можно сделать так, чтобы метод execute фиксировал в объекте-запросе время выполнения, которое потом можно будет получить и проанализировать. Так вы сможете узнать, сколько времени ваша программа потратила на обработку результатов запроса. Большинство баз данных позволяют узнать, сколько времени было потрачено на обработку запроса. Сравнив эти два показателя, вы можете сделать вывод о том, где находятся узкие места в вашем коде.

Объект запроса (как и большинство объектов Perl) является хэшем, и наша задача сводится к простому добавлению ещё одного элемента в этот хэш. Для начала, я предлагаю вот такую реализацию:

 1: use strict;

 2: @DBItime::ISA = qw(DBI);

 3: @DBItime::db::ISA = qw(DBI::db);

 4:

 5: package DBItime::st;

 6: @DBItime::st::ISA = qw(DBI::st);

 7: sub execute {

 8:   my $st=shift;

 9:   my $s=$st->SUPER::execute(@_);

10:   $st->{execure_at => time()} if ($s);

11:   return $s;

12: }

13: 1;

Теперь в основной программе в любой момент можно обратиться к свойству объекта execure_at и получить время окончания операции execute для этого объекта. С точки зрения чистого ОО-программирования было бы корректнее написать метод для доступа к этому полю, но основные проблемы кроются совсем не в этом.

Конструкторы и подводные камни

Основные опасности чаще всего возникают как раз при добавлении данных.

Во-первых, все данные лучше всего добавлять в конструкторе, а не в обычном методе, как мы это сделали в предыдущем примере.

Иначе получается, что метод, добавляющий данные, как-бы доделывает объект – берёт на себя часть функциональности конструктора, что противоречит принципам объектно-ориентированного программирования. Вас это не смущает? Тогда я скажу ту же мысль иначе: получается, что конструктор создаёт «недоделанный» объект, нуждающийся в дальнейшей доработке. Это нехорошо.

Во-вторых, и в конструкторе это надо делать вдвойне осторожно. Дело в том, что SUPER-конструктор в случае ошибки может вернуть не объект, а ноль или неопределённое значение.

Если мы не отследим эту ситуацию и попытаемся добавить некоторые данные к объекту, то Perl автоматически создаст ссылку на хэш, но она не будет ассоциирована ни с каким классом. Таким образом ваш конструктор отработает без видимых ошибок, и даже результат его работы будет похож на правду (true), но дальнейшее использование созданного «объекта» может вызвать странные ошибки, природу и причины которых будет не просто понять.

Что ещё можно добавить в DBI

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

Новый метод md5

При работе больших баз данных иногда полезно отслеживать изменения некоторых таблиц (например, чтобы не делать лишних резервных копий больших и редко изменяющихся массивов информации). Как быстро и надёжно определить, изменилась ли таблица, в которой, скажем, несколько миллионов записей? MySQL 4.1 (и выше) предоставляет прекрасную возможность – подсчёт контрольной суммы. (В некоторых ситуациях можно ограничиться просто подсчётом строк, это гораздо быстрее.) Давайте добавим новый метод базы данных $dbh->md5(). Для этого создадим модуль DBImd5:

 1: use strict;

 2: @DBImd5::ISA = qw(DBI);

 3: @DBImd5::st::ISA = qw(DBI::st);

 4:

 5: package DBImd5::db;

 6: @DBImd5::db::ISA = qw(DBI::db);

 7: sub md5 {

 8:   my ($db, $table)=@_;

 9:   my $sql="CHECKSUM TABLE ".

10:          $db->quote_identifier($table);

11:   my $st=$db->prepare($sql);

12:   $st->execute();

13:   my (undef, $md5)=$st->fetchrow_array();

14:   $st->finish();

15:   return $md5;

16: }

17: 1;

Если вместо DBI использовать этот модуль, то в вашем распоряжении окажется ещё один метод соединения:

$dbh->md5(имя_таблицы);

который будет возвращать контрольную сумму всех данных таблицы. Чтобы код не потерял наглядность, я не стал проверять в нём ошибки, ограничившись только самыми важными и необходимыми процедурами. Я имею в виду вызов функции quote_identifier. Ни в коем случае не подставляйте в SQL-запрос имя таблицы «как есть». Это сразу создаст большую брешь в безопасности. Остальные проверки не так принципиальны, и читатель может добавить их сам – по вкусу.

Чем же плох этот метод? Ответ может показаться философским, но весь мой опыт показывает, что за этой философией стоит железный прикладной смысл.

Дело в том, что модуль DBI реализует определённую абстракцию. Он, как известно, передаёт запросы серверу и получает данные. Обратите внимание: он не формирует запросы, он их только передаёт. Конечно, эта абстракция соблюдена не совсем строго, не все методы одинаково хороши для разных запросов и наоборот. Но в данном случае мы нарушили концепцию.

Не менее важно и то, что DBI задумывался как интерфейс, не зависящий от конкретной СУБД. Мы же, добавив метод md5, прочно привязались не только к конкретной базе данных, но и к конкретной её версии. Если учитывать тот факт, что мы никому не навязываем наш модуль и не позиционируем его как универсальный, то наше «преступление» не будет казаться столь уж большим, но концепция всё равно оказывается попрана.

Я вовсе не утверждаю, что наш метод md5 не применим или вреден. Если посмотреть на него непредвзято, то он ничем не хуже, скажем, метода last_insert_id(). Но, следуя по этому пути, надо быть предельно осторожным. Если вы разрушите абстракцию модуля DBI, то он во многом утратит свою ценность. Возможно, методы и функции, противоречащие философии DBI, лучше выносить в другие модули? Нужно взвесить все «за» и «против», прежде чем начать кодировать.

Замена NULL-значений на лету

Давайте переопределим метод fetchrow_array так, чтобы он автоматически заменял все неопределённые значения на строку «N/A».

 1: use strict;

 2: @DBIautodef::ISA = qw(DBI);

 3: @DBIautodef::db::ISA = qw(DBI::db);

 4:

 5: package DBIautodef::st;

 6: @DBIautodef::st::ISA = qw(DBI::st);

 7: sub fetchrow_array {

 8:   my $st=shift;

 9:   return map {defined($_)?$_:"N/A"} ?

    $st->SUPER::fetchrow_array();

10: }

11: 1;

Ничего, требующего дополнительных комментариев, в этом коде нет. Мы корректно наследуем методы из DBI, DBI::db и DBI::st; в пространстве имён DBIautodef::st создаём метод fetchrow_array.

В нашем методе fetchrow_array мы вызываем оригинальный fetchrow_array, используя префикс SUPER. Результат обрабатываем и возвращаем.

В определённых условиях тот метод может быть очень полезен. Одной только заменой DBI на DBIautodef вы избавитесь от всех неопределённых значений, возвращаемых запросами. Если вы выбираете данные для отчётов, наш модуль может весьма и весьма облегчить вашу работу. Но у него есть и важные недостатки.

Во-первых, не станет ли подобное поведение метода fetchrow_array помехой? Надеюсь, вы всё хорошо продумали?

Во-вторых, а как поведут себя методы fetchrow_arrayref и fetchrow_hashref? А многочисленные методы selectrow_array, selectall_arrayref и их близнецы? Документация DBI не говорит ничего определённого о том, какой из этих методов является «основным». Это может зависеть от конкретного DBD-драйвера, и здесь надо быть предельно осторожным.

В-третьих (и, на мой взгляд, это наиболее важно), наш метод снова нарушает логику DBI, ведь DBI не производит никаких манипуляций с данными, он их просто передаёт. Не лучше ли в данном случае сделать одну-единственную функцию, избавляющую вас от неопределённых значений? Этот вопрос не риторический, ответ зависит от конкретных обстоятельств. Если вы выберете более «правильный» подход и создадите простую функцию, то вам придётся скорректировать весь код, включив эту функцию везде, где это необходимо. А если код писали не вы? А те, кто его писали, уже уволились и забыли вообще, как это написано? Тогда, возможно, лучше попрать свои религиозные чувства и грубо наступить на горло идеалам, заложенным в архитектуру DBI.

Заключение

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

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


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru