File | Date | Author | Commit |
---|---|---|---|
lib | 2024-04-18 |
![]() |
[4134eb] Added context analize of peek method calling |
src | 2024-04-17 |
![]() |
[1856bc] Added Dump module |
t | 2024-04-18 |
![]() |
[4134eb] Added context analize of peek method calling |
.gitignore | 2024-02-04 |
![]() |
[fb79a1] Added init files |
.proverc | 2024-02-04 |
![]() |
[fb79a1] Added init files |
Changes | 2024-04-18 |
![]() |
[b73198] Added dump method |
INSTALL.md | 2024-02-04 |
![]() |
[fb79a1] Added init files |
LICENSE | 2024-02-04 |
![]() |
[fb79a1] Added init files |
MANIFEST | 2024-04-17 |
![]() |
[1856bc] Added Dump module |
Makefile.PL | 2024-02-04 |
![]() |
[fb79a1] Added init files |
NOTES | 2024-08-21 |
![]() |
[d6f171] Added notes |
README.md | 2024-04-19 |
![]() |
[433e23] Added README content |
RELEASE.md | 2024-04-18 |
![]() |
[96dbf6] Added release remarks |
TODO | 2024-02-04 |
![]() |
[fb79a1] Added init files |
build.sh | 2024-04-17 |
![]() |
[1856bc] Added Dump module |
Acrux::DBI - Database independent interface for Acrux applications
Acrux::DBI это модуль-оболочка для DBI призванный концептуально заменить устаревающий CTK::DBI. Пакет Acrux::DBI входит в семейство пакетов Acrux, которое определяет основную область применения нашего нового модуля - написание моделей для проектов Acrux и связанных с ним проектов WWW::Suffit. Наш новый модуль базируется на успешной практике применения модулей Mojo::Pg, Mojo::mysql и Mojo::SQLite. Acrux::DBI, также как и перечисленные выше модули, базируется на фреймворке Mojolicious, но имеет ряд своих особенностей:
Знакомство с модулем лучше всего начать с практики. Для примера я буду в описании использовать тестовый WEB проект под именем altair, написанный на WWW::Suffit и Acrux. Сам модуль Acrux::DBI в проекте будет расширяться наследованием в модуле модели - WWW::Altair::Model
В проекте altair модель будет создаваться с помощью атрибута model
:
package WWW::Altair::Server;
use Mojo::Base 'WWW::Suffit::Server';
use WWW::Altair::Model;
has 'model' => sub {
WWW::Altair::Model->new(
$_[0]->conf->latest("/modelurl")
);
};
В этом примере модуль модели подключается в основном классе сервера WWW::Suffit::Server, а экземпляр модели создаётся на этапе первого вызова атрибута model
. Конструктор имеет первый аргумент - ModelURL, который в свою очередь получается из одноименной директивы конфигурационного файла проекта. ModelURL -- это строка вида классического URL, пример нескольких таких строк:
mysql://username:password@hostname/altair?mysql_auto_reconnect=1
mariadb://username:password@hostname/altair?mariadb_auto_reconnect=1
sqlite:///var/lib/altair/altair.db?sqlite_unicode=1
Далее указанная URL строка будет преобразована автоматически в DSN будущего соединения
При запуске WEB сервера и переходе на главную страницу тестового сайта будет осуществлен "вход" в обработчик этого маршрута WWW::Altair::Server::Alpha::root
, именно в нём и будет производиться инициализация соединения с базой данных:
my $model = $self->app->model->init;
return $self->reply->error(500 => "E0500" => $model->error)
if $model->error;
Сам код инициализации находится в классе WWW::Altair::Model
:
package WWW::Altair::Model;
use parent 'Acrux::DBI';
use Acrux::Util qw/ touch dformat /;
use Acrux::RefUtil qw/ is_hash_ref is_void /;
sub init {
my $self = shift->connect_cached;
my $is_new = 0;
my $dbh = $self->dbh;
if (defined($dbh)) {
if ($self->is_sqlite) {
my $file = $dbh->sqlite_db_filename();
unless ($file && (-e $file) && !(-z $file)) {
touch($file);
chmod(0666, $file);
$is_new = 1;
}
} elsif ($self->is_mysql) {
if (my $res = $self->query("SHOW TABLES FROM
`" . $self->database() . "` LIKE 'altair'")) {
$is_new = 1 if is_void($res->array);
}
}
}
# Check DB handler
return $self->error(sprintf("Can't connect to database \"%s\": %s",
$self->dsn, $self->errstr || "unknown error")) unless $dbh;
# Import schema
if ($is_new) {
$self->dump->from_data->poke("ddl_" . $self->driver());
return $self if $self->error;
}
# Check connect
return $self->error(sprintf("Can't init database \"%s\". Ping failed: %s",
$self->dsn, $self->errstr() || "unknown error")) unless $self->ping;
return $self;
}
sub is_mysql {
my $self = shift;
my $dr = $self->driver;
return ($dr eq 'mysql' or $dr eq 'mariadb' or $dr eq 'maria') ? 1 : 0;
}
sub is_sqlite {
my $self = shift;
return $self->driver eq 'sqlite' ? 1 : 0;
}
Инициализация начинается с создания кешированного соединения с базой данных. Далее идёт проверка инициализации схемы, если схема не инициализирована, то выставляется признак необходимости выполнить предварительную инициализацию схемы, которая заключается в импорте SQL дампа с помощью строки $self->dump->from_data->poke("ddl_" . $self->driver())
. Эта строка использует данные сеции __DATA__
текущего класса и "выбирает" SQL блок помеченный тегом ddl_sqlite
или ddl_mysql
в зависимости от типа СУБД:
__DATA__
@@ schema
-- # ddl_sqlite
CREATE TABLE IF NOT EXISTS "altair" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
"status" INTEGER DEFAULT NULL,
"comment" TEXT DEFAULT NULL
)
-- # ddl_mysql
CREATE TABLE IF NOT EXISTS `altair` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`status` INT(11) DEFAULT NULL,
`comment` TEXT DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
После успешной инициализации происходит контрольный вызов проверки активности соединения, и возврат ошибки если что-то пошло не так
В обработчиках маршрутов класса WWW::Altair::Server::Alpha
инициализация уже не потребуется, а этих обработчиках происходит использование ранее инициализированного кэшированного соединения, например:
sub tail { # GET /tail
my $self = shift;
# Get model
my $model = $self->app->model->connect_cached;
return $self->reply->json_error(500 => "E9003", $model->error)
if $model->error;
# Get log tail
my @tail = $model->get_log_tail;
return $self->reply->json_error(500 => "E9004", $model->error)
if $model->error;
# Render
return $self->reply->json_ok({tail => \@tail});
}
В классе модели метод get_log_tail
выглядит так:
use constant DML_LOG_TAIL => <<'DML';
SELECT * FROM altair ORDER BY `id` DESC LIMIT 100
DML
sub get_log_tail {
my $self = shift;
return () unless $self->ping;
# Log tail
my $tbl = {};
if (my $res = $self->query(DML_LOG_TAIL)) {
$tbl = $res->hashed_by( 'id' );
} else {
return ();
}
# Last 100 records (tail)
my @tail = ();
foreach my $id (sort {$b <=> $a} keys %$tbl) {
my $v = $tbl->{$id};
foreach my $k (keys %$v) {
$v->{$k} //= "";
}
push @tail, $v;
}
return @tail;
}