Rose::DB::Object — разные засады

Rose::DB в целом хороший фреймворк, но есть некоторые неприятности...

В Rose::DB используется хитрый бестолковый бессмысленный идиотский механизм подключения к базе. Чем это вызвано непонятно. Мне хочется иметь подключение такое же, как обычно в скриптах, использующих DBI, то есть что-то типа

my $dbh = DBI->connect( $c->{dsn}, $c->{user}, $c->{password}, $c->{opt} ); 
<инициализация Rose с указанием нужного $dbh> 
<использование всех прелестей Rose> 

Вместо этого надо создать конфигурацию для типа базы и домена, и наслаждаться постоянными подключениями к базе с сопутствующими потерями производительности. С этим можно было бы мириться, так как $dbh из Rose получить можно, но проблемы начинаются при восстановлении подключения при обрыве связи с базой - встроенного механизма в Rose нет. Выход - как-то заставить Rose использовать не свой Rose::DB и $dbh, а то что мы ему дадим. Механизмы для этого есть - это организация надстройки (потомка) над Rose::DB и Rose::DB::Object и перехват функций dbi_connect, init_dbh, init_db. Управление соединением, таким образом, выносится за пределы Rose и может быть как угодно восстанавливаться и проверяться на обрыв. Единственная засада - В Rose::DB правитcя стандартный $dbh , добавляя в него кучу лишних опций. Непонятно пока что такое $self->{'__dbh_attributes'} и откуда оно берётся. По идее это атрибуты $dbh;

Rose надо уведомить, что не он управляет соединением с базой. Для этого надо установить атрибут
_dbh_has_foreign_owner:

$db - объект класса Rose::DB.

$db->{_dbh_has_foreign_owner}=1;

Другой способ - переопределить процедуру Rose::DB::release_dbh:

package ORM::DB;
use strict;
use base qw(Rose::DB);

__PACKAGE__->use_private_registry;
__PACKAGE__->register_db(
  driver => 'Pg',
);

sub release_dbh {
  return 1;
}

1;

Особый интерес представляют атрибутs private_pid и private_pid. Они устанавливаются в init_dbh(), так что при написании своего init_dbh() их надо не забыть установить. Если этого не сделать, то будет забавная проблема - в morbo всё работает, а в hypnotoad нет (это Mojolicious).

# Rose::DB проверяет эти переменные. И установить их надо именно в текущем процессе.
$dbh_dc->{'private_pid'} = $$; 
$dbh_dc->{'private_tid'} = threads->tid if ($INC{'threads.pm'});
sub init_dbh
{
  my($self) = shift;

  my $options = $self->connect_options;

  $options->{'private_pid'} = $$;
  $options->{'private_tid'} = threads->tid  if($INC{'threads.pm'});

  my $dsn = $self->dsn;

  $self->{'error'} = undef;
  $self->{'database_version'} = undef;
  $self->{'_dbh_refcount'} = 0;
  $self->{'_dbh_has_foreign_owner'} = undef;

  my $dbh = $self->dbi_connect($dsn, $self->username, $self->password, $options);

  unless($dbh)
  {
    $self->error("Could not connect to database: $DBI::errstr");
    return undef;
  }

  if($dbh->{'private_rose_db_inited'})
  {
    # Someone else owns this dbh
    $self->{'_dbh_has_foreign_owner'} = 1;
  }
  else # Only initialize if this is really a new connection
  {
    $dbh->{'private_rose_db_inited'} = 1;

    if($self->{'__dbh_attributes'})
    {
      foreach my $attr (keys %{$self->{'__dbh_attributes'}})
      {
        my $val = $self->dbh_attribute($attr);
        next  unless(defined $val);
        $dbh->{$attr} = $val;
      }
    }  

    if((my $sqls = $self->post_connect_sql) && !$dbh->{DID_PCSQL_KEY()})
    {
      my $error;

      TRY:
      {
        local $@;

        eval
        {
          foreach my $sql (@$sqls)
          {
            #$Debug && warn "$dbh DO: $sql\n";
            $dbh->do($sql) or die "$sql - " . $dbh->errstr;
          }
        };

        $error = $@;
      }

      if($error)
      {
        $self->error("Could not do post-connect SQL: $error");
        $dbh->disconnect;
        return undef;
      }

      $dbh->{DID_PCSQL_KEY()} = 1;
    }
  }

  $self->{'_dbh_refcount'} = 1;

  return $self->{'dbh'} = $dbh;
}

По хорошему надо полностью продублировать дополнительный функционал, но обязательно надо добавить опции 'private_pid' и 'private_tid' - они будут потом постоянно проверяться. Вообще создаётся впечатление, что половину кода из Rose::DB можно смело убрать, так как на поддержание всяких странных фич тратится куча кода. И всё равно остаётся открытым вопрос - как использовать одну модель с разными подключениями, например с разными именами пользователей и с разными схемами. Стандартный способ - в каждый запрос типа ORM::Table или ORM::Table::Manager передавать свой $db - криво. Хорошо было бы создавать объект Rose и для него вызывать функции - было бы гораздо проще. Так как есть сейчас удобно для очень простых применений, и есть подозрение что создавалось в рассчёте на Mysql.

Leave a Reply