DBIx-TempDB
view release on metacpan or search on metacpan
lib/DBIx/TempDB/Util.pm view on Meta::CPAN
use strict;
use warnings;
use Exporter 'import';
use Carp qw(confess croak);
use IO::Select;
use POSIX;
use Scalar::Util 'blessed';
use URI::db;
use URI::QueryParam;
use constant DEBUG => $ENV{DBIX_TEMP_DB_DEBUG} || 0;
use constant KILL_SLEEP_INTERVAL => $ENV{DBIX_TEMP_DB_KILL_SLEEP_INTERVAL} || 2;
our @EXPORT_OK = qw(dsn_for on_process_end parse_sql);
sub dsn_for {
my ($url, $database_name) = @_;
$url = URI::db->new($url) unless blessed $url;
croak "Unknown engine for $url" unless $url->has_recognized_engine;
my $engine = $url->canonical_engine;
$database_name //= $url->dbname;
return _dsn_for_mysql($url, $database_name) if $engine eq 'mysql';
return _dsn_for_pg($url, $database_name) if $engine eq 'pg';
return _dsn_for_sqlite($url, $database_name) if $engine eq 'sqlite';
croak "Can't create DSN for engine $engine.";
}
sub on_process_end {
my $code = pop;
my $mode = shift // 'fork';
return _on_process_end_fork($code) if $mode eq 'fork';
return _on_process_end_double_fork($code) if $mode eq 'double_fork';
return DBIx::TempDB::Guard->new($code, $$);
}
sub parse_sql {
my ($type, $sql) = @_;
$type = $type->canonical_engine if blessed $type;
return _parse_mysql($sql) if $type eq 'mysql';
return $sql;
}
sub _dsn_for_mysql {
my ($url, $database_name) = @_;
my %opt = %{$url->query_form_hash};
my ($dsn, @userinfo);
$url = URI::db->new($url);
$url->dbname($database_name);
$url->query(undef);
$dsn = $url->dbi_dsn;
@userinfo = ($url->user, $url->password);
$opt{AutoCommit} //= 1;
$opt{AutoInactiveDestroy} //= 1;
$opt{PrintError} //= 0;
$opt{RaiseError} //= 1;
$opt{mysql_enable_utf8} //= 1;
return $dsn, @userinfo[0, 1], \%opt;
}
sub _dsn_for_pg {
my ($url, $database_name) = @_;
my %opt = %{$url->query_form_hash};
my ($dsn, @userinfo);
$url = URI::db->new($url);
$url->dbname($database_name);
$url->query(undef);
if (my $service = delete $opt{service}) { $url->query_param(service => $service) }
$dsn = $url->dbi_dsn;
@userinfo = ($url->user, $url->password);
$opt{AutoCommit} //= 1;
$opt{AutoInactiveDestroy} //= 1;
$opt{PrintError} //= 0;
$opt{RaiseError} //= 1;
return $dsn, @userinfo[0, 1], \%opt;
}
sub _dsn_for_sqlite {
my ($url, $database_name) = @_;
my %opt = %{$url->query_form_hash};
$url = URI::db->new($url);
$url->dbname($database_name);
$url->query(undef);
my $dsn = $url->dbi_dsn;
$opt{AutoCommit} //= 1;
$opt{AutoInactiveDestroy} //= 1;
$opt{PrintError} //= 0;
$opt{RaiseError} //= 1;
$opt{sqlite_unicode} //= 1;
return $dsn, "", "", \%opt;
}
sub _on_process_end_double_fork {
my $code = shift;
my $ppid = $$;
warn "[TempDB:$$] Watching process using double fork.\n" if DEBUG;
local $SIG{CHLD} = 'DEFAULT';
pipe(my ($READER), my ($WRITER)) or confess "Couldn't create pipe: $!";
# Parent
if (my $pid_1 = fork // confess "Couldn't fork: $!") {
my $pid_2;
# Wait around until the second fork is done so that when we return from
# here there are no new child processes that could mess things up if the
# calling process does any process handling.
close $WRITER;
$pid_2 = <$READER>;
$pid_2 = $pid_2 =~ m!(\d+)! ? $1 : undef;
( run in 1.235 second using v1.01-cache-2.11-cpan-f56aa216473 )